Changed funding fee tracking method, need to get funding_rate and open prices at multiple candles

This commit is contained in:
Sam Germain 2021-09-06 02:24:15 -06:00
parent 92e630eb69
commit f5248be043
8 changed files with 157 additions and 98 deletions

View File

@ -1,6 +1,6 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import logging import logging
from typing import Dict from typing import Dict, Optional
import ccxt import ccxt

View File

@ -9,7 +9,7 @@ import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timezone from datetime import datetime, timezone
from math import ceil from math import ceil
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple
import arrow import arrow
import ccxt import ccxt
@ -1516,35 +1516,13 @@ 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))
@retrier # https://www.binance.com/en/support/faq/360033525031
def get_funding_fees(self, pair: str, since: Union[datetime, int]) -> float: def fetch_funding_rate(self):
"""
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
:param pair: (e.g. ADA/USDT)
:param since: The earliest time of consideration for calculating funding fees,
in unix time or as a datetime
"""
if not self.exchange_has("fetchFundingHistory"): if not self.exchange_has("fetchFundingHistory"):
raise OperationalException( raise OperationalException(
f"fetch_funding_history() has not been implemented on ccxt.{self.name}") f"fetch_funding_history() has not been implemented on ccxt.{self.name}")
if type(since) is datetime: return self._api.fetch_funding_rates()
since = int(since.strftime('%s'))
try:
funding_history = self._api.fetch_funding_history(
pair=pair,
since=since
)
return sum(fee['amount'] for fee in funding_history)
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: 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 from typing import Any, Dict, Optional
import ccxt import ccxt

View File

@ -242,11 +242,6 @@ 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 add_funding_fees(self, trade: Trade):
if self.trading_mode == TradingMode.FUTURES:
funding_fees = self.exchange.get_funding_fees(trade.pair, trade.open_date)
trade.funding_fees = funding_fees
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.

View File

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

View File

@ -0,0 +1,74 @@
from datetime import datetime
from typing import Optional
from freqtrade.exceptions import OperationalException
def funding_fees(
exchange_name: str,
pair: str,
contract_size: float,
open_date: datetime,
close_date: datetime
# index_price: float,
# interest_rate: float
):
"""
Equation to calculate funding_fees on futures trades
:param exchange_name: The exchanged being trading on
:param borrowed: The amount of currency being borrowed
:param rate: The rate of interest
:param hours: The time in hours that the currency has been borrowed for
Raises:
OperationalException: Raised if freqtrade does
not support margin trading for this exchange
Returns: The amount of interest owed (currency matches borrowed)
"""
exchange_name = exchange_name.lower()
# fees = 0
if exchange_name == "binance":
for timeslot in ["23:59:45", "07:59:45", "15:59:45"]:
# for each day in close_date - open_date
# mark_price = mark_price at this time
# rate = rate at this time
# fees = fees + funding_fee(exchange_name, contract_size, mark_price, rate)
# return fees
return
elif exchange_name == "kraken":
raise OperationalException("Funding_fees has not been implemented for Kraken")
elif exchange_name == "ftx":
# for timeslot in every hour since open_date:
# mark_price = mark_price at this time
# fees = fees + funding_fee(exchange_name, contract_size, mark_price, rate)
return
else:
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
def funding_fee(
exchange_name: str,
contract_size: float,
mark_price: float,
rate: Optional[float],
# index_price: float,
# interest_rate: float
):
"""
Calculates a single funding fee
"""
if exchange_name == "binance":
assert isinstance(rate, float)
nominal_value = mark_price * contract_size
adjustment = nominal_value * rate
return adjustment
elif exchange_name == "kraken":
raise OperationalException("Funding fee has not been implemented for kraken")
elif exchange_name == "ftx":
"""
Always paid in USD on FTX # TODO: How do we account for this
"""
(contract_size * mark_price) / 24
return

View File

