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_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 load_leverage_tiers(self) -> Dict[str, List[Dict]]:
|
def get_leverage_tiers(self) -> Dict[str, List[Dict]]:
|
||||||
if self.trading_mode == TradingMode.FUTURES and self.exchange_has('fetchLeverageTiers'):
|
|
||||||
try:
|
try:
|
||||||
return self._api.fetch_leverage_tiers()
|
return self._api.fetch_leverage_tiers()
|
||||||
except ccxt.DDoSProtection as e:
|
except ccxt.DDoSProtection as e:
|
||||||
raise DDosProtection(e) from e
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not load leverage tiers due to {e.__class__.__name__}.'
|
f'Could not load leverage tiers due to {e.__class__.__name__}. Message: {e}'
|
||||||
f'Message: {e}'
|
|
||||||
) from e
|
) from e
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from 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:
|
||||||
|
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:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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 {}
|
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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,9 +172,12 @@ 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,
|
||||||
|
})
|
||||||
|
api_mock.fetch_market_leverage_tiers = MagicMock(side_effect=[
|
||||||
|
[
|
||||||
{
|
{
|
||||||
'tier': 1,
|
'tier': 1,
|
||||||
'notionalFloor': 0,
|
'notionalFloor': 0,
|
||||||
@ -235,10 +238,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets):
|
|||||||
'uly': 'ADA-USDT'
|
'uly': 'ADA-USDT'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
[
|
||||||
{
|
|
||||||
'ETH/USDT:USDT': [
|
|
||||||
{
|
{
|
||||||
'tier': 1,
|
'tier': 1,
|
||||||
'notionalFloor': 0,
|
'notionalFloor': 0,
|
||||||
@ -300,7 +301,6 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
|
||||||
])
|
])
|
||||||
default_conf['trading_mode'] = 'futures'
|
default_conf['trading_mode'] = 'futures'
|
||||||
default_conf['margin_mode'] = 'isolated'
|
default_conf['margin_mode'] = 'isolated'
|
||||||
|
Loading…
Reference in New Issue
Block a user