Use startup_candle-count to determine call count

This commit is contained in:
Matthias 2021-11-07 13:10:40 +01:00
parent 9fa64c2647
commit a08dd17bc1
2 changed files with 40 additions and 12 deletions

View File

@ -155,8 +155,8 @@ class Exchange:
self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {})) self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {}))
self.validate_required_startup_candles(config.get('startup_candle_count', 0), self.required_candle_call_count = self.validate_required_startup_candles(
config.get('timeframe', '')) config.get('startup_candle_count', 0), config.get('timeframe', ''))
# Converts the interval provided in minutes in config to seconds # Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get( 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. 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) 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( raise OperationalException(
f"This strategy requires {startup_candles} candles to start. " f"This strategy requires {startup_candles} candles to start, which is more than 5x "
f"{self.name} only provides {candle_limit - 5} for {timeframe}.") 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: def exchange_has(self, endpoint: str) -> bool:
""" """
@ -1283,11 +1296,10 @@ class Exchange:
for pair, timeframe in set(pair_list): for pair, timeframe in set(pair_list):
if ((pair, timeframe) not in self._klines if ((pair, timeframe) not in self._klines
or self._now_is_time_to_refresh(pair, timeframe)): 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 self.required_candle_call_count > 1:
if not since_ms and call_count > 1:
# Multiple calls for one pair - to get more history # Multiple calls for one pair - to get more history
one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) 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) now = timeframe_to_next_date(timeframe)
since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000)

View File

@ -941,12 +941,26 @@ def test_validate_required_startup_candles(default_conf, mocker, caplog):
default_conf['startup_candle_count'] = 20 default_conf['startup_candle_count'] = 20
ex = Exchange(default_conf) ex = Exchange(default_conf)
assert ex 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) Exchange(default_conf)
def test_exchange_has(default_conf, mocker): def test_exchange_has(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
assert not exchange.exchange_has('ASDFASDF') 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 assert exchange._api_async.fetch_ohlcv.call_count == 2
exchange._api_async.fetch_ohlcv.reset_mock() exchange._api_async.fetch_ohlcv.reset_mock()
exchange.required_candle_call_count = 2
res = exchange.refresh_latest_ohlcv(pairs) res = exchange.refresh_latest_ohlcv(pairs)
assert len(res) == len(pairs) assert len(res) == len(pairs)
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog) assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
assert exchange._klines 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: for pair in pairs:
assert isinstance(exchange.klines(pair), DataFrame) assert isinstance(exchange.klines(pair), DataFrame)
assert len(exchange.klines(pair)) > 0 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')]) res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
assert len(res) == len(pairs) 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]}, " assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
f"timeframe {pairs[0][1]} ...", f"timeframe {pairs[0][1]} ...",
caplog) caplog)