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 """
import logging
from typing import Dict
from typing import Dict, Optional
import ccxt

View File

@ -9,7 +9,7 @@ import logging
from copy import deepcopy
from datetime import datetime, timezone
from math import ceil
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple
import arrow
import ccxt
@ -361,7 +361,7 @@ class Exchange:
raise OperationalException(
'Could not load markets, therefore cannot start. '
'Please investigate the above error for more details.'
)
)
quote_currencies = self.get_quote_currencies()
if stake_currency not in quote_currencies:
raise OperationalException(
@ -1516,35 +1516,13 @@ class Exchange:
self._async_get_trade_history(pair=pair, since=since,
until=until, from_id=from_id))
@retrier
def get_funding_fees(self, pair: str, since: Union[datetime, int]) -> float:
"""
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
"""
# https://www.binance.com/en/support/faq/360033525031
def fetch_funding_rate(self):
if not self.exchange_has("fetchFundingHistory"):
raise OperationalException(
f"fetch_funding_history() has not been implemented on ccxt.{self.name}")
if type(since) is datetime:
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
return self._api.fetch_funding_rates()
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
from typing import Any, Dict, Optional
import ccxt

View File

@ -242,11 +242,6 @@ class FreqtradeBot(LoggingMixin):
open_trades = len(Trade.get_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):
"""
Updates open orders based on order list kept in the database.

View File

@ -1,2 +1,3 @@
# flake8: noqa: F401
from freqtrade.leverage.funding_fees import funding_fee
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.enums import SellType, TradingMode
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.persistence.migrations import check_migrate
@ -707,6 +707,7 @@ class LocalTrade():
return float(self._calc_base_close(amount, rate, fee) - total_interest)
elif (trading_mode == TradingMode.FUTURES):
self.add_funding_fees()
funding_fees = self.funding_fees or 0.0
return float(self._calc_base_close(amount, rate, fee)) + funding_fees
else:
@ -785,6 +786,16 @@ class LocalTrade():
else:
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
def get_trades_proxy(*, pair: str = None, is_open: bool = 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
@pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
def test_get_funding_fees(default_conf, mocker, exchange_name):
api_mock = MagicMock()
api_mock.fetch_funding_history = MagicMock(return_value=[
{
'amount': 0.14542341,
'code': 'USDT',
'datetime': '2021-09-01T08:00:01.000Z',
'id': '485478',
'info': {'asset': 'USDT',
'income': '0.14542341',
'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT',
'time': '1630512001000',
'tradeId': '',
'tranId': '4854789484855218760'},
'symbol': 'XRP/USDT',
'timestamp': 1630512001000
},
{
'amount': -0.14642341,
'code': 'USDT',
'datetime': '2021-09-01T16:00:01.000Z',
'id': '485479',
'info': {'asset': 'USDT',
'income': '-0.14642341',
'incomeType': 'FUNDING_FEE',
'info': 'FUNDING_FEE',
'symbol': 'XRPUSDT',
'time': '1630512001000',
'tradeId': '',
'tranId': '4854789484855218760'},
'symbol': 'XRP/USDT',
'timestamp': 1630512001000
}
])
type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True})
# @pytest.mark.parametrize("exchange_name", ['binance', 'ftx'])
# def test_get_funding_fees(default_conf, mocker, exchange_name):
# api_mock = MagicMock()
# api_mock.fetch_funding_history = MagicMock(return_value=[
# {
# 'amount': 0.14542341,
# 'code': 'USDT',
# 'datetime': '2021-09-01T08:00:01.000Z',
# 'id': '485478',
# 'info': {'asset': 'USDT',
# 'income': '0.14542341',
# 'incomeType': 'FUNDING_FEE',
# 'info': 'FUNDING_FEE',
# 'symbol': 'XRPUSDT',
# 'time': '1630512001000',
# 'tradeId': '',
# 'tranId': '4854789484855218760'},
# 'symbol': 'XRP/USDT',
# 'timestamp': 1630512001000
# },
# {
# 'amount': -0.14642341,
# 'code': 'USDT',
# 'datetime': '2021-09-01T16:00:01.000Z',
# 'id': '485479',
# 'info': {'asset': 'USDT',
# 'income': '-0.14642341',
# 'incomeType': 'FUNDING_FEE',
# 'info': 'FUNDING_FEE',
# 'symbol': 'XRPUSDT',
# 'time': '1630512001000',
# 'tradeId': '',
# 'tranId': '4854789484855218760'},
# 'symbol': 'XRP/USDT',
# 'timestamp': 1630512001000
# }
# ])
# type(api_mock).has = PropertyMock(return_value={'fetchFundingHistory': True})
# mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
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')
unix_time = int(date_time.strftime('%s'))
expected_fees = -0.001 # 0.14542341 + -0.14642341
fees_from_datetime = exchange.get_funding_fees(
pair='XRP/USDT',
since=date_time
)
fees_from_unix_time = exchange.get_funding_fees(
pair='XRP/USDT',
since=unix_time
)
# # mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', lambda pair, since: y)
# 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')
# unix_time = int(date_time.strftime('%s'))
# expected_fees = -0.001 # 0.14542341 + -0.14642341
# fees_from_datetime = exchange.get_funding_fees(
# pair='XRP/USDT',
# since=date_time
# )
# fees_from_unix_time = exchange.get_funding_fees(
# pair='XRP/USDT',
# since=unix_time
# )
assert(isclose(expected_fees, fees_from_datetime))
assert(isclose(expected_fees, fees_from_unix_time))
# assert(isclose(expected_fees, fees_from_datetime))
# assert(isclose(expected_fees, fees_from_unix_time))
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
exchange_name,
"get_funding_fees",
"fetch_funding_history",
pair="XRP/USDT",
since=unix_time
)
# ccxt_exceptionhandlers(
# mocker,
# default_conf,
# api_mock,
# exchange_name,
# "get_funding_fees",
# "fetch_funding_history",
# pair="XRP/USDT",
# since=unix_time
# )