From dfb9937436a8dd5ad9c98e2cdfb9bf1437029bf5 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 9 Sep 2021 01:43:05 -0600 Subject: [PATCH] Added tests and docstring to exchange funding_fee methods, removed utils --- freqtrade/exchange/binance.py | 8 ++++ freqtrade/exchange/exchange.py | 12 ++--- freqtrade/exchange/ftx.py | 14 ++++-- freqtrade/leverage/funding_fees.py | 75 ++++++++++++++++++++++++++++++ freqtrade/utils/__init__.py | 2 - freqtrade/utils/hours_to_time.py | 11 ----- tests/exchange/test_binance.py | 8 ++++ tests/exchange/test_exchange.py | 12 +++++ tests/exchange/test_ftx.py | 16 +++++++ 9 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 freqtrade/leverage/funding_fees.py delete mode 100644 freqtrade/utils/__init__.py delete mode 100644 freqtrade/utils/hours_to_time.py diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index aa18634cf..4161b627d 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -1,5 +1,6 @@ """ Binance exchange subclass """ import logging +from datetime import datetime from typing import Dict, List, Optional import ccxt @@ -91,6 +92,13 @@ class Binance(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e + def _get_funding_rate(self, pair: str, when: datetime) -> Optional[float]: + """ + Get's the funding_rate for a pair at a specific date and time in the past + """ + # TODO-lev: implement + raise OperationalException("_get_funding_rate has not been implement on binance") + def _get_funding_fee( self, contract_size: float, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index c9a932bff..3236ee8f8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1555,7 +1555,7 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e - def get_mark_price(self, pair: str, when: datetime): + 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 @@ -1563,7 +1563,7 @@ class Exchange: # 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): + 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 """ @@ -1587,7 +1587,7 @@ class Exchange: """ raise OperationalException(f"Funding fee has not been implemented for {self.name}") - def get_funding_fee_dates(self, open_date: datetime, close_date: datetime): + 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 """ @@ -1619,9 +1619,9 @@ class Exchange: """ 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) + 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 diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 42d7ce050..11af26b32 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -3,7 +3,7 @@ import logging from typing import Any, Dict, List, Optional import ccxt - +from datetime import datetime from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -154,6 +154,10 @@ class Ftx(Exchange): return safe_value_fallback2(order, order, 'id_stop', 'id') return order['id'] + def _get_funding_rate(self, pair: str, when: datetime) -> Optional[float]: + """FTX doesn't use this""" + return None + def _get_funding_fee( self, contract_size: float, @@ -162,9 +166,9 @@ class Ftx(Exchange): ) -> float: """ Calculates a single funding fee - Always paid in USD on FTX # TODO: How do we account for this - :param contract_size: The amount/quanity - :param mark_price: The price of the asset that the contract is based off of - :param funding_rate: Must be None on ftx + Always paid in USD on FTX # TODO: How do we account for this + : param contract_size: The amount/quanity + : param mark_price: The price of the asset that the contract is based off of + : param funding_rate: Must be None on ftx """ return (contract_size * mark_price) / 24 diff --git a/freqtrade/leverage/funding_fees.py b/freqtrade/leverage/funding_fees.py new file mode 100644 index 000000000..e6e9e9f0d --- /dev/null +++ b/freqtrade/leverage/funding_fees.py @@ -0,0 +1,75 @@ +from datetime import datetime, time +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, + funding_times: [time] + # 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 funding_times: + # 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 diff --git a/freqtrade/utils/__init__.py b/freqtrade/utils/__init__.py deleted file mode 100644 index e6e76c589..000000000 --- a/freqtrade/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# flake8: noqa: F401 -from freqtrade.utils.hours_to_time import hours_to_time diff --git a/freqtrade/utils/hours_to_time.py b/freqtrade/utils/hours_to_time.py deleted file mode 100644 index 139fd83a1..000000000 --- a/freqtrade/utils/hours_to_time.py +++ /dev/null @@ -1,11 +0,0 @@ -from datetime import datetime, time -from typing import List - - -def hours_to_time(hours: List[int]) -> List[time]: - ''' - :param hours: a list of hours as a time of day (e.g. [1, 16] is 01:00 and 16:00 o'clock) - :return: a list of datetime time objects that correspond to the hours in hours - ''' - # TODO-lev: These must be utc time - return [datetime.strptime(str(t), '%H').time() for t in hours] diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index f2b508761..6e51dd22d 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -105,3 +105,11 @@ def test_stoploss_adjust_binance(mocker, default_conf): # Test with invalid order case order['type'] = 'stop_loss' assert not exchange.stoploss_adjust(1501, order) + + +def test_get_funding_rate(): + return + + +def test__get_funding_fee(): + return diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1d23482fc..dc8e9ca2f 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2994,3 +2994,15 @@ def test_get_funding_fees(default_conf, mocker, exchange_name): pair="XRP/USDT", since=unix_time ) + + +def test_get_mark_price(): + return + + +def test_get_funding_fee_dates(): + return + + +def test_calculate_funding_fees(): + return diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3794bb79c..a4281c595 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta from random import randint from unittest.mock import MagicMock @@ -191,3 +192,18 @@ def test_get_order_id(mocker, default_conf): } } assert exchange.get_order_id_conditional(order) == '1111' + + +@pytest.mark.parametrize("pair,when", [ + ('XRP/USDT', datetime.utcnow()), + ('ADA/BTC', datetime.utcnow()), + ('XRP/USDT', datetime.utcnow() - timedelta(hours=30)), +]) +def test__get_funding_rate(default_conf, mocker, pair, when): + api_mock = MagicMock() + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="ftx") + assert exchange._get_funding_rate(pair, when) is None + + +def test__get_funding_fee(): + return