strengthened and fixed leverage_tier tests

This commit is contained in:
Sam Germain 2022-02-10 06:10:15 -06:00
parent a6043e6a85
commit 03b3756e4b
4 changed files with 140 additions and 54 deletions

View File

@ -1899,22 +1899,22 @@ class Exchange:
# When exchanges can load all their leverage tiers at once in the constructor
# then this method does nothing, it should only be implemented when the leverage
# tiers requires per symbol fetching to avoid excess api calls
if pair not in self._leverage_tiers:
self._leverage_tiers[pair] = []
if (
self._api.has['fetchLeverageTiers'] and
not self._ft_has['can_fetch_multiple_tiers'] and
self.trading_mode == TradingMode.FUTURES
):
self._leverage_tiers[pair] = []
try:
tiers = self._api.fetch_leverage_tiers(pair)
for tier in tiers[pair]:
self._leverage_tiers[pair].append(self.parse_leverage_tier(tier))
return tiers
except ccxt.BadRequest:
return []
else:
return []
return self._leverage_tiers[pair]
def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float:
"""
@ -1934,10 +1934,7 @@ class Exchange:
f'{self.name}.get_max_leverage requires argument stake_amount'
)
if pair not in self._leverage_tiers:
self.get_leverage_tiers_for_pair(pair)
pair_tiers = self._leverage_tiers[pair]
pair_tiers = self.get_leverage_tiers_for_pair(pair)
num_tiers = len(pair_tiers)
if num_tiers < 1:
return 1.0
@ -2282,23 +2279,30 @@ class Exchange:
"""
if self._api.has['fetchLeverageTiers']:
if pair not in self._leverage_tiers:
# Used when fetchLeverageTiers cannot fetch all symbols at once
tiers = self.get_leverage_tiers_for_pair(pair)
if not bool(tiers):
raise InvalidOrderException(f"Cannot calculate liquidation price for {pair}")
pair_tiers = self._leverage_tiers[pair]
pair_tiers = self.get_leverage_tiers_for_pair(pair)
if len(pair_tiers) < 1:
raise InvalidOrderException(
f"Maintenance margin rate for {pair} is unavailable for {self.name}"
)
for tier in reversed(pair_tiers):
if nominal_value >= tier['min']:
return (tier['mmr'], tier['maintAmt'])
raise OperationalException("nominal value can not be lower than 0")
# The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it
# describes the min amt for a tier, and the lowest tier will always go down to 0
else:
if pair not in self.markets:
raise InvalidOrderException(
f"{pair} is not tradeable on {self.name} {self.trading_mode.value}"
)
mmr = self.markets[pair]['maintenanceMarginRate']
if mmr is None:
raise OperationalException(
f"Maintenance margin rate is unavailable for {self.name}"
raise InvalidOrderException(
f"Maintenance margin rate for {pair} is unavailable for {self.name}"
)
return (mmr, None)

View File

@ -403,7 +403,6 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
}
}
],
})
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES

View File

