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_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 {}

View File

@ -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

View File

@ -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 {}

View File

@ -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

View File

@ -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')

View File

@ -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

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 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'