diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index d14c7c192..dd3d74e84 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -1,9 +1,10 @@ """ Bybit exchange subclass """ import logging -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple from freqtrade.enums import MarginMode, TradingMode from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_utils import timeframe_to_msecs logger = logging.getLogger(__name__) @@ -25,7 +26,9 @@ class Bybit(Exchange): "ohlcv_has_history": False, } _ft_has_futures: Dict = { + "ohlcv_candle_limit": 200, "ohlcv_has_history": True, + "mark_ohlcv_timeframe": "4h", } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ @@ -47,3 +50,26 @@ class Bybit(Exchange): }) config.update(super()._ccxt_config) return config + + async def _fetch_funding_rate_history( + self, + pair: str, + timeframe: str, + limit: int, + since_ms: Optional[int] = None, + ) -> List[List]: + """ + Fetch funding rate history + Necessary workaround until https://github.com/ccxt/ccxt/issues/15990 is fixed. + """ + params = {} + if since_ms: + until = since_ms + (timeframe_to_msecs(timeframe) * self._ft_has['ohlcv_candle_limit']) + params.update({'until': until}) + # Funding rate + data = await self._api_async.fetch_funding_rate_history( + pair, since=since_ms, + params=params) + # Convert funding rate to candle pattern + data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] + return data diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py new file mode 100644 index 000000000..5eeb5ca24 --- /dev/null +++ b/tests/exchange/test_bybit.py @@ -0,0 +1,42 @@ +from unittest.mock import MagicMock + +from freqtrade.exchange.exchange_utils import timeframe_to_msecs +from tests.conftest import get_mock_coro, get_patched_exchange + + +async def test_bybit_fetch_funding_rate(default_conf, mocker): + default_conf['trading_mode'] = 'futures' + default_conf['margin_mode'] = 'isolated' + api_mock = MagicMock() + api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) + exchange = get_patched_exchange(mocker, default_conf, id='bybit', api_mock=api_mock) + limit = 200 + # Test fetch_funding_rate_history (current data) + await exchange._fetch_funding_rate_history( + pair='BTC/USDT:USDT', + timeframe='4h', + limit=limit, + ) + + assert api_mock.fetch_funding_rate_history.call_count == 1 + assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' + kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] + assert kwargs['params'] == {} + assert kwargs['since'] is None + + api_mock.fetch_funding_rate_history.reset_mock() + since_ms = 1610000000000 + since_ms_end = since_ms + (timeframe_to_msecs('4h') * limit) + # Test fetch_funding_rate_history (current data) + await exchange._fetch_funding_rate_history( + pair='BTC/USDT:USDT', + timeframe='4h', + limit=limit, + since_ms=since_ms, + ) + + assert api_mock.fetch_funding_rate_history.call_count == 1 + assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' + kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] + assert kwargs['params'] == {'until': since_ms_end} + assert kwargs['since'] == since_ms