Okx - conditional candle-length

This commit is contained in:
Matthias 2022-05-14 09:51:44 +02:00
parent 64668b11da
commit 111b04c9e6
3 changed files with 71 additions and 7 deletions

View File

@ -309,12 +309,15 @@ class Exchange:
if self.log_responses: if self.log_responses:
logger.info(f"API {endpoint}: {response}") 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 Exchange ohlcv candle limit
Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits
per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit
:param timeframe: Timeframe to check :param timeframe: Timeframe to check
:param candle_type: Candle-type
:param since_ms: Candle-type
:return: Candle limit as integer :return: Candle limit as integer
""" """
return int(self._ft_has.get('ohlcv_candle_limit_per_timeframe', {}).get( 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(). 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. 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. # Require one more candle - to account for the still open candle.
candle_count = startup_candles + 1 candle_count = startup_candles + 1
# Allow 5 calls to the exchange per pair # 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!) :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( logger.debug(
"one_call: %s msecs (%s)", "one_call: %s msecs (%s)",
one_call, one_call,
@ -1744,7 +1748,8 @@ class Exchange:
if (not since_ms if (not since_ms
and (self._ft_has["ohlcv_require_since"] or self.required_candle_call_count > 1)): and (self._ft_has["ohlcv_require_since"] or self.required_candle_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, candle_type, since_ms)
move_to = one_call * self.required_candle_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)
@ -1862,7 +1867,9 @@ class Exchange:
pair, timeframe, since_ms, s pair, timeframe, since_ms, s
) )
params = deepcopy(self._ft_has.get('ohlcv_params', {})) 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: if candle_type != CandleType.SPOT:
params.update({'price': candle_type}) params.update({'price': candle_type})
if candle_type != CandleType.FUNDING_RATE: if candle_type != CandleType.FUNDING_RATE:

View File

@ -1,13 +1,16 @@
import logging import logging
from typing import Dict, List, Tuple from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Tuple
import ccxt import ccxt
from freqtrade.constants import BuySell from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
from freqtrade.enums.candletype import CandleType
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange import timeframe_to_minutes
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -20,7 +23,7 @@ class Okx(Exchange):
""" """
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 100, "ohlcv_candle_limit": 300, # Warning, special case with data prior to X months
"mark_ohlcv_timeframe": "4h", "mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h", "funding_fee_timeframe": "8h",
} }
@ -37,6 +40,27 @@ class Okx(Exchange):
net_only = True 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 @retrier
def additional_exchange_init(self) -> None: def additional_exchange_init(self) -> None:
""" """

View File

@ -13,6 +13,7 @@ import pytest
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date 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 freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_default_conf_usdt 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)) 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) 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): def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
exchange, exchangename = exchange_futures exchange, exchangename = exchange_futures
if not exchange: if not exchange: