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_pairs — Function
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
xis not empty. Raises anIsEmptyError.- No key repeats. Raises an
ArgumentError.
Returns
pr::Vector{<:Pair}: The collected Pairs, each with aStringkey.
Examples
julia> PortfolioOptimisers.cross_sectional_prior_pairs(["style" => "industry"], :neutralise)1-element Vector{Pair{String, String}}: "style" => "industry"Related
PortfolioOptimisers.cross_sectional_benchmark_carrier — Function
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 inrd.pnl.name: Name of the benchmark-weight Panel Field.W: The benchmark weights,observations × assets.
Validation
rd.pnlis anAssetPanel. Raises anIsNothingError.- The rules of
NumericPanelFieldand ofAssetPanel.
Returns
rd::ReturnsResult: The carrier, with the benchmark weights on its Asset Panel.
Related
PortfolioOptimisers.cross_sectional_exposure_order — Function
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
- Resolve the source name of every
DerivedExposureto a position in the list. - Sweep the list, appending every factor whose source is already computed, until every factor is placed.
- Refuse a sweep that places nothing, because the factors left over depend on each other.
Arguments
factors: Pairs offactor name => Exposure Estimator.
Validation
- Every
DerivedExposurenames a factor of the list. Raises anArgumentError. - No cycle of derived Factor Exposures. Raises an
ArgumentError.
Returns
ord::Vector{Int}: The positions offactors, in the order they are computed.src::Vector{Int}: The position of each factor's source, and0when it has none.
Related
PortfolioOptimisers.cross_sectional_exposure_widths — Function
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 offactor 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 offactors.
Related
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) -> nothingWrite 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
Amatches the observation and asset axes ofMs, and its factor axis isw. Raises aDimensionMismatch.
Returns
nothing.Mscarries the exposure.
Related
PortfolioOptimisers.cross_sectional_exposure_history — Function
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
- Read the factor axis with
cross_sectional_factor_axis, and the column count of each factor withcross_sectional_exposure_widths. - Take the computation order with
cross_sectional_exposure_order. - Compute each member in that order. A
DerivedExposureis handed the exposure of its source, which the order has already written. - Write each answer into the history with
cross_sectional_exposure_write!.
Arguments
factors: Pairs offactor name => Exposure Estimator.rd: The returns result to use. It carries the Asset Panel every member reads.
Validation
- The rules of
cross_sectional_factor_axisand ofcross_sectional_exposure_order. - The source of a
DerivedExposurecontributes one column. Raises anArgumentError.
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
PortfolioOptimisers.cross_sectional_exposures_finite — Function
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
PortfolioOptimisers.cross_sectional_warmup — Function
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
PortfolioOptimisers.cross_sectional_eligible — Function
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 withX.emsk: The estimation mask,observations × assets, aligned withX.
Validation
MsmatchesXon the observation and asset axes, andemskmatchesX. Raises aDimensionMismatch.
Returns
msk::BitMatrix: The eligibility mask,observations × assets.
Related
PortfolioOptimisers.assert_cross_sectional_coverage — Function
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
minraeligible assets. Raises anArgumentError.
Returns
nothing.
Related
PortfolioOptimisers.assert_cross_sectional_factor_moments — Function
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
PortfolioOptimisers.cross_sectional_idiosyncratic_covariance — Function
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
- A threshold of zero answers the latest idiosyncratic variances, so the block carries a vector and the asset covariance takes a diagonal.
- Otherwise, ask
cewhat a gapped cell of the standardised idiosyncratic returns is worth to it withgap_fill_value. A cell is left non-finite only where the asset is inactive. - A finite answer is written over every such cell, and
ceestimates 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. - A non-finite answer hands the sample as it stands, with
amskas theactive_mask, so a gap-awarecefreezes an inactive asset's block instead of decaying it. - Convert the covariance to a correlation.
- 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.
- 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, ornothing.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 acethat answers a non-finitegap_fill_value.
Returns
esigma::VecNum: The latest idiosyncratic variances, whenthis zero.esigma::MatNum: The idiosyncratic covariance, whenthis positive.
Related
PortfolioOptimisers.cross_sectional_finite_mean — Function
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 observationt, or zero.
Related
PortfolioOptimisers.cross_sectional_standardised_residuals — Function
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
PortfolioOptimisers.cross_sectional_scenarios — Function
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
LmatchesFson the factor axis, andSandevmatchLon the asset axis. Raises aDimensionMismatch.
Returns
Xs::Matrix{<:Real}: The asset return scenarios,scenarios × assets.
Related
PortfolioOptimisers.cross_sectional_investable — Function
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
PortfolioOptimisers.cross_sectional_panel_masks — Function
cross_sectional_panel_masks(pnl::AssetPanel{<:Any, Nothing, Nothing}) -> Union{}
cross_sectional_panel_masks(pnl::AssetPanel) -> TupleRead 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
PortfolioOptimisers.cross_sectional_cap_finite! — Function
cross_sectional_cap_finite!(msk::BitMatrix, mcap::Nothing) -> nothing
cross_sectional_cap_finite!(msk::BitMatrix, mcap::MatNum) -> nothingDrop 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, ornothing.
Validation
mcapmatchesmsk. Raises aDimensionMismatch.
Returns
nothing.mskcarries the cut.
Related
PortfolioOptimisers.cross_sectional_needs_market_cap — Function
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
PortfolioOptimisers.cross_sectional_rows — Function
cross_sectional_rows(A::Nothing, r) -> nothing
cross_sectional_rows(A::MatNum, r) -> MatNumTake 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, ornothing.r: The observations to keep.
Returns
A::Option{<:MatNum}: The rows, ornothing.
Related
PortfolioOptimisers.cross_sectional_reduced_loadings — Function
cross_sectional_reduced_loadings(fcb::Nothing, L::MatNum) -> nothing
cross_sectional_reduced_loadings(fcb::FactorFamilyBasis, L::MatNum) -> MatNumReturn 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, ornothing.L: The loadings on the reduced axis.
Returns
L::Option{<:MatNum}: The reduced loadings, ornothing.
Related
PortfolioOptimisers.cross_sectional_neutralise! — Function
cross_sectional_neutralise!(neutralise::Nothing, args...) -> nothing
cross_sectional_neutralise!(neutralise::AbstractVector{<:Pair}, Ms, cre, bw, nf, fam)
-> nothingRun 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 ofkey => targets, ornothing.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
- The rules of
neutralise_exposures!.
Returns
nothing.Mscarries the neutralised exposures.
Related
PortfolioOptimisers.cross_sectional_family_basis — Function
cross_sectional_family_basis(families::Nothing, Ms, bw, nf, fam) -> NamedTuple
cross_sectional_family_basis(families::AbstractVector{<:Pair}, Ms, bw, nf, fam)
-> NamedTupleBuild 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 offamily label => dropped member, ornothing.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
- The rules of
factor_family_basis.
Returns
fcb::Option{<:FactorFamilyBasis}: The basis, ornothing.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
PortfolioOptimisers.cross_sectional_basis_now — Function
cross_sectional_basis_now(fcb::Nothing, r) -> nothing
cross_sectional_basis_now(fcb::FactorFamilyBasis, r) -> FactorFamilyBasisSlice 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, ornothing.r: The fitted observations, as an index into the post-warm-up axis.
Returns
fcb::Option{<:FactorFamilyBasis}: The basis of the fitted observations, ornothing.
Related
PortfolioOptimisers.cross_sectional_expand — Function
cross_sectional_expand(fcb::Nothing, r, lag::Integer, f, mu, sigma) -> NamedTuple
cross_sectional_expand(fcb::FactorFamilyBasis, r, lag::Integer, f, mu, sigma)
-> NamedTupleExpand 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, ornothing.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
- The rules of
expand_factor_returns,expand_factor_muandexpand_factor_covariance.
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
PortfolioOptimisers.cross_sectional_residual_block — Function
cross_sectional_residual_block(esigma::VecNum, idx) -> NamedTuple
cross_sectional_residual_block(esigma::MatNum, idx) -> NamedTupleReturn 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
idxfactorises. Raises aPosDefException.
Returns
D::MatNum: The block the asset covariance adds.R::MatNum: A square root of the block,investable assets × investable assets.
Related
PortfolioOptimisers.cross_sectional_lift — Function
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
- Take the loadings of the investable assets, and project the factor mean and the factor covariance through them.
- Process the systematic covariance with
mp, asfactor_liftdoes. - Add the idiosyncratic block, and re-condition the sum.
- Build the low-rank square root
[L·chol(F).L | R], whose trailing block is a square root of the idiosyncratic block. - Scatter the three answers into the full asset universe, writing
NaNat 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_muandf_sigmaagree on the factor axis. Raises aDimensionMismatch.
Returns
mu::Vector{<:Real}: Expected asset returns,NaNat a non-investable asset.sigma::Matrix{<:Real}: Asset covariance,NaNin the row and the column of a non-investable asset.chol::Matrix{<:Real}: The low-rank square root,NaNin the column of a non-investable asset.
Related
PortfolioOptimisers.cross_sectional_alpha_split — Function
cross_sectional_alpha_split(cre::AbstractCrossSectionalRegressionEstimator, mu::VecNum,
L::MatNum, w::VecNum) -> NamedTupleSplit a Return Forecast into the part the latest Factor Exposures span and the part they do not.
Algorithm
- A forecast that is zero at every asset spans nothing, so the split is zero on both sides and no regression runs.
- 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.
- 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. - Subtract the spanned part
L * gfrom the forecast, giving the orthogonal part. The entry of an asset whose forecast or whose exposures are not finite isNaN, 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
- The rules of
cross_sectional_regression.
Returns
g::VecNum: The spanned coefficients, one per factor ofL.ap::VecNum: The orthogonal part of the forecast, one entry per asset.
Related
PortfolioOptimisers.cross_sectional_return_forecast — Function
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) -> NamedTupleFit 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.
- 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 itreturn_forecast_rowsfinds by size. - Split the forecast against the latest exposures with
cross_sectional_alpha_split. - Rebuild the block with
bthe orthogonal part shrunk byc, and with the Return Forecast Result inrf.Lis read withgetfield, because theswap(L, M)rule ofCrossSectionalFactorModelwould otherwise materialise it as a copy ofM.
Arguments
rfe: Return Forecast Estimator, ornothing.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 zeroband no Return Forecast.cre: Cross-Sectional Regression Estimator of the split.c: Confidence in the orthogonal part of the forecast.
Validation
- The rules of
return_forecastand ofcross_sectional_alpha_split.
Returns
rr::CrossSectionalFactorModel: The block, withbandrfset.g::Option{<:VecNum}: The spanned coefficients, ornothingwhen the prior states no estimator.
Related
PortfolioOptimisers.cross_sectional_forecast_mu — Function
cross_sectional_forecast_mu(lambda::Real, mu::VecNum, g::Nothing) -> VecNum
cross_sectional_forecast_mu(lambda::Real, mu::VecNum, g::VecNum) -> VecNumBlend 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, ornothing.
Returns
mu::VecNum: The blended expected factor returns, on the reduced axis.
Related