quantlab.risk.attribution#

Factor attribution: a book’s per-bar return split over a factor risk model (ADR 0026).

Given the holdings at the start of each bar, as signed fractions of the NAV, factor_attribution splits each bar’s NAV return into five terms that sum to it exactly:

  • factor: per factor k, the holdings at the close of t-1 times the exposures of t-1 times the regression store’s factor return of row t (factor_contribution); a NaN factor return (a thin industry) counts as 0;

  • specific: the covered holdings times their specific returns of row t;

  • uncovered: the held symbols without a full set of exposures at t-1, without a specific return at t or without a risk-free rate at t-1 (the three pieces a covered symbol’s return is split into), times their own return of bar t;

  • risk_free: the covered holdings times the risk-free rate of t-1, read from the risk model’s price inputs (the regression is on excess returns);

  • trading: the NAV return less the four terms above: the fills at t’s open, fees, slippage and idle cash.

The terms are linked over time as log contributions, each bar’s terms times ln(1+r)/r, so the cumulative sums over bars and terms are log NAV at every bar, and no bar’s value depends on the bars after it.

The risk of the same book is attributed too:

  • ex ante, at bar t, from the model’s forecast made at t-1 for bar t (FactorRiskModel.forecast on the estimate row and exposures of t-1, the coverage rule the portfolio’s covariance estimator uses): the held symbols it covers give the forecast variance x'Fx + sum w^2 s^2, x their net exposures, F the factor covariance and s their specific risks; each factor’s x-sigma-rho contribution x_k (Fx)_k / sigma and the specific part sum w^2 s^2 / sigma sum to sigma, a factor the forecast leaves out at the bar contributing 0. Holdings it does not cover are left out;

  • ex post, over a run of bars, each term’s cov(c_j, r) / sigma(r) on the per-bar (arithmetic) contributions, which sum to the realized volatility.

Factors are grouped by FactorRiskModel.factor_groups (country, industry, style). attribution_summary gives, for a run of bars (a segment), each term’s, factor’s and group’s annualized log growth, the styles’ mean exposures, the top and bottom industries, the ex-ante and ex-post risk contributions and the coverage.

The module reads a FactorRiskModel’s forecast_window only; it builds nothing (ADR 0024) and imports nothing of the portfolio, backtest or runs layers.

Examples#

With holdings, nav_returns and symbol_returns a book’s panels and model a FactorRiskModel whose regression store covers them:

>>> attribution = factor_attribution(holdings, nav_returns, symbol_returns, model)
>>> bool(np.allclose(attribution["contribution"].sum("term"), nav_returns))
True
>>> sorted(attribution_summary(attribution, bars_per_year=252)["annualized_log_return"])
['factor', 'risk_free', 'specific', 'total', 'trading', 'uncovered']

Functions

attribution_summary

Summarize a run of bars (a segment) of factor_attribution's dataset.

factor_attribution

Split each bar's NAV return and risk over risk_model's factors (see the module docstring).