strengthened and fixed leverage_tier tests
This commit is contained in:
parent
a6043e6a85
commit
03b3756e4b
@ -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 (
|
||||
self._api.has['fetchLeverageTiers'] and
|
||||
not self._ft_has['can_fetch_multiple_tiers'] and
|
||||
self.trading_mode == TradingMode.FUTURES
|
||||
):
|
||||
if pair not in self._leverage_tiers:
|
||||
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))
|
||||
if (
|
||||
self._api.has['fetchLeverageTiers'] and
|
||||
not self._ft_has['can_fetch_multiple_tiers'] and
|
||||
self.trading_mode == TradingMode.FUTURES
|
||||
):
|
||||
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 []
|
||||
except ccxt.BadRequest:
|
||||
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)
|
||||
|
||||
|
@ -403,7 +403,6 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
||||
|
@ -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,26 +4414,25 @@ 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', [
|
||||
@ pytest.mark.parametrize('pair,value,mmr,maintAmt', [
|
||||
('ADA/BUSD', 500, 0.025, 0.0),
|
||||
('ADA/BUSD', 20000000, 0.5, 1527500.0),
|
||||
('ZEC/USDT', 500, 0.01, 0.0),
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user