Added funding_fee method headers to exchange, and implemented some of the methods

This commit is contained in:
Sam Germain 2021-09-09 01:19:24 -06:00
parent d559b6d6c6
commit d54117990b
8 changed files with 78 additions and 25 deletions

View File

@ -3,12 +3,12 @@ import logging
from typing import Dict, List, Optional from typing import Dict, List, Optional
import ccxt import ccxt
from datetime import time
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError) OperationalException, TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.utils import hours_to_time
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,7 +23,7 @@ class Binance(Exchange):
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
} }
funding_fee_times: List[time] = hours_to_time([0, 8, 16]) funding_fee_times: List[int] = [0, 8, 16] # hours of the day
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
""" """

View File

@ -7,7 +7,7 @@ import http
import inspect import inspect
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime, time, timezone from datetime import datetime, timedelta, 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, Union
@ -69,7 +69,7 @@ class Exchange:
"l2_limit_range_required": True, # Allow Empty L2 limit (kucoin) "l2_limit_range_required": True, # Allow Empty L2 limit (kucoin)
} }
_ft_has: Dict = {} _ft_has: Dict = {}
funding_fee_times: List[time] = [] funding_fee_times: List[int] = [] # hours of the day
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None: def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
""" """
@ -1555,6 +1555,21 @@ class Exchange:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
def get_mark_price(self, pair: str, when: datetime):
"""
Get's the value of the underlying asset for a futures contract
at a specific date and time in the past
"""
# TODO-lev: implement
raise OperationalException(f"get_mark_price has not been implemented for {self.name}")
def get_funding_rate(self, pair: str, when: datetime):
"""
Get's the funding_rate for a pair at a specific date and time in the past
"""
# TODO-lev: implement
raise OperationalException(f"get_funding_rate has not been implemented for {self.name}")
def _get_funding_fee( def _get_funding_fee(
self, self,
contract_size: float, contract_size: float,
@ -1572,6 +1587,45 @@ class Exchange:
""" """
raise OperationalException(f"Funding fee has not been implemented for {self.name}") raise OperationalException(f"Funding fee has not been implemented for {self.name}")
def get_funding_fee_dates(self, open_date: datetime, close_date: datetime):
"""
Get's the date and time of every funding fee that happened between two datetimes
"""
open_date = datetime(open_date.year, open_date.month, open_date.day, open_date.hour)
close_date = datetime(close_date.year, close_date.month, close_date.day, close_date.hour)
results = []
date_iterator = open_date
while date_iterator < close_date:
date_iterator += timedelta(hours=1)
if date_iterator.hour in self.funding_fee_times:
results.append(date_iterator)
return results
def calculate_funding_fees(
self,
pair: str,
amount: float,
open_date: datetime,
close_date: datetime
) -> float:
"""
calculates the sum of all funding fees that occurred for a pair during a futures trade
:param pair: The quote/base pair of the trade
:param amount: The quantity of the trade
:param open_date: The date and time that the trade started
:param close_date: The date and time that the trade ended
"""
fees: float = 0
for date in self.get_funding_fee_dates(open_date, close_date):
funding_rate = self.get_funding_rate(pair, date)
mark_price = self.get_mark_price(pair, date)
fees += self._get_funding_fee(amount, mark_price, funding_rate)
return fees
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:
return exchange_name in ccxt_exchanges(ccxt_module) return exchange_name in ccxt_exchanges(ccxt_module)

View File

@ -3,13 +3,13 @@ import logging
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import ccxt import ccxt
from datetime import time
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError) OperationalException, TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT, retrier
from freqtrade.misc import safe_value_fallback2 from freqtrade.misc import safe_value_fallback2
from freqtrade.utils import hours_to_time
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -20,7 +20,7 @@ class Ftx(Exchange):
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
} }
funding_fee_times: List[time] = hours_to_time(list(range(0, 23))) funding_fee_times: List[int] = list(range(0, 23))
def market_is_tradable(self, market: Dict[str, Any]) -> bool: def market_is_tradable(self, market: Dict[str, Any]) -> bool:
""" """
@ -159,9 +159,7 @@ class Ftx(Exchange):
contract_size: float, contract_size: float,
mark_price: float, mark_price: float,
funding_rate: Optional[float], funding_rate: Optional[float],
# index_price: float, ) -> float:
# interest_rate: float)
):
""" """
Calculates a single funding fee Calculates a single funding fee
Always paid in USD on FTX # TODO: How do we account for this Always paid in USD on FTX # TODO: How do we account for this
@ -169,5 +167,4 @@ class Ftx(Exchange):
:param mark_price: The price of the asset that the contract is based off of :param mark_price: The price of the asset that the contract is based off of
:param funding_rate: Must be None on ftx :param funding_rate: Must be None on ftx
""" """
(contract_size * mark_price) / 24 return (contract_size * mark_price) / 24
return

