microsoft/qlib · error · ValueError
None in [trade_start_time, account_value, cash, return_rate,
Error message
None in [trade_start_time, account_value, cash, return_rate, total_turnover, turnover_rate, total_cost, cost_rate, stock_value]
What it means
PortfolioMetric.fill (the per-step record API) requires complete accounting inputs; if any of trade_start_time, account_value, cash, return_rate, total_turnover, turnover_rate, total_cost, cost_rate, stock_value is None it raises ValueError listing them. The method writes one row per trade step into the accounts/returns/turnover/cost dicts, and None would poison all derived report metrics.
Source
Thrown at qlib/backtest/report.py:179
turnover_rate: float | None = None,
total_cost: float | None = None,
cost_rate: float | None = None,
stock_value: float | None = None,
bench_value: float | None = None,
) -> None:
# check data
if None in [
trade_start_time,
account_value,
cash,
return_rate,
total_turnover,
turnover_rate,
total_cost,
cost_rate,
stock_value,
]:
raise ValueError(
"None in [trade_start_time, account_value, cash, return_rate, total_turnover, turnover_rate, "
"total_cost, cost_rate, stock_value]",
)
if trade_end_time is None and bench_value is None:
raise ValueError("Both trade_end_time and bench_value is None, benchmark is not usable.")
elif bench_value is None:
bench_value = self._sample_benchmark(self.bench, trade_start_time, trade_end_time)
# update pm data
self.accounts[trade_start_time] = account_value
self.returns[trade_start_time] = return_rate
self.total_turnovers[trade_start_time] = total_turnover
self.turnovers[trade_start_time] = turnover_rate
self.total_costs[trade_start_time] = total_cost
self.costs[trade_start_time] = cost_rate
self.values[trade_start_time] = stock_value
self.cashes[trade_start_time] = cashView on GitHub (pinned to 79633dd950)
Solutions
- Supply every required field: default missing numeric fields to 0.0 and a valid trade_start_time
- Trace which argument is None: print/log the tuple before calling fill
- If a step genuinely had no trades, still pass zeros rather than None so reports stay consistent
Example fix
# before
pm.fill(trade_start_time=t, account_value=v, cash=c) # rest default to None
# after
pm.fill(trade_start_time=t, account_value=v, cash=c, return_rate=r or 0.0,
total_turnover=tov or 0.0, turnover_rate=tr or 0.0,
total_cost=tc or 0.0, cost_rate=cr or 0.0, stock_value=sv or 0.0) Defensive patterns
Strategy: validation
Validate before calling
required = dict(trade_start_time=trade_start_time, account_value=account_value, cash=cash,
return_rate=return_rate, total_turnover=total_turnover, turnover_rate=turnover_rate,
total_cost=total_cost, cost_rate=cost_rate, stock_value=stock_value)
none_fields = [k for k, v in required.items() if v is None]
assert not none_fields, f"fill() missing: {none_fields}" Try / catch
try:
pm.fill(...)
except ValueError as e:
raise RuntimeError(f"incomplete metrics for step: {e}") from e Prevention
- Default numeric metrics to 0.0, never None
- Log the full metric tuple once per backtest to catch missing fields early
- Pin keyword names when calling fill — avoid positional args
When it happens
Trigger: Calling pm.fill(...) with a forgotten keyword argument (defaults None) or a computed metric that evaluated to None — most often bench_value-related returns or cost fields left unset by custom executors; also positional-argument mix-ups.
Common situations: Custom executors/nested flows calling fill with only a few fields; porting old code to a newer qlib signature that added stock_value; account fields returned as None when the exchange produced no deals.
Related errors
- benchmark freq can't be None!
- Both trade_end_time and bench_value is None, benchmark is no
- The benchmark {_codes} does not exist. Please provide the ri
- trade_calendar is necessary for getting TradeRangeByTime.
- The decision didn't provide an index range
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/1ab68d0cd27eed93.
Report an issue: GitHub.