{"record":{"id":"b0e2a38d40c8ffa6","repo":"microsoft/qlib","slug":"close-is-necessray-in-extra-quote","errorCode":null,"errorMessage":"$close is necessray in extra_quote","messagePattern":"\\$close is necessray in extra_quote","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/backtest/exchange.py","lineNumber":240,"sourceCode":"        if (self.quote_df[\"$factor\"].isna() & ~self.quote_df[\"$close\"].isna()).any():\n            # The 'factor.day.bin' file not exists, and `factor` field contains `nan`\n            # Use adjusted price\n            self.trade_w_adj_price = True\n            self.logger.warning(\"factor.day.bin file not exists or factor contains `nan`. Order using adjusted_price.\")\n            if self.trade_unit is not None:\n                self.logger.warning(f\"trade unit {self.trade_unit} is not supported in adjusted_price mode.\")\n        else:\n            # The `factor.day.bin` file exists and all data `close` and `factor` are not `nan`\n            # Use normal price\n            self.trade_w_adj_price = False\n        # update limit\n        self._update_limit(self.limit_threshold)\n\n        # concat extra_quote\n        if self.extra_quote is not None:\n            # process extra_quote\n            if \"$close\" not in self.extra_quote:\n                raise ValueError(\"$close is necessray in extra_quote\")\n            for attr in \"buy_price\", \"sell_price\":\n                pstr = getattr(self, attr)  # price string\n                if pstr not in self.extra_quote.columns:\n                    self.extra_quote[pstr] = self.extra_quote[\"$close\"]\n                    self.logger.warning(f\"No {pstr} set for extra_quote. Use $close as {pstr}.\")\n            if \"$factor\" not in self.extra_quote.columns:\n                self.extra_quote[\"$factor\"] = 1.0\n                self.logger.warning(\"No $factor set for extra_quote. Use 1.0 as $factor.\")\n            if \"limit_sell\" not in self.extra_quote.columns:\n                self.extra_quote[\"limit_sell\"] = False\n                self.logger.warning(\"No limit_sell set for extra_quote. All stock will be able to be sold.\")\n            if \"limit_buy\" not in self.extra_quote.columns:\n                self.extra_quote[\"limit_buy\"] = False\n                self.logger.warning(\"No limit_buy set for extra_quote. All stock will be able to be bought.\")\n            assert set(self.extra_quote.columns) == set(self.quote_df.columns) - {\"$change\"}\n            self.quote_df = pd.concat([self.quote_df, self.extra_quote], sort=False, axis=0)\n\n    LT_TP_EXP = \"(exp)\"  # Tuple[str, str]:  the limitation is calculated by a Qlib expression.","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/backtest/exchange.py#L222-L258","documentation":"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.","triggerScenarios":"Exchange(extra_quote=df) where df has columns like $vwap, volume, etc. but no '$close'; column named 'close' (without the $ prefix) instead of '$close'.","commonSituations":"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.","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"],"exampleFix":"# before\nexch = Exchange(extra_quote=df)  # df lacks $close\n# after\ndf = df.rename(columns={'close': '$close'})\nexch = Exchange(extra_quote=df)","handlingStrategy":"validation","validationCode":"if extra_quote is not None:\n    assert '$close' in extra_quote.columns, \"extra_quote must contain '$close'\"\nexch = Exchange(extra_quote=extra_quote)","typeGuard":"def extra_quote_ok(df) -> bool:\n    return '$close' in df.columns","tryCatchPattern":null,"preventionTips":["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"],"tags":["qlib","exchange","extra-quote","data-validation"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}