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 """
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,

View File

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

View File

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

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
order['type'] = 'stop_loss'
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",
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 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