Added tests and docstring to exchange funding_fee methods, removed utils

This commit is contained in:
Sam Germain 2021-09-09 01:43:05 -06:00
parent d54117990b
commit dfb9937436
9 changed files with 134 additions and 24 deletions

View File

@ -1,5 +1,6 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import logging import logging
from datetime import datetime
from typing import Dict, List, Optional from typing import Dict, List, Optional
import ccxt import ccxt
@ -91,6 +92,13 @@ class Binance(Exchange):
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from 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( def _get_funding_fee(
self, self,
contract_size: float, contract_size: float,

View File

@ -1555,7 +1555,7 @@ 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): def _get_mark_price(self, pair: str, when: datetime):
""" """
Get's the value of the underlying asset for a futures contract Get's the value of the underlying asset for a futures contract
at a specific date and time in the past at a specific date and time in the past
@ -1563,7 +1563,7 @@ class Exchange:
# TODO-lev: implement # TODO-lev: implement
raise OperationalException(f"get_mark_price has not been implemented for {self.name}") 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 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}") 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 Get's the date and time of every funding fee that happened between two datetimes
""" """
@ -1619,9 +1619,9 @@ class Exchange:
""" """
fees: float = 0 fees: float = 0
for date in self.get_funding_fee_dates(open_date, close_date): for date in self._get_funding_fee_dates(open_date, close_date):
funding_rate = self.get_funding_rate(pair, date) funding_rate = self._get_funding_rate(pair, date)
mark_price = self.get_mark_price(pair, date) mark_price = self._get_mark_price(pair, date)
fees += self._get_funding_fee(amount, mark_price, funding_rate) fees += self._get_funding_fee(amount, mark_price, funding_rate)
return fees return fees

View File

@ -3,7 +3,7 @@ import logging
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import ccxt import ccxt
from datetime import datetime
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
@ -154,6 +154,10 @@ class Ftx(Exchange):
return safe_value_fallback2(order, order, 'id_stop', 'id') return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['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( def _get_funding_fee(
self, self,
contract_size: float, contract_size: float,
@ -162,9 +166,9 @@ class Ftx(Exchange):
) -> float: ) -> 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
:param contract_size: The amount/quanity : param contract_size: The amount/quanity
: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
""" """
return (contract_size * mark_price) / 24 return (contract_size * mark_price) / 24

View File

@ -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

View File

@ -1,2 +0,0 @@
# flake8: noqa: F401
from freqtrade.utils.hours_to_time import hours_to_time

View File

@ -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]

View File

@ -105,3 +105,11 @@ def test_stoploss_adjust_binance(mocker, default_conf):
# Test with invalid order case # Test with invalid order case
order['type'] = 'stop_loss' order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order) assert not exchange.stoploss_adjust(1501, order)
def test_get_funding_rate():
return
def test__get_funding_fee():
return

View File

@ -2994,3 +2994,15 @@ def test_get_funding_fees(default_conf, mocker, exchange_name):
pair="XRP/USDT", pair="XRP/USDT",
since=unix_time since=unix_time
) )
def test_get_mark_price():
return
def test_get_funding_fee_dates():
return
def test_calculate_funding_fees():
return

View File

@ -1,3 +1,4 @@
from datetime import datetime, timedelta
from random import randint from random import randint
from unittest.mock import MagicMock 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' 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