Merge pull request #6456 from samgermain/lev-tier-update

Lev tier update
This commit is contained in:
Matthias 2022-02-26 19:55:30 +01:00 committed by GitHub
commit 12a1e27708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 190 additions and 253 deletions

View File

@ -74,7 +74,6 @@ class Exchange:
"mark_ohlcv_price": "mark", "mark_ohlcv_price": "mark",
"mark_ohlcv_timeframe": "8h", "mark_ohlcv_timeframe": "8h",
"ccxt_futures_name": "swap", "ccxt_futures_name": "swap",
"can_fetch_multiple_tiers": True,
} }
_ft_has: Dict = {} _ft_has: Dict = {}
@ -1874,19 +1873,63 @@ class Exchange:
raise OperationalException(e) from e raise OperationalException(e) from e
@retrier @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]]: def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'): if self.trading_mode == TradingMode.FUTURES:
try: if self.exchange_has('fetchLeverageTiers'):
return self._api.fetch_leverage_tiers() # Fetch all leverage tiers at once
except ccxt.DDoSProtection as e: return self.get_leverage_tiers()
raise DDosProtection(e) from e elif self.exchange_has('fetchMarketLeverageTiers'):
except (ccxt.NetworkError, ccxt.ExchangeError) as e: # Must fetch the leverage tiers for each market separately
raise TemporaryError( # * This is slow(~45s) on Okx, makes ~90 api calls to load all linear swap markets
f'Could not load leverage tiers due to {e.__class__.__name__}.' markets = self.markets
f'Message: {e}' symbols = []
) from e
except ccxt.BaseError as e: for symbol, market in markets.items():
raise OperationalException(e) from e 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: else:
return {} return {}

View File

@ -1,6 +1,6 @@
""" Gate.io exchange subclass """ """ Gate.io exchange subclass """
import logging import logging
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Tuple
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -40,26 +40,3 @@ class Gateio(Exchange):
if any(v == 'market' for k, v in order_types.items()): if any(v == 'market' for k, v in order_types.items()):
raise OperationalException( raise OperationalException(
f'Exchange {self.name} does not support market orders.') 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

View File

@ -22,7 +22,6 @@ class Okx(Exchange):
"ohlcv_candle_limit": 300, "ohlcv_candle_limit": 300,
"mark_ohlcv_timeframe": "4h", "mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h", "funding_fee_timeframe": "8h",
"can_fetch_multiple_tiers": False,
} }
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
@ -93,31 +92,3 @@ class Okx(Exchange):
pair_tiers = self._leverage_tiers[pair] pair_tiers = self._leverage_tiers[pair]
return pair_tiers[-1]['max'] / leverage 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 {}

View File

@ -2,7 +2,7 @@ numpy==1.22.2
pandas==1.4.1 pandas==1.4.1
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==1.73.70 ccxt==1.74.22
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==36.0.1 cryptography==36.0.1
aiohttp==3.8.1 aiohttp==3.8.1

View File

