microsoft/qlib · error · NotImplementedError

This type of `limit_threshold` is not supported

Error message

This type of `limit_threshold` is not supported

What it means

Exchange._get_limit_type classifies limit_threshold into three regimes: tuple -> explicit limit up/down expressions (LT_TP_EXP), float -> |$change| < threshold (LT_FLT), None -> no limit (LT_NONE). Any other type raises NotImplementedError. Note the isinstance check is strictly float, so a Python int (e.g. 0 from config) is rejected even though it looks numeric.

Source

Thrown at qlib/backtest/exchange.py:271

                self.extra_quote["limit_buy"] = False
                self.logger.warning("No limit_buy set for extra_quote. All stock will be able to be bought.")
            assert set(self.extra_quote.columns) == set(self.quote_df.columns) - {"$change"}
            self.quote_df = pd.concat([self.quote_df, self.extra_quote], sort=False, axis=0)

    LT_TP_EXP = "(exp)"  # Tuple[str, str]:  the limitation is calculated by a Qlib expression.
    LT_FLT = "float"  # float:  the trading limitation is based on `abs($change) < limit_threshold`
    LT_NONE = "none"  # none:  there is no trading limitation

    def _get_limit_type(self, limit_threshold: Union[tuple, float, None]) -> str:
        """get limit type"""
        if isinstance(limit_threshold, tuple):
            return self.LT_TP_EXP
        elif isinstance(limit_threshold, float):
            return self.LT_FLT
        elif limit_threshold is None:
            return self.LT_NONE
        else:
            raise NotImplementedError(f"This type of `limit_threshold` is not supported")

    def _update_limit(self, limit_threshold: Union[Tuple, float, None]) -> None:
        # $close may contain NaN, the nan indicates that the stock is not tradable at that timestamp
        suspended = self.quote_df["$close"].isna()
        # check limit_threshold
        limit_type = self._get_limit_type(limit_threshold)
        if limit_type == self.LT_NONE:
            self.quote_df["limit_buy"] = suspended
            self.quote_df["limit_sell"] = suspended
        elif limit_type == self.LT_TP_EXP:
            # set limit
            limit_threshold = cast(tuple, limit_threshold)
            # astype bool is necessary, because quote_df is an expression and could be float
            self.quote_df["limit_buy"] = self.quote_df[limit_threshold[0]].astype("bool") | suspended
            self.quote_df["limit_sell"] = self.quote_df[limit_threshold[1]].astype("bool") | suspended
        elif limit_type == self.LT_FLT:
            limit_threshold = cast(float, limit_threshold)
            self.quote_df["limit_buy"] = self.quote_df["$change"].ge(limit_threshold) | suspended

View on GitHub (pinned to 79633dd950)

Solutions

  1. Cast to float: Exchange(limit_threshold=float(threshold))
  2. For asymmetric CN-style limits, pass a tuple of (limit_up_expression, limit_down_expression)
  3. Pass None explicitly when you want no limit check

Example fix

# before
exch = Exchange(limit_threshold=cfg['limit_threshold'])  # int 0 -> NotImplementedError
# after
exch = Exchange(limit_threshold=float(cfg['limit_threshold']))
Defensive patterns

Strategy: type-guard

Validate before calling

if limit_threshold is not None and not isinstance(limit_threshold, tuple):
    limit_threshold = float(limit_threshold)  # int/str -> float
exch = Exchange(limit_threshold=limit_threshold)

Type guard

def is_valid_limit_threshold(lt) -> bool:
    return lt is None or isinstance(lt, (tuple, float))

Prevention

When it happens

Trigger: Exchange(limit_threshold=0) or limit_threshold=1 (int); limit_threshold='0.1' (string from YAML/env); passing a pandas/numpy scalar that is not a float instance.

Common situations: Config files where the threshold is parsed as int or str; CLI/env-passed values that keep string types; code that computed the threshold with integer arithmetic.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/daa34d2d450c308e. Report an issue: GitHub.