@ -4300,15 +4300,63 @@ def test_parse_leverage_tier(mocker, default_conf):
}
def test_get_leverage_tiers_for_pair(mocker, default_conf, leverage_tiers):
def test_get_leverage_tiers_for_pair(
mocker,
default_conf,
leverage_tiers,
):
api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(return_value={
'DOGE/USDT:USDT': [
{
'tier': 1,
'notionalFloor': 0,
'notionalCap': 500,
'maintenanceMarginRatio': 0.02,
'maxLeverage': 75,
'info': {
'baseMaxLoan': '',
'imr': '0.013',
'instId': '',
'maxLever': '75',
'maxSz': '500',
'minSz': '0',
'mmr': '0.01',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '1',
'uly': 'DOGE-USDT'
}
},
{
'tier': 2,
'notionalFloor': 501,
'notionalCap': 1000,
'maintenanceMarginRatio': 0.025,
'maxLeverage': 50,
'info': {
'baseMaxLoan': '',
'imr': '0.02',
'instId': '',
'maxLever': '50',
'maxSz': '1000',
'minSz': '501',
'mmr': '0.015',
'optMgnFactor': '0',
'quoteMaxLoan': '',
'tier': '2',
'uly': 'DOGE-USDT'
}
}
],
})
# Spot
type(api_mock)._ft_has = PropertyMock(return_value={'fetchLeverageTiers': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._ft_has['can_fetch_multiple_tiers'] = False
assert exchange.get_leverage_tiers_for_pair('ADA/USDT') == []
assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == []
# 'can_fetch_multiple_tiers': True
default_conf['trading_mode'] = 'futures'
@ -4316,20 +4364,36 @@ def test_get_leverage_tiers_for_pair(mocker, default_conf, leverage_tiers):
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._ft_has['can_fetch_multiple_tiers'] = True
assert exchange.get_leverage_tiers_for_pair('ADA/USDT:USDT') == []
assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == []
# 'fetchLeverageTiers': False
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._ft_has['can_fetch_multiple_tiers'] = False
assert exchange.get_leverage_tiers_for_pair('ADA/USDT:USDT') == []
assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == []
# 'fetchLeverageTiers': False
# 'fetchLeverageTiers': True
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._ft_has['can_fetch_multiple_tiers'] = False
assert exchange.get_leverage_tiers_for_pair('ADA/USDT:USDT') != []
assert exchange.get_leverage_tiers_for_pair('DOGE/USDT:USDT') == [
{
'min': 0,
'max': 500,
'mmr': 0.02,
'lev': 75,
'maintAmt': None
},
{
'min': 501,
'max': 1000,
'mmr': 0.025,
'lev': 50,
'maintAmt': None
}
]
# exception_handlers
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock)
@ -4341,7 +4405,7 @@ def test_get_leverage_tiers_for_pair(mocker, default_conf, leverage_tiers):
"binance",
"get_leverage_tiers_for_pair",
"fetch_leverage_tiers",
pair='ETH/USDT:USDT',
pair='DOGE/USDT:USDT',
)
@ -4350,23 +4414,22 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf, api_mock)
pair = '1000SHIB/USDT'
exchange._leverage_tiers = {}
exchange.get_leverage_tiers_for_pair = MagicMock(return_value=[])
with pytest.raises(
InvalidOrderException,
match=f"Cannot calculate liquidation price for {pair}",
):
exchange.get_maintenance_ratio_and_amt(pair, 10000)
exchange._leverage_tiers = leverage_tiers
with pytest.raises(
OperationalException,
match='nominal value can not be lower than 0',
):
exchange.get_maintenance_ratio_and_amt(pair, -1)
exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', -1)
exchange._leverage_tiers = {}
exchange.get_leverage_tiers_for_pair = MagicMock(return_value=[])
with pytest.raises(
InvalidOrderException,
match="Maintenance margin rate for 1000SHIB/USDT is unavailable for",
):
exchange.get_maintenance_ratio_and_amt('1000SHIB/USDT', 10000)
@ pytest.mark.parametrize('pair,value,mmr,maintAmt', [

View File

@ -2,7 +2,7 @@ from unittest.mock import MagicMock, PropertyMock
import pytest
from freqtrade.exceptions import OperationalException
from freqtrade.exceptions import InvalidOrderException, OperationalException
from freqtrade.exchange import Gateio
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_patched_exchange
@ -31,12 +31,10 @@ def test_validate_order_types_gateio(default_conf, mocker):
ExchangeResolver.load_exchange('gateio', default_conf, True)
@pytest.mark.parametrize('pair,mm_ratio', [
("ETH/USDT:USDT", 0.005),
("ADA/USDT:USDT", 0.003),
])
def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_ratio):
def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker):
api_mock = MagicMock()
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio")
mocker.patch(
@ -59,7 +57,29 @@ def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_rat
'id': 'ADA_USDT',
'symbol': 'ADA/USDT:USDT',
},
'DOGE/USDT:USDT': {
'taker': 0.0000075,
'maker': -0.0000025,
'maintenanceMarginRate': None,
'info': {},
'id': 'ADA_USDT',
'symbol': 'ADA/USDT:USDT',
},
}
)
)
assert exchange.get_maintenance_ratio_and_amt(pair) == (mm_ratio, None)
assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT") == (0.005, None)
assert exchange.get_maintenance_ratio_and_amt("ADA/USDT:USDT") == (0.003, None)
with pytest.raises(
InvalidOrderException,
match="Maintenance margin rate for DOGE/USDT:USDT is unavailable for Gateio",
):
exchange.get_maintenance_ratio_and_amt('DOGE/USDT:USDT')
with pytest.raises(
InvalidOrderException,
match="SHIB/USDT:USDT is not tradeable on Gateio futures",
):
exchange.get_maintenance_ratio_and_amt('SHIB/USDT:USDT')