microsoft/qlib · error · ValueError

benchmark freq can't be None!

Error message

benchmark freq can't be None!

What it means

PortfolioMetric.init_bench -> _cal_benchmark builds the benchmark return series from data. If the benchmark is given as code(s) (not an inline pd.Series) the series must be fetched at frequency `freq`; when freq is None the query cannot be formed and ValueError('benchmark freq can't be None!') is raised. freq is stored on init_bench(freq=...) — it is None when the metric was initialized without freq and never set.

Source

Thrown at qlib/backtest/report.py:111

        self.benchmark_config = benchmark_config
        self.bench = self._cal_benchmark(self.benchmark_config, self.freq)

    @staticmethod
    def _cal_benchmark(benchmark_config: Optional[dict], freq: str) -> Optional[pd.Series]:
        if benchmark_config is None:
            return None
        benchmark = benchmark_config.get("benchmark", CSI300_BENCH)
        if benchmark is None:
            return None

        if isinstance(benchmark, pd.Series):
            return benchmark
        else:
            start_time = benchmark_config.get("start_time", None)
            end_time = benchmark_config.get("end_time", None)

            if freq is None:
                raise ValueError("benchmark freq can't be None!")
            _codes = benchmark if isinstance(benchmark, (list, dict)) else [benchmark]
            fields = ["$close/Ref($close,1)-1"]
            _temp_result, _ = get_higher_eq_freq_feature(_codes, fields, start_time, end_time, freq=freq)
            if len(_temp_result) == 0:
                raise ValueError(f"The benchmark {_codes} does not exist. Please provide the right benchmark")
            return (
                _temp_result.groupby(level="datetime", group_keys=False)[_temp_result.columns.tolist()[0]]
                .mean()
                .fillna(0)
            )

    def _sample_benchmark(
        self,
        bench: pd.Series,
        trade_start_time: Union[str, pd.Timestamp],
        trade_end_time: Union[str, pd.Timestamp],
    ) -> Optional[float]:
        if self.bench is None:

View on GitHub (pinned to 79633dd950)

Solutions

  1. Pass a freq when initializing: PortfolioMetric(..., freq='day') or init_bench(freq='day', benchmark_config=cfg)
  2. Call init_bench with an explicit freq each time you change benchmark_config
  3. Alternatively supply the benchmark as a precomputed pd.Series, which bypasses the freq requirement

Example fix

# before
pm.init_bench(benchmark_config={"benchmark": "SH000300"})

# after
pm.init_bench(freq="day", benchmark_config={"benchmark": "SH000300"})
Defensive patterns

Strategy: validation

Validate before calling

bench = (benchmark_config or {}).get("benchmark")
if bench is not None and not isinstance(bench, pd.Series):
    assert freq is not None, "benchmark freq can't be None — pass freq='day' (or your freq)"

Type guard

def needs_freq(benchmark_config) -> bool:
    b = (benchmark_config or {}).get("benchmark")
    return b is not None and not isinstance(b, pd.Series)

Try / catch

try:
    pm.init_bench(freq=freq, benchmark_config=cfg)
except ValueError as e:
    pm.init_bench(freq="day", benchmark_config=cfg)  # retry with explicit freq

Prevention

When it happens

Trigger: Creating PortfolioMetric (or calling init_bench) with a benchmark_config containing a string/list benchmark but no freq argument; e.g. PortfolioMetric(benchmark_config={'benchmark': 'SH000300'}) with freq left None, then any code path that materializes the bench.

Common situations: Migrating report code that previously supplied freq via a global/config; calling init_bench(benchmark_config=...) and forgetting the freq keyword; high-frequency backtests where freq is carried in executor config but not passed to the metric.

Related errors


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