microsoft/qlib · error · ValueError

{lack_stock} doesn't have close price in qlib in the latest

Error message

{lack_stock} doesn't have close price in qlib in the latest {last_days} days

What it means

Position.fill_stock_value tries to backfill missing prices for the initial holdings by querying D.features for '$close' over the last `last_days` (default 30) days before backtest start. If any instrument has no non-NaN close price in that window, it raises ValueError listing the offending stocks. It almost always means the qlib data provider has no (or only NaN) bars for those instruments before the backtest start date.

Source

Thrown at qlib/backtest/position.py:318

            return

        start_time = pd.Timestamp(start_time)
        # note that start time is 2020-01-01 00:00:00 if raw start time is "2020-01-01"
        price_end_time = start_time
        price_start_time = start_time - timedelta(days=last_days)
        price_df = D.features(
            stock_list,
            ["$close"],
            price_start_time,
            price_end_time,
            freq=freq,
            disk_cache=True,
        ).dropna()
        price_dict = price_df.groupby(["instrument"], group_keys=False).tail(1)["$close"].to_dict()

        if len(price_dict) < len(stock_list):
            lack_stock = set(stock_list) - set(price_dict)
            raise ValueError(f"{lack_stock} doesn't have close price in qlib in the latest {last_days} days")

        for stock in stock_list:
            self.position[stock]["price"] = price_dict[stock]
        self.position["now_account_value"] = self.calculate_value()

    def _init_stock(self, stock_id: str, amount: float, price: float | None = None) -> None:
        """
        initialization the stock in current position

        Parameters
        ----------
        stock_id :
            the id of the stock
        amount : float
            the amount of the stock
        price :
             the price when buying the init stock
        """

View on GitHub (pinned to 79633dd950)

Solutions

  1. Provide the 'price' field explicitly for each stock in the initial position config so fill_stock_value skips them
  2. Push the backtest start_time later, past the listing date of every held instrument
  3. Increase last_days (e.g. fill_stock_value(start, freq, last_days=120)) if the stock was suspended longer
  4. Verify the instruments exist in your data: D.features([stock], ['$close'], start, end, freq=freq) and drop delisted names from the initial position

Example fix

# before
position_dict = {"SH600000": {"amount": 1000, "price": None}}

# after
position_dict = {"SH600000": {"amount": 1000, "price": 11.5}}
Defensive patterns

Strategy: validation

Validate before calling

from qlib.data import D
lack = [s for s, v in position.position.items() if isinstance(v, dict) and v.get("price") is None]
if lack:
    df = D.features(lack, ["$close"], start_time - pd.Timedelta(days=30), start_time, freq=freq)
    missing = set(lack) - set(df.dropna().index.get_level_values("instrument").unique())
    assert not missing, f"no close price for {missing}"

Try / catch

try:
    position.fill_stock_value(start_time, freq)
except ValueError as e:
    # drop unpriceable holdings or extend window and retry once
    position.fill_stock_value(start_time, freq, last_days=120)

Prevention

When it happens

Trigger: Building a Position from a config/dict whose stock entries lack a 'price' key, then running fill_stock_value(start_time, freq). Occurs when: the instrument is not in the local bin data; the backtest start_time is before the instrument's listing date (new IPO); wrong freq passed; delisted/suspended stock with no recent close.

Common situations: Warm-starting a backtest with an inherited real portfolio that includes recently listed stocks; using cn_data demo dump that lacks some instruments; passing freq='1min' while only daily data exists; start_time set earlier than data coverage.

Related errors


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