Update _calculate_funding_fees
to reuse existing async infrastructure
This commit is contained in:
parent
35f9549e98
commit
aabca85a5f
@ -1498,11 +1498,17 @@ class Exchange:
|
||||
params = deepcopy(self._ft_has.get('ohlcv_params', {}))
|
||||
if candle_type != CandleType.SPOT:
|
||||
params.update({'price': candle_type})
|
||||
data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe,
|
||||
since=since_ms,
|
||||
limit=self.ohlcv_candle_limit(timeframe),
|
||||
params=params)
|
||||
|
||||
if candle_type != CandleType.FUNDING_RATE:
|
||||
data = await self._api_async.fetch_ohlcv(
|
||||
pair, timeframe=timeframe, since=since_ms,
|
||||
limit=self.ohlcv_candle_limit(timeframe), params=params)
|
||||
else:
|
||||
# Funding rate
|
||||
data = await self._api_async.fetch_funding_rate_history(
|
||||
pair, since=since_ms,
|
||||
limit=self.ohlcv_candle_limit(timeframe))
|
||||
# Convert funding rate to candle pattern
|
||||
data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data]
|
||||
# Some exchanges sort OHLCV in ASC order and others in DESC.
|
||||
# Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last)
|
||||
# while GDAX returns the list of OHLCV in DESC order (newest first, oldest last)
|
||||
@ -1882,28 +1888,23 @@ class Exchange:
|
||||
close_date = datetime.now(timezone.utc)
|
||||
open_timestamp = int(open_date.timestamp()) * 1000
|
||||
# close_timestamp = int(close_date.timestamp()) * 1000
|
||||
funding_rate_history = self.get_funding_rate_history(
|
||||
pair,
|
||||
open_timestamp
|
||||
|
||||
mark_comb: PairWithTimeframe = (
|
||||
pair, '1h', CandleType.from_string(self._ft_has["mark_ohlcv_price"]))
|
||||
# TODO-lev: funding_rate downloading this way is not yet possible.
|
||||
funding_comb: PairWithTimeframe = (pair, '1h', CandleType.FUNDING_RATE)
|
||||
candle_histories = self.refresh_latest_ohlcv(
|
||||
[mark_comb, funding_comb],
|
||||
since_ms=open_timestamp,
|
||||
cache=False,
|
||||
drop_incomplete=False,
|
||||
)
|
||||
mark_price_history = self._get_mark_price_history(
|
||||
pair,
|
||||
open_timestamp
|
||||
)
|
||||
for timestamp in funding_rate_history.keys():
|
||||
funding_rate = funding_rate_history[timestamp]
|
||||
if timestamp in mark_price_history:
|
||||
mark_price = mark_price_history[timestamp]
|
||||
fees += self._get_funding_fee(
|
||||
size=amount,
|
||||
mark_price=mark_price,
|
||||
funding_rate=funding_rate
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Mark price for {pair} at timestamp {timestamp} not found in "
|
||||
f"funding_rate_history Funding fee calculation may be incorrect"
|
||||
)
|
||||
funding_rates = candle_histories[funding_comb]
|
||||
mark_rates = candle_histories[mark_comb]
|
||||
|
||||
df = funding_rates.merge(mark_rates, on='date', how="inner", suffixes=["_fund", "_mark"])
|
||||
# TODO-lev: filter for relevant timeperiod?
|
||||
fees = sum(df['open_fund'] * df['open_mark'] * amount)
|
||||
|
||||
return fees
|
||||
|
||||
|
@ -195,7 +195,6 @@ class TestCCXTExchange():
|
||||
assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now)
|
||||
|
||||
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
|
||||
@ -203,15 +202,21 @@ class TestCCXTExchange():
|
||||
|
||||
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
|
||||
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
|
||||
pair_tf = (pair, '1h', CandleType.FUNDING_RATE)
|
||||
|
||||
rate = exchange.get_funding_rate_history(pair, since)
|
||||
assert isinstance(rate, dict)
|
||||
funding_ohlcv = exchange.refresh_latest_ohlcv(
|
||||
[pair_tf],
|
||||
since_ms=since,
|
||||
drop_incomplete=False)
|
||||
|
||||
assert isinstance(funding_ohlcv, dict)
|
||||
rate = funding_ohlcv[pair_tf]
|
||||
|
||||
expected_tf = exchange._ft_has['mark_ohlcv_timeframe']
|
||||
this_hour = timeframe_to_prev_date(expected_tf)
|
||||
prev_tick = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
|
||||
assert rate[int(this_hour.timestamp() * 1000)] != 0.0
|
||||
assert rate[int(prev_tick.timestamp() * 1000)] != 0.0
|
||||
prev_hour = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
|
||||
assert rate[rate['date'] == this_hour].iloc[0]['open'] != 0.0
|
||||
assert rate[rate['date'] == prev_hour].iloc[0]['open'] != 0.0
|
||||
|
||||
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
|
||||
exchange, exchangename = exchange_futures
|
||||
@ -237,6 +242,19 @@ class TestCCXTExchange():
|
||||
assert mark_candles[mark_candles['date'] == prev_hour].iloc[0]['open'] != 0.0
|
||||
assert mark_candles[mark_candles['date'] == this_hour].iloc[0]['open'] != 0.0
|
||||
|
||||
def test_ccxt__calculate_funding_fees(self, exchange_futures):
|
||||
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 = datetime.now(timezone.utc) - timedelta(days=5)
|
||||
|
||||
funding_fee = exchange._calculate_funding_fees(pair, 20, open_date=since)
|
||||
|
||||
assert isinstance(funding_fee, float)
|
||||
# assert funding_fee > 0
|
||||
|
||||
# TODO: tests fetch_trades (?)
|
||||
|
||||
def test_ccxt_get_fee(self, exchange):
|
||||
|
@ -3566,14 +3566,14 @@ def test__calculate_funding_fees(
|
||||
'gateio': funding_rate_history_octohourly,
|
||||
}[exchange][rate_start:rate_end]
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history)
|
||||
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
|
||||
api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history)
|
||||
api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv)
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||
funding_fees = exchange._calculate_funding_fees('ADA/USDT', amount, d1, d2)
|
||||
assert funding_fees == expected_fees
|
||||
assert pytest.approx(funding_fees, expected_fees)
|
||||
|
||||
|
||||
@ pytest.mark.parametrize('exchange,expected_fees', [
|
||||
@ -3590,8 +3590,8 @@ def test__calculate_funding_fees_datetime_called(
|
||||
expected_fees
|
||||
):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
|
||||
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history_octohourly)
|
||||
api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv)
|
||||
api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history_octohourly)
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user