Cross-Sectional Factor Prior internals: private API

The verbs a CrossSectionalFactorPrior composes: the carrier it hands its Exposure Estimators, the exposure history and its dependency order, the two masks the fit runs on, the idiosyncratic block, the scenario reconstruction, and the lift onto the assets.

PortfolioOptimisers.cross_sectional_prior_pairsFunction
cross_sectional_prior_pairs(
    x::Union{AbstractDict, AbstractVector{<:Pair}},
    sym::Union{AbstractString, Symbol}
) -> Any

Collect one list-valued argument of a CrossSectionalFactorPrior into a vector of Pairs.

Three fields of the prior take the same form: the factors, the Neutralisation and the constrained Factor Families. Each takes Pairs in the order the caller wrote them, or any AbstractDict. A dictionary states no order, so the collected order is the one it iterates in, and a caller who needs a stated order writes Pairs.

Arguments

  • x: The Pairs or the dictionary.
  • sym: Name of the field, for the messages.

Validation

  • x is not empty. Raises an IsEmptyError.
  • No key repeats. Raises an ArgumentError.

Returns

  • pr::Vector{<:Pair}: The collected Pairs, each with a String key.

Examples

julia> PortfolioOptimisers.cross_sectional_prior_pairs(["style" => "industry"], :neutralise)1-element Vector{Pair{String, String}}: "style" => "industry"

Related

source
PortfolioOptimisers.cross_sectional_benchmark_carrierFunction
cross_sectional_benchmark_carrier(
    rd::ReturnsResult,
    name::AbstractString,
    W::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> ReturnsResult{_A, _B, _C, _D, _E, _F, _G, _H, _I, AssetPanel{__T_pf, __T_amsk, __T_emsk}} where {_A, _B, _C, _D, _E, _F, _G, _H, _I, __T_pf, __T_amsk, __T_emsk}

Return the carrier the Exposure Estimators of a Cross-Sectional Factor Prior are fitted on.

An Exposure Estimator weights its cross-sectional transforms by a benchmark-weight Panel Field it names. The prior computes those weights itself from the market capitalisation, so it writes them onto a copy of the Asset Panel before it builds any Factor Exposure. A field of that name already on the panel is replaced, so the prior's own weights are the ones every member reads.

Arguments

  • rd: The returns result to use. It must carry an Asset Panel in rd.pnl.
  • name: Name of the benchmark-weight Panel Field.
  • W: The benchmark weights, observations × assets.

Validation

Returns

  • rd::ReturnsResult: The carrier, with the benchmark weights on its Asset Panel.

Related

source
PortfolioOptimisers.cross_sectional_exposure_orderFunction
cross_sectional_exposure_order(
    factors::AbstractVector{<:Pair}
) -> Tuple{Vector{Int64}, Any}

Return the order in which a factor list is computed, and the source index of each factor.

A DerivedExposure is computed from the Factor Exposure of another factor of the same list, so the list is not always computed in the order it was written. The order this returns puts every source before the factor derived from it.

Algorithm

  1. Resolve the source name of every DerivedExposure to a position in the list.
  2. Sweep the list, appending every factor whose source is already computed, until every factor is placed.
  3. Refuse a sweep that places nothing, because the factors left over depend on each other.

Arguments

  • factors: Pairs of factor name => Exposure Estimator.

Validation

  • Every DerivedExposure names a factor of the list. Raises an ArgumentError.
  • No cycle of derived Factor Exposures. Raises an ArgumentError.

Returns

  • ord::Vector{Int}: The positions of factors, in the order they are computed.
  • src::Vector{Int}: The position of each factor's source, and 0 when it has none.

Related

source
PortfolioOptimisers.cross_sectional_exposure_widthsFunction
cross_sectional_exposure_widths(
    factors::AbstractVector{<:Pair},
    rd::ReturnsResult
) -> Vector{Int64}

Return the number of columns each factor of a list contributes to the factor axis.

A one-hot member contributes one column per level of its categorical Panel Field, and every other member contributes one.

Arguments

  • factors: Pairs of factor name => Exposure Estimator.
  • rd: The returns result to use. It carries the Asset Panel the one-hot levels are read from.

Returns

  • wid::Vector{Int}: The column count of each factor, in the order of factors.

Related

source
PortfolioOptimisers.cross_sectional_exposure_write!Function
cross_sectional_exposure_write!(Ms::AbstractArray{<:Number, 3}, A::MatNum, c::Integer,
                                w::Integer, nm::AbstractString) -> nothing
cross_sectional_exposure_write!(Ms::AbstractArray{<:Number, 3}, A::Arr3Num, c::Integer,
                                w::Integer, nm::AbstractString) -> nothing

Write one factor's Factor Exposure into the exposure history, in place.

A member answers a matrix when it contributes one factor and a three-dimensional array when it contributes several, so the two shapes are two methods rather than a branch.

Arguments

  • Ms: The exposure history, observations × assets × factors, changed in place.
  • A: The Factor Exposure of one member.
  • c: First column of the member on the factor axis.
  • w: Number of columns the member contributes.
  • nm: Name of the factor, for the messages.

Validation

  • A matches the observation and asset axes of Ms, and its factor axis is w. Raises a DimensionMismatch.

Returns

  • nothing. Ms carries the exposure.

Related

source
PortfolioOptimisers.cross_sectional_exposure_historyFunction
cross_sectional_exposure_history(
    factors::AbstractVector{<:Pair},
    rd::ReturnsResult
) -> NamedTuple{(:Ms, :nf, :fam), <:Tuple{Array{_A, 3} where _A, Vector{String}, Vector{String}}}

Build the exposure history of a factor list, and the factor axis it is written on.

Algorithm

  1. Read the factor axis with cross_sectional_factor_axis, and the column count of each factor with cross_sectional_exposure_widths.
  2. Take the computation order with cross_sectional_exposure_order.
  3. Compute each member in that order. A DerivedExposure is handed the exposure of its source, which the order has already written.
  4. Write each answer into the history with cross_sectional_exposure_write!.

Arguments

  • factors: Pairs of factor name => Exposure Estimator.
  • rd: The returns result to use. It carries the Asset Panel every member reads.

Validation

Returns

  • Ms::Array{<:Real, 3}: The exposure history, observations × assets × factors.
  • nf::Vector{String}: Name of each factor.
  • fam::Vector{String}: Family label of each factor.

Related

source
PortfolioOptimisers.cross_sectional_exposures_finiteFunction
cross_sectional_exposures_finite(
    Ms::AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}, 3},
    t::Integer,
    i::Integer
) -> Bool

Return whether one asset carries a finite Factor Exposure to every factor at one observation.

Arguments

  • Ms: The exposure history, observations × assets × factors.
  • t: The observation.
  • i: The asset.

Returns

  • ans::Bool: Whether every exposure of the pair is finite.

Related

source
PortfolioOptimisers.cross_sectional_warmupFunction
cross_sectional_warmup(
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    Ms::AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}, 3},
    emsk::AbstractMatrix{Bool}
) -> Int64

Return the number of leading observations a Cross-Sectional Factor Prior discards.

A Descriptor warms up, so the first observations of an exposure history carry no usable asset. An observation is cold when no asset of the estimation universe carries both a finite return and a finite Factor Exposure to every factor. The prior fits from the first observation that is not cold.

Arguments

  • X: Asset returns, observations × assets.
  • Ms: The exposure history, observations × assets × factors.
  • emsk: The estimation mask, observations × assets.

Validation

  • At least one observation is not cold. Raises an ArgumentError.

Returns

  • n::Int: The count of leading cold observations.

Related

source
PortfolioOptimisers.cross_sectional_eligibleFunction
cross_sectional_eligible(
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    Ms::AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}, 3},
    emsk::AbstractMatrix{Bool}
) -> BitMatrix

Return the eligibility mask of a cross-sectional fit.

An asset enters the fit of an observation when it is in the estimation universe, its return is finite, and its lagged Factor Exposure is finite for every factor. The mask is what the weight policy writes its weights on, so an ineligible pair carries a weight of zero and the fit never sees it.

Arguments

  • X: Asset returns, observations × assets.
  • Ms: The lagged exposure history, observations × assets × factors, aligned with X.
  • emsk: The estimation mask, observations × assets, aligned with X.

Validation

  • Ms matches X on the observation and asset axes, and emsk matches X. Raises a DimensionMismatch.

Returns

  • msk::BitMatrix: The eligibility mask, observations × assets.

Related

source
PortfolioOptimisers.assert_cross_sectional_coverageFunction
assert_cross_sectional_coverage(
    msk::AbstractMatrix{Bool},
    minra::Integer
)

