Changed funding fee tracking method, need to get funding_rate and open prices at multiple candles
This commit is contained in:
parent
92e630eb69
commit
f5248be043
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
74
freqtrade/leverage/funding_fees.py
Normal file
74
freqtrade/leverage/funding_fees.py
Normal 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
|
@ -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,
|
||||||
|
@ -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
|
||||||
)
|
# )
|
||||||
|
Loading…
Reference in New Issue
Block a user