Merge pull request #4275 from freqtrade/markets_ref
Cache markets in the exchange object
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
| @@ -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()) | ||||||
|   | |||||||
| @@ -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'}, | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user