High Order Factor Prior
PortfolioOptimisers.HighOrderFactorPriorEstimator — Type
struct HighOrderFactorPriorEstimator{__T_pe, __T_kte, __T_ske, __T_ex, __T_rsd, __T_cache} <: AbstractHighOrderPriorEstimator_FProjects factor coskewness and cokurtosis onto the asset axis through the regression loadings.
HighOrderFactorPriorEstimator extends a low-order factor prior with coskewness and cokurtosis moments estimated from a factor model. It supports error correction of higher-order moments using residuals from the factor regression.
Fields
pe: Prior estimator.
kte: Cokurtosis estimator.
ske: Coskewness estimator.
ex: Parallel execution strategy.
rsd: Whether to include residual variance in the posterior covariance.
cache: Optional partial-fit state. It isnothinguntilpartial_fit!writes one, and the estimator's read-out verb reads it when the caller gives no data matrix. Each propagation channel does one thing with it:factorycarries it unchanged, because a factory call resolves configuration rather than the sample;port_opt_viewslices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; andobs_weights_viewdrops it, because no slice of a state exists on the observation axis. A family whose state has no exact asset slice drops it on both axes and names the reason.
Constructors
HighOrderFactorPriorEstimator(; pe::AbstractLowOrderPriorEstimator_F_AF = FactorPrior(), kte::Option{<:CokurtosisEstimator} = Cokurtosis(; alg = FullMoment()), ske::Option{<:CoskewnessEstimator} = Coskewness(; alg = FullMoment()), ex::FLoops.Transducers.Executor = FLoops.ThreadedEx(), rsd::Bool = true, cache::Option{<:AbstractPartialFitState} = nothing) -> HighOrderFactorPriorEstimatorKeywords correspond to the struct's fields.
Propagated parameters
When factory is called on this type, the following @fprop-tagged fields are automatically propagated:
pe: Recursively updated viafactory.kte: Recursively updated viafactory.ske: Recursively updated viafactory.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
pe: Recursively viewed viaport_opt_view.
Examples
julia> HighOrderFactorPriorEstimator()HighOrderFactorPriorEstimator pe ┼ FactorPrior │ pe ┼ EmpiricalPrior │ │ ce ┼ PortfolioOptimisersCovariance │ │ │ ce ┼ Covariance │ │ │ │ me ┼ SimpleExpectedReturns │ │ │ │ │ w ┴ nothing │ │ │ │ ce ┼ GeneralCovariance │ │ │ │ │ ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true) │ │ │ │ │ w ┴ nothing │ │ │ │ alg ┼ FullMoment() │ │ │ │ w ┴ nothing │ │ │ mp ┼ MatrixProcessing │ │ │ │ pdm ┼ Posdef │ │ │ │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ │ │ │ dn ┼ nothing │ │ │ │ dt ┼ nothing │ │ │ │ alg ┼ nothing │ │ │ │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) │ │ me ┼ SimpleExpectedReturns │ │ │ w ┴ nothing │ │ horizon ┼ nothing │ │ fill_limit ┴ nothing │ mp ┼ MatrixProcessing │ │ pdm ┼ Posdef │ │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ │ dn ┼ nothing │ │ dt ┼ nothing │ │ alg ┼ nothing │ │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) │ re ┼ StepwiseRegression │ │ crit ┼ PValue │ │ │ t ┴ Float64: 0.05 │ │ alg ┼ ForwardSelection() │ │ tgt ┼ LinearModel │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ ve ┼ SimpleVariance │ │ me ┼ SimpleExpectedReturns │ │ │ w ┴ nothing │ │ w ┼ nothing │ │ corrected ┴ Bool: true │ rsd ┴ Bool: true kte ┼ Cokurtosis │ me ┼ SimpleExpectedReturns │ │ w ┴ nothing │ mp ┼ MatrixProcessing │ │ pdm ┼ Posdef │ │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ │ dn ┼ nothing │ │ dt ┼ nothing │ │ alg ┼ nothing │ │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) │ alg ┼ FullMoment() │ w ┼ nothing │ cache ┴ nothing ske ┼ Coskewness │ me ┼ SimpleExpectedReturns │ │ w ┴ nothing │ mp ┼ MatrixProcessing │ │ pdm ┼ Posdef │ │ │ alg ┼ UnionAll: NearestCorrelationMatrix.Newton │ │ │ kwargs ┴ @NamedTuple{}: NamedTuple() │ │ dn ┼ nothing │ │ dt ┼ nothing │ │ alg ┼ nothing │ │ order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg) │ alg ┼ FullMoment() │ w ┼ nothing │ cache ┴ nothing ex ┼ Transducers.ThreadedEx{@NamedTuple{}}: Transducers.ThreadedEx() rsd ┴ Bool: trueRelated
AbstractHighOrderPriorEstimator_FFactorPriorCokurtosisEstimatorCoskewnessEstimatorHighOrderPriorfactoryport_opt_view
References
- [84] K. Boudt, W. Lu and B. Peeters. Higher order comoments of multifactor models and asset allocation. Finance Research Letters 13, 225–233 (2015).
- [85] L. Martellini and V. Ziemann. Improved estimates of higher-order comoments and implications for portfolio selection. The Review of Financial Studies 23, 1467–1502 (2010).
PortfolioOptimisers.prior — Function
prior(pe::HighOrderFactorPriorEstimator, X::MatNum, F::MatNum,
pnl::Option{<:AssetPanel} = nothing; dims::Int = 1,
kwargs...)Compute high order factor prior moments for asset returns using a factor model.
prior estimates the mean, covariance, coskewness, and cokurtosis of asset returns using a factor model with residual error correction. It first computes low order moments via the embedded factor prior, then maps factor higher-order moments to asset space via the Kronecker product of the factor loadings, optionally adding residual corrections.
A Black-Litterman prior underneath this estimator now returns numbers where it used to throw. Every wrapping estimator forwards rr and the factor block, so HighOrderFactorPriorEstimator(; pe = BlackLittermanPrior(; pe = FactorPrior(…))) reaches a regression instead of an IsNothingError.
What comes back is worth understanding. The higher co-moments project through rr.M while mu and sigma carry the views — Black-Litterman makes no claim about third and fourth moments, so the factor projection is the only estimate available.
The co-moments are computed from F as supplied, so they always describe the pre-view factor distribution, whichever Black-Litterman member is underneath. Where that member reports a posterior factor block — FactorBlackLittermanPrior and BayesianBlackLittermanPrior — the nested fpr therefore mixes orders: fpr.mu and fpr.sigma carry the views, fpr.kt, fpr.sk and fpr.V do not. The fpr.pr === pr.fpr invariant still holds, because both routes reach the same posterior low order block; what differs is the order at which the views stop.
This is a consequence of Black-Litterman having no higher-moment update to apply, not of a value being discarded, and it is the same under BlackLittermanPrior — where the low order factor block is pre-view too, so the carrier happens to be uniform.
Mathematical definition
Factor comoments are mapped to asset space through the loadings matrix $\mathbf{B}$:
\[\begin{align} \hat{\mathbf{\Sigma}}_4 &= (\mathbf{B} \otimes \mathbf{B}) \hat{\mathbf{\Sigma}}_{4,f} (\mathbf{B} \otimes \mathbf{B})^\intercal + \hat{\mathbf{\Sigma}}_{4,\varepsilon}\,, \\ \hat{\mathbf{M}}_3 &= \mathbf{B} \hat{\mathbf{M}}_{3,f} (\mathbf{B} \otimes \mathbf{B})^\intercal + \hat{\mathbf{M}}_{3,\varepsilon}\,. \end{align}\]
Where:
- $\hat{\mathbf{\Sigma}}_4$: $N^2 \times N^2$ asset square cokurtosis matrix,
kt. - $\hat{\mathbf{M}}_3$: $N \times N^2$ asset coskewness matrix,
sk. - $\mathbf{B}$: $N \times K$ factor loadings matrix,
pr.rr.M. - $\hat{\mathbf{\Sigma}}_{4,f}$: $K^2 \times K^2$ factor square cokurtosis matrix,
fpr.kt. - $\hat{\mathbf{M}}_{3,f}$: $K \times K^2$ factor coskewness matrix,
fpr.sk. - $\hat{\mathbf{\Sigma}}_{4,\varepsilon}$: Residual cokurtosis correction from
cokurtosis_residuals, present only whenrsdistrue. - $\hat{\mathbf{M}}_{3,\varepsilon}$: Residual coskewness correction from
coskewness_residuals, present only whenrsdistrue. - $\otimes$: Kronecker product.
The factor comoments come from pe.kte and pe.ske fit on F, so a non-default alg on either replaces its display above. Either estimator set to nothing drops its moment from both the asset result and the nested factor block.
Algorithm
- Orient
XandFtoobservations × variableswithdims_oriented. - Compute the low order block
prwithpe.pe, and check that it carries a regression result. Derive the Investable Mask from it withinvestable_mask, and reduce the whole carrier to the investable universe withport_opt_view, givingrpr;Xis an argument rather than a block of the carrier, so it is cut alongside asXr. Take the reconstructed returnsposterior_X = rpr.Xand the loadingsM = rpr.rr.M. Thenothingsentinel of an all-investable universe reduces nothing, and every step below then runs on the carrier itself. The reduction happens before the lift because the lift squares the reach of a non-investable asset: oneNaNloading row makes a wholeNaNband ofkron(M, M), and the projection carries it across most of the cokurtosis, whereposdef!refuses it in the name of LAPACK rather than of the asset. - Compute the factor square cokurtosis
f_ktwithpe.kteonF. When it exists, buildkM = kron(M, M), projectposterior_kt = kM * f_kt * transpose(kM), and process it withpe.kte.mp. - Compute the factor coskewness
f_skand its negative spectral formf_Vwithpe.skeonF. Whenf_skexists, buildkMif step 3 did not, and projectposterior_sk = M * f_sk * transpose(kM). - Build the structure matrices with
dup_elim_sum_matrices, twice: at the full asset count forD2,L2andS2, and at the factor count forf_D2,f_L2andf_S2. The asset count is the full one because step 12 expands the co-moments, and the constructor validates the triple againstlength(pr.mu). The all-or-none rule is the oneprior(::HighOrderPriorEstimator, …)applies, at both dimensions. - When
pe.rsdistrue, take the reconstruction errorerr = Xr - posterior_X. - Still under
pe.rsd, addcoskewness_residuals(err, pe.ske.me)to theposterior_skof step 4, when there is one. - Still under
pe.rsd, and when step 3 produced aposterior_kt, read the wrapped estimator's residual declaration withfactor_residual_configand check its shape withassert_factor_residual_config. - Recover the systematic covariance
sigmafromrpr.sigma. Anothingdeclaration, and one whosersdisfalse, both mean that no residual block was added, sosigmaisrpr.sigmaunchanged. Otherwise size the block aserr_sigma, the column variances oferrunderrsd_cfg.ve, subtract its diagonal matrix fromrpr.sigma, and re-condition the difference withposdef!underrsd_cfg.pdm. When any entry oferr_sigmaexceeds the matching diagonal entry ofrpr.sigmathe subtraction would leave a negative variance, so the step warns and keepsrpr.sigmawhole. That happens when the wrapped estimator reports a covariance the block was never added to — a posterior that shrank it, rather than the lift's own sum. - Still under
pe.rsd, addcokurtosis_residuals(sigma, err, pe.kte.me, pe.ex)toposterior_kt, and re-condition the sum withposdef!underpe.kte.mp.pdm. - When step 4 produced a
posterior_sk, recomputeposterior_Vfrom it withnegative_spectral_coskewness, soVdescribes the corrected coskewness rather than the projected one. - Expand
posterior_kt,posterior_skandposterior_Vback to the full asset universe withexpand_moment,NaNoutside the investable block.Vis expanded rather than recomputed, because it is a spectral quantity of the reduced coskewness and the frame carries no reduced returns to rebuild it from. The all-investable sentinel needs no branch of its own:expand_momenton anothingmask hands the block straight back, at every one of its arities. - Build the nested factor carrier
fproverpr.fpr, from the factor moments of steps 3 to 5. It isnothingwhen neitherf_ktnorf_skexists. The factor block is untouched by steps 2 and 12:iindexes assets, and these co-moments live on the factor axis. - Assemble the asset
HighOrderPriorthrough its keyword constructor, over the fullprrather than the reducedrpr, so the carrier lives on the full asset universe as the contract requires.
Steps 9 and 10 are ordered, not independent. cokurtosis_residuals is defined on the systematic covariance, so step 9 has to undo the residual block that the wrapped estimator's own lift added before step 10 adds the residual cokurtosis.
Arguments
pe: High order factor prior estimator.X: Asset returns matrix (observations × assets).F: Factor returns matrix (observations × factors).pnl: OptionalAssetPanel, the panel the carrier held. A wrapping prior forwards it unchanged, so that it can compose an estimator that is fitted on a panel. An estimator that reads no panel ignores it.dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments passed to underlying estimators.
Validation
dims in (1, 2).- The prior produced by
pe.pemust carry a regression result, viaassert_prior_regression. - The wrapped estimator must declare its residual block through
factor_residual_config, which has no default: an estimator that declares nothing throws anArgumentErrorrather than reading as no residual block. The declaration's shape is checked withassert_factor_residual_config, which throws anArgumentErrorwhen it is neithernothingnor aNamedTuplecarryingve,pdmandrsd. Both raises happen only whenpe.rsdistrueand there is a cokurtosis to correct.
Returns
pr::HighOrderPrior: Result object containing asset returns, mean, covariance, coskewness tensor, cokurtosis tensor, and factor moments, on the full asset universe. An asset the wrapped prior could not estimate carriesNaNinmu, on the diagonal ofsigma, and at every fourth-moment index that names it inkt,skandV— the carrierHighOrderPriorEstimatormakes on the same gapped panel, whichport_opt_viewslices clean. Itsfpris a nestedHighOrderPriorbuilt over the wrapped prior's own factor block, sofpr.pr === pr.fpr: the factor co-moments and the low order factor moments describe one distribution, reachable by either route.
Related
HighOrderFactorPriorEstimatorassert_prior_regressionHighOrderPriorHighOrderPriorEstimatorFactorPriorpriorinvestable_mask: the mask step 2 derives, and thenothingsentinel that keeps an all-investable universe on the path it took.port_opt_view: the reduction of step 2, which cuts every block the lift reads.expand_moment: the expansion of step 12.factor_residual_config: the declaration that names the residual block step 9 removes.pe.peis boundedAbstractLowOrderPriorEstimator_F_AF, and onlyFactorPriorandFactorBlackLittermanPriorcarry the fields the block is sized from, so a wrapper over either forwards the declaration and everything else declaresnothingin an explicit method.assert_factor_residual_config: the shape check that runs on that declaration.coskewness_residualscokurtosis_residuals
References
- [84]
- K. Boudt, W. Lu and B. Peeters. Higher order comoments of multifactor models and asset allocation. Finance Research Letters 13, 225–233 (2015).
- [85]
- L. Martellini and V. Ziemann. Improved estimates of higher-order comoments and implications for portfolio selection. The Review of Financial Studies 23, 1467–1502 (2010).