Refuse a fit whose observations do not carry enough eligible assets.

A cross-sectional fit of K factors needs more assets than factors, and a fit that has barely more is noise. The prior refuses the whole fit rather than dropping the thin observations, because a factor-return series with a gap in it is not a series.

Arguments

  • msk: The eligibility mask, observations × assets.
  • minra: The smallest eligible asset count an observation may carry.

Validation

  • Every observation carries at least minra eligible assets. Raises an ArgumentError.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_cross_sectional_factor_momentsFunction
assert_cross_sectional_factor_moments(
    mu::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    sigma::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    n::Integer
)

Refuse a fit whose factor prior states a non-finite factor moment.

A Descriptor's warm-up and the factor prior's own warm-up are cumulative: the Descriptors fix the first observation of the factor-return history, and the estimator that reads that history warms up over it in turn. A window that clears the first warm-up can still be too short for the second, and a factor prior that answers a NaN rather than raising leaves the whole prior non-finite. So the fit reads what the factor prior answered and refuses by name, rather than handing a NaN moment to a consumer that meets it as an anonymous factorisation failure.

Arguments

  • mu: The factor means the factor prior stated.
  • sigma: The factor covariance the factor prior stated.
  • n: The count of fitted observations the factor prior read.

Validation

  • Every factor mean and every entry of the factor covariance is finite. Raises an IsNonFiniteError.

Returns

  • nothing.

Related

source
PortfolioOptimisers.cross_sectional_idiosyncratic_covarianceFunction
cross_sectional_idiosyncratic_covariance(
    th::Real,
    ce::CovarianceEstimator,
    pdm::Union{Nothing, AbstractPosdefEstimator},
    S::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    ev::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    amsk::AbstractMatrix{<:Bool}
) -> Any

Return the idiosyncratic covariance of the latest observation.

Algorithm

  1. A threshold of zero answers the latest idiosyncratic variances, so the block carries a vector and the asset covariance takes a diagonal.
  2. Otherwise, ask ce what a gapped cell of the standardised idiosyncratic returns is worth to it with gap_fill_value. A cell is left non-finite only where the asset is inactive.
  3. A finite answer is written over every such cell, and ce estimates the covariance of a sample with no gap left in it. Zero, the fallback, is the neutral value of a standardised series, so a gapped pair pulls the correlation toward the threshold it is dropped by.
  4. A non-finite answer hands the sample as it stands, with amsk as the active_mask, so a gap-aware ce freezes an inactive asset's block instead of decaying it.
  5. Convert the covariance to a correlation.
  6. Zero every correlation whose magnitude does not exceed the threshold, and keep the diagonal. A correlation that is not finite is zeroed by the same step.
  7. Rescale the correlation by the latest idiosyncratic volatilities, and make the block of assets with a finite variance positive definite.

Arguments

  • th: The correlation threshold.
  • ce: Covariance estimator of the standardised idiosyncratic returns.
  • pdm: Positive definite matrix estimator, or nothing.
  • S: Standardised idiosyncratic returns, observations × assets.
  • ev: The latest idiosyncratic variances, one per asset.
  • amsk: The active mask, observations × assets. It is read only by a ce that answers a non-finite gap_fill_value.

Returns

  • esigma::VecNum: The latest idiosyncratic variances, when th is zero.
  • esigma::MatNum: The idiosyncratic covariance, when th is positive.

Related

source
PortfolioOptimisers.cross_sectional_finite_meanFunction
cross_sectional_finite_mean(
    S::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    t::Integer
) -> Any

Return the average of the finite entries of one observation of a matrix.

The cross-section of standardised idiosyncratic returns is the one caller, and it needs the average to stand in for a cell it could not standardise. An observation with no finite entry answers zero, which is the average of a standardised series.

Arguments

  • S: The matrix, observations × assets.
  • t: The observation.

Returns

  • avg::Real: The average of the finite entries of observation t, or zero.

Related

source
PortfolioOptimisers.cross_sectional_standardised_residualsFunction
cross_sectional_standardised_residuals(
    eps::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    vs::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    amsk::AbstractMatrix{Bool}
) -> Matrix

Return the standardised idiosyncratic returns a scenario set is rebuilt from.

Each idiosyncratic return is divided by its own contemporaneous idiosyncratic volatility, so the history is on one scale and the latest volatilities can rescale it. An active pair whose standardised return is not finite takes the average standardised return of its own observation, so a sparse history does not shorten the scenario set. An inactive pair stays NaN.

