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', {}))
|
params = deepcopy(self._ft_has.get('ohlcv_params', {}))
|
||||||
if candle_type != CandleType.SPOT:
|
if candle_type != CandleType.SPOT:
|
||||||
params.update({'price': candle_type})
|
params.update({'price': candle_type})
|
||||||
data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe,
|
if candle_type != CandleType.FUNDING_RATE:
|
||||||
since=since_ms,
|
data = await self._api_async.fetch_ohlcv(
|
||||||
limit=self.ohlcv_candle_limit(timeframe),
|
pair, timeframe=timeframe, since=since_ms,
|
||||||
params=params)
|
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.
|
# 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)
|
# 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)
|
# 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)
|
close_date = datetime.now(timezone.utc)
|
||||||
open_timestamp = int(open_date.timestamp()) * 1000
|
open_timestamp = int(open_date.timestamp()) * 1000
|
||||||
# close_timestamp = int(close_date.timestamp()) * 1000
|
# close_timestamp = int(close_date.timestamp()) * 1000
|
||||||
funding_rate_history = self.get_funding_rate_history(
|
|
||||||
pair,
|
mark_comb: PairWithTimeframe = (
|
||||||
open_timestamp
|
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(
|
funding_rates = candle_histories[funding_comb]
|
||||||
pair,
|
mark_rates = candle_histories[mark_comb]
|
||||||
open_timestamp
|
|
||||||
)
|
df = funding_rates.merge(mark_rates, on='date', how="inner", suffixes=["_fund", "_mark"])
|
||||||
for timestamp in funding_rate_history.keys():
|
# TODO-lev: filter for relevant timeperiod?
|
||||||
funding_rate = funding_rate_history[timestamp]
|
fees = sum(df['open_fund'] * df['open_mark'] * amount)
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
return fees
|
return fees
|
||||||
|
|
||||||
|
@ -195,7 +195,6 @@ class TestCCXTExchange():
|
|||||||
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_fetch_funding_rate_history(self, exchange_futures):
|
def test_ccxt_fetch_funding_rate_history(self, exchange_futures):
|
||||||
# TODO-lev: enable this test once Futures mode is enabled.
|
|
||||||
exchange, exchangename = exchange_futures
|
exchange, exchangename = exchange_futures
|
||||||
if not exchange:
|
if not exchange:
|
||||||
# exchange_futures only returns values for supported exchanges
|
# exchange_futures only returns values for supported exchanges
|
||||||
@ -203,15 +202,21 @@ class TestCCXTExchange():
|
|||||||
|
|
||||||
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
|
pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair'])
|
||||||
since = int((datetime.now(timezone.utc) - timedelta(days=5)).timestamp() * 1000)
|
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)
|
funding_ohlcv = exchange.refresh_latest_ohlcv(
|
||||||
assert isinstance(rate, dict)
|
[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']
|
expected_tf = exchange._ft_has['mark_ohlcv_timeframe']
|
||||||
this_hour = timeframe_to_prev_date(expected_tf)
|
this_hour = timeframe_to_prev_date(expected_tf)
|
||||||
prev_tick = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
|
prev_hour = timeframe_to_prev_date(expected_tf, this_hour - timedelta(minutes=1))
|
||||||
assert rate[int(this_hour.timestamp() * 1000)] != 0.0
|
assert rate[rate['date'] == this_hour].iloc[0]['open'] != 0.0
|
||||||
assert rate[int(prev_tick.timestamp() * 1000)] != 0.0
|
assert rate[rate['date'] == prev_hour].iloc[0]['open'] != 0.0
|
||||||
|
|
||||||
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
|
def test_ccxt_fetch_mark_price_history(self, exchange_futures):
|
||||||
exchange, exchangename = 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'] == prev_hour].iloc[0]['open'] != 0.0
|
||||||
assert mark_candles[mark_candles['date'] == this_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 (?)
|
# TODO: tests fetch_trades (?)
|
||||||
|
|
||||||
def test_ccxt_get_fee(self, exchange):
|
def test_ccxt_get_fee(self, exchange):
|
||||||
|
@ -3566,14 +3566,14 @@ def test__calculate_funding_fees(
|
|||||||
'gateio': funding_rate_history_octohourly,
|
'gateio': funding_rate_history_octohourly,
|
||||||
}[exchange][rate_start:rate_end]
|
}[exchange][rate_start:rate_end]
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history)
|
api_mock.fetch_funding_rate_history = get_mock_coro(return_value=funding_rate_history)
|
||||||
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
|
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={'fetchOHLCV': True})
|
||||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||||
funding_fees = exchange._calculate_funding_fees('ADA/USDT', amount, d1, d2)
|
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', [
|
@ pytest.mark.parametrize('exchange,expected_fees', [
|
||||||
@ -3590,8 +3590,8 @@ def test__calculate_funding_fees_datetime_called(
|
|||||||
expected_fees
|
expected_fees
|
||||||
):
|
):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_ohlcv = MagicMock(return_value=mark_ohlcv)
|
api_mock.fetch_ohlcv = get_mock_coro(return_value=mark_ohlcv)
|
||||||
api_mock.fetch_funding_rate_history = MagicMock(return_value=funding_rate_history_octohourly)
|
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={'fetchOHLCV': True})
|
||||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user