Factor attribution: private API

PortfolioOptimisers.attribution_idiosyncratic_covarianceFunction
attribution_idiosyncratic_covariance(rr::AbstractLoadingsRegressionResult)
attribution_idiosyncratic_covariance(rr::CrossSectionalFactorModel)
attribution_idiosyncratic_covariance(rr::Regression)

Return the idiosyncratic covariance a factor attribution adds to the systematic block.

One of the five reads factor_attribution takes off a loadings result. The root refuses, so a loadings result that carries no idiosyncratic block is named rather than silently attributed to zero.

A Regression answers its own esigma, which FactorPrior fills under rsd = true. Under rsd = false the field is nothing and the read answers a vector of zeros rather than refusing, because the carrier's covariance carries no residual block either: the predicted idiosyncratic component is zero, the systematic component reaches the total on its own, and the remainder stays at rounding level. A realised attribution is unaffected, because it measures the idiosyncratic series from the returns rather than from this field.

Arguments

  • rr: A loadings regression result.

Validation

  • The root method always raises an ArgumentError naming the type.
  • A CrossSectionalFactorModel whose esigma is nothing raises an IsNothingError.

Returns

  • esigma::VecNum_MatNum: The idiosyncratic variances, or the idiosyncratic covariance. A Regression that carries none answers a vector of zeros.

Related

source
PortfolioOptimisers.attribution_idiosyncratic_returnsFunction
attribution_idiosyncratic_returns(rr::AbstractLoadingsRegressionResult,
                                  pr::AbstractPriorResult)
attribution_idiosyncratic_returns(rr::CrossSectionalFactorModel,
                                  pr::AbstractPriorResult)
attribution_idiosyncratic_returns(rr::Regression, pr::AbstractPriorResult)

Return the idiosyncratic return series a realised factor attribution weights by the portfolio.

One of the five reads factor_attribution takes off a loadings result. The root refuses, so a loadings result that keeps no residual history is named rather than attributed to zero.

The read takes the carrier beside the block, because a block that stores no series recovers it from the result it travels on. A CrossSectionalFactorModel keeps its own residuals and ignores the carrier. A Regression keeps none, so the series is original_returns(pr) - pr.X: the difference between the returns the carrier was fitted on and the reconstruction F * M' .+ b' it holds. A wrapping prior that replaces X moves this series, and the difference lands in the unattributed remainder.

Arguments

  • rr: A loadings regression result.
  • pr: The prior result the block travels on.

Validation

  • The root method always raises an ArgumentError naming the type.
  • A CrossSectionalFactorModel whose csr is nothing raises an IsNothingError.
  • A Regression on a carrier whose o_X is nothing raises an IsNothingError, because such a carrier reconstructed nothing and the difference is zero at every observation.

Returns

  • eps::MatNum: The idiosyncratic returns, observations × assets.

Related

source
PortfolioOptimisers.attribution_factor_returnsFunction
attribution_factor_returns(rr::AbstractLoadingsRegressionResult,
                           pr::AbstractPriorResult)
attribution_factor_returns(rr::CrossSectionalFactorModel, pr::AbstractPriorResult)
attribution_factor_returns(rr::Regression, pr::AbstractPriorResult)

Return the factor return series a realised factor attribution multiplies by the exposures.

One of the five reads factor_attribution takes off a loadings result. The series is on the raw factor axis, which is the axis the loadings name, so a family re-basis does not move it.

The read takes the carrier beside the block, as attribution_idiosyncratic_returns does and for the same reason. A CrossSectionalFactorModel fits the factor returns itself and ignores the carrier. A Regression regresses on factors the caller supplied, so the series is pr.fpr.X, the scenarios of the nested factor-axis prior. That field needs no refusal of its own: LowOrderPrior admits rr and fpr only together, so a carrier that answers a loadings result answers a factor-axis prior beside it.

Arguments

  • rr: A loadings regression result.
  • pr: The prior result the block travels on.

Validation

  • The root method always raises an ArgumentError naming the type.
  • A CrossSectionalFactorModel whose csr is nothing raises an IsNothingError.

Returns

  • f::MatNum: The factor returns, observations × factors.

Related

source
PortfolioOptimisers.attribution_exposuresFunction
attribution_exposures(rr::AbstractLoadingsRegressionResult)
attribution_exposures(rr::CrossSectionalFactorModel)
attribution_exposures(rr::Regression)

Return the exposure history a realised factor attribution reads, one slice per observation.

One of the five reads factor_attribution takes off a loadings result. A block whose exposures do not move answers its loadings matrix, and the attribution then reads one static slice.

