Coskewness: private API

PortfolioOptimisers.negative_spectral_coskewnessFunction
negative_spectral_coskewness(cskew::MatNum, X::MatNum,
             mp::Option{<:AbstractMatrixProcessingEstimator})

Internal helper that builds the negative spectral skewness matrix.

negative_spectral_coskewness splits the coskewness tensor into its N symmetric blocks of size N x N, keeps the negative part of the spectrum of each block, and sums the negated parts into one N x N matrix. The matrix processing estimator runs once, on the summed result, and not on the individual blocks.

The reduction runs on the block, which is the same law matrix_processing_block! keeps for a covariance frame, read at this quantity's own resolution. An available-case fit under a CoveragePolicy emits a frame: the tensor carries NaN across the rows and the pair columns of every asset the policy refused. The block is derived from the tensor's own per-asset diagonal, cskew[i, (i - 1) * N + i], which is asset i's third central moment and is finite exactly where the fit answered for that asset. A non-finite cell inside the block — a triple whose three assets were each estimated and which share no observation — is refused by name with assert_finite_block, and never fed to eigen. The spectral step and mp then run on the block alone, and the answer is written back into a NaN frame of the full width, so an asset the policy refused leaves this verb as it entered it.

This verb is the one place that rule is written for a coskewness, and the three call sites that reduce one — _coskewness, port_opt_view of a HighOrderPrior and the high order factor prior — all reach it.

Mathematical definition

Write the coskewness tensor as $\mathbf{S} = [\mathbf{S}_{1} \vert \mathbf{S}_{2} \vert \ldots \vert \mathbf{S}_{N}]$. Each block $\mathbf{S}_{i}$ is symmetric, so its eigendecomposition is real. Keep the negative eigenvalues alone and negate the sum:

\[\begin{align} \mathbf{S}_{i} &= \mathbf{Q}_{i} \mathbf{\Lambda}_{i} \mathbf{Q}_{i}^{\intercal}\,, \\ \mathbf{S}_{i}^{-} &= \mathbf{Q}_{i} \mathbf{\Lambda}_{i}^{-} \mathbf{Q}_{i}^{\intercal}\,, \\ \mathbf{V} &= -\sum\limits_{i=1}^{N} \mathbf{S}_{i}^{-}\,. \end{align}\]

Where:

  • $\mathbf{S}_{i}$: $i$-th $N \times N$ block of the coskewness tensor.
  • $\mathbf{Q}_{i}$: Matrix of eigenvectors of $\mathbf{S}_{i}$.
  • $\mathbf{\Lambda}_{i}^{-}$: Diagonal matrix holding the negative eigenvalues of $\mathbf{S}_{i}$, with every non-negative one set to zero.
  • $\mathbf{V}$: Negative spectral skewness matrix. It is positive semidefinite, because it is a sum of negated negative semidefinite matrices.
  • $N$: Number of assets.

The entry $\mathbf{S}_{i,\,aj}$ is the third comoment of the deviations of the assets $a$, $i$ and $j$. That comoment does not depend on the order of its three assets, which is why the block is symmetric.

Algorithm

  1. Read N from the row count of cskew, and take the block blk as isfinite.(cskew[i, (i - 1) * N + i]) over i.
  2. Reduce the tensor whole when blk holds no true. It carries no block to refuse anything inside, so it is the plain path's and meets its refusal.
  3. Refuse a non-finite cell inside the block with assert_finite_block. The block is the whole tensor when blk holds no false, and the refusal covers that case too.
  4. Cut the tensor to the block, at the rows blk and the pair columns of coverage_pair_index, and cut X to the same assets. Both are the objects themselves where blk holds no false.
  5. Allocate the accumulator V of zeros at the width of the block.
  6. For each block index i, take coskew_jk, the view of the columns (i - 1) * N + 1 to i * N of the cut tensor.
  7. Eigendecompose coskew_jk, giving the eigenvalues vals and the eigenvectors vecs, and subtract from V the negative semidefinite part that negative_spectral_part rebuilds from them.
  8. Run matrix_processing! once, on the accumulated V. No block is processed on its own.
  9. Return V where the block is the whole tensor, and write V into the block of a NaN frame of the full width otherwise.

Arguments

  • cskew: Coskewness tensor, assets × assets², laid out as N blocks of N columns. It may be a frame, whose refused assets carry NaN across their rows and their pair columns.
  • X: Data matrix (observations × assets). matrix_processing! reads it, and the spectral step does not.
  • mp: Optional matrix processing estimator.
    • ::AbstractMatrixProcessingEstimator: The estimator processes the accumulated V in-place.
    • ::Nothing: No-op. V is the raw sum of the negated negative parts.

Validation

  • The block of cskew is finite. An IsNonFiniteError naming the cell is thrown otherwise.

