chore: improve comments

This commit is contained in:
Meng Xiangzhuo
2024-11-13 17:14:49 +08:00
parent cf7016b36d
commit 39b4263b8b
3 changed files with 47 additions and 19 deletions

View File

@@ -5,12 +5,13 @@ from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
import ccxt import ccxt
from pandas import DataFrame, concat from pandas import DataFrame
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange, binance_public_data from freqtrade.exchange import Exchange, binance_public_data
from freqtrade.exchange.binance_public_data import concat
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas, Tickers from freqtrade.exchange.exchange_types import FtHas, Tickers
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
@@ -162,10 +163,11 @@ class Binance(Exchange):
candle_type: CandleType, candle_type: CandleType,
is_new_pair: bool = False, is_new_pair: bool = False,
until_ms: int | None = None, until_ms: int | None = None,
): ) -> DataFrame:
""" """
Fetch ohlcv fast by utilizing https://data.binance.vision Fastly fetch OHLCV data by leveraging https://data.binance.vision.
""" """
# only download timeframes with significant improvements, otherwise fall back to rest API
if (candle_type == CandleType.SPOT and timeframe in ["1s", "1m", "3m", "5m"]) or ( if (candle_type == CandleType.SPOT and timeframe in ["1s", "1m", "3m", "5m"]) or (
candle_type == CandleType.FUTURES and timeframe in ["1m", "3m", "5m", "15m", "30m"] candle_type == CandleType.FUTURES and timeframe in ["1m", "3m", "5m", "15m", "30m"]
): ):
@@ -179,10 +181,14 @@ class Binance(Exchange):
markets=self.markets, markets=self.markets,
) )
) )
# download the remaining data from rest API
if df.empty: if df.empty:
rest_since_ms = since_ms rest_since_ms = since_ms
else: else:
rest_since_ms = dt_ts(df.iloc[-1].date) + timeframe_to_msecs(timeframe) rest_since_ms = dt_ts(df.iloc[-1].date) + timeframe_to_msecs(timeframe)
# make sure since <= until
if until_ms and rest_since_ms > until_ms: if until_ms and rest_since_ms > until_ms:
rest_df = DataFrame() rest_df = DataFrame()
else: else:
@@ -195,6 +201,7 @@ class Binance(Exchange):
until_ms=until_ms, until_ms=until_ms,
) )
all_df = concat([df, rest_df]) all_df = concat([df, rest_df])
return all_df
else: else:
return super().get_historic_ohlcv( return super().get_historic_ohlcv(
pair=pair, pair=pair,
@@ -204,7 +211,6 @@ class Binance(Exchange):
is_new_pair=is_new_pair, is_new_pair=is_new_pair,
until_ms=until_ms, until_ms=until_ms,
) )
return all_df
def funding_fee_cutoff(self, open_date: datetime): def funding_fee_cutoff(self, open_date: datetime):
""" """

View File