A Regression fits one loadings matrix over the whole sample, so it always answers that matrix and every observation reads the same slice.

Arguments

  • rr: A loadings regression result.

Validation

  • The root method always raises an ArgumentError naming the type.

Returns

  • Ms::Union{<:MatNum, <:Arr3Num}: The exposure history observations × assets × factors, or the static loadings assets × factors.

Related

source
PortfolioOptimisers.attribution_lagFunction
attribution_lag(rr::AbstractLoadingsRegressionResult)
attribution_lag(rr::CrossSectionalFactorModel)
attribution_lag(rr::Regression)

Return the number of observations by which the exposures lag the returns.

One of the five reads factor_attribution takes off a loadings result. The attribution keeps the exposures of observation t - lag with the returns of observation t, so a block that states no lag answers zero and the two axes line up as they stand.

A Regression fits the returns of an observation on the factor returns of the same observation, so its lag is zero.

Arguments

  • rr: A loadings regression result.

Validation

  • The root method always raises an ArgumentError naming the type.

Returns

  • lag::Int: The exposure lag, as a count.

Related

source
PortfolioOptimisers.attribution_familiesFunction
attribution_families(rr::AbstractLoadingsRegressionResult)
attribution_families(rr::CrossSectionalFactorModel)

Return the family label of each raw factor, or nothing when the block names none.

The family axis of a FactorAttributionResult exists exactly when this read answers a vector, so a block type that carries no family concept answers nothing through the root and needs no method of its own.

Arguments

  • rr: A loadings regression result.

Returns

  • fam::Option{<:VecStr}: The family label of each raw factor, or nothing.

Related

source
PortfolioOptimisers.attribution_family_basisFunction
attribution_family_basis(rr::AbstractLoadingsRegressionResult)
attribution_family_basis(rr::CrossSectionalFactorModel)

Return the family re-basis the block's fit was written in, or nothing.

The standard errors of a realised attribution are computed in the reduced full-rank basis and mapped back onto the raw axis, because a constrained family makes the raw Gram matrix singular. A block type that constrains no family answers nothing through the root, and the sandwich then runs on the raw axis.

Arguments

  • rr: A loadings regression result.

Returns

  • fcb::Option{<:AbstractFactorFamilyBasis}: The family re-basis, or nothing.

Related

source
PortfolioOptimisers.attribution_regression_weightsFunction
attribution_regression_weights(rr::AbstractLoadingsRegressionResult)
attribution_regression_weights(rr::CrossSectionalFactorModel)

Return the regression weight history the sandwich covariance reads, or nothing.

The standard errors of a realised attribution need the weight each asset carried in the fit of each observation. A block type that records none answers nothing through the root, and se = true then refuses.

Arguments

  • rr: A loadings regression result.

Returns

  • rw::Option{<:MatNum}: The regression weight history observations × assets, or nothing.

Related

source
PortfolioOptimisers.attribution_idiosyncratic_variancesFunction
attribution_idiosyncratic_variances(rr::AbstractLoadingsRegressionResult)
attribution_idiosyncratic_variances(rr::CrossSectionalFactorModel)

Return the idiosyncratic variance history the sandwich covariance reads, or nothing.

The standard errors of a realised attribution need the idiosyncratic variance of each asset at each observation. A block type that records none answers nothing through the root, and se = true then refuses.

Arguments

  • rr: A loadings regression result.

Returns

  • vs::Option{<:MatNum}: The idiosyncratic variance history observations × assets, or nothing.

Related

source
PortfolioOptimisers.assert_attribution_fieldFunction
assert_attribution_field(x::Nothing, sym::Symbol)
assert_attribution_field(x, sym::Symbol)

Return an optional field of a factor model block, or raise naming it.

The five reads a factor attribution takes are optional fields of the block, so one refusal names the field a caller must keep rather than letting a nothing reach the arithmetic.

Arguments

  • x: The field's value.
  • sym: The field's name.

Validation

  • x is not nothing, else an IsNothingError naming the field is raised.

Returns

  • x: The field's value, unchanged.

Related

source
PortfolioOptimisers.assert_attribution_carrierFunction
assert_attribution_carrier(x::Nothing, sym::Symbol)
assert_attribution_carrier(x, sym::Symbol)

Return an optional field of the prior result a factor model block travels on, or raise naming it.

The sibling of assert_attribution_field, and it names the carrier rather than the block. A block that stores no return series of its own — a Regression — reads the two series off the carrier, so a nothing there is a missing input of the attribution and not a missing field of the block.

Arguments

  • x: The field's value.
  • sym: The field's name.

Validation

  • x is not nothing, else an IsNothingError naming the field is raised.

