diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9be3169c2..cf1e16b8d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1609,12 +1609,11 @@ class Exchange: def _get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float: """ Returns the sum of all funding fees that were exchanged for a pair within a timeframe + Dry-run handling happens as part of _calculate_funding_fees. :param pair: (e.g. ADA/USDT) :param since: The earliest time of consideration for calculating funding fees, in unix time or as a datetime """ - # TODO-lev: Add dry-run handling for this. - if not self.exchange_has("fetchFundingHistory"): raise OperationalException( f"fetch_funding_history() is not available using {self.name}" @@ -1889,13 +1888,8 @@ class Exchange: d = datetime.fromtimestamp(int(fund['timestamp'] / 1000), timezone.utc) # Round down to the nearest hour, in case of a delayed timestamp # The millisecond timestamps can be delayed ~20ms - time = datetime( - d.year, - d.month, - d.day, - d.hour, - tzinfo=timezone.utc - ).timestamp() * 1000 + time = int(timeframe_to_prev_date('1h', d).timestamp() * 1000) + funding_history[time] = fund['fundingRate'] return funding_history except ccxt.DDoSProtection as e: diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 2f629528c..c3aee7e92 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -12,7 +12,7 @@ import pytest from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_default_conf +from tests.conftest import get_default_conf_usdt # Exchanges that should be tested @@ -33,9 +33,11 @@ EXCHANGES = { 'timeframe': '5m', }, 'ftx': { - 'pair': 'BTC/USDT', + 'pair': 'BTC/USD', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures_pair': 'BTC-PERP', + 'futures': True, }, 'kucoin': { 'pair': 'BTC/USDT', @@ -46,6 +48,7 @@ EXCHANGES = { 'pair': 'BTC/USDT', 'hasQuoteVolume': True, 'timeframe': '5m', + 'futures': True, }, 'okex': { 'pair': 'BTC/USDT', @@ -57,7 +60,7 @@ EXCHANGES = { @pytest.fixture(scope="class") def exchange_conf(): - config = get_default_conf((Path(__file__).parent / "testdata").resolve()) + config = get_default_conf_usdt((Path(__file__).parent / "testdata").resolve()) config['exchange']['pair_whitelist'] = [] config['exchange']['key'] = '' config['exchange']['secret'] = '' @@ -73,6 +76,19 @@ def exchange(request, exchange_conf): yield exchange, request.param +@pytest.fixture(params=EXCHANGES, scope="class") +def exchange_futures(request, exchange_conf): + if not EXCHANGES[request.param].get('futures') is True: + yield None, request.param + else: + exchange_conf['exchange']['name'] = request.param + exchange_conf['trading_mode'] = 'futures' + exchange_conf['collateral'] = 'cross' + exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) + + yield exchange, request.param + + @pytest.mark.longrun class TestCCXTExchange(): @@ -149,6 +165,24 @@ 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) + @pytest.mark.skip("No futures support yet") + def test_ccxt_fetch_funding_rate_history(self, exchange_futures): + # TODO-lev: enable this test once Futures mode is enabled. + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + + pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) + since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000) + + rate = exchange.get_funding_rate_history(pair, since) + assert isinstance(rate, dict) + this_hour = timeframe_to_prev_date('1h') + prev_hour = this_hour - timedelta(hours=1) + assert rate[int(this_hour.timestamp() * 1000)] != 0.0 + assert rate[int(prev_hour.timestamp() * 1000)] != 0.0 + # TODO: tests fetch_trades (?) def test_ccxt_get_fee(self, exchange):