View File

@ -3,12 +3,12 @@ import logging
from typing import Any, Dict, List from typing import Any, Dict, List
import ccxt import ccxt
from datetime import time
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
OperationalException, TemporaryError) OperationalException, TemporaryError)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.utils import hours_to_time
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,7 +22,7 @@ class Kraken(Exchange):
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "since", "trades_pagination_arg": "since",
} }
funding_fee_times: List[time] = hours_to_time([0, 4, 8, 12, 16, 20]) funding_fee_times: List[int] = [0, 4, 8, 12, 16, 20] # hours of the day
def market_is_tradable(self, market: Dict[str, Any]) -> bool: def market_is_tradable(self, market: Dict[str, Any]) -> bool:
""" """

View File

@ -4,13 +4,13 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
import copy import copy
import logging import logging
import traceback import traceback
import schedule
from datetime import datetime, timezone from datetime import datetime, timezone
from math import isclose from math import isclose
from threading import Lock from threading import Lock
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import arrow import arrow
import schedule
from freqtrade import __version__, constants from freqtrade import __version__, constants
from freqtrade.configuration import validate_config_consistency from freqtrade.configuration import validate_config_consistency
@ -251,7 +251,10 @@ class FreqtradeBot(LoggingMixin):
def update_funding_fees(self): def update_funding_fees(self):
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
for trade in Trade.get_open_trades(): for trade in Trade.get_open_trades():
funding_fees = self.exchange.get_funding_fees(trade.pair, trade.open_date) funding_fees = self.exchange.get_funding_fees_from_exchange(
trade.pair,
trade.open_date
)
trade.funding_fees = funding_fees trade.funding_fees = funding_fees
def update_open_orders(self): def update_open_orders(self):
@ -583,7 +586,7 @@ class FreqtradeBot(LoggingMixin):
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
open_date = datetime.utcnow() open_date = datetime.utcnow()
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
funding_fees = self.exchange.get_funding_fees(pair, open_date) funding_fees = self.exchange.get_funding_fees_from_exchange(pair, open_date)
else: else:
funding_fees = 0.0 funding_fees = 0.0

View File

@ -1,3 +1,2 @@
# 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

@ -2972,11 +2972,11 @@ def test_get_funding_fees(default_conf, mocker, 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_from_exchange(
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_from_exchange(
pair='XRP/USDT', pair='XRP/USDT',
since=unix_time since=unix_time
) )
@ -2989,7 +2989,7 @@ def test_get_funding_fees(default_conf, mocker, exchange_name):
default_conf, default_conf,
api_mock, api_mock,
exchange_name, exchange_name,
"get_funding_fees", "get_funding_fees_from_exchange",
"fetch_funding_history", "fetch_funding_history",
pair="XRP/USDT", pair="XRP/USDT",
since=unix_time since=unix_time

View File

@ -112,7 +112,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'interest_rate': 0.0, 'interest_rate': 0.0,
'isolated_liq': None, 'isolated_liq': None,
'is_short': False, 'is_short': False,
'funding_fees': None, 'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT 'trading_mode': TradingMode.SPOT
} }
@ -185,7 +185,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'interest_rate': 0.0, 'interest_rate': 0.0,
'isolated_liq': None, 'isolated_liq': None,
'is_short': False, 'is_short': False,
'funding_fees': None, 'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT 'trading_mode': TradingMode.SPOT
} }