From 52fc180c75383cb669f0be3532b676a5e3c9667d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Jan 2026 11:12:20 +0100 Subject: [PATCH] feat: add typedDict for LeverageTiers - fix unbound upper bound for get_max_leverage --- freqtrade/exchange/exchange.py | 11 ++++++++--- freqtrade/exchange/exchange_types.py | 22 ++++++++++++++++++++++ freqtrade/exchange/okx.py | 5 ++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1fe4c152b..ac19696fa 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -73,6 +73,7 @@ from freqtrade.exchange.exchange_types import ( CcxtPosition, FtHas, FundingRate, + LeverageTier, OHLCVResponse, OrderBook, Ticker, @@ -194,7 +195,7 @@ class Exchange: self._exchange_ws: ExchangeWS | None = None self._markets: dict = {} self._trading_fees: dict[str, Any] = {} - self._leverage_tiers: dict[str, list[dict]] = {} + self._leverage_tiers: dict[str, list[LeverageTier]] = {} # Lock event loop. This is necessary to avoid race-conditions when using force* commands # Due to funding fee fetching. self._loop_lock = Lock() @@ -3621,7 +3622,7 @@ class Exchange: pair_tiers.append(self.parse_leverage_tier(tier)) self._leverage_tiers[pair] = pair_tiers - def parse_leverage_tier(self, tier) -> dict: + def parse_leverage_tier(self, tier) -> LeverageTier: info = tier.get("info", {}) return { "minNotional": tier["minNotional"], @@ -3662,7 +3663,11 @@ class Exchange: for tier in pair_tiers: # Adjust notional by leverage to do a proper comparison min_stake = tier["minNotional"] / (prior_max_lev or tier["maxLeverage"]) - max_stake = tier["maxNotional"] / tier["maxLeverage"] + max_stake = ( + tier["maxNotional"] / tier["maxLeverage"] + if tier["maxNotional"] is not None + else float("inf") + ) prior_max_lev = tier["maxLeverage"] if min_stake <= stake_amount <= max_stake: return tier["maxLeverage"] diff --git a/freqtrade/exchange/exchange_types.py b/freqtrade/exchange/exchange_types.py index fd5faecf7..842bc7c14 100644 --- a/freqtrade/exchange/exchange_types.py +++ b/freqtrade/exchange/exchange_types.py @@ -115,5 +115,27 @@ class CcxtPosition(TypedDict): CcxtOrder = dict[str, Any] + +class LeverageTier(TypedDict): + """ + Represents a single leverage tier returned by the exchange. + + Attributes: + minNotional: Minimum notional value (quote currency) for which this tier applies. + maxNotional: Maximum notional value (quote currency) for which this tier applies. + When ``maxNotional`` is ``None``, the tier is unbounded on the upper side, + i.e. there is no maximum notional limit for this tier + maintenanceMarginRate: Maintenance margin rate for this tier (fraction, e.g. 0.005 for 0.5%) + maxLeverage: Maximum leverage allowed for this tier + maintAmt: Optional fixed maintenance margin amount, if provided by the exchange + """ + + minNotional: float + maxNotional: float | None + maintenanceMarginRate: float + maxLeverage: float + maintAmt: float | None + + # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = tuple[str, str, CandleType, list, bool] diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 5347f2417..df7f23299 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -182,7 +182,10 @@ class Okx(Exchange): return float("inf") pair_tiers = self._leverage_tiers[pair] - return pair_tiers[-1]["maxNotional"] / leverage + last_max_notional = pair_tiers[-1]["maxNotional"] + if last_max_notional is None: + return float("inf") + return last_max_notional / leverage def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict: params = super()._get_stop_params(side, ordertype, stop_price)