Infinite OHLCV re-fetch loop for pairs with no recent trades on exchanges that omit empty candles (e.g. Bitvavo)
Describe your environment
- Operating system: Linux (Docker,
freqtradeorg/freqtrade:stable) - Freqtrade Version: 2026.5.1
- Exchange: Bitvavo (spot, EUR pairs)
- Run mode: dry_run (live trading path — the affected code is the common REST refresh path)
Describe the problem
Some exchanges (Bitvavo among them) omit candles for periods with zero trades from
their OHLCV responses instead of returning flat zero-volume candles. For any pair that
currently has no trades (thin/inactive pairs in a large whitelist), this interacts badly
with the refresh bookkeeping in exchange.py:
_process_ohlcv_df()sets_pairs_last_refresh_timefrom the timestamps of the ticks contained in the response (ticks[-2][0]/ticks[-1][0]).- For a pair with no recent trades, the response only contains old candles, so the stored timestamp never advances.
_now_is_time_to_refresh()therefore returns True on every bot iteration for that pair, a new REST request is issued every ~5 seconds, and the response again fails to advance the timestamp — an infinite re-fetch loop, invisible in logs (the candle-cadence cache never engages for these pairs).
With a 119-pair whitelist, we measured 72 (pair, timeframe) combinations stuck in this loop, generating ~650 REST requests/minute from a single bot instance (~180 requests per pair per 10 minutes instead of the expected 2 for 5m candles). Several bots sharing one IP pushed the aggregate over Bitvavo's rate limit and produced IP bans (errorCode 105) precisely during high-volatility periods — i.e. the bot loses market data access at the worst possible moment while holding open positions.
Steps to reproduce
- Run a bot on Bitvavo (dry-run is enough) with a whitelist containing thin pairs that regularly go 10+ minutes without any trade.
- Observe (e.g. via debug logging or a network trace) repeated
_async_get_candle_historyGETs for those pairs on every worker iteration, instead of once per candle interval.
Observed Results
- One request per bot iteration (~5s) per inactive (pair, timeframe), indefinitely.
- Rate-limit exhaustion / IP bans on Bitvavo with multi-bot or large-whitelist setups.
Expected behaviour
A pair whose response contains no new candle should be treated as "checked, nothing new" and re-polled at candle cadence, not at loop cadence.
Suggested fix (tested)
In _process_ohlcv_df(), advance the refresh timestamp to at least the open time of
the last complete candle interval even when the response contains no new ticks:
# semantics: "we checked, nothing new" — poll again next candle, not next loop
last_tick = ticks[-2][0] // 1000 if ticks and len(ticks) > 1 else 0
interval_s = timeframe_to_seconds(timeframe)
last_complete_open = (int(time.time()) // interval_s - 1) * interval_s
self._pairs_last_refresh_time[(pair, timeframe, c_type)] = max(last_tick, last_complete_open)
For active pairs this is a no-op (their latest tick IS the open of the last complete
candle). We have been running this patch (file-mounted over exchange.py) on a canary
bot: request volume for the affected pairs dropped from 6562 to 161 requests/10 min
(the theoretical floor for 72 stuck combinations at candle cadence), with no errors and
no behavioural change for active pairs. Strategy-side staleness detection is unaffected
(dataframes still carry the real candle dates).
Happy to provide additional measurements if useful.
Source: freqtrade/freqtrade