Arguments

  • eps: Idiosyncratic returns, observations × assets.
  • vs: Idiosyncratic variance history, observations × assets.
  • amsk: The active mask, observations × assets.

Returns

  • S::Matrix{<:Real}: The standardised idiosyncratic returns, observations × assets.

Related

source
PortfolioOptimisers.cross_sectional_scenariosFunction
cross_sectional_scenarios(
    Fs::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    L::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    S::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    ev::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Any

Rebuild the asset return scenarios of a Cross-Sectional Factor Prior.

The factor prior's own scenarios are mapped onto the assets through the latest loadings, and the standardised idiosyncratic returns are rescaled by the latest idiosyncratic volatilities and added to them. A scenario therefore carries the factor risk and the idiosyncratic risk of the latest observation, whatever the risk of the observation it was drawn from was. The two histories may differ in length, so the last rows of the longer one are the ones that pair up.

Arguments

  • Fs: Factor return scenarios on the reduced axis, scenarios × factors.
  • L: The reduced loadings of the latest observation, assets × factors.
  • S: The standardised idiosyncratic returns, observations × assets.
  • ev: The latest idiosyncratic variances, one per asset.

Validation

  • L matches Fs on the factor axis, and S and ev match L on the asset axis. Raises a DimensionMismatch.

Returns

  • Xs::Matrix{<:Real}: The asset return scenarios, scenarios × assets.

Related

source
PortfolioOptimisers.cross_sectional_investableFunction
cross_sectional_investable(
    amsk::AbstractVector{Bool},
    L::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    ev::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Vector{Int64}

Return the assets a Cross-Sectional Factor Prior can state a finite moment for.

The prior fits on the coverage universe and answers on it, so an asset it cannot state a moment for carries NaN rather than leaving the result. Three facts make an asset investable: the Asset Panel activates it at the latest observation, its idiosyncratic variance is finite, and its latest loadings are finite.

Arguments

  • amsk: The active mask of the latest observation, one entry per asset.
  • L: The reduced loadings of the latest observation, assets × factors.
  • ev: The latest idiosyncratic variances, one per asset.

Returns

  • idx::Vector{Int}: The investable assets, in ascending order.

Related

source
PortfolioOptimisers.cross_sectional_panel_masksFunction
cross_sectional_panel_masks(pnl::AssetPanel{<:Any, Nothing, Nothing}) -> Union{}
cross_sectional_panel_masks(pnl::AssetPanel) -> Tuple

Read the two universe masks a Cross-Sectional Factor Prior fits against.

A static Asset Panel carries no observation axis and no masks, so it states no point-in-time universe. The two shapes are two methods rather than a test, which is what the mask fields being a type parameter is for.

Arguments

  • pnl: The Asset Panel.

Validation

  • The Asset Panel is time-varying. Raises an ArgumentError.

Returns

  • amsk::AbstractMatrix{Bool}: The active mask, observations × assets.
  • emsk::AbstractMatrix{Bool}: The estimation mask, observations × assets.

Related

source
PortfolioOptimisers.cross_sectional_cap_finite!Function
cross_sectional_cap_finite!(msk::BitMatrix, mcap::Nothing) -> nothing
cross_sectional_cap_finite!(msk::BitMatrix, mcap::MatNum) -> nothing

Drop from a mask every pair whose market capitalisation is not finite, in place.

A weight raised from a market capitalisation needs that capitalisation, and the two weight masks of the prior — the benchmark one and the regression one — take the same cut. A power of zero reads no capitalisation, so the nothing method leaves the mask as it stands.

Arguments

  • msk: The mask, observations × assets, changed in place.
  • mcap: The market capitalisation, observations × assets, or nothing.

Validation

  • mcap matches msk. Raises a DimensionMismatch.

Returns

  • nothing. msk carries the cut.

Related

source
PortfolioOptimisers.cross_sectional_needs_market_capFunction
cross_sectional_needs_market_cap(
    bp::Real,
    alg::AbstractCrossSectionalWeightsAlgorithm
) -> Bool

Return whether a Cross-Sectional Factor Prior reads the market capitalisation.

Two powers raise a weight from it: the benchmark one, which the prior carries, and the regression one, which the weight policy carries. Both at zero make every eligible asset take the same weight, and the prior then needs no market capitalisation and reads no Panel Field for it. Every member of the weight family carries p, which is what cs_weights_initial already reads.

Arguments

  • bp: The benchmark market-capitalisation power.
  • alg: The weight policy of the cross-sectional fit.

Returns

  • ans::Bool: Whether the market capitalisation is read.

Related

source
PortfolioOptimisers.cross_sectional_rowsFunction
cross_sectional_rows(A::Nothing, r) -> nothing
cross_sectional_rows(A::MatNum, r) -> MatNum

Take a set of observations out of an optional matrix.

The prior trims every parallel array to the same observations, and the market capitalisation is the one that may be absent. The nothing method keeps it absent.

Arguments

  • A: The matrix, observations × assets, or nothing.
  • r: The observations to keep.

Returns

  • A::Option{<:MatNum}: The rows, or nothing.

Related

source
PortfolioOptimisers.cross_sectional_reduced_loadingsFunction
cross_sectional_reduced_loadings(fcb::Nothing, L::MatNum) -> nothing
cross_sectional_reduced_loadings(fcb::FactorFamilyBasis, L::MatNum) -> MatNum

Return the reduced loadings a CrossSectionalFactorModel stores, or nothing.

The block holds L exactly when it holds a Factor Family Basis, so the two are answered by the same dispatch and cannot disagree.

Arguments

  • fcb: The Factor Family Basis, or nothing.
  • L: The loadings on the reduced axis.

Returns

  • L::Option{<:MatNum}: The reduced loadings, or nothing.

Related

source
PortfolioOptimisers.cross_sectional_neutralise!Function
cross_sectional_neutralise!(neutralise::Nothing, args...) -> nothing
cross_sectional_neutralise!(neutralise::AbstractVector{<:Pair}, Ms, cre, bw, nf, fam)
    -> nothing

Run the Neutralisation of a Cross-Sectional Factor Prior, in place.

A prior that states no Neutralisation runs none, which is the nothing method.

Arguments

  • neutralise: Pairs of key => targets, or nothing.
  • Ms: The exposure history, observations × assets × factors, changed in place.
  • cre: The prior's own Cross-Sectional Regression Estimator.
  • bw: The benchmark weights, observations × assets.
  • nf: Name of each factor.
  • fam: Family label of each factor.

Validation

Returns

  • nothing. Ms carries the neutralised exposures.

Related

source
PortfolioOptimisers.cross_sectional_family_basisFunction
cross_sectional_family_basis(families::Nothing, Ms, bw, nf, fam) -> NamedTuple
cross_sectional_family_basis(families::AbstractVector{<:Pair}, Ms, bw, nf, fam)
    -> NamedTuple

Build the Factor Family Basis of a Cross-Sectional Factor Prior, and reduce the factor axis through it.

A prior that constrains no Factor Family fits on the raw axis, which is the nothing method: the basis is absent and the three answers are the inputs.

Arguments

  • families: Pairs of family label => dropped member, or nothing.
  • Ms: The exposure history on the raw axis, observations × assets × factors.
  • bw: The benchmark weights, observations × assets.
  • nf: Name of each raw factor.
  • fam: Family label of each raw factor.

Validation

Returns

  • fcb::Option{<:FactorFamilyBasis}: The basis, or nothing.
  • Ms::Arr3Num: The exposure history on the reduced axis.
  • nf::VecStr: Name of each reduced factor.
  • fam::VecStr: Family label of each reduced factor.

Related

source
PortfolioOptimisers.cross_sectional_basis_nowFunction
cross_sectional_basis_now(fcb::Nothing, r) -> nothing
cross_sectional_basis_now(fcb::FactorFamilyBasis, r) -> FactorFamilyBasis

Slice a Factor Family Basis onto the observations a fit ran on.

The basis is built over the whole post-warm-up history, and the exposure lag then shortens the fit. The block stores the slice the fit ran on, so its observation axis is the one every other history on the block carries.

Arguments

  • fcb: The Factor Family Basis, or nothing.
  • r: The fitted observations, as an index into the post-warm-up axis.

Returns

  • fcb::Option{<:FactorFamilyBasis}: The basis of the fitted observations, or nothing.

Related

source
PortfolioOptimisers.cross_sectional_expandFunction
cross_sectional_expand(fcb::Nothing, r, lag::Integer, f, mu, sigma) -> NamedTuple
cross_sectional_expand(fcb::FactorFamilyBasis, r, lag::Integer, f, mu, sigma)
    -> NamedTuple

Expand a factor distribution from the reduced axis onto the raw one.

The nested factor prior of a LowOrderPrior sits on the raw axis, so a constraint written in a dropped factor's name still resolves. A prior that constrains no Factor Family fits on the raw axis already, which is the nothing method.

The fit of observation t regresses the returns of t on the exposures of t - lag, so its coefficients are coordinates in the basis of t - lag. The realised factor returns therefore expand with the lagged ratios, and the moments, which describe the next observation, expand with the current ones. The verb slices the basis itself rather than taking the two slices, because two arguments of that kind admit a mixed pair that no method answers.

Arguments

  • fcb: The Factor Family Basis over the post-warm-up history, or nothing.
  • r: The fitted observations, as an index into the post-warm-up axis.
  • lag: The exposure lag.
  • f: Realised factor returns on the reduced axis, observations × factors.
  • mu: Expected factor returns on the reduced axis.
  • sigma: Factor covariance on the reduced axis.

Validation

Returns

  • f::MatNum: The realised factor returns on the raw axis.
  • mu::VecNum: The expected factor returns on the raw axis.
  • sigma::MatNum: The factor covariance on the raw axis.

Related

source
PortfolioOptimisers.cross_sectional_residual_blockFunction
cross_sectional_residual_block(esigma::VecNum, idx) -> NamedTuple
cross_sectional_residual_block(esigma::MatNum, idx) -> NamedTuple

Return the idiosyncratic block of the asset covariance and a square root of it.

A diagonal block and a full block are the two shapes the idiosyncratic covariance takes, so they are two methods. Both answer the block over the investable assets alone, because a non-investable asset carries no finite variance to factorise.

Arguments

  • esigma: The idiosyncratic variances, or the idiosyncratic covariance.
  • idx: The investable assets.

Validation

  • A full block restricted to idx factorises. Raises a PosDefException.

Returns

  • D::MatNum: The block the asset covariance adds.
  • R::MatNum: A square root of the block, investable assets × investable assets.

Related

source
PortfolioOptimisers.cross_sectional_liftFunction
cross_sectional_lift(
    mp::AbstractMatrixProcessingEstimator,
    L::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    f_mu::AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    f_sigma::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    esigma::Union{AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    idx::AbstractVector{<:Integer},
    Xs::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}};
    kwargs...
) -> NamedTuple{(:mu, :sigma, :chol), <:Tuple{Any, Any, Any}}

Lift a factor distribution onto the assets of a Cross-Sectional Factor Prior.

Algorithm

  1. Take the loadings of the investable assets, and project the factor mean and the factor covariance through them.
  2. Process the systematic covariance with mp, as factor_lift does.
  3. Add the idiosyncratic block, and re-condition the sum.
  4. Build the low-rank square root [L·chol(F).L | R], whose trailing block is a square root of the idiosyncratic block.
  5. Scatter the three answers into the full asset universe, writing NaN at every asset the prior states no moment for.

Arguments

  • mp: Matrix processing estimator.
  • L: The reduced loadings of the latest observation, assets × factors.
  • f_mu: Expected factor returns on the reduced axis.
  • f_sigma: Factor covariance on the reduced axis.
  • esigma: The idiosyncratic variances, or the idiosyncratic covariance.
  • idx: The investable assets.
  • Xs: The asset return scenarios, scenarios × assets, which the processing reads.

Validation

  • L, f_mu and f_sigma agree on the factor axis. Raises a DimensionMismatch.

Returns

  • mu::Vector{<:Real}: Expected asset returns, NaN at a non-investable asset.
  • sigma::Matrix{<:Real}: Asset covariance, NaN in the row and the column of a non-investable asset.
  • chol::Matrix{<:Real}: The low-rank square root, NaN in the column of a non-investable asset.

Related

source
PortfolioOptimisers.cross_sectional_alpha_splitFunction
cross_sectional_alpha_split(cre::AbstractCrossSectionalRegressionEstimator, mu::VecNum,
                            L::MatNum, w::VecNum) -> NamedTuple

Split a Return Forecast into the part the latest Factor Exposures span and the part they do not.

Algorithm

  1. A forecast that is zero at every asset spans nothing, so the split is zero on both sides and no regression runs.
  2. Zero the weight of every asset whose forecast, or whose row of exposures, is not finite. The weights come from the lagged fit, so a positive weight there says nothing about the finiteness of the latest forecast. A cross-section with no positive weight left is the zero split of step 1.
  3. Regress the forecast on the exposures across the assets, under those weights and through the prior's own Cross-Sectional Regression Estimator, giving the spanned coefficients g.
  4. Subtract the spanned part L * g from the forecast, giving the orthogonal part. The entry of an asset whose forecast or whose exposures are not finite is NaN, which is how the family says that it forecasts nothing there.

Step 4 subtracts L * g rather than the fitted values of step 3, so that the split telescopes: the asset expected return is L * (λ μ_f + (1 - λ) g) + c (mu - L g), which is the forecast itself at λ = 0 and c = 1, whatever the regression's own intercept. An intercept the estimator fits is therefore carried by the orthogonal part.

Arguments

  • cre: Cross-Sectional Regression Estimator of the split.
  • mu: The Return Forecast, one entry per asset of the coverage universe.
  • L: The latest Factor Exposures, assets × factors, in the basis the fit ran in.
  • w: The regression weights of the latest fit, one entry per asset.

Validation

Returns

  • g::VecNum: The spanned coefficients, one per factor of L.
  • ap::VecNum: The orthogonal part of the forecast, one entry per asset.

Related

source
PortfolioOptimisers.cross_sectional_return_forecastFunction
cross_sectional_return_forecast(rfe::Nothing, rd::ReturnsResult,
                                csfm::CrossSectionalFactorModel,
                                cre::AbstractCrossSectionalRegressionEstimator,
                                c::Real) -> NamedTuple
cross_sectional_return_forecast(rfe::AbstractReturnForecastEstimator, rd::ReturnsResult,
                                csfm::CrossSectionalFactorModel,
                                cre::AbstractCrossSectionalRegressionEstimator,
                                c::Real) -> NamedTuple

Fit the Return Forecast of a CrossSectionalFactorPrior, and write its split onto the factor-model block.

Algorithm

The method that Julia selects is the algorithm, and a prior that states no Return Forecast Estimator is the method over Nothing: the block passes through with the zero b it was built with, and the factor mean is left alone.

  1. Fit the estimator on the coverage universe, through return_forecast. The carrier is the whole one, so the Descriptors of the forecast warm up over every observation the panel has, and the block is the suffix of it return_forecast_rows finds by size.
  2. Split the forecast against the latest exposures with cross_sectional_alpha_split.
  3. Rebuild the block with b the orthogonal part shrunk by c, and with the Return Forecast Result in rf. L is read with getfield, because the swap(L, M) rule of CrossSectionalFactorModel would otherwise materialise it as a copy of M.

Arguments

  • rfe: Return Forecast Estimator, or nothing.
  • rd: The returns result to use. It is the whole carrier the prior was fitted on, and the block is a suffix of it.
  • csfm: The factor-model block, built with a zero b and no Return Forecast.
  • cre: Cross-Sectional Regression Estimator of the split.
  • c: Confidence in the orthogonal part of the forecast.

Validation

Returns

  • rr::CrossSectionalFactorModel: The block, with b and rf set.
  • g::Option{<:VecNum}: The spanned coefficients, or nothing when the prior states no estimator.

Related

source
PortfolioOptimisers.cross_sectional_forecast_muFunction
cross_sectional_forecast_mu(lambda::Real, mu::VecNum, g::Nothing) -> VecNum
cross_sectional_forecast_mu(lambda::Real, mu::VecNum, g::VecNum) -> VecNum

Blend the expected factor returns with the spanned part of a Return Forecast.

Algorithm

The two are blended as lambda * mu + (1 - lambda) * g, so lambda = 1 keeps the fitted factor mean and lambda = 0 takes the spanned forecast alone. A prior that states no Return Forecast Estimator has a spanned part of zero, which is the method over Nothing, so lambda there shrinks the factor mean towards zero and lambda = 0 gives an expected return of zero.

Arguments

  • lambda: Shrinkage of the factor mean towards the spanned forecast.
  • mu: The expected factor returns of the nested factor prior, on the reduced axis.
  • g: The spanned coefficients of the Return Forecast, or nothing.

Returns

  • mu::VecNum: The blended expected factor returns, on the reduced axis.

Related

source