diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8d74a8446..864aa36e9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -309,12 +309,15 @@ class Exchange: if self.log_responses: logger.info(f"API {endpoint}: {response}") - def ohlcv_candle_limit(self, timeframe: str) -> int: + def ohlcv_candle_limit( + self, timeframe: str, candle_type: CandleType, since_ms: Optional[int]) -> int: """ Exchange ohlcv candle limit Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit :param timeframe: Timeframe to check + :param candle_type: Candle-type + :param since_ms: Candle-type :return: Candle limit as integer """ return int(self._ft_has.get('ohlcv_candle_limit_per_timeframe', {}).get( @@ -616,7 +619,7 @@ class Exchange: Checks if required startup_candles is more than ohlcv_candle_limit(). 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, self._config['candle_type_def'], None) # Require one more candle - to account for the still open candle. candle_count = startup_candles + 1 # Allow 5 calls to the exchange per pair @@ -1708,7 +1711,8 @@ class Exchange: :param candle_type: Any of the enum CandleType (must match trading mode!) """ - one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) + one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit( + timeframe, candle_type, since_ms) logger.debug( "one_call: %s msecs (%s)", one_call, @@ -1744,7 +1748,8 @@ class Exchange: if (not since_ms and (self._ft_has["ohlcv_require_since"] or 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) + one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit( + timeframe, candle_type, since_ms) 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) @@ -1862,7 +1867,9 @@ class Exchange: pair, timeframe, since_ms, s ) params = deepcopy(self._ft_has.get('ohlcv_params', {})) - candle_limit = self.ohlcv_candle_limit(timeframe) + candle_limit = self.ohlcv_candle_limit( + timeframe, candle_type=candle_type, since_ms=since_ms) + if candle_type != CandleType.SPOT: params.update({'price': candle_type}) if candle_type != CandleType.FUNDING_RATE: diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 654021182..5e24997d7 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -1,13 +1,16 @@ import logging -from typing import Dict, List, Tuple +from datetime import datetime, timedelta, timezone +from typing import Dict, List, Optional, Tuple import ccxt from freqtrade.constants import BuySell from freqtrade.enums import MarginMode, TradingMode +from freqtrade.enums.candletype import CandleType from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier +from freqtrade.exchange.exchange import timeframe_to_minutes logger = logging.getLogger(__name__) @@ -20,7 +23,7 @@ class Okx(Exchange): """ _ft_has: Dict = { - "ohlcv_candle_limit": 100, + "ohlcv_candle_limit": 300, # Warning, special case with data prior to X months "mark_ohlcv_timeframe": "4h", "funding_fee_timeframe": "8h", } @@ -37,6 +40,27 @@ class Okx(Exchange): net_only = True + def ohlcv_candle_limit( + self, timeframe: str, candle_type: CandleType, since_ms: Optional[int]) -> int: + """ + Exchange ohlcv candle limit + Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits + per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit + :param timeframe: Timeframe to check + :param candle_type: Candle-type + :param since_ms: Candle-type + :return: Candle limit as integer + """ + now = datetime.now(timezone.utc) + offset_mins = timeframe_to_minutes(timeframe) * self._ft_has['ohlcv_candle_limit'] + if since_ms and since_ms < ((now - timedelta(minutes=offset_mins)).timestamp() * 1000): + return 100 + if candle_type not in (CandleType.FUTURES, CandleType.SPOT): + return 100 + + return int(self._ft_has.get('ohlcv_candle_limit_per_timeframe', {}).get( + timeframe, self._ft_has.get('ohlcv_candle_limit'))) + @retrier def additional_exchange_init(self) -> None: """ diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index ac7c8a528..ea9a166f6 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -13,6 +13,7 @@ import pytest from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date +from freqtrade.exchange.exchange import timeframe_to_msecs from freqtrade.resolvers.exchange_resolver import ExchangeResolver from tests.conftest import get_default_conf_usdt @@ -236,6 +237,38 @@ class TestCCXTExchange(): now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) + def test_ccxt__async_get_candle_history(self, exchange): + exchange, exchangename = exchange + # For some weired reason, this test returns random lengths for bittrex. + if not exchange._ft_has['ohlcv_has_history'] or exchangename == 'bittrex': + return + pair = EXCHANGES[exchangename]['pair'] + timeframe = EXCHANGES[exchangename]['timeframe'] + candle_type = CandleType.SPOT + timeframe_ms = timeframe_to_msecs(timeframe) + now = timeframe_to_prev_date( + timeframe, datetime.now(timezone.utc)) + for offset in (360, 120, 30, 10, 5, 2): + since = now - timedelta(days=offset) + since_ms = int(since.timestamp() * 1000) + + res = exchange.loop.run_until_complete(exchange._async_get_candle_history( + pair=pair, + timeframe=timeframe, + since_ms=since_ms, + candle_type=candle_type + ) + ) + assert res + assert res[0] == pair + assert res[1] == timeframe + assert res[2] == candle_type + candles = res[3] + candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * 0.9 + candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms + assert len(candles) >= min(candle_count, candle_count1) + assert candles[0][0] == since_ms or (since_ms + timeframe_ms) + def test_ccxt_fetch_funding_rate_history(self, exchange_futures): exchange, exchangename = exchange_futures if not exchange: