Factor attribution
PortfolioOptimisers.AttributionComponent — Type
struct AttributionComponent{__T_vol, __T_vol_contrib, __T_pct_var, __T_mu_contrib, __T_corr, __T_mu_se}One row of a factor attribution: the volatility, the volatility contribution, the variance share, the mean return contribution and the correlation of one component of the portfolio return.
A component is the systematic part, the idiosyncratic part, the unattributed remainder or the total. The four instances a FactorAttributionResult carries hold the same six numbers, so a reader that tabulates one tabulates all four. Every number is scaled to the ppy the Result carries.
Fields
vol: Volatility of the component's own return series. It isNaNon the predicted side of the unattributed remainder, which is a gap between two moments and carries no series.
vol_contrib: Contribution of the component to the portfolio volatility. The four components sum to the portfolio volatility exactly.
pct_var: Share of the portfolio variance the component explains. The four components sum to one exactly.
mu_contrib: Contribution of the component to the portfolio mean return. The four components sum to the portfolio mean return exactly.
corr: Correlation of the component's return series with the portfolio return series. It isNaNon the predicted side of the unattributed remainder.
mu_se: Standard error ofmu_contrib, ornothing. It is filled on the realised side underse = true, and it isnothingeverywhere else.
Constructors
AttributionComponent(vol, vol_contrib, pct_var, mu_contrib, corr, mu_se) -> AttributionComponentArguments correspond to the struct's fields, in the order they are declared. The type is a part of a Result, so factor_attribution builds it and a caller reads it; there is no keyword constructor, and the type validates nothing of its own.
Related
PortfolioOptimisers.AttributionBreakdown — Type
struct AttributionBreakdown{__T_labels, __T_exposure, __T_exposure_std, __T_vol, __T_corr, __T_vol_contrib, __T_pct_var, __T_mu, __T_mu_contrib, __T_mu_se}The factor axis or the family axis of a factor attribution, one entry per row of the axis.
The factor axis carries no labels, because the factor names are carried input that the caller already holds. The family axis carries its own labels, because they are derived from the family labels of the block and exist nowhere else. Every number is scaled to the ppy the Result carries.
A family is a set of factors, so the four additive fields — exposure, vol_contrib, pct_var and mu_contrib — carry the sum of the rows of the family, and vol, corr and mu are nothing, because no single standalone volatility, correlation or mean return describes a set.
Fields
labels: Label of each row, ornothing. It isnothingon the factor axis and the sorted unique family labels on the family axis.
exposure: Portfolio exposure to each row. It isM' * won the predicted side, and the mean of the per-observation exposure on the realised side.
exposure_std: Standard deviation of the per-observation portfolio exposure to each row, ornothing. It isnothingon the predicted side, which reads one exposure and no history.
vol: Standalone volatility of each factor, ornothingon the family axis.
corr: Correlation of each factor with the portfolio return, ornothingon the family axis.
vol_contrib: Contribution of each row to the portfolio volatility. The rows sum to the systematic component.
pct_var: Share of the portfolio variance each row explains. The rows sum to the variance share of the systematic component.
mu: Mean return of each factor, ornothingon the family axis.
mu_contrib: Contribution of each row to the portfolio mean return. The rows sum to the mean return contribution of the systematic component.
mu_se: Standard error ofmu_contrib, one entry per row, ornothing. It is filled on the realised side underse = true, and a row of a currency family reportsNaN.
Constructors
AttributionBreakdown(labels, exposure, exposure_std, vol, corr, vol_contrib, pct_var, mu, mu_contrib, mu_se) -> AttributionBreakdownArguments correspond to the struct's fields, in the order they are declared. The type is a part of a Result, so factor_attribution builds it and a caller reads it; there is no keyword constructor, and the type validates nothing of its own.
Related
PortfolioOptimisers.AssetAttributionBreakdown — Type
struct AssetAttributionBreakdown{__T_weight, __T_weight_std, __T_sys_vol_contrib, __T_sys_mu_contrib, __T_idio_vol_contrib, __T_idio_mu_contrib, __T_vol, __T_corr, __T_vol_contrib, __T_pct_var, __T_mu, __T_mu_contrib}The asset axis of a factor attribution, one entry per asset.
Each asset carries its weight, its standalone moments and its systematic, idiosyncratic and total contributions, so a reader sees which holding drove a factor row. Every number is scaled to the ppy the Result carries.
The axis decomposes the factor model, so the systematic rows sum to the systematic component, the idiosyncratic rows to the idiosyncratic component, and vol_contrib, which is the two together, to both of them. It does not reach the total: the difference is the unattributed remainder, which is a property of the portfolio and has no per-asset split.
Fields
weight: Weight of each asset. It is the weight vector on the predicted side and under a constant weight, and the mean of the weight history otherwise.
weight_std: Standard deviation of the weight of each asset, ornothing. It isnothingwherever the weights are constant, and it is filled from a weight history.
sys_vol_contrib: Contribution of each asset to the systematic part of the portfolio volatility.
sys_mu_contrib: Contribution of each asset to the systematic part of the portfolio mean return.
idio_vol_contrib: Contribution of each asset to the idiosyncratic part of the portfolio volatility.
idio_mu_contrib: Contribution of each asset to the idiosyncratic part of the portfolio mean return.
vol: Standalone volatility of each asset.
corr: Correlation of each asset with the portfolio return.
vol_contrib: Contribution of each asset to the portfolio volatility, the systematic and the idiosyncratic parts together. The rows sum to the systematic and idiosyncratic components together.
pct_var: Share of the portfolio variance each asset explains. The rows sum to the variance share of the systematic and idiosyncratic components together.
mu: Mean return of each asset.
mu_contrib: Contribution of each asset to the portfolio mean return, the systematic and the idiosyncratic parts together. The rows sum to the systematic and idiosyncratic components together.
Constructors
AssetAttributionBreakdown(weight, weight_std, sys_vol_contrib, sys_mu_contrib, idio_vol_contrib, idio_mu_contrib, vol, corr, vol_contrib, pct_var, mu, mu_contrib) -> AssetAttributionBreakdownArguments correspond to the struct's fields, in the order they are declared. The type is a part of a Result, so factor_attribution builds it and a caller reads it; there is no keyword constructor, and the type validates nothing of its own.
Related
PortfolioOptimisers.AssetFactorContribution — Type
struct AssetFactorContribution{__T_vol_contrib, __T_mu_contrib}The asset-by-factor contributions of a factor attribution, two matrices of assets by factors.
The entry (i, k) is what asset i contributed through factor k. Summing a column over the assets gives the factor's row of the factor axis, and summing a row over the factors gives the asset's systematic row of the asset axis. Every number is scaled to the ppy the Result carries.
Fields
vol_contrib: Contribution to the portfolio volatility,assets × factors.
mu_contrib: Contribution to the portfolio mean return,assets × factors.
Constructors
AssetFactorContribution(vol_contrib, mu_contrib) -> AssetFactorContributionArguments correspond to the struct's fields, in the order they are declared. The type is a part of a Result, so factor_attribution builds it and a caller reads it; there is no keyword constructor, and the type validates nothing of its own.
Related
PortfolioOptimisers.FactorAttributionResult — Type
struct FactorAttributionResult{__T_sys, __T_idio, __T_unattr, __T_total, __T_fbd, __T_fmbd, __T_abd, __T_afc, __T_realised, __T_ppy} <: AbstractResultDecomposes a portfolio's volatility and mean return over the factors, the factor families and the assets of a factor model.
FactorAttributionResult is what factor_attribution returns. It carries the four components, the factor axis, and — when the block names families and when the caller asks for assets — the family axis, the asset axis and the asset-by-factor matrices, so an attribution can be tabulated, plotted, compared across runs or asserted on in a test.
Every field that an axis does not have is nothing, so a reader dispatches on ::Nothing rather than branching: fmbd is nothing when the block names no family, and abd and afc are nothing unless the caller passed assets = true.
Fields
sys: The systematic component, the part of the portfolio the factors explain.
idio: The idiosyncratic component, the part of the portfolio the assets' own residuals explain.
unattr: The unattributed remainder, the part of the portfolio the factor model does not explain.
total: The total, the portfolio's own volatility and mean return.
fbd: The factor axis, one row per raw factor.
fmbd: The family axis, one row per factor family, ornothingwhen the block names no family.
abd: The asset axis, ornothingunless the caller passedassets = true.
afc: The asset-by-factor contributions, ornothingunless the caller passedassets = true.
realised: Whether the attribution reads a realised history. A realised attribution reports a mean return, and a predicted one reports an expected return.
ppy: Periods per year the numbers are scaled to. Means and variances scale byppy, volatilities by its square root, and shares and correlations not at all.
Constructors
FactorAttributionResult(sys, idio, unattr, total, fbd, fmbd, abd, afc, realised, ppy) -> FactorAttributionResultArguments correspond to the struct's fields, in the order they are declared. The type is a Result, so factor_attribution builds it and a caller reads it; there is no keyword constructor, and the type validates nothing of its own.
Related
PortfolioOptimisers.factor_attribution — Function
factor_attribution(w::VecNum, pr::AbstractPriorResult; assets::Bool = false,
ppy::Number = 1, strict::Bool = false) -> FactorAttributionResult
factor_attribution(res::OptimisationResult, pr::Option{<:Pr_RR} = nothing;
kwargs...) -> FactorAttributionResult
factor_attribution(w::VecNum, pr::AbstractPriorResult, X::MatNum,
fees::Option{<:Fees} = nothing; assets::Bool = false,
se::Bool = false, ppy::Number = 1,
strict::Bool = false) -> FactorAttributionResult
factor_attribution(w::VecNum, pr::AbstractPriorResult, rd::ReturnsResult,
fees::Option{<:Fees} = nothing; kwargs...) -> FactorAttributionResult
factor_attribution(res::OptimisationResult, pr::Option{<:Pr_RR}, rd::ReturnsResult;
kwargs...) -> FactorAttributionResult
factor_attribution(W::MatNum, pr::AbstractPriorResult, ret::VecNum;
kwargs...) -> FactorAttributionResult
factor_attribution(pred::MultiPeriodPredictionResult, pr::AbstractPriorResult;
kwargs...) -> FactorAttributionResult
factor_attribution(args..., window::Integer; step::Integer = 1,
kwargs...) -> Vector{<:FactorAttributionResult}Decompose a portfolio's volatility and mean return over the factors of a factor model.
The verb reads the weights and the factor model block, and returns one FactorAttributionResult. The predicted methods take no return series and decompose the moments the optimiser saw. The realised methods take one, and decompose the history the portfolio actually produced. Each realised method has a rolling twin that takes a positional window and returns one Result per window.
The predicted totals anchor on the prior result, not on the model. pr.mu and pr.sigma are what the optimiser saw and what expected_return and expected_risk report, so they are the totals. A wrapping prior replaces them while it forwards the block unchanged, so the model no longer reproduces them, and the two gaps dot(w, pr.mu - M * fpr.mu - b) and dot(w, (pr.sigma - M * F * M' - D) * w) / sigma_P land in the unattributed remainder. The remainder is therefore present on the predicted side too, and it is at rounding level on a plain fit.
Every source of unexplained return lands in the remainder, and no guard reports it. On the realised side the identity per observation is portfolio return = systematic + idiosyncratic + unattributed, and the remainder holds the per-observation intercept share b_t * sum(w), the fees, the cash, the weight drift inside a period and the exposure lag. A large pct_var on the remainder means the model does not explain the portfolio, and the reader draws that conclusion.
A holding the prior could not estimate is warned about and zeroed, and strict turns the warning into a refusal. A non-investable asset carries NaN in mu, on the diagonal of sigma and across its rows of the block, so no moment of it exists to attribute. A portfolio that holds one takes the library's strictness policy through attribution_investable_diagnostic: under the default strict = false a warning names the assets, every NaN is replaced by zero, and the decomposition proceeds with nothing attributed to them; under strict = true an ArgumentError names them. On the realised side a held asset whose return is non-finite at an observation takes the same policy through attribution_net_returns, which names the observations and the assets, and zeroes those pairs. A weight history from a walk-forward holds exactly this shape whenever an asset delisted inside the history, which is why the default warns rather than refuses.
The factor shares disagree with factor_risk_contribution, and the disagreement is one term. That verb computes (M' w)_k * (pinv(M) * grad)_k with grad a finite difference of any risk measure, so for the variance and sigma = M F M' + D it reads grad = (M F M' w + D w) / sigma_P and its factor share is (M' w)_k * (F M' w + pinv(M) D w)_k / sigma_P. This decomposition's factor share is the first term alone, (M' w)_k * (F M' w)_k / sigma_P, and it holds the second, the leakage (M' w)_k * (pinv(M) D w)_k / sigma_P, in the idiosyncratic component instead. The two therefore agree exactly when pinv(M) D w is zero, and neither is wrong: one is an Euler decomposition through a pseudo-inverse, generic in the risk measure, and this one is the analytic model split, specific to the variance.
Algorithm
- Read the block and the factor distribution off
pr, and the five series off the block. - Anchor the totals:
pr.muandpr.sigmaon the predicted side, the return series on the realised side. - Decompose the systematic and the idiosyncratic parts, and put every gap into the remainder.
- Sum the factor rows by family when the block names families, and over the assets when
assets = true. - Scale by
ppy: means and variances byppy, volatilities by its square root.
Arguments
w: Portfolio weights.W: Portfolio weight history,observations × assets.pr: Prior result carrying the factor model block.res: Optimisation result whose weights, prior and fees the verb reads throughresult_investable_view. The result's own prior is on the universe the fit solved, and its weights are on the caller's, so the two meet on the result's investable universe, a caller'sprorrdis viewed at the same mask, and the attribution's asset axis spans the investable assets alone.X: Asset returns,observations × assets.rd: Returns result carrying the asset returns.ret: Net portfolio return series.pred: Multi-period prediction result whose folds give the weight history.fees: Fees the net series is formed against.window: Size of the rolling window, in observations.step: Stride between two consecutive windows.assets: Whether to fill the asset axis and the asset-by-factor matrices.se: Whether to fill the standard errors of the mean return contributions.ppy: Periods per year the numbers are scaled to.strict: Whether a holding in a non-investable asset, or a non-finite return at a held observation, raises anArgumentErrorrather than a warning.
Validation
prcarries a factor model block, else theIsNothingErrorofassert_prior_regressionis raised.ppy > 0, else aDomainErroris raised.- Every weight at a non-investable asset is zero, else a warning names the assets, or an
ArgumentErrornames them understrict. - Every held
(observation, asset)pair ofXis finite, else a warning names the pairs, or anArgumentErrornames them understrict. retis finite throughout, else anIsNonFiniteErrornaming the observations is raised.- The block carries the fields the chosen decomposition reads, else an
IsNothingErrornames the field. - The portfolio variance is positive on the predicted side, and the portfolio volatility is positive on the realised side, else a
DomainErroris raised. 1 <= window <= T, else aDomainErroris raised.step >= 1, else aDomainErroris raised.se = trueneeds the regression weight history and the idiosyncratic variance history, else anIsNothingErrornames the field.
Returns
fa::FactorAttributionResult: The attribution, or a vector of them from a rolling method.
Related