Added tests and docstring to exchange funding_fee methods, removed utils
This commit is contained in:
parent
d54117990b
commit
dfb9937436
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
75
freqtrade/leverage/funding_fees.py
Normal file
75
freqtrade/leverage/funding_fees.py
Normal 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
|
@ -1,2 +0,0 @@
|
|||||||
# flake8: noqa: F401
|
|
||||||
from freqtrade.utils.hours_to_time import hours_to_time
|
|
@ -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]
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user