Merge pull request #6456 from samgermain/lev-tier-update
Lev tier update
This commit is contained in:
commit
12a1e27708
@ -74,7 +74,6 @@ class Exchange:
|
||||
"mark_ohlcv_price": "mark",
|
||||
"mark_ohlcv_timeframe": "8h",
|
||||
"ccxt_futures_name": "swap",
|
||||
"can_fetch_multiple_tiers": True,
|
||||
}
|
||||
_ft_has: Dict = {}
|
||||
|
||||
@ -1874,19 +1873,63 @@ class Exchange:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def get_leverage_tiers(self) -> Dict[str, List[Dict]]:
|
||||
try:
|
||||
return self._api.fetch_leverage_tiers()
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not load leverage tiers due to {e.__class__.__name__}. Message: {e}'
|
||||
) from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
@retrier
|
||||
def get_market_leverage_tiers(self, symbol) -> List[Dict]:
|
||||
try:
|
||||
return self._api.fetch_market_leverage_tiers(symbol)
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not load leverage tiers for {symbol}'
|
||||
f' due to {e.__class__.__name__}. Message: {e}'
|
||||
) from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
|
||||
if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'):
|
||||
try:
|
||||
return self._api.fetch_leverage_tiers()
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not load leverage tiers due to {e.__class__.__name__}.'
|
||||
f'Message: {e}'
|
||||
) from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
if self.exchange_has('fetchLeverageTiers'):
|
||||
# Fetch all leverage tiers at once
|
||||
return self.get_leverage_tiers()
|
||||
elif self.exchange_has('fetchMarketLeverageTiers'):
|
||||
# Must fetch the leverage tiers for each market separately
|
||||
# * This is slow(~45s) on Okx, makes ~90 api calls to load all linear swap markets
|
||||
markets = self.markets
|
||||
symbols = []
|
||||
|
||||
for symbol, market in markets.items():
|
||||
if (self.market_is_future(market)
|
||||
and market['quote'] == self._config['stake_currency']):
|
||||
symbols.append(symbol)
|
||||
|
||||
tiers: Dict[str, List[Dict]] = {}
|
||||
|
||||
# Be verbose here, as this delays startup by ~1 minute.
|
||||
logger.info(
|
||||
f"Initializing leverage_tiers for {len(symbols)} markets. "
|
||||
"This will take about a minute.")
|
||||
|
||||
for symbol in sorted(symbols):
|
||||
tiers[symbol] = self.get_market_leverage_tiers(symbol)
|
||||
|
||||
logger.info(f"Done initializing {len(symbols)} markets.")
|
||||
|
||||
return tiers
|
||||
else:
|
||||
return {}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
""" Gate.io exchange subclass """
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
@ -40,26 +40,3 @@ class Gateio(Exchange):
|
||||
if any(v == 'market' for k, v in order_types.items()):
|
||||
raise OperationalException(
|
||||
f'Exchange {self.name} does not support market orders.')
|
||||
|
||||
def get_maintenance_ratio_and_amt(
|
||||
self,
|
||||
pair: str,
|
||||
nominal_value: Optional[float] = 0.0,
|
||||
) -> Tuple[float, Optional[float]]:
|
||||
"""
|
||||
:return: The maintenance margin ratio and maintenance amount
|
||||
"""
|
||||
info = self.markets[pair]['info']
|
||||
return (float(info['maintenance_rate']), None)
|
||||
|
||||
def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float:
|
||||
"""
|
||||
Returns the maximum leverage that a pair can be traded at
|
||||
:param pair: The base/quote currency pair being traded
|
||||
:param nominal_value: The total value of the trade in quote currency (margin_mode + debt)
|
||||
"""
|
||||
market = self.markets[pair]
|
||||
if market['limits']['leverage']['max'] is not None:
|
||||
return market['limits']['leverage']['max']
|
||||
else:
|
||||
return 1.0
|
||||
|
@ -22,7 +22,6 @@ class Okx(Exchange):
|
||||
"ohlcv_candle_limit": 300,
|
||||
"mark_ohlcv_timeframe": "4h",
|
||||
"funding_fee_timeframe": "8h",
|
||||
"can_fetch_multiple_tiers": False,
|
||||
}
|
||||
|
||||
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
|
||||
@ -93,31 +92,3 @@ class Okx(Exchange):
|
||||
|
||||
pair_tiers = self._leverage_tiers[pair]
|
||||
return pair_tiers[-1]['max'] / leverage
|
||||
|
||||
@retrier
|
||||
def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
|
||||
# * This is slow(~45s) on Okex, must make 90-some api calls to load all linear swap markets
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
markets = self.markets
|
||||
symbols = []
|
||||
|
||||
for symbol, market in markets.items():
|
||||
if (self.market_is_future(market)
|
||||
and market['quote'] == self._config['stake_currency']):
|
||||
symbols.append(symbol)
|
||||
|
||||
tiers: Dict[str, List[Dict]] = {}
|
||||
|
||||
# Be verbose here, as this delays startup by ~1 minute.
|
||||
logger.info(
|
||||
f"Initializing leverage_tiers for {len(symbols)} markets. "
|
||||
"This will take about a minute.")
|
||||
|
||||
for symbol in sorted(symbols):
|
||||
res = self._api.fetch_leverage_tiers(symbol)
|
||||
tiers[symbol] = res[symbol]
|
||||
logger.info(f"Done initializing {len(symbols)} markets.")
|
||||
|
||||
return tiers
|
||||
else:
|
||||
return {}
|
||||
|
@ -2,7 +2,7 @@ numpy==1.22.2
|
||||
pandas==1.4.1
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==1.73.70
|
||||
ccxt==1.74.22
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==36.0.1
|
||||
aiohttp==3.8.1
|
||||
|
@ -50,7 +50,7 @@ EXCHANGES = {
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'futures_pair': 'BTC/USD:USD',
|
||||
'futures': True,
|
||||
'futures': False,
|
||||
'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT
|
||||
'leverage_in_spot_market': True,
|
||||
},
|
||||
@ -69,7 +69,7 @@ EXCHANGES = {
|
||||
'timeframe': '5m',
|
||||
'futures': True,
|
||||
'futures_pair': 'BTC/USDT:USDT',
|
||||
'leverage_tiers_public': False, # TODO-lev: Set to True once implemented on CCXT
|
||||
'leverage_tiers_public': True,
|
||||
'leverage_in_spot_market': True,
|
||||
},
|
||||
'okx': {
|
||||
@ -123,9 +123,6 @@ def exchange_futures(request, exchange_conf, class_mocker):
|
||||
exchange_conf['margin_mode'] = 'isolated'
|
||||
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency']
|
||||
|
||||
# TODO-lev: This mock should no longer be necessary once futures are enabled.
|
||||
class_mocker.patch(
|
||||
'freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_margin_mode')
|
||||
class_mocker.patch(
|
||||
'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import Gateio
|
||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||
from tests.conftest import get_patched_exchange
|
||||
|
||||
|
||||
def test_validate_order_types_gateio(default_conf, mocker):
|
||||
@ -29,51 +26,3 @@ def test_validate_order_types_gateio(default_conf, mocker):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
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):
|
||||
api_mock = MagicMock()
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gateio")
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets',
|
||||
PropertyMock(
|
||||
return_value={
|
||||
'ETH/USDT:USDT': {
|
||||
'taker': 0.0000075,
|
||||
'maker': -0.0000025,
|
||||
'info': {
|
||||
'maintenance_rate': '0.005',
|
||||
},
|
||||
'id': 'ETH_USDT',
|
||||
'symbol': 'ETH/USDT:USDT',
|
||||
},
|
||||
'ADA/USDT:USDT': {
|
||||
'taker': 0.0000075,
|
||||
'maker': -0.0000025,
|
||||
'info': {
|
||||
'maintenance_rate': '0.003',
|
||||
},
|
||||
'id': 'ADA_USDT',
|
||||
'symbol': 'ADA/USDT:USDT',
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
assert exchange.get_maintenance_ratio_and_amt(pair) == (mm_ratio, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pair,nominal_value,max_lev', [
|
||||
("ETH/BTC", 0.0, 2.0),
|
||||
("TKN/BTC", 100.0, 5.0),
|
||||
("BLK/BTC", 173.31, 3.0),
|
||||
("LTC/BTC", 0.0, 1.0),
|
||||
("TKN/USDT", 210.30, 1.0),
|
||||
])
|
||||
def test_get_max_leverage_gateio(default_conf, mocker, pair, nominal_value, max_lev):
|
||||
# Binance has a different method of getting the max leverage
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="gateio")
|
||||
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
|
||||
|
@ -1,4 +1,4 @@
|
||||
from unittest.mock import MagicMock # , PropertyMock
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from tests.conftest import get_patched_exchange
|
||||
@ -172,135 +172,135 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
|
||||
|
||||
def test_load_leverage_tiers_okx(default_conf, mocker, markets):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_leverage_tiers = MagicMock(side_effect=[
|
||||
{
|
||||
'ADA/USDT:USDT': [
|
||||
{
|
||||
'tier': 1,
|
||||
'notionalFloor': 0,
|
||||
'notionalCap': 500,
|
||||
'maintenanceMarginRate': 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': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 2,
|
||||
'notionalFloor': 501,
|
||||
'notionalCap': 1000,
|
||||
'maintenanceMarginRate': 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': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 3,
|
||||
'notionalFloor': 1001,
|
||||
'notionalCap': 2000,
|
||||
'maintenanceMarginRate': 0.03,
|
||||
'maxLeverage': 20,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.05',
|
||||
'instId': '',
|
||||
'maxLever': '20',
|
||||
'maxSz': '2000',
|
||||
'minSz': '1001',
|
||||
'mmr': '0.02',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '3',
|
||||
'uly': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'ETH/USDT:USDT': [
|
||||
{
|
||||
'tier': 1,
|
||||
'notionalFloor': 0,
|
||||
'notionalCap': 2000,
|
||||
'maintenanceMarginRate': 0.01,
|
||||
'maxLeverage': 75,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.013',
|
||||
'instId': '',
|
||||
'maxLever': '75',
|
||||
'maxSz': '2000',
|
||||
'minSz': '0',
|
||||
'mmr': '0.01',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '1',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 2,
|
||||
'notionalFloor': 2001,
|
||||
'notionalCap': 4000,
|
||||
'maintenanceMarginRate': 0.015,
|
||||
'maxLeverage': 50,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.02',
|
||||
'instId': '',
|
||||
'maxLever': '50',
|
||||
'maxSz': '4000',
|
||||
'minSz': '2001',
|
||||
'mmr': '0.015',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '2',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 3,
|
||||
'notionalFloor': 4001,
|
||||
'notionalCap': 8000,
|
||||
'maintenanceMarginRate': 0.02,
|
||||
'maxLeverage': 20,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.05',
|
||||
'instId': '',
|
||||
'maxLever': '20',
|
||||
'maxSz': '8000',
|
||||
'minSz': '4001',
|
||||
'mmr': '0.02',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '3',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
type(api_mock).has = PropertyMock(return_value={
|
||||
'fetchLeverageTiers': False,
|
||||
'fetchMarketLeverageTiers': True,
|
||||
})
|
||||
api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
'notionalFloor': 0,
|
||||
'notionalCap': 500,
|
||||
'maintenanceMarginRate': 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': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 2,
|
||||
'notionalFloor': 501,
|
||||
'notionalCap': 1000,
|
||||
'maintenanceMarginRate': 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': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 3,
|
||||
'notionalFloor': 1001,
|
||||
'notionalCap': 2000,
|
||||
'maintenanceMarginRate': 0.03,
|
||||
'maxLeverage': 20,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.05',
|
||||
'instId': '',
|
||||
'maxLever': '20',
|
||||
'maxSz': '2000',
|
||||
'minSz': '1001',
|
||||
'mmr': '0.02',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '3',
|
||||
'uly': 'ADA-USDT'
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
'tier': 1,
|
||||
'notionalFloor': 0,
|
||||
'notionalCap': 2000,
|
||||
'maintenanceMarginRate': 0.01,
|
||||
'maxLeverage': 75,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.013',
|
||||
'instId': '',
|
||||
'maxLever': '75',
|
||||
'maxSz': '2000',
|
||||
'minSz': '0',
|
||||
'mmr': '0.01',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '1',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 2,
|
||||
'notionalFloor': 2001,
|
||||
'notionalCap': 4000,
|
||||
'maintenanceMarginRate': 0.015,
|
||||
'maxLeverage': 50,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.02',
|
||||
'instId': '',
|
||||
'maxLever': '50',
|
||||
'maxSz': '4000',
|
||||
'minSz': '2001',
|
||||
'mmr': '0.015',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '2',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
{
|
||||
'tier': 3,
|
||||
'notionalFloor': 4001,
|
||||
'notionalCap': 8000,
|
||||
'maintenanceMarginRate': 0.02,
|
||||
'maxLeverage': 20,
|
||||
'info': {
|
||||
'baseMaxLoan': '',
|
||||
'imr': '0.05',
|
||||
'instId': '',
|
||||
'maxLever': '20',
|
||||
'maxSz': '8000',
|
||||
'minSz': '4001',
|
||||
'mmr': '0.02',
|
||||
'optMgnFactor': '0',
|
||||
'quoteMaxLoan': '',
|
||||
'tier': '3',
|
||||
'uly': 'ETH-USDT'
|
||||
}
|
||||
},
|
||||
]
|
||||
])
|
||||
default_conf['trading_mode'] = 'futures'
|
||||
default_conf['margin_mode'] = 'isolated'
|
||||
|
Loading…
Reference in New Issue
Block a user