Returns

  • V::MatNum: Processed coskewness matrix assets x assets. It is the $\mathbf{V}$ above, so a portfolio's negative quadratic skewness is the quadratic form $\boldsymbol{w}^{\intercal} \mathbf{V} \boldsymbol{w}$. An asset outside the block carries NaN across its row and its column.

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 7.2.5.1, Equations 7.104 and 7.105.
  • [31] D. Cajas. On the Spectral Decomposition of Portfolio Skewness and its Application to Portfolio Optimization. Available at SSRN 4540021 (2023).
source
PortfolioOptimisers.negative_spectral_partFunction
negative_spectral_part(vals::AbstractVector{<:Real}, vecs::MatNum, Tf::Type) -> MatNum
negative_spectral_part(vals::AbstractVector{<:Complex}, vecs::MatNum, Tf::Type) -> MatNum

Rebuilds one block of a coskewness tensor from the negative part of its spectrum alone.

The inner step of negative_spectral_coskewness, taken out so the real and the complex spectrum are two methods rather than one branch. LinearAlgebra.eigen of a general real matrix answers a real spectrum or a complex one, and only the value says which, so a branch on eltype(vals) is a runtime test that no caller's types settle: the two methods settle it on the argument instead, and each body then sees a concrete element type.

Every non-negative eigenvalue is clamped to zero and every negative one is kept, so the reconstruction is the negative semidefinite part of the block. The caller subtracts it, which is what makes the accumulated matrix positive semidefinite.

The complex method is a fallback rather than a second definition: a block of the tensor is symmetric, so its spectrum is real, and eigen answers a complex one only where round-off has left the block asymmetric. It clamps the real and the imaginary parts the same way and takes the real part of the reconstruction.

Arguments

  • vals: The eigenvalues of the block.
  • vecs: The eigenvectors of the block.
  • Tf: Element type of the tensor, which sets the clamp's bounds.

Returns

  • S::MatNum: The negative semidefinite part of the block, of the shape of the block.

Related

source
PortfolioOptimisers.coverage_coskewnessFunction
coverage_coskewness(ske, cvg, X; dims::Int = 1, mean = nothing,
                    active_mask = nothing, kwargs...) -> (MatNum, MatNum)

Fits a coskewness tensor over the arm the estimator's coverage policy selects.

The cvg field of the estimator is passed as the second argument, so the arm is chosen by dispatch on the policy rather than by a branch on its value, exactly as coverage_covariance does for a covariance. The moment algorithm of ske chooses between the FullMoment arm, which centres on each asset's own mean and takes the deviations whole, and the SemiMoment arm, which clips them at zero first.

Arguments

  • ske: Coskewness estimator.
  • cvg: The policy the estimator carries, which selects the arm.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • mean: Optional mean vector. The available-case arms refuse one.
  • active_mask: The active mask of the Asset Panel over the window, or nothing. The Coverage Universe arm ignores it.
  • kwargs...: Additional keyword arguments passed to the mean estimator.

Returns

  • cskew::MatNum: Coskewness tensor assets x assets².
  • V::MatNum: Processed coskewness matrix assets x assets.

Related

source
PortfolioOptimisers.coverage_coskewnessMethod
coverage_coskewness(
    ske::Coskewness{<:Any, <:Any, <:FullMoment},
    ::Nothing,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}};
    dims,
    mean,
    active_mask,
    kwargs...
) -> Tuple{Any, Any}

Nothing method of the FullMoment arm of coverage_coskewness. The Coverage Universe arm, which centres the whole window on one vector and delegates to _coskewness, and which is the body the verb has always had.

Related

source
PortfolioOptimisers.coverage_coskewnessMethod
coverage_coskewness(
    ske::Coskewness{<:Any, <:Any, <:SemiMoment},
    ::Nothing,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}};
    dims,
    mean,
    active_mask,
    kwargs...
) -> Tuple{Any, Any}

Nothing method of the SemiMoment arm of coverage_coskewness. The Coverage Universe arm, which clips the de-meaned returns at zero before delegating, and which is the body the verb has always had.

Related

source
PortfolioOptimisers.coverage_coskewnessMethod
coverage_coskewness(
    ske::Coskewness,
    cvg::CoveragePolicy,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}};
    dims,
    mean,
    active_mask,
    kwargs...
) -> Tuple{Any, Any}

CoveragePolicy method of coverage_coskewness. The available-case arm: each triple is fitted on the observations at which its three assets are all finite and active, and each cell carries its own denominator.

The centre is each asset's own available-case mean, its own finite and active observations alone, and not the triple's. A per-triple centre would be assets³ running means, which is the same reason the SemiMoment arm of coverage_covariance centres per asset. So an external mean is refused, as it is there.

The SemiMoment arm differs in one line, the clip, which is why the two share this body through coverage_comoment_block. Neither folds: an exact per-cell recursion at this order needs the pairwise second co-moments over each triple's own observation set, so the online form of an available-case coskewness is a buffer refit through Online.

