diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index d1fa28aa6..f9890270f 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -177,7 +177,6 @@ class Binance(Exchange): timeframe=timeframe, since_ms=since_ms, until_ms=until_ms, - stop_on_404=False, ) ) if df.empty: diff --git a/freqtrade/exchange/binance_public_data.py b/freqtrade/exchange/binance_public_data.py index 4cd000c77..c14aacd9d 100644 --- a/freqtrade/exchange/binance_public_data.py +++ b/freqtrade/exchange/binance_public_data.py @@ -20,6 +20,10 @@ from freqtrade.util.datetime_helpers import dt_from_ts, dt_now logger = logging.getLogger(__name__) +class Http404(Exception): + pass + + class BadHttpStatus(Exception): """Not 200/404""" @@ -32,7 +36,7 @@ async def fetch_ohlcv( timeframe: str, since_ms: int, until_ms: int | None, - stop_on_404: bool = False, + stop_on_404: bool = True, ) -> DataFrame: """ Fetch OHLCV data from https://data.binance.vision/ @@ -103,6 +107,7 @@ async def _fetch_ohlcv( stop_on_404: bool, ) -> DataFrame: dfs: list[DataFrame | None] = [] + current_day = 0 connector = aiohttp.TCPConnector(limit=100) async with aiohttp.ClientSession(connector=connector) as session: @@ -112,14 +117,27 @@ async def _fetch_ohlcv( *(get_daily_ohlcv(asset_type, symbol, timeframe, date, session) for date in dates) ) for result in results: - if isinstance(result, BaseException): + current_day += 1 + if isinstance(result, Http404): + if stop_on_404: + if current_day == 1: + # https://github.com/freqtrade/freqtrade/blob/acc53065e5fa7ab5197073276306dc9dc3adbfa3/tests/exchange_online/test_binance_compare_ohlcv.py#L7 + logger.warning( + "Failed to use fast download, fall back to rest API download, this " + "can take more time. If you're downloading BTC/USDT:USDT, " + "ETH/USDT:USDT, BCH/USDT:USDT, please first download " + "data before 2020 (using `--timerange yyyymmdd-20200101`), and " + "then download the full data you need." + ) + logger.debug("Abort downloading from data.binance.vision due to 404") + return concat(dfs) + else: + dfs.append(None) + elif isinstance(result, BaseException): logger.warning(f"An exception raised: : {result}") # Directly return the existing data, do not allow the gap # between the data return concat(dfs) - elif result is None and stop_on_404: - logger.debug("Abort downloading from data.binance.vision due to 404") - return concat(dfs) else: dfs.append(result) return concat(dfs) @@ -160,6 +178,7 @@ async def get_daily_ohlcv( date: datetime.date, session: aiohttp.ClientSession, retry_count: int = 3, + retry_delay: float = 0.0, ) -> DataFrame | None | Exception: """ Get daily OHLCV from https://data.binance.vision @@ -176,7 +195,7 @@ async def get_daily_ohlcv( retry = 0 while True: if retry > 0: - sleep_secs = retry * 0.5 + sleep_secs = retry * retry_delay logger.debug( f"[{retry}/{retry_count}] retry to download {url} after {sleep_secs} seconds" ) @@ -206,7 +225,7 @@ async def get_daily_ohlcv( return df elif resp.status == 404: logger.debug(f"No data available for {symbol} in {format_date(date)}") - return None + raise Http404 else: raise BadHttpStatus(f"{resp.status} - {resp.reason}") except Exception as e: diff --git a/tests/exchange/test_binance_public_data.py b/tests/exchange/test_binance_public_data.py index e4f665afd..2257efc9f 100644 --- a/tests/exchange/test_binance_public_data.py +++ b/tests/exchange/test_binance_public_data.py @@ -13,6 +13,7 @@ import pytest from freqtrade.enums import CandleType from freqtrade.exchange.binance_public_data import ( BadHttpStatus, + Http404, fetch_ohlcv, get_daily_ohlcv, symbol_ccxt_to_binance, @@ -256,8 +257,8 @@ async def test_get_daily_ohlcv(mocker, testdatadir): assert df["date"].iloc[-1] == last_date mocker.patch("aiohttp.ClientSession.get", return_value=MockResponse(b"", 404)) - df = await get_daily_ohlcv("spot", symbol, timeframe, date, session) - assert df is None + df = await get_daily_ohlcv("spot", symbol, timeframe, date, session, retry_delay=0) + assert isinstance(df, Http404) mocker.patch("aiohttp.ClientSession.get", return_value=MockResponse(b"", 500)) mocker.patch("asyncio.sleep")