@ -50,7 +50,7 @@ EXCHANGES = {
'hasQuoteVolume': True, 'hasQuoteVolume': True,
'timeframe': '5m', 'timeframe': '5m',
'futures_pair': 'BTC/USD:USD', 'futures_pair': 'BTC/USD:USD',
'futures': True, 'futures': False,
'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT 'leverage_tiers_public': False, # TODO: Set to True once implemented on CCXT
'leverage_in_spot_market': True, 'leverage_in_spot_market': True,
}, },
@ -69,7 +69,7 @@ EXCHANGES = {
'timeframe': '5m', 'timeframe': '5m',
'futures': True, 'futures': True,
'futures_pair': 'BTC/USDT:USDT', '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, 'leverage_in_spot_market': True,
}, },
'okx': { 'okx': {
@ -123,9 +123,6 @@ def exchange_futures(request, exchange_conf, class_mocker):
exchange_conf['margin_mode'] = 'isolated' exchange_conf['margin_mode'] = 'isolated'
exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] 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( class_mocker.patch(
'freqtrade.exchange.binance.Binance.fill_leverage_tiers') 'freqtrade.exchange.binance.Binance.fill_leverage_tiers')

View File

@ -1,11 +1,8 @@
from unittest.mock import MagicMock, PropertyMock
import pytest import pytest
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import 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
def test_validate_order_types_gateio(default_conf, mocker): 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, with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'): match=r'Exchange .* does not support market orders.'):
ExchangeResolver.load_exchange('gateio', default_conf, True) 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

View File

@ -1,4 +1,4 @@
from unittest.mock import MagicMock # , PropertyMock from unittest.mock import MagicMock, PropertyMock
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
from tests.conftest import get_patched_exchange 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): def test_load_leverage_tiers_okx(default_conf, mocker, markets):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_leverage_tiers = MagicMock(side_effect=[ type(api_mock).has = PropertyMock(return_value={
{ 'fetchLeverageTiers': False,
'ADA/USDT:USDT': [ 'fetchMarketLeverageTiers': True,
{ })
'tier': 1, api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
'notionalFloor': 0, [
'notionalCap': 500, {
'maintenanceMarginRate': 0.02, 'tier': 1,
'maxLeverage': 75, 'notionalFloor': 0,
'info': { 'notionalCap': 500,
'baseMaxLoan': '', 'maintenanceMarginRate': 0.02,
'imr': '0.013', 'maxLeverage': 75,
'instId': '', 'info': {
'maxLever': '75', 'baseMaxLoan': '',
'maxSz': '500', 'imr': '0.013',
'minSz': '0', 'instId': '',
'mmr': '0.01', 'maxLever': '75',
'optMgnFactor': '0', 'maxSz': '500',
'quoteMaxLoan': '', 'minSz': '0',
'tier': '1', 'mmr': '0.01',
'uly': 'ADA-USDT' 'optMgnFactor': '0',
} 'quoteMaxLoan': '',
}, 'tier': '1',
{ 'uly': 'ADA-USDT'
'tier': 2, }
'notionalFloor': 501, },
'notionalCap': 1000, {
'maintenanceMarginRate': 0.025, 'tier': 2,
'maxLeverage': 50, 'notionalFloor': 501,
'info': { 'notionalCap': 1000,
'baseMaxLoan': '', 'maintenanceMarginRate': 0.025,
'imr': '0.02', 'maxLeverage': 50,
'instId': '', 'info': {
'maxLever': '50', 'baseMaxLoan': '',
'maxSz': '1000', 'imr': '0.02',
'minSz': '501', 'instId': '',
'mmr': '0.015', 'maxLever': '50',
'optMgnFactor': '0', 'maxSz': '1000',
'quoteMaxLoan': '', 'minSz': '501',
'tier': '2', 'mmr': '0.015',
'uly': 'ADA-USDT' 'optMgnFactor': '0',
} 'quoteMaxLoan': '',
}, 'tier': '2',
{ 'uly': 'ADA-USDT'
'tier': 3, }
'notionalFloor': 1001, },
'notionalCap': 2000, {
'maintenanceMarginRate': 0.03, 'tier': 3,
'maxLeverage': 20, 'notionalFloor': 1001,
'info': { 'notionalCap': 2000,
'baseMaxLoan': '', 'maintenanceMarginRate': 0.03,
'imr': '0.05', 'maxLeverage': 20,
'instId': '', 'info': {
'maxLever': '20', 'baseMaxLoan': '',
'maxSz': '2000', 'imr': '0.05',
'minSz': '1001', 'instId': '',
'mmr': '0.02', 'maxLever': '20',
'optMgnFactor': '0', 'maxSz': '2000',
'quoteMaxLoan': '', 'minSz': '1001',
'tier': '3', 'mmr': '0.02',
'uly': 'ADA-USDT' 'optMgnFactor': '0',
} 'quoteMaxLoan': '',
}, 'tier': '3',
] 'uly': 'ADA-USDT'
}, }
{ },
'ETH/USDT:USDT': [ ],
{ [
'tier': 1, {
'notionalFloor': 0, 'tier': 1,
'notionalCap': 2000, 'notionalFloor': 0,
'maintenanceMarginRate': 0.01, 'notionalCap': 2000,
'maxLeverage': 75, 'maintenanceMarginRate': 0.01,
'info': { 'maxLeverage': 75,
'baseMaxLoan': '', 'info': {
'imr': '0.013', 'baseMaxLoan': '',
'instId': '', 'imr': '0.013',
'maxLever': '75', 'instId': '',
'maxSz': '2000', 'maxLever': '75',
'minSz': '0', 'maxSz': '2000',
'mmr': '0.01', 'minSz': '0',
'optMgnFactor': '0', 'mmr': '0.01',
'quoteMaxLoan': '', 'optMgnFactor': '0',
'tier': '1', 'quoteMaxLoan': '',
'uly': 'ETH-USDT' 'tier': '1',
} 'uly': 'ETH-USDT'
}, }
{ },
'tier': 2, {
'notionalFloor': 2001, 'tier': 2,
'notionalCap': 4000, 'notionalFloor': 2001,
'maintenanceMarginRate': 0.015, 'notionalCap': 4000,
'maxLeverage': 50, 'maintenanceMarginRate': 0.015,
'info': { 'maxLeverage': 50,
'baseMaxLoan': '', 'info': {
'imr': '0.02', 'baseMaxLoan': '',
'instId': '', 'imr': '0.02',
'maxLever': '50', 'instId': '',
'maxSz': '4000', 'maxLever': '50',
'minSz': '2001', 'maxSz': '4000',
'mmr': '0.015', 'minSz': '2001',
'optMgnFactor': '0', 'mmr': '0.015',
'quoteMaxLoan': '', 'optMgnFactor': '0',
'tier': '2', 'quoteMaxLoan': '',
'uly': 'ETH-USDT' 'tier': '2',
} 'uly': 'ETH-USDT'
}, }
{ },
'tier': 3, {
'notionalFloor': 4001, 'tier': 3,
'notionalCap': 8000, 'notionalFloor': 4001,
'maintenanceMarginRate': 0.02, 'notionalCap': 8000,
'maxLeverage': 20, 'maintenanceMarginRate': 0.02,
'info': { 'maxLeverage': 20,
'baseMaxLoan': '', 'info': {
'imr': '0.05', 'baseMaxLoan': '',
'instId': '', 'imr': '0.05',
'maxLever': '20', 'instId': '',
'maxSz': '8000', 'maxLever': '20',
'minSz': '4001', 'maxSz': '8000',
'mmr': '0.02', 'minSz': '4001',
'optMgnFactor': '0', 'mmr': '0.02',
'quoteMaxLoan': '', 'optMgnFactor': '0',
'tier': '3', 'quoteMaxLoan': '',
'uly': 'ETH-USDT' 'tier': '3',
} 'uly': 'ETH-USDT'
}, }
] },
}, ]
]) ])
default_conf['trading_mode'] = 'futures' default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated' default_conf['margin_mode'] = 'isolated'