Changed funding fee implementation
This commit is contained in:
parent
194bb24a55
commit
b854350e8d
@ -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
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
|
||||||
|
@ -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:
|
||||||
|
@ -1 +1,2 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
from freqtrade.leverage.interest import interest
|
||||||
|
@ -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)
|
|
@ -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)
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user