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 """ """ Binance exchange subclass """
import logging import logging
from typing import Dict, Optional from typing import Dict
import ccxt import ccxt
@ -89,17 +89,3 @@ class Binance(Exchange):
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from 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, self._async_get_trade_history(pair=pair, since=since,
until=until, from_id=from_id)) until=until, from_id=from_id))
def fetch_funding_rates(self): def get_funding_fees(self, pair: str, since: datetime):
return self._api.fetch_funding_rates() try:
funding_history = self._api.fetch_funding_history(
# https://www.binance.com/en/support/faq/360033525031 pair=pair,
def get_funding_fee( since=since
self, )
contract_size: float, # TODO: sum all the funding fees in funding_history together
mark_price: float, funding_fees = funding_history
rate: Optional[float], return funding_fees
# index_price: float, except ccxt.DDoSProtection as e:
# interest_rate: float raise DDosProtection(e) from e
): except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise OperationalException(f"{self.name} has not implemented get_funding_rate") 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: def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:

View File

@ -1,6 +1,6 @@
""" FTX exchange subclass """ """ FTX exchange subclass """
import logging import logging
from typing import Any, Dict, Optional from typing import Any, Dict
import ccxt import ccxt
@ -152,18 +152,3 @@ class Ftx(Exchange):
if order['type'] == 'stop': if order['type'] == 'stop':
return safe_value_fallback2(order, order, 'id_stop', 'id') return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['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() self._sell_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) 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: if self.trading_mode == TradingMode.FUTURES:
self.funding_fee = FundingFee() self.funding_fee = FundingFee()
self.funding_fee.start() self.funding_fee.start()
@ -243,6 +243,10 @@ class FreqtradeBot(LoggingMixin):
open_trades = len(Trade.get_open_trades()) open_trades = len(Trade.get_open_trades())
return max(0, self.config['max_open_trades'] - 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): def update_open_orders(self):
""" """
Updates open orders based on order list kept in the database. Updates open orders based on order list kept in the database.
@ -258,7 +262,6 @@ class FreqtradeBot(LoggingMixin):
try: try:
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
order.ft_order_side == 'stoploss') order.ft_order_side == 'stoploss')
self.update_trade_state(order.trade, order.order_id, fo) self.update_trade_state(order.trade, order.order_id, fo)
except ExchangeError as e: except ExchangeError as e:

View File

@ -1 +1,2 @@
# flake8: noqa: F401 # 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 = detail_data.loc[
(detail_data['date'] >= sell_candle_time) & (detail_data['date'] >= sell_candle_time) &
(detail_data['date'] < sell_candle_end) (detail_data['date'] < sell_candle_end)
] ]
if len(detail_data) == 0: if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle # 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) return self._get_sell_trade_entry_for_candle(trade, sell_row)

View File

@ -8,7 +8,7 @@ import pytest
from numpy import isnan from numpy import isnan
from freqtrade.edge import PairInfo 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.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.persistence.pairlock_middleware import PairLocks 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, 'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'trading_mode': TradingMode.SPOT,
'isolated_liq': None,
'is_short': False,
'leverage': 1.0, 'leverage': 1.0,
'interest_rate': 0.0, 'interest_rate': 0.0,
'funding_fee': None, 'isolated_liq': None,
'last_funding_adjustment': None, 'is_short': False,
} }
mocker.patch('freqtrade.exchange.Exchange.get_rate', 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, 'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None, 'open_order': None,
'exchange': 'binance', 'exchange': 'binance',
'trading_mode': TradingMode.SPOT,
'isolated_liq': None,
'is_short': False,
'leverage': 1.0, 'leverage': 1.0,
'interest_rate': 0.0, 'interest_rate': 0.0,
'funding_fee': None, 'isolated_liq': None,
'last_funding_adjustment': None, 'is_short': False,
} }