Factor attribution

PortfolioOptimisers.AttributionComponentType
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 is NaN on 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 is NaN on the predicted side of the unattributed remainder.
  • mu_se: Standard error of mu_contrib, or nothing. It is filled on the realised side under se = true, and it is nothing everywhere else.

Constructors

AttributionComponent(vol, vol_contrib, pct_var, mu_contrib, corr, mu_se)    -> AttributionComponent

Arguments 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

source
PortfolioOptimisers.AttributionBreakdownType
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, or nothing. It is nothing on the factor axis and the sorted unique family labels on the family axis.
  • exposure: Portfolio exposure to each row. It is M' * w on 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, or nothing. It is nothing on the predicted side, which reads one exposure and no history.
  • vol: Standalone volatility of each factor, or nothing on the family axis.
  • corr: Correlation of each factor with the portfolio return, or nothing on 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, or nothing on 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 of mu_contrib, one entry per row, or nothing. It is filled on the realised side under se = true, and a row of a currency family reports NaN.

Constructors

AttributionBreakdown(labels, exposure, exposure_std, vol, corr, vol_contrib, pct_var,                     mu, mu_contrib, mu_se) -> AttributionBreakdown

Arguments 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

source
PortfolioOptimisers.AssetAttributionBreakdownType
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, or nothing. It is nothing wherever 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) -> AssetAttributionBreakdown

Arguments 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

source
PortfolioOptimisers.AssetFactorContributionType
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) -> AssetFactorContribution

Arguments 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

source
PortfolioOptimisers.FactorAttributionResultType
struct FactorAttributionResult{__T_sys, __T_idio, __T_unattr, __T_total, __T_fbd, __T_fmbd, __T_abd, __T_afc, __T_realised, __T_ppy} <: AbstractResult

Decomposes 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, or nothing when the block names no family.
  • abd: The asset axis, or nothing unless the caller passed assets = true.
  • afc: The asset-by-factor contributions, or nothing unless the caller passed assets = 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 by ppy, volatilities by its square root, and shares and correlations not at all.

Constructors

FactorAttributionResult(sys, idio, unattr, total, fbd, fmbd, abd, afc, realised, ppy)    -> FactorAttributionResult

Arguments 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

source
PortfolioOptimisers.factor_attributionFunction
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

  1. Read the block and the factor distribution off pr, and the five series off the block.
  2. Anchor the totals: pr.mu and pr.sigma on the predicted side, the return series on the realised side.
  3. Decompose the systematic and the idiosyncratic parts, and put every gap into the remainder.
  4. Sum the factor rows by family when the block names families, and over the assets when assets = true.
  5. Scale by ppy: means and variances by ppy, 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 through result_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's pr or rd is 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 an ArgumentError rather than a warning.

Validation

  • pr carries a factor model block, else the IsNothingError of assert_prior_regression is raised.
  • ppy > 0, else a DomainError is raised.
  • Every weight at a non-investable asset is zero, else a warning names the assets, or an ArgumentError names them under strict.
  • Every held (observation, asset) pair of X is finite, else a warning names the pairs, or an ArgumentError names them under strict.
  • ret is finite throughout, else an IsNonFiniteError naming the observations is raised.
  • The block carries the fields the chosen decomposition reads, else an IsNothingError names the field.
  • The portfolio variance is positive on the predicted side, and the portfolio volatility is positive on the realised side, else a DomainError is raised.
  • 1 <= window <= T, else a DomainError is raised.
  • step >= 1, else a DomainError is raised.
  • se = true needs the regression weight history and the idiosyncratic variance history, else an IsNothingError names the field.

Returns

  • fa::FactorAttributionResult: The attribution, or a vector of them from a rolling method.

Related

source