From a08dd17bc19c81156ace7735bad0cbb2fb98e2ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 7 Nov 2021 13:10:40 +0100 Subject: [PATCH] Use startup_candle-count to determine call count --- freqtrade/exchange/exchange.py | 28 ++++++++++++++++++++-------- tests/exchange/test_exchange.py | 24 ++++++++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 01f0864c4..4e9a6c71d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -155,8 +155,8 @@ class Exchange: self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) - self.validate_required_startup_candles(config.get('startup_candle_count', 0), - config.get('timeframe', '')) + self.required_candle_call_count = self.validate_required_startup_candles( + config.get('startup_candle_count', 0), config.get('timeframe', '')) # Converts the interval provided in minutes in config to seconds self.markets_refresh_interval: int = exchange_config.get( @@ -477,10 +477,23 @@ class Exchange: Requires a grace-period of 5 candles - so a startup-period up to 494 is allowed by default. """ candle_limit = self.ohlcv_candle_limit(timeframe) - if startup_candles + 5 > candle_limit: + # Require one more candle - to account for the still open candle. + candle_count = startup_candles + 1 + # Allow 5 calls to the exchange per pair + required_candle_call_count = int( + (candle_count / candle_limit) + (0 if candle_count % candle_limit == 0 else 1)) + + if required_candle_call_count > 5: + # Only allow 5 calls per pair to somewhat limit the impact raise OperationalException( - f"This strategy requires {startup_candles} candles to start. " - f"{self.name} only provides {candle_limit - 5} for {timeframe}.") + f"This strategy requires {startup_candles} candles to start, which is more than 5x " + f"the amount of candles {self.name} provides for {timeframe}.") + + if required_candle_call_count > 1: + logger.warning(f"Using {required_candle_call_count} calls to get OHLCV. " + f"This can result in slower operations for the bot. Please check " + f"if you really need {startup_candles} candles for your strategy") + return required_candle_call_count def exchange_has(self, endpoint: str) -> bool: """ @@ -1283,11 +1296,10 @@ class Exchange: for pair, timeframe in set(pair_list): if ((pair, timeframe) not in self._klines or self._now_is_time_to_refresh(pair, timeframe)): - call_count = self._ft_has.get('ohlcv_candle_call_count', 1) - if not since_ms and call_count > 1: + if not since_ms and self.required_candle_call_count > 1: # Multiple calls for one pair - to get more history one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) - move_to = one_call * call_count + move_to = one_call * self.required_candle_call_count now = timeframe_to_next_date(timeframe) since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 34e2b04ab..ff78321c5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -941,12 +941,26 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog): default_conf['startup_candle_count'] = 20 ex = Exchange(default_conf) assert ex - default_conf['startup_candle_count'] = 600 + # assumption is that the exchange provides 500 candles per call.s + assert ex.validate_required_startup_candles(200, '5m') == 1 + assert ex.validate_required_startup_candles(499, '5m') == 1 + assert ex.validate_required_startup_candles(600, '5m') == 2 + assert ex.validate_required_startup_candles(501, '5m') == 2 + assert ex.validate_required_startup_candles(499, '5m') == 1 + assert ex.validate_required_startup_candles(1000, '5m') == 3 + assert ex.validate_required_startup_candles(2499, '5m') == 5 + assert log_has_re(r'Using 5 calls to get OHLCV. This.*', caplog) - with pytest.raises(OperationalException, match=r'This strategy requires 600.*'): + with pytest.raises(OperationalException, match=r'This strategy requires 2500.*'): + ex.validate_required_startup_candles(2500, '5m') + + # Ensure the same also happens on init + default_conf['startup_candle_count'] = 6000 + with pytest.raises(OperationalException, match=r'This strategy requires 6000.*'): Exchange(default_conf) + def test_exchange_has(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) assert not exchange.exchange_has('ASDFASDF') @@ -1632,12 +1646,14 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert exchange._api_async.fetch_ohlcv.call_count == 2 exchange._api_async.fetch_ohlcv.reset_mock() + exchange.required_candle_call_count = 2 res = exchange.refresh_latest_ohlcv(pairs) assert len(res) == len(pairs) assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog) assert exchange._klines - assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert exchange._api_async.fetch_ohlcv.call_count == 4 + exchange._api_async.fetch_ohlcv.reset_mock() for pair in pairs: assert isinstance(exchange.klines(pair), DataFrame) assert len(exchange.klines(pair)) > 0 @@ -1653,7 +1669,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) assert len(res) == len(pairs) - assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert exchange._api_async.fetch_ohlcv.call_count == 0 assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, " f"timeframe {pairs[0][1]} ...", caplog)