Algorithm

  1. Read the valid entries, the per-asset available-case centre and the per-asset bookkeeping of the block with coverage_comoment_block.
  2. Take the numerator as transpose(Y) * z and the denominator as transpose(Mi) * zc, where z and zc are the pairwise expansions of the zeroed deviations and of the valid mask.
  3. Divide with coverage_divide, and frame the refused assets down the asset axis and the pair axis with coverage_refuse_comoment!.
  4. Reduce the frame with negative_spectral_coskewness, which keeps the block rule.

Related

source
PortfolioOptimisers._coskewnessFunction
_coskewness(Y::MatNum, X::MatNum, mp::AbstractMatrixProcessingEstimator, w::Option{<:StatsBase.AbstractWeights}) -> (MatNum, MatNum)

Internal helper that builds the coskewness tensor from a deviation matrix.

_coskewness returns the tensor together with its negative spectral skewness matrix. The matrix processing estimator runs on the second one alone, inside negative_spectral_coskewness, and never on the tensor.

Mathematical definition

The $N \times N^{2}$ coskewness tensor is the third comoment matrix of the deviations:

Unweighted:

\[\begin{align} \hat{\mathbf{S}} &= \frac{1}{T} \mathbf{Y}^\intercal \mathbf{Z}\,, \\ \mathbf{Z}_{t,\cdot} &= (\boldsymbol{1}^\intercal \otimes \boldsymbol{y}_t^\intercal) \odot (\boldsymbol{y}_t^\intercal \otimes \boldsymbol{1}^\intercal)\,. \end{align}\]

Weighted:

\[\begin{align} \hat{\mathbf{S}} &= \frac{1}{\sum_{t=1}^{T} w_t} (\boldsymbol{w} \odot \mathbf{Y})^\intercal \mathbf{Z}\,. \end{align}\]

Where:

  • $\hat{\mathbf{S}}$: $N \times N^{2}$ coskewness tensor. Its entry $\hat{\mathbf{S}}_{a,\,(i-1)N+j}$ is the third comoment of the deviations of the assets $a$, $i$ and $j$, so each of its $N$ blocks of $N$ columns is symmetric.
  • $\mathbf{Y}$: $T \times N$ deviation matrix. FullMoment takes the centred returns, and SemiMoment clips every positive entry of them to zero.
  • $\boldsymbol{y}_t$: $N \times 1$ deviation vector of observation $t$, the $t$-th row of $\mathbf{Y}$. Its $i$-th entry is $y_{t,\,i}$.
  • $\mathbf{Z}$: $T \times N^{2}$ pairwise expansion of $\mathbf{Y}$, whose $t$-th row is $\mathbf{Z}_{t,\cdot}$ and whose entry $\mathbf{Z}_{t,\,(i-1)N+j}$ is the product $y_{t,\,i} \, y_{t,\,j}$.
  • $\boldsymbol{w}$: $T \times 1$ observation weights vector.
  • $w_{t}$: Observation weight of observation $t$.
  • $T$: Number of observations.
  • $N$: Number of assets.
  • $\boldsymbol{1}$: $N \times 1$ vector of ones.
  • $\otimes$: Kronecker product.
  • $\odot$: Element-wise product. Where the operands differ in shape, it broadcasts along the row axis.

Algorithm

  1. Build o, the $1 \times N$ row of ones.
  2. Build z, the pairwise expansion kron(o, Y) ⊙ kron(Y, o). Its column (i - 1) * N + j is the element-wise product of the columns i and j of Y.
  3. Without weights, form cskew as transpose(Y) * z / size(Y, 1).
  4. With weights, form cskew as transpose(w .* Y) * z / sum(w). The weights multiply the left factor alone, so each summand carries one weight and not three.
  5. Reduce cskew to V with negative_spectral_coskewness, which runs mp on the reduced matrix.
  6. Return the pair (cskew, V).

Arguments

  • Y: Deviation matrix (observations × assets), already centred by the caller.
  • X: Data matrix (observations × assets). It reaches matrix_processing! through negative_spectral_coskewness.
  • mp: Matrix processing estimator.
  • w: Optional observation weights. The unweighted method takes nothing through its args....

Returns

  • cskew::MatNum: Coskewness tensor assets x assets².
  • V::MatNum: Processed coskewness matrix assets x assets.

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 3.1.4, Equation 3.6.
  • [30] D. Cajas. Convex Optimization of Portfolio Kurtosis. Available at SSRN 4202967 (2022).
source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[30]
D. Cajas. Convex Optimization of Portfolio Kurtosis. Available at SSRN 4202967 (2022).
[31]
D. Cajas. On the Spectral Decomposition of Portfolio Skewness and its Application to Portfolio Optimization. Available at SSRN 4540021 (2023).