@@ -42,12 +42,22 @@ async def fetch_ohlcv(
stop_on_404: bool = True, stop_on_404: bool = True,
) -> DataFrame: ) -> DataFrame:
""" """
Fetch OHLCV data from https://data.binance.vision/ Fetch OHLCV data from https://data.binance.vision
The function makes its best effort to download data within the time range
[`since_ms`, `until_ms`) -- including `since_ms`, but excluding `until_ms`.
If `stop_one_404` is True, this returned DataFrame is guaranteed to start from `since_ms`
with no gaps in the data.
:candle_type: Currently only spot and futures are supported :candle_type: Currently only spot and futures are supported
:pair: symbol name in CCXT convention
:since_ms: the start timestamp of data, including itself
:until_ms: the end timestamp of data, excluding itself
:param until_ms: `None` indicates the timestamp of the latest available data :param until_ms: `None` indicates the timestamp of the latest available data
:markets: the CCXT markets dict, when it's None, the function will load the markets data
from a new `ccxt.binance` instance
:param stop_on_404: Stop to download the following data when a 404 returned :param stop_on_404: Stop to download the following data when a 404 returned
:return: the date range is between [since_ms, until_ms), :return: the date range is between [since_ms, until_ms), return an empty DataFrame if no data
return and empty DataFrame if no data available in the time range available in the time range
""" """
try: try:
if candle_type == CandleType.SPOT: if candle_type == CandleType.SPOT:
@@ -82,6 +92,7 @@ async def fetch_ohlcv(
df = DataFrame() df = DataFrame()
if not df.empty: if not df.empty:
# only return the data within the requested time range
return df.loc[(df["date"] >= start) & (df["date"] < end)] return df.loc[(df["date"] >= start) & (df["date"] < end)]
else: else:
return df return df
@@ -102,7 +113,9 @@ async def _fetch_ohlcv(
end: datetime.date, end: datetime.date,
stop_on_404: bool, stop_on_404: bool,
) -> DataFrame: ) -> DataFrame:
# daily dataframes
dfs: list[DataFrame | None] = [] dfs: list[DataFrame | None] = []
# the current day being processing, starting at 1.
current_day = 0 current_day = 0
connector = aiohttp.TCPConnector(limit=100) connector = aiohttp.TCPConnector(limit=100)
@@ -116,8 +129,10 @@ async def _fetch_ohlcv(
current_day += 1 current_day += 1
if isinstance(result, Http404): if isinstance(result, Http404):
if stop_on_404: if stop_on_404:
if current_day == 1: # A 404 error on the first day indicates missing data
# on https://data.binance.vision, we provide the warning and the advice.
# https://github.com/freqtrade/freqtrade/blob/acc53065e5fa7ab5197073276306dc9dc3adbfa3/tests/exchange_online/test_binance_compare_ohlcv.py#L7 # https://github.com/freqtrade/freqtrade/blob/acc53065e5fa7ab5197073276306dc9dc3adbfa3/tests/exchange_online/test_binance_compare_ohlcv.py#L7
if current_day == 1:
logger.warning( logger.warning(
"Failed to use fast download, fall back to rest API download, this " "Failed to use fast download, fall back to rest API download, this "
"can take more time. If you're downloading BTC/USDT:USDT, " "can take more time. If you're downloading BTC/USDT:USDT, "
@@ -131,8 +146,7 @@ async def _fetch_ohlcv(
dfs.append(None) dfs.append(None)
elif isinstance(result, BaseException): elif isinstance(result, BaseException):
logger.warning(f"An exception raised: : {result}") logger.warning(f"An exception raised: : {result}")
# Directly return the existing data, do not allow the gap # Directly return the existing data, do not allow the gap within the data
# between the data
return concat(dfs) return concat(dfs)
else: else:
dfs.append(result) dfs.append(result)
@@ -175,7 +189,7 @@ async def get_daily_ohlcv(
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
retry_count: int = 3, retry_count: int = 3,
retry_delay: float = 0.0, retry_delay: float = 0.0,
) -> DataFrame | None | Exception: ) -> DataFrame | Exception:
""" """
Get daily OHLCV from https://data.binance.vision Get daily OHLCV from https://data.binance.vision
See https://github.com/binance/binance-public-data See https://github.com/binance/binance-public-data
@@ -225,7 +239,10 @@ async def get_daily_ohlcv(
else: else:
raise BadHttpStatus(f"{resp.status} - {resp.reason}") raise BadHttpStatus(f"{resp.status} - {resp.reason}")
except Exception as e: except Exception as e:
retry += 1 if isinstance(e, Http404):
return e
else:
if retry >= retry_count: if retry >= retry_count:
logger.debug(f"Failed to get data from {url}: {e}") logger.debug(f"Failed to get data from {url}: {e}")
return e return e
retry += 1

View File

@@ -254,43 +254,48 @@ async def test_get_daily_ohlcv(mocker, testdatadir):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
path = testdatadir / "binance/binance_public_data/spot-klines-BTCUSDT-1h-2024-10-28.zip" path = testdatadir / "binance/binance_public_data/spot-klines-BTCUSDT-1h-2024-10-28.zip"
mocker.patch( get = mocker.patch(
"freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get", "freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get",
return_value=MockResponse(path.read_bytes(), 200), return_value=MockResponse(path.read_bytes(), 200),
) )
df = await get_daily_ohlcv("spot", symbol, timeframe, date, session) df = await get_daily_ohlcv("spot", symbol, timeframe, date, session)
assert get.call_count == 1
assert df["date"].iloc[0] == first_date assert df["date"].iloc[0] == first_date
assert df["date"].iloc[-1] == last_date assert df["date"].iloc[-1] == last_date
path = ( path = (
testdatadir / "binance/binance_public_data/futures-um-klines-BTCUSDT-1h-2024-10-28.zip" testdatadir / "binance/binance_public_data/futures-um-klines-BTCUSDT-1h-2024-10-28.zip"
) )
mocker.patch( get = mocker.patch(
"freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get", "freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get",
return_value=MockResponse(path.read_bytes(), 200), return_value=MockResponse(path.read_bytes(), 200),
) )
df = await get_daily_ohlcv("futures/um", symbol, timeframe, date, session) df = await get_daily_ohlcv("futures/um", symbol, timeframe, date, session)
assert get.call_count == 1
assert df["date"].iloc[0] == first_date assert df["date"].iloc[0] == first_date
assert df["date"].iloc[-1] == last_date assert df["date"].iloc[-1] == last_date
mocker.patch( get = mocker.patch(
"freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get", "freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get",
return_value=MockResponse(b"", 404), return_value=MockResponse(b"", 404),
) )
df = await get_daily_ohlcv("spot", symbol, timeframe, date, session, retry_delay=0) df = await get_daily_ohlcv("spot", symbol, timeframe, date, session, retry_delay=0)
assert get.call_count == 1
assert isinstance(df, Http404) assert isinstance(df, Http404)
mocker.patch( get = mocker.patch(
"freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get", "freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get",
return_value=MockResponse(b"", 500), return_value=MockResponse(b"", 500),
) )
mocker.patch("asyncio.sleep") mocker.patch("asyncio.sleep")
df = await get_daily_ohlcv("spot", symbol, timeframe, date, session) df = await get_daily_ohlcv("spot", symbol, timeframe, date, session)
assert get.call_count == 4 # 1 + 3 default retries
assert isinstance(df, BadHttpStatus) assert isinstance(df, BadHttpStatus)
mocker.patch( get = mocker.patch(
"freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get", "freqtrade.exchange.binance_public_data.aiohttp.ClientSession.get",
return_value=MockResponse(b"nop", 200), return_value=MockResponse(b"nop", 200),
) )
df = await get_daily_ohlcv("spot", symbol, timeframe, date, session) df = await get_daily_ohlcv("spot", symbol, timeframe, date, session)
assert get.call_count == 4 # 1 + 3 default retries
assert isinstance(df, zipfile.BadZipFile) assert isinstance(df, zipfile.BadZipFile)