quantlab.risk.bias.risk_model_bias_statistics#
- quantlab.risk.bias.risk_model_bias_statistics(model, start, end, *, horizon=1, window=None, min_observations=None, random_portfolios=100, portfolio_size=500, seed=0)[source]#
Return the bias statistics of a factor risk model’s forecasts from its stores.
Every estimate row is a forecast made at its bar
tof the next bar. Over ahorizonofhbars, a forecast is taken everyhestimate bars fromstart(the outcomes do not overlap), its outcome is the sum of the nexthbars’ returns in the regression store, and its forecast volatilitysqrt(h)times the one-bar one: the one-bar forecasts are of the variance per bar of a multi-bar return, which is what Newey-West adjusts, so a horizon longer than one bar tests that adjustment. A forecast withouthregression bars after it, or an outcome missing one of its bars, has none. The forecasts, outcomes, exposures, market caps and estimation universe are read aligned byFactorRiskModel.forecast_window.The eigenfactors of a bar are those of the covariance block of the factors
covered_factorskeeps, numbered from 1, the lowest eigenvalue; with fewer factors kept, the last ones are missing that bar. An eigenfactor has no outcome on a bar missing the return of a kept factor, nor when its eigenvalue is not positive (a pairwise estimate need not be positive semi-definite).A random active portfolio holds, at each bar, the
portfolio_sizeeligible stocks it ranks first, capitalization weighted, less every eligible stock capitalization weighted, held over the horizon. Each portfolio ranks the symbols of the estimate store at random once, fromseed(our choice: a stock is kept while it stays eligible, and models sharing a regression store get the same portfolios). A stock is eligible attwhen the model’s forecast attcovers it (FactorRiskModel.forecast: every exposure, a specific risk, no exposure to a factor without a covariance, the rule the portfolio’s covariance estimator uses), and it is in the estimation universe, with a market cap and a return over every bar of the horizon (our choice: the stores hold no return for a stock that stops trading, so it leaves the portfolios before). Its forecast variance is that forecast’s,h (x' F x + sum w^2 s^2)withwits active weights andxits factor exposures; its realized return issum w (X f + u)summed over the horizon’s bars, the stocks’ excess returns (each bar’sXthe exposures of the bar before it), a factor without a return on a bar contributing nothing.- Parameters:
- modelFactorRiskModel
A model whose regression and estimate stores cover the range (the regression store to at least the bar after
endfor the last forecast to have an outcome).- start, endstr, datetime.date or pd.Timestamp
The forecast bars, both inclusive, inside the estimate store’s range.
- horizonint, default 1
Bars per outcome. USE4 tests monthly (21 daily bars).
- windowint, optional
Outcomes per rolling window (
bias_statistics); by default about a year,round(252 / horizon)(USE4: 12 months).- min_observationsint, optional
Fewest outcomes in a rolling window (
bias_statistics).- random_portfoliosint, default 100
Random active portfolios (USE4 Empirical Notes: 100).
- portfolio_sizeint, default 500
Stocks per random portfolio (USE4 Empirical Notes: 500). With as many stocks as the eligible universe, a portfolio is the benchmark and has no active risk.
- seedint, default 0
Seed of the random ranking.
- Returns:
- dict of str to xr.Dataset
"factor"on(timestamp, factor),"eigenfactor"on(timestamp, eigenfactor),"specific"on(timestamp, symbol)and"random"on(timestamp, portfolio), each as returned bybias_statistics,timestampthe forecast bar.
- Raises:
- ValueError
If
horizonis below 1, or a store has no recorded range or does not cover the range (RiskStore.read).
Examples
With
modeltheUse4RiskModelofexamples/sharadar_us_equity/risk_model.py, its stores built over the Sharadar history:>>> stats = risk_model_bias_statistics(model, "2007-07-13", "2026-10-02") >>> sorted(stats) ['eigenfactor', 'factor', 'random', 'specific'] >>> round(float(stats["random"]["bias"].mean()), 3) 1.003 >>> round(float(stats["factor"]["bias"].sel(factor="style_beta")), 3) 1.119