diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 43d7acbd6..402fbbb32 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -54,22 +54,25 @@ def available_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]: return [x for x in exchanges if validate_exchange(x)[0]] -def _exchange_has_helper(ex_mod: ccxt.Exchange, required: dict[str, list[str]]) -> list[str]: +def _exchange_has_helper(ex_has: dict[str, Any], required: dict[str, list[str]]) -> list[str]: """ - Checks availability of methods (or their replacement)s in ex_mod.has - :param ex_mod: ccxt Exchange module + Checks availability of methods (or their replacement)s in a merged has-dict. + :param ex_has: merged "has" dict (ccxt + freqtrade overrides) :param required: dict of required methods, with possible replacement methods as list :return: list of missing required methods """ return [ k for k, v in required.items() - if ex_mod.has.get(k) is not True - and (len(v) == 0 or not (all(ex_mod.has.get(x) for x in v))) + if ex_has.get(k) is not True and (len(v) == 0 or not (all(ex_has.get(x) for x in v))) ] -def validate_exchange(exchange: str) -> tuple[bool, str, str, ccxt.Exchange | None]: +def validate_exchange( + exchange: str, + ft_has_overrides: dict[str, Any] | None = None, +) -> tuple[bool, str, str, ccxt.Exchange | None]: + """ returns: can_use, reason, exchange_object with Reason including both missing and missing_opt @@ -82,17 +85,22 @@ def validate_exchange(exchange: str) -> tuple[bool, str, str, ccxt.Exchange | No if not ex_mod or not ex_mod.has: return False, "", "", None + ex_has = dict(ex_mod.has or {}) + if ft_has_overrides: + ex_has.update(ft_has_overrides) + + result = True reasons = [] reasons_fut = "" - missing = _exchange_has_helper(ex_mod, EXCHANGE_HAS_REQUIRED) + missing = _exchange_has_helper(ex_has, EXCHANGE_HAS_REQUIRED) if missing: result = False reasons.append(f"missing: {', '.join(missing)}") - missing_opt = _exchange_has_helper(ex_mod, EXCHANGE_HAS_OPTIONAL) + missing_opt = _exchange_has_helper(ex_has, EXCHANGE_HAS_OPTIONAL) - missing_futures = _exchange_has_helper(ex_mod, EXCHANGE_HAS_OPTIONAL_FUTURES) + missing_futures = _exchange_has_helper(ex_has, EXCHANGE_HAS_OPTIONAL_FUTURES) if exchange.lower() in BAD_EXCHANGES: result = False @@ -111,8 +119,17 @@ def _build_exchange_list_entry( exchange_name: str, exchangeClasses: dict[str, Any] ) -> ValidExchangesType: exchange_name = exchange_name.lower() - valid, comment, comment_fut, ex_mod = validate_exchange(exchange_name) mapped_exchange_name = MAP_EXCHANGE_CHILDCLASS.get(exchange_name, exchange_name).lower() + + resolved = exchangeClasses.get(mapped_exchange_name) + ft_has_overrides = None + if resolved: + get_ft_has = getattr(resolved["class"], "get_ft_has", None) + if callable(get_ft_has): + ft_has_overrides = get_ft_has() or None + + valid, comment, comment_fut, ex_mod = validate_exchange(exchange_name, ft_has_overrides) + is_alias = getattr(ex_mod, "alias", False) result: ValidExchangesType = { "name": getattr(ex_mod, "name", exchange_name), @@ -128,7 +145,7 @@ def _build_exchange_list_entry( else None, "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } - if resolved := exchangeClasses.get(mapped_exchange_name): + if resolved: supported_modes: list[TradeModeType] = [ {"trading_mode": tm.value, "margin_mode": mm.value} for tm, mm in resolved["class"]._supported_trading_mode_margin_pairs