Returns

  • x: The field's value, unchanged.

Related

source
PortfolioOptimisers.attribution_finiteFunction
attribution_finite(A::AbstractArray)

Return an array with every non-finite entry replaced by zero, or the array itself.

A Prior Estimator fits on the coverage universe and answers on the full one, so an asset it could not estimate carries NaN in its row of mu, of sigma and of the block. A holding in such an asset is reported by attribution_investable_diagnostic before the arithmetic starts, and 0 * NaN is NaN and would poison every sum whether the asset is held or not. The entries are replaced once here rather than guarded at each of the sums, so a non-investable asset contributes nothing to any component.

An array that is finite throughout is returned unchanged and is not copied, which is the whole universe's case.

Arguments

  • A: The array.

Returns

  • A::AbstractArray: The array, with every non-finite entry replaced by zero.

Related

source
PortfolioOptimisers.attribution_investable_diagnosticFunction
attribution_investable_diagnostic(w::VecNum_MatNum, pr::AbstractPriorResult,
                                  strict::Bool)

Report a portfolio that holds an asset the prior could not estimate.

A non-investable asset carries NaN in mu and on the diagonal of sigma, so no moment of it exists to attribute. A portfolio that holds none of it is decomposed exactly, with a zero row wherever the asset appears. A portfolio that holds some of it is a term that cannot contribute a row, and it takes the library's strictness policy through strict_diagnostic: a warning names the assets and the decomposition proceeds with their contributions zeroed, or an ArgumentError names them under strict.

The zeroing is the work of attribution_finite on every array the decomposition reads, so the held asset contributes nothing to the systematic and idiosyncratic components. On the predicted side the totals read pr.mu and pr.sigma with the same zeroing, so they describe the portfolio without the holding. On the realised side the net series still carries whatever return the holding earned, and that return lands in the unattributed remainder, which is where the reader looks for what the model does not explain.

A weight history that holds a non-investable asset is the shape a walk-forward produces: a prior fit on the whole history marks every asset that delisted inside it non-investable, and an early fold held it while it was listed. That is why the default is to warn.

Arguments

  • w: The constant weights, or the weight history.
  • pr: The prior result.
  • strict: Whether a held non-investable asset raises rather than warns.

Validation

  • Every weight at a non-investable asset is zero, else a warning naming the assets is emitted, or an ArgumentError naming them is raised under strict.

Returns

  • Nothing is returned.

Related

source
PortfolioOptimisers.attribution_investable_rowsFunction
attribution_investable_rows(A::AbstractArray, imsk::Option{BitVector})

Return the loadings or the intercept with the rows of the non-investable assets replaced by zero.

A non-investable asset can carry finite loadings while its idiosyncratic variance is NaN: the prior needs three facts to state a moment, and one missing fact is enough. attribution_finite replaces only the NaN, so the finite loadings of a held non-investable asset would reach the systematic component while the totals, which read pr.mu and pr.sigma, exclude the asset. The row is zeroed whole, so every component describes the portfolio without the non-investable assets, and the four still sum to the total.

An absent mask means that every asset is investable, and the array is returned unchanged.

Arguments

  • A: The loadings, assets × factors, or the intercept, one entry per asset.
  • imsk: The investable mask, or nothing.

Returns

  • A::AbstractArray: The array, with the rows of the non-investable assets replaced by zero.

Related

source
PortfolioOptimisers.attribution_investable_blockFunction
attribution_investable_block(E::VecNum_MatNum, imsk::Option{BitVector})

Return the idiosyncratic covariance with the rows and the columns of the non-investable assets replaced by zero.

The covariance sibling of attribution_investable_rows. A diagonal covariance travels as a vector and loses the entries, and a full one loses the rows and the columns, so w' D w reads nothing of a held non-investable asset through either.

Arguments

  • E: The idiosyncratic variances, one entry per asset, or the idiosyncratic covariance, assets × assets.
  • imsk: The investable mask, or nothing.

Returns

  • E::VecNum_MatNum: The variances or the covariance, with the non-investable assets replaced by zero.

Related

source
PortfolioOptimisers.attribution_prior_blockFunction
attribution_prior_block(pr::AbstractPriorResult)

Return the factor model block and the factor distribution a factor attribution decomposes.

A prior result carries the loadings in rr and the factor distribution in fpr, and its constructor keeps the two together, so one check establishes the whole block. Every wrapping prior forwards both unchanged while it replaces mu and sigma, which is why the totals anchor on the carrier and the gaps land in the unattributed remainder.

Arguments

  • pr: A prior result.

Validation

