microsoft/qlib · error · ValueError

at least one of `N` and `freq` should exist

Error message

at least one of `N` and `freq` should exist

What it means

risk_analysis requires an annualization factor: either an explicit number of periods per year (N) or a data frequency (freq) from which it derives N via Freq.parse and a per-frequency scaler table. Passing neither leaves no way to annualize mean/std into annualized_return and information_ratio, so it raises this ValueError.

Source

Thrown at qlib/contrib/evaluate.py:59

        analysis frequency used for calculating the scaler, at least one of `N` and `freq` should exist
    mode: Literal["sum", "product"]
        the method by which returns are accumulated:
        - "sum": Arithmetic accumulation (linear returns).
        - "product": Geometric accumulation (compounded returns).
    """

    def cal_risk_analysis_scaler(freq):
        _count, _freq = Freq.parse(freq)
        _freq_scaler = {
            Freq.NORM_FREQ_MINUTE: 240 * 238,
            Freq.NORM_FREQ_DAY: 238,
            Freq.NORM_FREQ_WEEK: 50,
            Freq.NORM_FREQ_MONTH: 12,
        }
        return _freq_scaler[_freq] / _count

    if N is None and freq is None:
        raise ValueError("at least one of `N` and `freq` should exist")
    if N is not None and freq is not None:
        warnings.warn("risk_analysis freq will be ignored")
    if N is None:
        N = cal_risk_analysis_scaler(freq)

    if mode == "sum":
        mean = r.mean()
        std = r.std(ddof=1)
        annualized_return = mean * N
        max_drawdown = (r.cumsum() - r.cumsum().cummax()).min()
    elif mode == "product":
        cumulative_curve = (1 + r).cumprod()
        # geometric mean (compound annual growth rate)
        mean = cumulative_curve.iloc[-1] ** (1 / len(r)) - 1
        # volatility of log returns
        std = np.log(1 + r).std(ddof=1)

        cumulative_return = cumulative_curve.iloc[-1] - 1

View on GitHub (pinned to 79633dd950)

Solutions

  1. Pass N directly for full control: risk_analysis(r, N=250) for daily returns.
  2. Or pass freq: risk_analysis(r, freq='day') — minute/week/month are also mapped to annualization scalars.
  3. If both are given, remove one — freq is silently ignored with a warning when N is present.

Example fix

// before
res = risk_analysis(report.normal_return)

// after
res = risk_analysis(report.normal_return, N=250)
# or: risk_analysis(report.normal_return, freq="day")
Defensive patterns

Strategy: validation

Validate before calling

if N is None and freq is None:
    N = 250  # daily data default
risk_analysis(r, N=N)

Type guard

def has_annualization(N, freq) -> bool:
    return N is not None or freq is not None

Try / catch

try:
    risk_analysis(r)
except ValueError as e:
    if "at least one of" in str(e):
        risk_analysis(r, N=250)
    else:
        raise

Prevention

When it happens

Trigger: Calling qlib.contrib.evaluate.risk_analysis(r) with both N=None and freq=None (their defaults), e.g. risk_analysis(report.normal_return).

Common situations: Upgrading from older qlib versions where freq had a different default or N was inferred; passing report analysis output without specifying report_freq; passing freq in an unrecognized form that ends up None.

Related errors


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