microsoft/qlib · error · ValueError

$close is necessray in extra_quote

Error message

$close is necessray in extra_quote

What it means

When extra_quote is provided to Exchange, it must contain a '$close' column because $close is the mandatory fallback price and the basis for detecting suspended stocks. The constructor checks for it explicitly and raises ValueError; other missing columns ($factor, limit_buy/limit_sell, buy/sell price fields) are only auto-filled with warnings, but $close is not optional.

Source

Thrown at qlib/backtest/exchange.py:240

        if (self.quote_df["$factor"].isna() & ~self.quote_df["$close"].isna()).any():
            # The 'factor.day.bin' file not exists, and `factor` field contains `nan`
            # Use adjusted price
            self.trade_w_adj_price = True
            self.logger.warning("factor.day.bin file not exists or factor contains `nan`. Order using adjusted_price.")
            if self.trade_unit is not None:
                self.logger.warning(f"trade unit {self.trade_unit} is not supported in adjusted_price mode.")
        else:
            # The `factor.day.bin` file exists and all data `close` and `factor` are not `nan`
            # Use normal price
            self.trade_w_adj_price = False
        # update limit
        self._update_limit(self.limit_threshold)

        # concat extra_quote
        if self.extra_quote is not None:
            # process extra_quote
            if "$close" not in self.extra_quote:
                raise ValueError("$close is necessray in extra_quote")
            for attr in "buy_price", "sell_price":
                pstr = getattr(self, attr)  # price string
                if pstr not in self.extra_quote.columns:
                    self.extra_quote[pstr] = self.extra_quote["$close"]
                    self.logger.warning(f"No {pstr} set for extra_quote. Use $close as {pstr}.")
            if "$factor" not in self.extra_quote.columns:
                self.extra_quote["$factor"] = 1.0
                self.logger.warning("No $factor set for extra_quote. Use 1.0 as $factor.")
            if "limit_sell" not in self.extra_quote.columns:
                self.extra_quote["limit_sell"] = False
                self.logger.warning("No limit_sell set for extra_quote. All stock will be able to be sold.")
            if "limit_buy" not in self.extra_quote.columns:
                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.

View on GitHub (pinned to 79633dd950)

Solutions

  1. Ensure the extra_quote DataFrame has a literal '$close' column before passing it in
  2. If your raw column is 'close', rename it: extra_quote = extra_quote.rename(columns={'close': '$close'})
  3. Optionally also pre-add your deal-price columns (e.g. '$vwap'), otherwise $close is used with a warning

Example fix

# before
exch = Exchange(extra_quote=df)  # df lacks $close
# after
df = df.rename(columns={'close': '$close'})
exch = Exchange(extra_quote=df)
Defensive patterns

Strategy: validation

Validate before calling

if extra_quote is not None:
    assert '$close' in extra_quote.columns, "extra_quote must contain '$close'"
exch = Exchange(extra_quote=extra_quote)

Type guard

def extra_quote_ok(df) -> bool:
    return '$close' in df.columns

Prevention

When it happens

Trigger: Exchange(extra_quote=df) where df has columns like $vwap, volume, etc. but no '$close'; column named 'close' (without the $ prefix) instead of '$close'.

Common situations: Attaching custom quote data (e.g. live/paper-trading quotes) for instruments not in the qlib data provider; renaming columns during preprocessing and dropping the $-prefix.

Related errors


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