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
- Ensure the extra_quote DataFrame has a literal '$close' column before passing it in
- If your raw column is 'close', rename it: extra_quote = extra_quote.rename(columns={'close': '$close'})
- 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
- Always include '$close' (with the $ prefix) when attaching custom quotes
- Consider also pre-adding '$factor' and deal-price columns to avoid silent $close/1.0 fallbacks
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
- Get Unexpected arguments {kwargs}
- This type of input is not supported
- This type of `limit_threshold` is not supported
- direction {direction} is not supported!
- trade_account and position can only choose one
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/b0e2a38d40c8ffa6.
Report an issue: GitHub.