Variance Skew Kurtosis
PortfolioOptimisers.MaxRiskMeasureSettings — Type
struct MaxRiskMeasureSettings{__T_scale, __T_lb, __T_rke} <: JuMPRiskMeasureSettingsWeights a risk measure inside an aggregate, and bounds its risk expression from below.
This is the settings type of a quantity the optimisation wants more of, so its bound is a floor rather than a ceiling — the shape of the return floor $\bar{\mu}$ in Equation 8.7 of [5]. Skewness is its one holder, and a stated lb reaches the model only through VarianceSkewKurtosis, where that skewness term is built: Skewness is a NonOptimisationRiskMeasure, so no optimiser takes it on its own.
Unlike the ub of RiskMeasureSettings, lb is not checked for non-negativity. A negative floor is meaningful here and works: lb = -1e-8 returns the unconstrained -6.1627e-9, which satisfies it.
Fields
scale: Weight of this risk measure in the aggregate risk expression formed from a vector of measures. It is a combination weight, so it is inert on a single measure: an optimiser given one measure drops it before the risk expression is built, and the value-level readers ignore it too. The upper bound inubbinds on the measure's own expression, beforescaleis applied.
lb: Lower bound(s) on the measure's own risk expression, for a quantity the optimisation maximises. A scalar bounds one model. A vector and aFrontierare sweep axes, one solve per entry. A negative value is meaningful, because the quantity it bounds may be negative.
rke: Whether to include the risk measure value in theJuMPrisk expression.
Constructors
MaxRiskMeasureSettings(; scale::Number = 1.0, lb::Option{<:RkRtBounds} = nothing, rke::Bool = true,) -> MaxRiskMeasureSettingsKeywords correspond to the struct's fields.
Validation
isfinite(scale).lbis validated withassert_nonempty_finite_val.
Examples
julia> MaxRiskMeasureSettings()MaxRiskMeasureSettings scale ┼ Float64: 1.0 lb ┼ nothing rke ┴ Bool: trueRelated
JuMPRiskMeasureSettingsRiskMeasureSettingsSkewnessVarianceSkewKurtosisFrontierset_variance_risk_bounds_and_expression!
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 8.2.1, Equation 8.7.
PortfolioOptimisers.Skewness — Type
struct Skewness{__T_settings, __T_ve, __T_sk, __T_w, __T_mu, __T_pe} <: NonOptimisationRiskMeasureRepresents the standardised Skewness risk measure.
Skewness computes the third standardised central moment (skewness) of portfolio returns. Positive skewness is preferred (the distribution is skewed towards more positive values), so bigger_is_better returns true for this measure.
Mathematical definition
Let $\mu$ be the specified centre, $\delta_t = x_t - \mu$, and $\sigma$ the standard deviation of returns. The skewness is:
\[\begin{align} \mathrm{Skew}(\boldsymbol{x}) &= \frac{1}{T \sigma^3} \sum_{t=1}^{T} \delta_t^3\,. \end{align}\]
Where:
- $\mathrm{Skew}(\boldsymbol{x})$: Standardised skewness of portfolio returns. The
wfield replaces $1/T$ with a weighted average when it holds observation weights. - $\boldsymbol{x}$: Portfolio returns vector $T \times 1$.
- $T$: Number of observations.
- $\mu$: Specified centre of the distribution.
- $\delta_t = x_t - \mu$: Centred deviation at period $t$.
- $\sigma$: Standard deviation of returns, computed by
ve. Itscorrectedfield sets the divisor.
Fields
settings: Risk measure settings.
ve: Variance estimator.
sk: Optional coskewness matrixassets × assets^2. Also admits a Deferred Quantity — a coskewness estimator or a prior estimator that computes the matrix against the optimisation's own prior, atfactorytime (seeSkSlotandresolve_deferred_quantities). A coskewness estimator suppliesmuas well, from its ownme, so that the tensor and the centre it was taken about come out of one object. Ifnothing, the prior supplies it.
w: Optional observation weights vectorobservations × 1, or a concrete subtype ofDynamicAbstractWeights. Ifnothing, the computation is unweighted.
mu: Optional centre the moment is taken about, a scalar or a vectorassets × 1. Also admits a Deferred Quantity — an expected returns estimator or a prior estimator that computes the centre against the optimisation's own prior, atfactorytime (seeMuSlotandresolve_deferred_quantities). Ifnothing, the prior supplies it.
pe: Optional prior estimator that fills every prior-derived slot the measure leaves unstated, from a single fit. A stated slot wins. Seeresolve_deferred_quantities.
Constructors
Skewness(; settings::MaxRiskMeasureSettings = MaxRiskMeasureSettings(), ve::AbstractVarianceEstimator = SimpleVariance(), sk::Option{<:SkSlot} = nothing, w::Option{<:ObsWeights} = nothing, mu::Option{<:MuSlot} = nothing, pe::Option{<:AbstractPriorEstimator} = nothing) -> SkewnessKeywords correspond to the struct's fields.
Validation
- If
skis notnothing:!isempty(sk)andsize(sk, 1)^2 == size(sk, 2). - If
muis aVecNum:!isempty(mu). - If
wis notnothing:!isempty(w).
mu and sk are stated independently, so nothing makes them agree with each other. A caller who wants one consistent set names pe alone and lets it fill both from a single fit. A caller who states them by hand must make sure that they agree.
sk also admits a CoskewnessEstimator or an AbstractPriorEstimator, and mu an AbstractExpectedReturnsEstimator or an AbstractPriorEstimator. Either is resolved against the optimisation's own prior — see resolve_deferred_quantities. A coskewness estimator in sk also supplies mu from its own me, so that the tensor and the centre it was taken about come out of one object. A deferred slot wins over pe.
Functor
(r::Skewness)(w::VecNum, X::MatNum, fees::Option{<:Fees} = nothing)(r::Skewness)(x::VecNum)Computes the skewness of the portfolio returns. The second arity takes a precomputed portfolio return series and skips the weighting step.
Arguments
w: Portfolio weights vectorassets × 1.X::MatNum: Asset returns matrix ($T \times N$).fees: Fees estimator or result.
Examples
julia> Skewness()Skewness settings ┼ MaxRiskMeasureSettings │ scale ┼ Float64: 1.0 │ lb ┼ nothing │ rke ┴ Bool: true ve ┼ SimpleVariance │ me ┼ SimpleExpectedReturns │ │ w ┴ nothing │ w ┼ nothing │ corrected ┴ Bool: true sk ┼ nothing w ┼ nothing mu ┼ nothing pe ┴ nothingRelated
NonOptimisationRiskMeasureMaxRiskMeasureSettingsThirdCentralMomentAbstractVarianceEstimatorbigger_is_betterresolve_deferred_quantities
References
- [116] D. Cajas. Semidefinite Relaxation of Higher Portfolio Moments. Available at SSRN 5284483 (2025).
PortfolioOptimisers.VarianceSkewKurtosis — Type
struct VarianceSkewKurtosis{__T_settings, __T_vr, __T_sk, __T_kt, __T_pe} <: RiskMeasureComposite risk measure combining variance, skewness, and kurtosis into a single expression.
VarianceSkewKurtosis encodes the joint SDP formulation $\sigma^2 - \mathrm{Skew} + \kappa$ where each component has its own scale weight. The skewness term is subtracted because higher skewness is preferable.
Mathematical definition
\[\begin{align} \mathcal{R}(\boldsymbol{w}) &= s_{\sigma^2}\,\mu_2(\boldsymbol{w}) - s_{\mathrm{sk}}\,\mu_3(\boldsymbol{w}) + s_{\kappa}\,\mu_4(\boldsymbol{w})\,, \\ \mu_2(\boldsymbol{w}) &= \boldsymbol{w}^\intercal \mathbf{\Sigma} \boldsymbol{w}\,, \\ \mu_3(\boldsymbol{w}) &= \boldsymbol{w}^\intercal \mathbf{M}_3 \left(\boldsymbol{w} \otimes \boldsymbol{w}\right)\,, \\ \mu_4(\boldsymbol{w}) &= \left(\boldsymbol{w} \otimes \boldsymbol{w}\right)^\intercal \mathbf{M}_4 \left(\boldsymbol{w} \otimes \boldsymbol{w}\right)\,. \end{align}\]
Where:
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $\mu_2, \mu_3, \mu_4$: Raw central portfolio moments of order two, three and four. The skewness is not standardised and the kurtosis is not square-rooted.
- $\mathbf{\Sigma}, \mathbf{M}_3, \mathbf{M}_4$: Covariance, coskewness and cokurtosis matrices, held by
vr,skandkt. - $s_{\sigma^2},\, s_{\mathrm{sk}},\, s_{\kappa}$: Respective scale factors from each sub-measure's settings.
- $\otimes$: Kronecker product.
The functor and the JuMP model compute this same quantity from these same matrices. They differ only by the gap of the semidefinite relaxation, which [116] states is tight over a range of skewness values and not beyond it.
Fields
settings: Risk measure settings.
vr: Variance risk measure component.
sk: Skewness risk measure component.
kt: Kurtosis risk measure component.
pe: Optional prior estimator that fills every prior-derived slot the measure leaves unstated, from a single fit. A stated slot wins. Seeresolve_deferred_quantities.
Constructors
VarianceSkewKurtosis(; settings::RiskMeasureSettings = RiskMeasureSettings(), vr::Variance = Variance(), sk::Skewness = Skewness(), kt::Kurtosis = Kurtosis(), pe::Option{<:AbstractPriorEstimator} = nothing) -> VarianceSkewKurtosisKeywords correspond to the struct's fields.
The three children each state their quantities independently, so nothing makes sigma, sk and kt agree with each other. A caller who wants one consistent set names pe on the container alone and lets it fill all three from a single fit. A child that names its own quantity keeps it, and the pe fills only what is left.
The constructor passes vr, sk and kt through no_risk_expr_risk_measure, so a child never registers a risk expression of its own. This is why the children show rke ┴ Bool: false below.
Propagated parameters
When factory is called on this type, the following @fprop-tagged fields are automatically propagated:
vr: Recursively updated viafactory.sk: Recursively updated viafactory.kt: 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:
vr: Recursively viewed viaport_opt_view.sk: Recursively viewed viaport_opt_view.kt: Recursively viewed viaport_opt_view.
JuMP Formulation
The JuMP model is the semidefinite relaxation of [116], equations 11 to 14. It relaxes the Kronecker products of the weights into the blocks of one positive semidefinite matrix, and reads each moment off a block with the trace operator.
\[\begin{align} \mathbf{M}^{(4)*} &= \begin{bmatrix} k & \boldsymbol{w}^\intercal & \left(\mathbf{L}_2 \mathrm{vec}(\mathbf{W}_1)\right)^\intercal \\ \boldsymbol{w} & \mathbf{W}_1 & \mathbf{W}_2^\intercal \\ \mathbf{L}_2 \mathrm{vec}(\mathbf{W}_1) & \mathbf{W}_2 & \mathbf{W}_3 \end{bmatrix} \succeq 0\,, \\ \mathcal{R}(\boldsymbol{w}) &= s_{\sigma^2} \mathrm{Tr}\left(\mathbf{\Sigma} \mathbf{W}_1\right) - s_{\mathrm{sk}} \mathrm{Tr}\left(\mathbf{M}_3 \mathbf{D}_2 \mathbf{W}_2\right) + s_{\kappa} \mathrm{Tr}\left(\mathbf{S}_2 \mathbf{M}_4 \mathbf{S}_2^\intercal \mathbf{W}_3\right)\,. \end{align}\]
Where:
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $k$: Homogenisation variable of the model.
- $\mathbf{W}_1, \mathbf{W}_2, \mathbf{W}_3$: Relaxation blocks standing for $\boldsymbol{w}\boldsymbol{w}^\intercal$, $(\boldsymbol{w} \otimes \boldsymbol{w})\boldsymbol{w}^\intercal$ and $(\boldsymbol{w} \otimes \boldsymbol{w})(\boldsymbol{w} \otimes \boldsymbol{w})^\intercal$.
- $\mathbf{\Sigma}, \mathbf{M}_3, \mathbf{M}_4$: Covariance, coskewness and cokurtosis matrices, from
vr,skandkt. - $\mathbf{D}_2, \mathbf{L}_2, \mathbf{S}_2$: Duplication, elimination and summation matrices.
- $s_{\sigma^2},\, s_{\mathrm{sk}},\, s_{\kappa}$: Respective scale factors from each sub-measure's settings.
The skewness term takes a lower bound, because a larger skewness is preferable. The other two take upper bounds.
A relaxation block stands in for an exact Kronecker product, so the model's value carries the relaxation gap. The functor reads the moments from the matrices directly and carries no gap. The two agree to the width of that gap.
Functor
(r::VarianceSkewKurtosis)(w::VecNum, X::MatNum, fees::Option{<:Fees} = nothing)Computes the composite of equation 12 of [116] from vr.sigma, sk.sk and kt.kt. X and fees take part in no term. The moments are pinned by the matrices, and a central moment does not move when fees shifts every observation by the same constant.
Arguments
w: Portfolio weights vectorassets × 1.X::MatNum: Asset returns matrix ($T \times N$).fees: Fees estimator or result.
Examples
julia> r = VarianceSkewKurtosis()VarianceSkewKurtosis settings ┼ RiskMeasureSettings │ scale ┼ Float64: 1.0 │ ub ┼ nothing │ rke ┴ Bool: true vr ┼ Variance │ settings ┼ RiskMeasureSettings │ │ scale ┼ Float64: 1.0 │ │ ub ┼ nothing │ │ rke ┴ Bool: false │ sigma ┼ nothing │ chol ┼ nothing │ rc ┼ nothing │ alg ┴ SquaredSOCRiskExpr() sk ┼ Skewness │ settings ┼ MaxRiskMeasureSettings │ │ scale ┼ Float64: 1.0 │ │ lb ┼ nothing │ │ rke ┴ Bool: false │ ve ┼ SimpleVariance │ │ me ┼ SimpleExpectedReturns │ │ │ w ┴ nothing │ │ w ┼ nothing │ │ corrected ┴ Bool: true │ sk ┼ nothing │ w ┼ nothing │ mu ┼ nothing │ pe ┴ nothing kt ┼ Kurtosis │ settings ┼ RiskMeasureSettings │ │ scale ┼ Float64: 1.0 │ │ ub ┼ nothing │ │ rke ┴ Bool: false │ w ┼ nothing │ mu ┼ nothing │ kt ┼ nothing │ N ┼ nothing │ alg1 ┼ FullMoment() │ alg2 ┼ SOCRiskExpr() │ pe ┴ nothing pe ┴ nothingRelated
References
- [116] D. Cajas. Semidefinite Relaxation of Higher Portfolio Moments. Available at SSRN 5284483 (2025).
PortfolioOptimisers.factory — Method
factory(
r::Skewness,
pr::HighOrderPrior,
args...;
kwargs...
) -> Skewness{MaxRiskMeasureSettings{Float64, Nothing, Bool}, var"#s185", _A, _B, _C, Nothing} where {var"#s185"<:AbstractVarianceEstimator, _A, _B, _C}
Create an instance of Skewness by selecting observation weights and expected returns from the risk-measure instance or falling back to the prior result.
Related
PortfolioOptimisers.factory — Method
factory(
r::Skewness,
pr::LowOrderPrior,
args...;
kwargs...
) -> Skewness{MaxRiskMeasureSettings{Float64, Nothing, Bool}, var"#s185", _A, _B, _C, Nothing} where {var"#s185"<:AbstractVarianceEstimator, _A, _B, _C}
Create an instance of Skewness from a LowOrderPrior result, selecting observation weights and expected returns while preserving the coskewness matrix from the risk measure.
Related
PortfolioOptimisers.port_opt_view — Method
port_opt_view(
r::Skewness,
i,
args...
) -> Skewness{MaxRiskMeasureSettings{__T_scale, __T_lb, __T_rke}, <:AbstractVarianceEstimator} where {__T_scale, __T_lb, __T_rke}
Return a view of Skewness r sliced to asset indices i.
Slices the expected returns mu for cluster-based optimisation. sk passes through: it is nothing, or it holds a Deferred Quantity, which crosses the view unresolved and then fits on the subset.
Related
PortfolioOptimisers.port_opt_view — Method
port_opt_view(
r::Skewness{<:Any, <:Any, <:AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
i,
args...
) -> Skewness{MaxRiskMeasureSettings{__T_scale, __T_lb, __T_rke}, <:AbstractVarianceEstimator} where {__T_scale, __T_lb, __T_rke}
Return a view of Skewness r sliced to asset indices i, also slicing the coskewness matrix sk.
Related
PortfolioOptimisers.no_risk_expr_risk_measure — Method
no_risk_expr_risk_measure(
r::Skewness
) -> Skewness{MaxRiskMeasureSettings{__T_scale, __T_lb, Bool}, <:AbstractVarianceEstimator} where {__T_scale, __T_lb}
Return a copy of Skewness r with rke = false, disabling its contribution to the JuMP objective expression.
Related
PortfolioOptimisers.no_bounds_no_risk_expr_risk_measure — Method
no_bounds_no_risk_expr_risk_measure(
r::Skewness
) -> Skewness{MaxRiskMeasureSettings{Int64, Nothing, Bool}, <:AbstractVarianceEstimator}
no_bounds_no_risk_expr_risk_measure(
r::Skewness,
) -> Skewness{MaxRiskMeasureSettings{Int64, Nothing, Bool}, <:AbstractVarianceEstimator}
Return a copy of Skewness r with rke = false and lb = nothing, removing bounds and disabling its contribution to the JuMP objective expression.
Related
PortfolioOptimisers.bounds_risk_measure — Method
bounds_risk_measure(
r::Skewness,
ub::Number
) -> Skewness{MaxRiskMeasureSettings{var"#s185", var"#s1851", Bool}, <:AbstractVarianceEstimator} where {var"#s185"<:Number, var"#s1851"<:Number}
Return a copy of Skewness r with the lower bound set to ub.
Related
PortfolioOptimisers.factory — Method
factory(
r::VarianceSkewKurtosis,
pr::AbstractPriorResult,
args...;
kwargs...
) -> VarianceSkewKurtosis{RiskMeasureSettings{__T_scale, __T_ub, __T_rke}, _A, Skewness{__T_settings, __T_ve, __T_sk, __T_w, __T_mu, __T_pe}, _B, Nothing} where {__T_scale, __T_ub, __T_rke, _A, __T_settings, __T_ve, __T_sk, __T_w, __T_mu, __T_pe, _B}
Create an instance of VarianceSkewKurtosis by fanning pe out over its three children, then threading pr into each of them.
The container holds no quantity of its own, so @fprop alone would reach the children and leave pe standing — the measure would say one thing and compute another. This method resolves first, which is the same order the JuMP path already uses in set_risk_constraints!.
Related
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
- [116]
- D. Cajas. Semidefinite Relaxation of Higher Portfolio Moments. Available at SSRN 5284483 (2025).