Changed funding fee implementation

This commit is contained in:
Sam Germain 2021-08-25 22:09:32 -06:00
parent 194bb24a55
commit b854350e8d
8 changed files with 30 additions and 146 deletions

View File

@ -1,6 +1,6 @@
""" Binance exchange subclass """
import logging
from typing import Dict, Optional
from typing import Dict
import ccxt
@ -89,17 +89,3 @@ class Binance(Exchange):
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
# https://www.binance.com/en/support/faq/360033525031
def get_funding_fee(
self,
contract_size: float,
mark_price: float,
rate: Optional[float],
# index_price: float,
# interest_rate: float
):
assert isinstance(rate, float)
nominal_value = mark_price * contract_size
adjustment = nominal_value * rate
return adjustment

View File

@ -1516,19 +1516,22 @@ class Exchange:
self._async_get_trade_history(pair=pair, since=since,
until=until, from_id=from_id))
def fetch_funding_rates(self):
return self._api.fetch_funding_rates()
# https://www.binance.com/en/support/faq/360033525031
def get_funding_fee(
self,
contract_size: float,
mark_price: float,
rate: Optional[float],
# index_price: float,
# interest_rate: float
):
raise OperationalException(f"{self.name} has not implemented get_funding_rate")
def get_funding_fees(self, pair: str, since: datetime):
try:
funding_history = self._api.fetch_funding_history(
pair=pair,
since=since
)
# TODO: sum all the funding fees in funding_history together
funding_fees = funding_history
return funding_fees
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get funding fees due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:

View File

@ -1,6 +1,6 @@
""" FTX exchange subclass """
import logging
from typing import Any, Dict, Optional
from typing import Any, Dict
import ccxt
@ -152,18 +152,3 @@ class Ftx(Exchange):
if order['type'] == 'stop':
return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['id']
# https://help.ftx.com/hc/en-us/articles/360027946571-Funding
def get_funding_fee(
self,
contract_size: float,
mark_price: float,
rate: Optional[float],
# index_price: float,
# interest_rate: float
):
"""
Always paid in USD on FTX # TODO: How do we account for this
"""
(contract_size * mark_price) / 24
return

View File

@ -103,7 +103,7 @@ class FreqtradeBot(LoggingMixin):
self._sell_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
self.trading_mode = TradingMode.SPOT
self.trading_mode = self.config['trading_mode']
if self.trading_mode == TradingMode.FUTURES:
self.funding_fee = FundingFee()
self.funding_fee.start()
@ -243,6 +243,10 @@ class FreqtradeBot(LoggingMixin):
open_trades = len(Trade.get_open_trades())
return max(0, self.config['max_open_trades'] - open_trades)
def get_funding_fees():
if self.trading_mode == TradingMode.FUTURES:
return
def update_open_orders(self):
"""
Updates open orders based on order list kept in the database.
@ -258,7 +262,6 @@ class FreqtradeBot(LoggingMixin):
try:
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
order.ft_order_side == 'stoploss')
self.update_trade_state(order.trade, order.order_id, fo)
except ExchangeError as e:

View File

@ -1 +1,2 @@
# flake8: noqa: F401
from freqtrade.leverage.interest import interest

View File

@ -1,88 +0,0 @@
from datetime import datetime, timedelta
from typing import List
import schedule
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade
class FundingFee:
trades: List[Trade]
# Binance
begin_times = [
# TODO-lev: Make these UTC time
"23:59:45",
"07:59:45",
"15:59:45",
]
exchange: Exchange
# FTX
# begin_times = every hour
def __init__(self, exchange: Exchange):
self.exchange = exchange
def _is_time_between(self, begin_time, end_time):
# If check time is not given, default to current UTC time
check_time = datetime.utcnow().time()
if begin_time < end_time:
return check_time >= begin_time and check_time <= end_time
else: # crosses midnight
return check_time >= begin_time or check_time <= end_time
def _apply_current_funding_fees(self):
funding_rates = self.exchange.fetch_funding_rates()
for trade in self.trades:
funding_rate = funding_rates[trade.pair]
self._apply_fee_to_trade(funding_rate, trade)
def _apply_fee_to_trade(self, funding_rate: dict, trade: Trade):
amount = trade.amount
mark_price = funding_rate['markPrice']
rate = funding_rate['fundingRate']
# index_price = funding_rate['indexPrice']
# interest_rate = funding_rate['interestRate']
funding_fee = self.exchange.get_funding_fee(
amount,
mark_price,
rate,
# interest_rate
# index_price,
)
trade.adjust_funding_fee(funding_fee)
def initial_funding_fee(self, amount) -> float:
# A funding fee interval is applied immediately if within 30s of an iterval
# May only exist on binance
for begin_string in self.begin_times:
begin_time = datetime.strptime(begin_string, "%H:%M:%S")
end_time = (begin_time + timedelta(seconds=30))
if self._is_time_between(begin_time.time(), end_time.time()):
return self._calculate(amount)
return 0.0
def start(self):
for interval in self.begin_times:
schedule.every().day.at(interval).do(self._apply_funding_fees())
# https://stackoverflow.com/a/30393162/6331353
# TODO-futures: Put schedule.run_pending() somewhere in the bot_loop
def reboot(self):
# TODO-futures Find out how many begin_times have passed since last funding_fee added
amount_missed = 0
self.apply_funding_fees(num_of=amount_missed)
self.start()
def add_new_trade(self, trade):
self.trades.append(trade)
def remove_trade(self, trade):
self.trades.remove(trade)

View File

@ -386,7 +386,7 @@ class Backtesting:
detail_data = detail_data.loc[
(detail_data['date'] >= sell_candle_time) &
(detail_data['date'] < sell_candle_end)
]
]
if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle
return self._get_sell_trade_entry_for_candle(trade, sell_row)

View File

@ -8,7 +8,7 @@ import pytest
from numpy import isnan
from freqtrade.edge import PairInfo
from freqtrade.enums import State, TradingMode
from freqtrade.enums import State
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.persistence.pairlock_middleware import PairLocks
@ -108,13 +108,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'trading_mode': TradingMode.SPOT,
'isolated_liq': None,
'is_short': False,
'leverage': 1.0,
'interest_rate': 0.0,
'funding_fee': None,
'last_funding_adjustment': None,
'isolated_liq': None,
'is_short': False,
}
mocker.patch('freqtrade.exchange.Exchange.get_rate',
@ -182,13 +179,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'trading_mode': TradingMode.SPOT,
'isolated_liq': None,
'is_short': False,
'leverage': 1.0,
'interest_rate': 0.0,
'funding_fee': None,
'last_funding_adjustment': None,
'isolated_liq': None,
'is_short': False,
}