High Order Factor Prior

PortfolioOptimisers.HighOrderFactorPriorEstimatorType
struct HighOrderFactorPriorEstimator{__T_pe, __T_kte, __T_ske, __T_ex, __T_rsd, __T_cache} <: AbstractHighOrderPriorEstimator_F

Projects 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 is nothing until partial_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: factory carries it unchanged, because a factory call resolves configuration rather than the sample; port_opt_view slices it to the selected assets by index copy, so the viewed estimator answers over those assets alone; and obs_weights_view drops 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) -> HighOrderFactorPriorEstimator

Keywords 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 via factory.
  • kte: Recursively updated via factory.
  • ske: Recursively updated via factory.

View parameters

When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:

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: true

Related

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

Note

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.

Warning

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 when rsd is true.
  • $\hat{\mathbf{M}}_{3,\varepsilon}$: Residual coskewness correction from coskewness_residuals, present only when rsd is true.
  • $\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

  1. Orient X and F to observations × variables with dims_oriented.
  2. Compute the low order block pr with pe.pe, and check that it carries a regression result. Derive the Investable Mask from it with investable_mask, and reduce the whole carrier to the investable universe with port_opt_view, giving rpr; X is an argument rather than a block of the carrier, so it is cut alongside as Xr. Take the reconstructed returns posterior_X = rpr.X and the loadings M = rpr.rr.M. The nothing sentinel 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: one NaN loading row makes a whole NaN band of kron(M, M), and the projection carries it across most of the cokurtosis, where posdef! refuses it in the name of LAPACK rather than of the asset.
  3. Compute the factor square cokurtosis f_kt with pe.kte on F. When it exists, build kM = kron(M, M), project posterior_kt = kM * f_kt * transpose(kM), and process it with pe.kte.mp.
  4. Compute the factor coskewness f_sk and its negative spectral form f_V with pe.ske on F. When f_sk exists, build kM if step 3 did not, and project posterior_sk = M * f_sk * transpose(kM).
  5. Build the structure matrices with dup_elim_sum_matrices, twice: at the full asset count for D2, L2 and S2, and at the factor count for f_D2, f_L2 and f_S2. The asset count is the full one because step 12 expands the co-moments, and the constructor validates the triple against length(pr.mu). The all-or-none rule is the one prior(::HighOrderPriorEstimator, …) applies, at both dimensions.
  6. When pe.rsd is true, take the reconstruction error err = Xr - posterior_X.
  7. Still under pe.rsd, add coskewness_residuals(err, pe.ske.me) to the posterior_sk of step 4, when there is one.
  8. Still under pe.rsd, and when step 3 produced a posterior_kt, read the wrapped estimator's residual declaration with factor_residual_config and check its shape with assert_factor_residual_config.
  9. Recover the systematic covariance sigma from rpr.sigma. A nothing declaration, and one whose rsd is false, both mean that no residual block was added, so sigma is rpr.sigma unchanged. Otherwise size the block as err_sigma, the column variances of err under rsd_cfg.ve, subtract its diagonal matrix from rpr.sigma, and re-condition the difference with posdef! under rsd_cfg.pdm. When any entry of err_sigma exceeds the matching diagonal entry of rpr.sigma the subtraction would leave a negative variance, so the step warns and keeps rpr.sigma whole. 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.
  10. Still under pe.rsd, add cokurtosis_residuals(sigma, err, pe.kte.me, pe.ex) to posterior_kt, and re-condition the sum with posdef! under pe.kte.mp.pdm.
  11. When step 4 produced a posterior_sk, recompute posterior_V from it with negative_spectral_coskewness, so V describes the corrected coskewness rather than the projected one.
  12. Expand posterior_kt, posterior_sk and posterior_V back to the full asset universe with expand_moment, NaN outside the investable block. V is 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_moment on a nothing mask hands the block straight back, at every one of its arities.
  13. Build the nested factor carrier fpr over pr.fpr, from the factor moments of steps 3 to 5. It is nothing when neither f_kt nor f_sk exists. The factor block is untouched by steps 2 and 12: i indexes assets, and these co-moments live on the factor axis.
  14. Assemble the asset HighOrderPrior through its keyword constructor, over the full pr rather than the reduced rpr, 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: Optional AssetPanel, 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.pe must carry a regression result, via assert_prior_regression.
  • The wrapped estimator must declare its residual block through factor_residual_config, which has no default: an estimator that declares nothing throws an ArgumentError rather than reading as no residual block. The declaration's shape is checked with assert_factor_residual_config, which throws an ArgumentError when it is neither nothing nor a NamedTuple carrying ve, pdm and rsd. Both raises happen only when pe.rsd is true and 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 carries NaN in mu, on the diagonal of sigma, and at every fourth-moment index that names it in kt, sk and V — the carrier HighOrderPriorEstimator makes on the same gapped panel, which port_opt_view slices clean. Its fpr is a nested HighOrderPrior built over the wrapped prior's own factor block, so fpr.pr === pr.fpr: the factor co-moments and the low order factor moments describe one distribution, reachable by either route.

Related

source

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).