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
|
# 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
|
# then this method does nothing, it should only be implemented when the leverage
|
||||||
# tiers requires per symbol fetching to avoid excess api calls
|
# tiers requires per symbol fetching to avoid excess api calls
|
||||||
if (
|
if pair not in self._leverage_tiers:
|
||||||
self._api.has['fetchLeverageTiers'] and
|
|
||||||
not self._ft_has['can_fetch_multiple_tiers'] and
|
|
||||||
self.trading_mode == TradingMode.FUTURES
|
|
||||||
):
|
|
||||||
self._leverage_tiers[pair] = []
|
self._leverage_tiers[pair] = []
|
||||||
try:
|
if (
|
||||||
tiers = self._api.fetch_leverage_tiers(pair)
|
self._api.has['fetchLeverageTiers'] and
|
||||||
for tier in tiers[pair]:
|
not self._ft_has['can_fetch_multiple_tiers'] and
|
||||||
self._leverage_tiers[pair].append(self.parse_leverage_tier(tier))
|
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:
|
||||||
except ccxt.BadRequest:
|
return []
|
||||||
return []
|
|
||||||
else:
|
return self._leverage_tiers[pair]
|
||||||
return []
|
|
||||||
|
|
||||||
def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float:
|
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'
|
f'{self.name}.get_max_leverage requires argument stake_amount'
|
||||||
)
|
)
|
||||||
|
|
||||||
if pair not in self._leverage_tiers:
|
pair_tiers = self.get_leverage_tiers_for_pair(pair)
|
||||||
self.get_leverage_tiers_for_pair(pair)
|
|
||||||
|
|
||||||
pair_tiers = self._leverage_tiers[pair]
|
|
||||||
num_tiers = len(pair_tiers)
|
num_tiers = len(pair_tiers)
|
||||||
if num_tiers < 1:
|
if num_tiers < 1:
|
||||||
return 1.0
|
return 1.0
|
||||||
@ -2282,23 +2279,30 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._api.has['fetchLeverageTiers']:
|
if self._api.has['fetchLeverageTiers']:
|
||||||
if pair not in self._leverage_tiers:
|
|
||||||
# Used when fetchLeverageTiers cannot fetch all symbols at once
|
pair_tiers = self.get_leverage_tiers_for_pair(pair)
|
||||||
tiers = self.get_leverage_tiers_for_pair(pair)
|
|
||||||
if not bool(tiers):
|
if len(pair_tiers) < 1:
|
||||||
raise InvalidOrderException(f"Cannot calculate liquidation price for {pair}")
|
raise InvalidOrderException(
|
||||||
pair_tiers = self._leverage_tiers[pair]
|
f"Maintenance margin rate for {pair} is unavailable for {self.name}"
|
||||||
|
)
|
||||||
|
|
||||||
for tier in reversed(pair_tiers):
|
for tier in reversed(pair_tiers):
|
||||||
if nominal_value >= tier['min']:
|
if nominal_value >= tier['min']:
|
||||||
return (tier['mmr'], tier['maintAmt'])
|
return (tier['mmr'], tier['maintAmt'])
|
||||||
|
|
||||||
raise OperationalException("nominal value can not be lower than 0")
|
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
|
# 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
|
# describes the min amt for a tier, and the lowest tier will always go down to 0
|
||||||
else:
|
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']
|
mmr = self.markets[pair]['maintenanceMarginRate']
|
||||||
if mmr is None:
|
if mmr is None:
|
||||||
raise OperationalException(
|
raise InvalidOrderException(
|
||||||
f"Maintenance margin rate is unavailable for {self.name}"
|
f"Maintenance margin rate for {pair} is unavailable for {self.name}"
|
||||||
)
|
)
|
||||||
return (mmr, None)
|
return (mmr, None)
|
||||||
|
|
||||||
|
@ -403,7 +403,6 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
})
|
})
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
default_conf['trading_mode'] = TradingMode.FUTURES
|
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 = 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
|
# Spot
|
||||||
type(api_mock)._ft_has = PropertyMock(return_value={'fetchLeverageTiers': True})
|
type(api_mock)._ft_has = PropertyMock(return_value={'fetchLeverageTiers': True})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange._ft_has['can_fetch_multiple_tiers'] = False
|
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
|
# 'can_fetch_multiple_tiers': True
|
||||||
default_conf['trading_mode'] = 'futures'
|
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})
|
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange._ft_has['can_fetch_multiple_tiers'] = True
|
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
|
# 'fetchLeverageTiers': False
|
||||||
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
|
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange._ft_has['can_fetch_multiple_tiers'] = False
|
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})
|
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
exchange._ft_has['can_fetch_multiple_tiers'] = False
|
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})
|
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': True})
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
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",
|
"binance",
|
||||||
"get_leverage_tiers_for_pair",
|
"get_leverage_tiers_for_pair",
|
||||||
"fetch_leverage_tiers",
|
"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['trading_mode'] = 'futures'
|
||||||
default_conf['margin_mode'] = 'isolated'
|
default_conf['margin_mode'] = 'isolated'
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
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
|
exchange._leverage_tiers = leverage_tiers
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
OperationalException,
|
OperationalException,
|
||||||
match='nominal value can not be lower than 0',
|
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', 500, 0.025, 0.0),
|
||||||
('ADA/BUSD', 20000000, 0.5, 1527500.0),
|
('ADA/BUSD', 20000000, 0.5, 1527500.0),
|
||||||
('ZEC/USDT', 500, 0.01, 0.0),
|
('ZEC/USDT', 500, 0.01, 0.0),
|
||||||
|
@ -2,7 +2,7 @@ from unittest.mock import MagicMock, PropertyMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import InvalidOrderException, OperationalException
|
||||||
from freqtrade.exchange import Gateio
|
from freqtrade.exchange import Gateio
|
||||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||||
from tests.conftest import get_patched_exchange
|
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)
|
ExchangeResolver.load_exchange('gateio', default_conf, True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('pair,mm_ratio', [
|
def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker):
|
||||||
("ETH/USDT:USDT", 0.005),
|
|
||||||
("ADA/USDT:USDT", 0.003),
|
|
||||||
])
|
|
||||||
def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_ratio):
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
|
default_conf['trading_mode'] = 'futures'
|
||||||
|
default_conf['margin_mode'] = 'isolated'
|
||||||
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
|
type(api_mock).has = PropertyMock(return_value={'fetchLeverageTiers': False})
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio")
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio")
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
@ -59,7 +57,29 @@ def test_get_maintenance_ratio_and_amt_gateio(default_conf, mocker, pair, mm_rat
|
|||||||
'id': 'ADA_USDT',
|
'id': 'ADA_USDT',
|
||||||
'symbol': 'ADA/USDT: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