skfolio.attribution.predicted_factor_attribution#

skfolio.attribution.predicted_factor_attribution(weights, loading_matrix, factor_covariance, idio_covariance, asset_names, factor_names, factor_families=None, factor_mu=None, idio_mu=None, annualization_factor=252.0, compute_asset_breakdowns=True)[source]#

Compute predicted (ex-ante) factor volatility and return attribution.

The volatility attribution follows the exposure-volatility-correlation framework (also called \(x-\sigma-\rho\)). It decomposes portfolio volatility into systematic (factor) and idiosyncratic (specific) contributions.

The return attribution decomposes portfolio expected return into spanned (factor-explained) and orthogonal expected return contributions.

Factor Model:

The asset covariance matrix is modeled as:

\[\Sigma = B F B^\top + D\]

where \(B\) is the asset-by-factor loading matrix, \(F\) is the factor covariance matrix and \(D\) is the idiosyncratic covariance matrix.

The expected return vector is modeled as:

\[\mu = B \mu_f + \mu_\perp\]

where \(\mu_f\) contains the expected factor returns (factor premia), \(B \mu_f\) is the factor-spanned expected return and \(\mu_\perp\) is the factor-orthogonal expected return, also called orthogonal alpha.

Portfolio Variance Decomposition:

Let \(w\) be portfolio weights and \(b = B^\top w\) be the portfolio factor

exposure vector. Then:

\[\sigma_P^2 = w^\top \Sigma w = b^\top F b + w^\top D w.\]

Portfolio Expected Return Decomposition:

\[\mu_P = w^\top \mu = b^\top \mu_f + w^\top \mu_\perp.\]

Volatility Contributions:

The contribution of factor \(k\) to portfolio volatility is defined as:

\[\operatorname{VolContrib}_k = \frac{b_k (F b)_k}{\sigma_P}.\]

where \(\sigma_P = \sqrt{w^\top \Sigma w}\) is total portfolio volatility.

These contributions are additive: they sum to the systematic component of volatility.

\[\sum_k \operatorname{VolContrib}_k = \frac{b^\top F b}{\sigma_P}.\]

The systematic vs. idiosyncratic vs. total component contributions are:

\[\operatorname{VolContrib}_{\mathrm{sys}} = \frac{b^\top F b}{\sigma_P}, \qquad \operatorname{VolContrib}_{\mathrm{idio}} = \frac{w^\top D w}{\sigma_P}, \qquad \operatorname{VolContrib}_{\mathrm{total}} = \sigma_P.\]

and sum exactly: \(\operatorname{VolContrib}_{\mathrm{sys}} + \operatorname{VolContrib}_{\mathrm{idio}} = \sigma_P\).

Expected Return Contributions:

The contribution of each factor to spanned expected return is:

\[\operatorname{MuContrib}_k = b_k \mu_{f,k}.\]

These are also additive:

\[\sum_k \operatorname{MuContrib}_k = b^\top \mu_f.\]

Correlation (x-sigma-rho framework):

Let \(\sigma_k = \sqrt{F_{kk}}\) be factor \(k\) standalone volatility. The correlation of factor \(k\) with the portfolio return is:

\[\rho_{k,P} = \frac{(F b)_k}{\sigma_k \sigma_P}.\]

The factor volatility contribution can then be written as:

\[\operatorname{VolContrib}_k = b_k \sigma_k \rho_{k,P}.\]

Percentage of Total Variance:

The variance share of each factor is:

\[\operatorname{PctTotalVariance}_k = \frac{\operatorname{VolContrib}_k}{\sigma_P}.\]

NaN handling:

loading_matrix, idio_covariance and idio_mu may contain NaN for non-investable assets (delisted, not-yet-listed, warm-up). For idio_covariance, inactive assets are identified by NaN diagonal entries, following the covariance estimator convention. When a non-zero weight falls on such an asset a warning is emitted and the asset’s contribution is effectively zeroed out. weights, factor_covariance and factor_mu must be finite.

Parameters:
weightsarray-like of shape (n_assets,)

Portfolio weights vector.

loading_matrixarray-like of shape (n_assets, n_factors)

Asset-by-factor loading (exposure) matrix.. NaN entries for non-investable assets are filled with 0 (requires corresponding weights to be zero).

factor_covariancearray-like of shape (n_factors, n_factors)

Covariance matrix of the factors. Must be per-period (e.g., daily covariance if using daily data). Use annualization_factor to scale to annualized values.

idio_covariancearray-like of shape (n_assets,) or (n_assets, n_assets)

Idiosyncratic (specific) covariance. If 1D, treated as diagonal variances. If 2D, used as full covariance matrix. Must be per-period, same as factor_covariance. NaN entries for non-investable assets are filled with 0.

asset_namesarray-like of shape (n_assets,)

Names for each asset (e.g., [“AAPL”, “GOOGL”, “MSFT”]).

factor_namesarray-like of shape (n_factors,)

Names for each factor (e.g., [“Momentum”, “Value”, “Size”]).

factor_familiesarray-like of shape (n_factors,), optional

Family/category for each factor (e.g., “Style”, “Industry”). If provided, enables family-level aggregation in DataFrame output.

factor_muarray-like of shape (n_factors,), optional

Expected returns of each factor (factor premia), \(\mu_f\). Defaults to zeros if not provided. Must be per-period (e.g., daily expected returns if using daily data). All inputs (factor_covariance, idio_covariance, factor_mu, idio_mu) must share the same periodicity. Use annualization_factor to scale outputs to annualized values.

idio_muarray-like of shape (n_assets,), optional

Factor-orthogonal expected return for each asset, \(\mu_\perp\). It is distinct from the time-series mean of idio_returns, which is not enforced to be factor-orthogonal. Defaults to zeros if not provided. Must be per-period, same as factor_mu. NaN entries for non-investable assets are filled with 0 (requires corresponding weights to be zero).

Note

This vector must already be orthogonal to the column span of the loading matrix \(B\) (with respect to your chosen regression metric, e.g., OLS or GLS). This function does not perform any orthogonalization. It assumes idio_mu satisfies the decomposition \(\mu = B \mu_f + \mu_\perp\) where \(B^\top \mu_\perp = 0\) (or the appropriate weighted inner product equals zero for GLS). Typically, this is the residual vector from regressing expected asset returns onto the factor loadings.

annualization_factorfloat, default=252.0

Used to annualize expected returns, variances and volatilities. Use 1.0 to disable annualization. Common values: 252 for daily data, 12 for monthly data.

compute_asset_breakdownsbool, default=True

If True, compute asset-level attribution (systematic/idiosyncratic decomposition). Set to False to skip asset attribution for faster computation.

Returns:
attributionAttribution

The Attribution dataclass containing component-level, factor-level and optionally asset-level attribution results. Use attribution.summary_df(), attribution.factors_df(), attribution.assets_df() to convert to pandas DataFrames.

Raises:
ValueError

If input dimensions are inconsistent, if weights,`factor_covariance` or factor_mu contain NaN, or if total variance is non-positive.

Examples

>>> from skfolio.attribution import predicted_factor_attribution
>>> import numpy as np
>>>
>>> # Volatility attribution only
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
... )
>>> print(f"Total volatility: {attribution.total.vol:.2%}")
>>> print(f"Factor exposures: {attribution.factors.exposure}")
>>>
>>> # With families
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
...     factor_families=["Style", "Style", "Size"],
... )
>>> print(f"Family names: {attribution.families.names}")
>>> print(f"Family vol contribs: {attribution.families.vol_contrib}")
>>>
>>> # Volatility and return attribution
>>> attribution = predicted_factor_attribution(
...     weights=np.array([0.4, 0.3, 0.3]),
...     loading_matrix=loading_matrix,
...     factor_covariance=factor_cov,
...     idio_covariance=idio_cov,
...     factor_names=["Momentum", "Value", "Size"],
...     factor_mu=np.array([0.05, 0.03, 0.02]),
... )
>>> attribution.summary_df()