microsoft/qlib · error · NotImplementedError

This type of input is not supported

Error message

This type of input is not supported

What it means

Exchange normalizes deal_price into $-prefixed column names: a str becomes both buy and sell price (e.g. 'close' -> '$close'), and a tuple/list of two strings becomes (buy_price, sell_price). Any other Python type (int, float, dict, None mishandled, pd.Series) cannot name a quote column, so __init__ raises NotImplementedError.

Source

Thrown at qlib/backtest/exchange.py:164

        # TODO: the quote, trade_dates, codes are not necessary.
        # It is just for performance consideration.
        self.limit_type = self._get_limit_type(limit_threshold)
        if limit_threshold is None:
            if C.region in [REG_CN, REG_TW]:
                self.logger.warning(f"limit_threshold not set. The stocks hit the limit may be bought/sold")
        elif self.limit_type == self.LT_FLT and abs(cast(float, limit_threshold)) > 0.1:
            if C.region in [REG_CN, REG_TW]:
                self.logger.warning(f"limit_threshold may not be set to a reasonable value")

        if isinstance(deal_price, str):
            if deal_price[0] != "$":
                deal_price = "$" + deal_price
            self.buy_price = self.sell_price = deal_price
        elif isinstance(deal_price, (tuple, list)):
            self.buy_price, self.sell_price = cast(Tuple[str, str], deal_price)
        else:
            raise NotImplementedError(f"This type of input is not supported")

        if isinstance(codes, str):
            codes = D.instruments(codes)
        self.codes = codes
        # Necessary fields
        # $close is for calculating the total value at end of each day.
        # - if $close is None, the stock on that day is regarded as suspended.
        # $factor is for rounding to the trading unit
        # $change is for calculating the limit of the stock

        #  get volume limit from kwargs
        self.buy_vol_limit, self.sell_vol_limit, vol_lt_fields = self._get_vol_limit(volume_threshold)

        necessary_fields = {self.buy_price, self.sell_price, "$close", "$change", "$factor", "$volume"}
        if self.limit_type == self.LT_TP_EXP:
            assert isinstance(limit_threshold, tuple)
            for exp in limit_threshold:
                necessary_fields.add(exp)

View on GitHub (pinned to 79633dd950)

Solutions

  1. Pass a single column name string: Exchange(deal_price='close') -> uses $close for both sides
  2. Or pass a 2-tuple of strings: Exchange(deal_price=('buy_vwap', 'sell_vwap'))
  3. Validate config-loaded values before constructing: assert isinstance(deal_price, (str, tuple, list))

Example fix

# before
exch = Exchange(deal_price=('close', 'vwap', 'open'))  # wrong arity/type combos
# after
exch = Exchange(deal_price=('close', 'vwap'))
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(deal_price, (str, tuple, list)), 'deal_price must be str or (buy, sell) strings'
if isinstance(deal_price, (tuple, list)):
    assert len(deal_price) == 2 and all(isinstance(p, str) for p in deal_price)
exch = Exchange(deal_price=deal_price)

Type guard

def is_valid_deal_price(p) -> bool:
    if isinstance(p, str):
        return True
    return isinstance(p, (tuple, list)) and len(p) == 2 and all(isinstance(x, str) for x in p)

Prevention

When it happens

Trigger: Exchange(deal_price=0.98), Exchange(deal_price=('close',)), or passing a DataFrame/Series of prices instead of a column name; also deal_price loaded from YAML as a non-string scalar.

Common situations: Confusing deal_price (a quote field name like 'close' or 'vwap') with an actual numeric price; configs where deal_price ends up as None after a lookup fails; tuples with the wrong arity.

Related errors


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