Cache markets in the exchange object

This commit is contained in:
Matthias 2021-01-28 19:40:10 +01:00
parent 65459086a3
commit 5cdd9dd445
4 changed files with 24 additions and 22 deletions

View File

@ -66,6 +66,7 @@ class Exchange:
""" """
self._api: ccxt.Exchange = None self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None self._api_async: ccxt_async.Exchange = None
self._markets: Dict = {}
self._config.update(config) self._config.update(config)
@ -198,10 +199,10 @@ class Exchange:
@property @property
def markets(self) -> Dict: def markets(self) -> Dict:
"""exchange ccxt markets""" """exchange ccxt markets"""
if not self._api.markets: if not self._markets:
logger.info("Markets were not loaded. Loading them now..") logger.info("Markets were not loaded. Loading them now..")
self._load_markets() self._load_markets()
return self._api.markets return self._markets
@property @property
def precisionMode(self) -> str: def precisionMode(self) -> str:
@ -291,7 +292,7 @@ class Exchange:
def _load_markets(self) -> None: def _load_markets(self) -> None:
""" Initialize markets both sync and async """ """ Initialize markets both sync and async """
try: try:
self._api.load_markets() self._markets = self._api.load_markets()
self._load_async_markets() self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
except ccxt.BaseError as e: except ccxt.BaseError as e:
@ -306,7 +307,7 @@ class Exchange:
return None return None
logger.debug("Performing scheduled market reload..") logger.debug("Performing scheduled market reload..")
try: try:
self._api.load_markets(reload=True) self._markets = self._api.load_markets(reload=True)
# Also reload async markets to avoid issues with newly listed pairs # Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True) self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
@ -660,8 +661,8 @@ class Exchange:
@retrier @retrier
def fetch_ticker(self, pair: str) -> dict: def fetch_ticker(self, pair: str) -> dict:
try: try:
if (pair not in self._api.markets or if (pair not in self.markets or
self._api.markets[pair].get('active', False) is False): self.markets[pair].get('active', False) is False):
raise ExchangeError(f"Pair {pair} not available") raise ExchangeError(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair) data = self._api.fetch_ticker(pair)
return data return data

View File

@ -73,7 +73,6 @@ def patched_configuration_load_config_file(mocker, config) -> None:
def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None: def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None:
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())

View File

@ -373,28 +373,25 @@ def test__load_markets(default_conf, mocker, caplog):
expected_return = {'ETH/BTC': 'available'} expected_return = {'ETH/BTC': 'available'}
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value=expected_return) api_mock.load_markets = MagicMock(return_value=expected_return)
type(api_mock).markets = expected_return mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
default_conf['exchange']['pair_whitelist'] = ['ETH/BTC'] default_conf['exchange']['pair_whitelist'] = ['ETH/BTC']
ex = get_patched_exchange(mocker, default_conf, api_mock, id="binance", mock_markets=False) ex = Exchange(default_conf)
assert ex.markets == expected_return assert ex.markets == expected_return
def test_reload_markets(default_conf, mocker, caplog): def test_reload_markets(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
initial_markets = {'ETH/BTC': {}} initial_markets = {'ETH/BTC': {}}
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
def load_markets(*args, **kwargs):
exchange._api.markets = updated_markets
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_markets = load_markets api_mock.load_markets = MagicMock(return_value=initial_markets)
type(api_mock).markets = initial_markets
default_conf['exchange']['markets_refresh_interval'] = 10 default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False) mock_markets=False)
exchange._load_async_markets = MagicMock() exchange._load_async_markets = MagicMock()
exchange._last_markets_refresh = arrow.utcnow().int_timestamp exchange._last_markets_refresh = arrow.utcnow().int_timestamp
updated_markets = {'ETH/BTC': {}, "LTC/BTC": {}}
assert exchange.markets == initial_markets assert exchange.markets == initial_markets
@ -403,6 +400,7 @@ def test_reload_markets(default_conf, mocker, caplog):
assert exchange.markets == initial_markets assert exchange.markets == initial_markets
assert exchange._load_async_markets.call_count == 0 assert exchange._load_async_markets.call_count == 0
api_mock.load_markets = MagicMock(return_value=updated_markets)
# more than 10 minutes have passed, reload is executed # more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60 exchange._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60
exchange.reload_markets() exchange.reload_markets()
@ -429,7 +427,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog): def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
default_conf['stake_currency'] = stake_currency default_conf['stake_currency'] = stake_currency
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
}) })
@ -443,7 +441,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
def test_validate_stake_currency_error(default_conf, mocker, caplog): def test_validate_stake_currency_error(default_conf, mocker, caplog):
default_conf['stake_currency'] = 'XRP' default_conf['stake_currency'] = 'XRP'
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'}, 'XRP/ETH': {'quote': 'ETH'}, 'NEO/USDT': {'quote': 'USDT'},
}) })
@ -489,7 +487,7 @@ def test_get_pair_base_currency(default_conf, mocker, pair, expected):
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'},
'LTC/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'XRP/BTC': {'quote': 'BTC'},
@ -540,7 +538,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
def test_validate_pairs_restricted(default_conf, mocker, caplog): def test_validate_pairs_restricted(default_conf, mocker, caplog):
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}}, 'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}},
'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ... 'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ...
@ -558,7 +556,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog): def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'},
@ -574,7 +572,7 @@ def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog): def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, caplog):
api_mock = MagicMock() api_mock = MagicMock()
default_conf['stake_currency'] = '' default_conf['stake_currency'] = ''
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'BTC'}, 'HELLO-WORLD': {'quote': 'BTC'},
@ -585,12 +583,13 @@ def test_validate_pairs_stakecompatibility_downloaddata(default_conf, mocker, ca
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
Exchange(default_conf) Exchange(default_conf)
assert type(api_mock).load_markets.call_count == 1
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog): def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD') default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD')
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).markets = PropertyMock(return_value={ type(api_mock).load_markets = MagicMock(return_value={
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'}, 'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'}, 'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
'HELLO-WORLD': {'quote': 'USDT'}, 'HELLO-WORLD': {'quote': 'USDT'},

View File

@ -2100,6 +2100,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_buy_order_open
def test_bot_loop_start_called_once(mocker, default_conf, caplog): def test_bot_loop_start_called_once(mocker, default_conf, caplog):
ftbot = get_patched_freqtradebot(mocker, default_conf) ftbot = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade')
patch_get_signal(ftbot) patch_get_signal(ftbot)
ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError) ftbot.strategy.bot_loop_start = MagicMock(side_effect=ValueError)
ftbot.strategy.analyze = MagicMock() ftbot.strategy.analyze = MagicMock()
@ -3810,6 +3811,8 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
open_order_id="123456" open_order_id="123456"
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
# Ticker rate cannot be found for this to work.
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError)
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004 assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004