krakenfutures: backfill leverage tier notionals
This commit is contained in:
@@ -206,6 +206,62 @@ class Krakenfutures(Exchange):
|
|||||||
logger.warning(f"Could not update funding fees for {pair}.")
|
logger.warning(f"Could not update funding fees for {pair}.")
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
def get_leverage_tiers(self) -> dict[str, list[dict]]:
|
||||||
|
"""
|
||||||
|
Kraken Futures returns leverage tiers with contract-based thresholds.
|
||||||
|
CCXT maps tiers to min/maxNotional using "numNonContractUnits", but many markets
|
||||||
|
only expose "contracts". Fill missing min/maxNotional from contract data and
|
||||||
|
maxPositionSize to keep get_max_leverage functional.
|
||||||
|
"""
|
||||||
|
tiers = super().get_leverage_tiers()
|
||||||
|
if not tiers:
|
||||||
|
return tiers
|
||||||
|
|
||||||
|
for pair, pair_tiers in tiers.items():
|
||||||
|
self._fill_leverage_tier_notionals(pair, pair_tiers)
|
||||||
|
|
||||||
|
return tiers
|
||||||
|
|
||||||
|
def _fill_leverage_tier_notionals(self, pair: str, pair_tiers: list[dict]) -> None:
|
||||||
|
if not pair_tiers:
|
||||||
|
return
|
||||||
|
self._fill_missing_min_notional(pair_tiers)
|
||||||
|
self._fill_missing_max_notional(pair, pair_tiers)
|
||||||
|
|
||||||
|
def _fill_missing_min_notional(self, pair_tiers: list[dict]) -> None:
|
||||||
|
for tier in pair_tiers:
|
||||||
|
if tier.get("minNotional") is None:
|
||||||
|
info = tier.get("info") or {}
|
||||||
|
contracts = self._safe_float(info.get("contracts"))
|
||||||
|
if contracts is not None:
|
||||||
|
tier["minNotional"] = contracts
|
||||||
|
|
||||||
|
def _fill_missing_max_notional(self, pair: str, pair_tiers: list[dict]) -> None:
|
||||||
|
for i in range(len(pair_tiers) - 1):
|
||||||
|
if pair_tiers[i].get("maxNotional") is None:
|
||||||
|
next_min = pair_tiers[i + 1].get("minNotional")
|
||||||
|
if next_min is not None:
|
||||||
|
pair_tiers[i]["maxNotional"] = next_min
|
||||||
|
|
||||||
|
last = pair_tiers[-1]
|
||||||
|
if last.get("maxNotional") is None:
|
||||||
|
max_notional = self._max_notional_from_market(pair)
|
||||||
|
if max_notional is not None:
|
||||||
|
last["maxNotional"] = max_notional
|
||||||
|
elif last.get("minNotional") is not None:
|
||||||
|
# Avoid None values, even if we cannot infer the real max.
|
||||||
|
last["maxNotional"] = last["minNotional"]
|
||||||
|
|
||||||
|
def _max_notional_from_market(self, pair: str) -> float | None:
|
||||||
|
market = self.markets.get(pair)
|
||||||
|
if not market:
|
||||||
|
return None
|
||||||
|
max_pos = self._safe_float(market.get("info", {}).get("maxPositionSize"))
|
||||||
|
if max_pos is None:
|
||||||
|
return None
|
||||||
|
contract_size = self._safe_float(market.get("contractSize")) or 1.0
|
||||||
|
return max_pos * contract_size
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _safe_float(v: Any) -> float | None:
|
def _safe_float(v: Any) -> float | None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -24,6 +24,47 @@ def test_krakenfutures_ft_has_overrides():
|
|||||||
assert ft_has["stop_price_type_field"] == "triggerSignal"
|
assert ft_has["stop_price_type_field"] == "triggerSignal"
|
||||||
|
|
||||||
|
|
||||||
|
def test_krakenfutures_get_leverage_tiers_fills_contracts(mocker, default_conf):
|
||||||
|
"""Fill missing min/maxNotional from contracts/maxPositionSize in leverage tiers."""
|
||||||
|
mock_markets = {
|
||||||
|
"BTC/USD:USD": {
|
||||||
|
"info": {"maxPositionSize": 1000000},
|
||||||
|
"contractSize": 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ex = get_patched_exchange(
|
||||||
|
mocker, default_conf, exchange="krakenfutures", mock_markets=mock_markets
|
||||||
|
)
|
||||||
|
assert isinstance(ex, Krakenfutures)
|
||||||
|
|
||||||
|
sample_tiers = {
|
||||||
|
"BTC/USD:USD": [
|
||||||
|
{
|
||||||
|
"minNotional": None,
|
||||||
|
"maxNotional": None,
|
||||||
|
"maintenanceMarginRate": 0.01,
|
||||||
|
"maxLeverage": 50.0,
|
||||||
|
"info": {"contracts": 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minNotional": None,
|
||||||
|
"maxNotional": None,
|
||||||
|
"maintenanceMarginRate": 0.02,
|
||||||
|
"maxLeverage": 25.0,
|
||||||
|
"info": {"contracts": 500000},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mocker.patch.object(Exchange, "get_leverage_tiers", return_value=sample_tiers)
|
||||||
|
tiers = ex.get_leverage_tiers()
|
||||||
|
pair_tiers = tiers["BTC/USD:USD"]
|
||||||
|
assert pair_tiers[0]["minNotional"] == 0.0
|
||||||
|
assert pair_tiers[0]["maxNotional"] == 500000.0
|
||||||
|
assert pair_tiers[1]["minNotional"] == 500000.0
|
||||||
|
assert pair_tiers[1]["maxNotional"] == 1000000.0
|
||||||
|
|
||||||
|
|
||||||
def test_krakenfutures_ohlcv_candle_limit_uses_ccxt_limit(mocker, default_conf):
|
def test_krakenfutures_ohlcv_candle_limit_uses_ccxt_limit(mocker, default_conf):
|
||||||
"""Test that OHLCV candle limit follows CCXT feature limit."""
|
"""Test that OHLCV candle limit follows CCXT feature limit."""
|
||||||
ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures")
|
ex = get_patched_exchange(mocker, default_conf, exchange="krakenfutures")
|
||||||
|
|||||||
@@ -569,7 +569,7 @@ EXCHANGES: dict[str, TestExchangeOnlineSetup] = {
|
|||||||
"candle_count": 2000,
|
"candle_count": 2000,
|
||||||
"futures_pair": "BTC/USD:USD",
|
"futures_pair": "BTC/USD:USD",
|
||||||
"hasQuoteVolumeFutures": False,
|
"hasQuoteVolumeFutures": False,
|
||||||
"leverage_tiers_public": False,
|
"leverage_tiers_public": True,
|
||||||
"leverage_in_spot_market": False,
|
"leverage_in_spot_market": False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user