Returns

  • rr::AbstractLoadingsRegressionResult: The factor model block.
  • fpr::LowOrderPrior: The factor distribution.

Related

source
PortfolioOptimisers.attribution_scaleFunction
attribution_scale(ppy::Number)

Return the two scale factors an annualisation applies.

Means and variances scale by ppy, and volatilities by its square root. Shares and correlations are ratios of two quantities that scale alike, so they are not scaled at all.

Arguments

  • ppy: Periods per year the numbers are scaled to.

Validation

  • ppy > 0, else a DomainError is raised.

Returns

  • s1::Real: The factor a mean or a variance takes.
  • s2::Real: The factor a volatility takes.

Related

source
PortfolioOptimisers.attribution_idiosyncratic_matrixFunction
attribution_idiosyncratic_matrix(esigma::VecNum)
attribution_idiosyncratic_matrix(esigma::MatNum)

Return the idiosyncratic covariance as a matrix the quadratic form reads.

The idiosyncratic block takes two shapes, a vector of variances and a full covariance, so the shape is the dispatch and no caller tests it.

Arguments

  • esigma: The idiosyncratic variances, or the idiosyncratic covariance.

Returns

  • D::AbstractMatrix: The idiosyncratic covariance.

Related

source
PortfolioOptimisers.attribution_safe_corrFunction
attribution_safe_corr(cv::Number, s1::Number, s2::Number)

Return a correlation, or NaN when the pair of volatilities cannot normalise it.

A factor whose standalone volatility is zero has no correlation with anything, and the quotient would be an infinity or a NaN of the arithmetic's own choosing. The verb answers NaN so that every such row reads alike.

Arguments

  • cv: The covariance.
  • s1: The first volatility.
  • s2: The second volatility.

Returns

  • rho::Real: The correlation, or NaN.

Related

source
PortfolioOptimisers.attribution_family_indexFunction
attribution_family_index(fam::VecStr)

Return the rows of the family axis and the raw factors each of them sums.

The axis is the sorted unique labels, so it is deterministic and independent of the order the factors were built in. Every consumer of the family axis reads the axis from here, so the labels, the sums, the exposure spread and the standard errors are in one order.

Arguments

  • fam: The family label of each raw factor.

Returns

  • labels::Vector{String}: The sorted unique family labels.
  • idx::Vector{Vector{Int}}: The raw factors of each family.

Related

source
PortfolioOptimisers.attribution_family_axisFunction
attribution_family_axis(fam::Nothing, args...)
attribution_family_axis(fam::VecStr, fbd::AttributionBreakdown, exposure_std, mu_se)

Return the family axis of a factor attribution as a sum of the rows of the factor axis.

A family is a set of factors, so the four additive fields sum over the rows of the family. The axis is the sorted unique labels, which makes it deterministic and independent of the order the factors were built in; a plot that shows the largest families sorts the rows it draws. vol, corr and mu are nothing, because no single standalone volatility, correlation or mean return describes a set of factors.

Arguments

  • fam: The family label of each raw factor, or nothing.
  • fbd: The factor axis.
  • exposure_std: The standard deviation of the per-observation family exposure, or nothing.
  • mu_se: The standard error of the family mean return contribution, or nothing.

Returns

  • fmbd::Option{<:AttributionBreakdown}: The family axis, or nothing when the block names no family.

Related

source
PortfolioOptimisers.predicted_attribution_assetsFunction
predicted_attribution_assets(assets::Bool, w, mdl::NamedTuple, Fb, sigma_p, sc)

Return the asset axis and the asset-by-factor matrices of a predicted attribution.

The asset axis decomposes the model, not the anchors: every row reads M F M' + D and M mu_f + b, so the systematic rows sum to the systematic component, the idiosyncratic rows to the idiosyncratic component, and vol_contrib to the two together. It therefore does not reach the total, and the difference is the unattributed remainder, which is a property of the portfolio and has no per-asset split. The realised asset axis satisfies the same identity, so a reader compares the two sides row by row.

Arguments

  • assets: Whether to compute the two answers at all.
  • w: Portfolio weights.
  • mdl: The factor model: the raw loadings M, the factor covariance F, the expected factor returns mu_f, the idiosyncratic covariance D and the factor-orthogonal expected return bp.
  • Fb: The product of the factor covariance and the portfolio exposure.
  • sigma_p: Portfolio volatility.
  • sc: The two annualisation factors.

Returns

  • abd::Option{<:AssetAttributionBreakdown}: The asset axis, or nothing.
  • afc::Option{<:AssetFactorContribution}: The asset-by-factor matrices, or nothing.

Related

source