@ -16,7 +16,7 @@ from sqlalchemy.sql.schema import UniqueConstraint
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
from freqtrade.enums import SellType, TradingMode from freqtrade.enums import SellType, TradingMode
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.leverage import interest from freqtrade.leverage import funding_fees, interest
from freqtrade.misc import safe_value_fallback from freqtrade.misc import safe_value_fallback
from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.migrations import check_migrate
@ -707,6 +707,7 @@ class LocalTrade():
return float(self._calc_base_close(amount, rate, fee) - total_interest) return float(self._calc_base_close(amount, rate, fee) - total_interest)
elif (trading_mode == TradingMode.FUTURES): elif (trading_mode == TradingMode.FUTURES):
self.add_funding_fees()
funding_fees = self.funding_fees or 0.0 funding_fees = self.funding_fees or 0.0
return float(self._calc_base_close(amount, rate, fee)) + funding_fees return float(self._calc_base_close(amount, rate, fee)) + funding_fees
else: else:
@ -785,6 +786,16 @@ class LocalTrade():
else: else:
return None return None
def add_funding_fees(self):
if self.trading_mode == TradingMode.FUTURES:
self.funding_fees = funding_fees(
self.exchange,
self.pair,
self.amount,
self.open_date_utc,
self.close_date_utc
)
@staticmethod @staticmethod
def get_trades_proxy(*, pair: str = None, is_open: bool = None, def get_trades_proxy(*, pair: str = None, is_open: bool = None,
open_date: datetime = None, close_date: datetime = None, open_date: datetime = None, close_date: datetime = None,

View File

@ -2928,69 +2928,69 @@ def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected assert calculate_backoff(retrycount, max_retries) == expected
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx']) # @pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
def test_get_funding_fees(default_conf, mocker, exchange_name): # def test_get_funding_fees(default_conf, mocker, exchange_name):
api_mock = MagicMock() # api_mock = MagicMock()
api_mock.fetch_funding_history = MagicMock(return_value=[ # api_mock.fetch_funding_history = MagicMock(return_value=[
{ # {
'amount': 0.14542341, # 'amount': 0.14542341,
'code': 'USDT', # 'code': 'USDT',
'datetime': '2021-09-01T08:00:01.000Z', # 'datetime': '2021-09-01T08:00:01.000Z',
'id': '485478', # 'id': '485478',
'info': {'asset': 'USDT', # 'info': {'asset': 'USDT',
'income': '0.14542341', # 'income': '0.14542341',
'incomeType': 'FUNDING_FEE', # 'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE', # 'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT', # 'symbol': 'XRPUSDT',
'time': '1630512001000', # 'time': '1630512001000',
'tradeId': '', # 'tradeId': '',
'tranId': '4854789484855218760'}, # 'tranId': '4854789484855218760'},
'symbol': 'XRP/USDT', # 'symbol': 'XRP/USDT',
'timestamp': 1630512001000 # 'timestamp': 1630512001000
}, # },
{ # {
'amount': -0.14642341, # 'amount': -0.14642341,
'code': 'USDT', # 'code': 'USDT',
'datetime': '2021-09-01T16:00:01.000Z', # 'datetime': '2021-09-01T16:00:01.000Z',
'id': '485479', # 'id': '485479',
'info': {'asset': 'USDT', # 'info': {'asset': 'USDT',
'income': '-0.14642341', # 'income': '-0.14642341',
'incomeType': 'FUNDING_FEE', # 'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE', # 'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT', # 'symbol': 'XRPUSDT',
'time': '1630512001000', # 'time': '1630512001000',
'tradeId': '', # 'tradeId': '',
'tranId': '4854789484855218760'}, # 'tranId': '4854789484855218760'},
'symbol': 'XRP/USDT', # 'symbol': 'XRP/USDT',
'timestamp': 1630512001000 # 'timestamp': 1630512001000
} # }
]) # ])
type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True}) # type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y) # # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) # exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ') # date_time = datetime.strptime("2021-09-01T00:00:01.000Z", '%Y-%m-%dT%H:%M:%S.%fZ')
unix_time = int(date_time.strftime('%s')) # unix_time = int(date_time.strftime('%s'))
expected_fees = -0.001 # 0.14542341 + -0.14642341 # expected_fees = -0.001 # 0.14542341 + -0.14642341
fees_from_datetime = exchange.get_funding_fees( # fees_from_datetime = exchange.get_funding_fees(
pair='XRP/USDT', # pair='XRP/USDT',
since=date_time # since=date_time
) # )
fees_from_unix_time = exchange.get_funding_fees( # fees_from_unix_time = exchange.get_funding_fees(
pair='XRP/USDT', # pair='XRP/USDT',
since=unix_time # since=unix_time
) # )
assert(isclose(expected_fees, fees_from_datetime)) # assert(isclose(expected_fees, fees_from_datetime))
assert(isclose(expected_fees, fees_from_unix_time)) # assert(isclose(expected_fees, fees_from_unix_time))
ccxt_exceptionhandlers( # ccxt_exceptionhandlers(
mocker, # mocker,
default_conf, # default_conf,
api_mock, # api_mock,
exchange_name, # exchange_name,
"get_funding_fees", # "get_funding_fees",
"fetch_funding_history", # "fetch_funding_history",
pair="XRP/USDT", # pair="XRP/USDT",
since=unix_time # since=unix_time
) # )