Coskewness

PortfolioOptimisers.CoskewnessEstimatorType
abstract type CoskewnessEstimator <: AbstractEstimator

Abstract supertype for all coskewness estimators.

All concrete and/or abstract types implementing coskewness estimation algorithms should be subtypes of CoskewnessEstimator.

Interfaces

In order to implement a new coskewness estimator which will work seamlessly with the library, subtype CoskewnessEstimator with all necessary parameters—including observation weights—as part of the struct, and implement the following methods:

Coskewness

  • PortfolioOptimisers.coskewness(ske::CoskewnessEstimator, X::MatNum; dims::Int = 1, mean = nothing, kwargs...) -> (MatNum, MatNum): Computes the coskewness tensor and processed matrix.

Arguments

  • ske: Coskewness estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • mean: Optional mean value to use for centering.
  • kwargs...: Additional keyword arguments.

Returns

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

Factory

  • PortfolioOptimisers.factory(ske::CoskewnessEstimator, w::PortfolioOptimisers.ObsWeights) -> CoskewnessEstimator: Factory method for creating instances of the estimator with new observation weights.

Arguments

  • ske: Coskewness estimator.
  • w: Observation weights vector observations × 1.

Returns

  • ske::CoskewnessEstimator: New coskewness estimator of the same type, with the new weights applied.

View

  • PortfolioOptimisers.port_opt_view(ske::CoskewnessEstimator, i) -> CoskewnessEstimator: Returns a view of the estimator for the i-th element(s).

Arguments

  • ske: Coskewness estimator.
  • i: Index or indices.

Returns

  • skev: New coskewness estimator of the same type as the argument, for the new view.

Examples

We can create a dummy coskewness estimator as follows:

julia> struct MyCoskewnessEstimator{T1} <: PortfolioOptimisers.CoskewnessEstimator           w::T1           function MyCoskewnessEstimator(w::PortfolioOptimisers.Option{<:PortfolioOptimisers.ObsWeights})               PortfolioOptimisers.assert_nonempty_nonneg_finite_val(w, :w)               return new{typeof(w)}(w)           end       endjulia> function MyCoskewnessEstimator(;                                      w::PortfolioOptimisers.Option{<:PortfolioOptimisers.ObsWeights} = nothing)           return MyCoskewnessEstimator(w)       endMyCoskewnessEstimatorjulia> function PortfolioOptimisers.factory(::MyCoskewnessEstimator,                                            w::PortfolioOptimisers.ObsWeights)           return MyCoskewnessEstimator(; w = w)       endjulia> function PortfolioOptimisers.port_opt_view(ske::MyCoskewnessEstimator, i)           return ske       endjulia> function PortfolioOptimisers.coskewness(ske::MyCoskewnessEstimator,                                               X::PortfolioOptimisers.MatNum; dims::Int = 1,                                               mean = nothing, kwargs...)           N = size(X, 2)           return zeros(N, N^2), zeros(N, N)       endjulia> cskew, V = coskewness(MyCoskewnessEstimator(), [1.0 2.0; 0.3 0.7; 0.5 1.1]);julia> cskew2×4 Matrix{Float64}: 0.0  0.0  0.0  0.0 0.0  0.0  0.0  0.0julia> V2×2 Matrix{Float64}: 0.0  0.0 0.0  0.0julia> PortfolioOptimisers.factory(MyCoskewnessEstimator(), StatsBase.Weights([1, 2, 3]))MyCoskewnessEstimator  w ┴ StatsBase.Weights{Int64, Int64, Vector{Int64}}: [1, 2, 3]

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
PortfolioOptimisers.CoskewnessType
struct Coskewness{__T_me, __T_mp, __T_alg, __T_w, __T_cvg, __T_cache} <: CoskewnessEstimator

Estimates the coskewness tensor of a returns matrix, together with its negative spectral skewness matrix.

Coskewness composes a mean estimator, a matrix processing estimator and a moment algorithm. coskewness returns both matrices as a pair: the assets × assets² tensor first, and the assets × assets matrix that negative_spectral_coskewness reduces it to second. The second is not a processed copy of the first.

Fields

  • me: Expected returns estimator.
  • mp: Matrix processing estimator.
  • alg: Moment algorithm.
  • w: Optional observation weights vector observations × 1, or a concrete subtype of DynamicAbstractWeights. If nothing, the computation is unweighted.
  • cvg: Optional CoveragePolicy. nothing is the reduce-and-expand path of the Coverage Universe, in which an asset that is non-finite or inactive at any observation of the window is NaN throughout the answer. A policy replaces it by available-case estimation: every cell is fitted on the observations at which the assets of that cell are all finite and active, each cell carries its own denominator, and an asset reaches the answer where admits says so.
  • 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

Coskewness(;    me::AbstractExpectedReturnsEstimator = SimpleExpectedReturns(),    mp::AbstractMatrixProcessingEstimator = MatrixProcessing(),    alg::AbstractMomentAlgorithm = FullMoment(),    w::Option{<:ObsWeights} = nothing,    cvg::Option{<:CoveragePolicy} = nothing,    cache::Option{<:AbstractPartialFitState} = nothing) -> Coskewness

Keywords correspond to the struct's fields.

Validation

  • If w is not nothing, !isempty(w).

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

View parameters

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

Observation weight parameters

When obs_weights_view is called on this type, the following fields are automatically indexed to the selected observations:

Examples

julia> Coskewness()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

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Sections 3.1.4 and 7.2.5.1, Equations 3.6, 7.104 and 7.105.
  • [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).
source
PortfolioOptimisers.coskewnessFunction
coskewness(ske::Option{<:Coskewness}, X::MatNum; dims::Int = 1,
           mean = nothing, kwargs...)

Compute the coskewness tensor of a dataset, together with its negative spectral skewness matrix. Observation weights in ske.w are applied if set. FullMoment takes the centred returns, and SemiMoment clips every positive deviation to zero. If the estimator is nothing, returns (nothing, nothing).

ske.w weights the whole estimate, so it reaches the centre as well as the deviations. When mean is nothing and ske.w is not, the method sends ske.me through factory with ske.w, so ske.w wins over the weights that ske.me carries. Pass mean for a centre that ske.w does not describe.

The two returned matrices are different objects. The first is the coskewness tensor itself, and the second is the negative spectral skewness matrix that negative_spectral_coskewness reduces it to.

Algorithm

  1. Orient X to observations × assets with dims_oriented, which validates dims.
  2. Resolve the observation weights w from ske.w with get_observation_weights.
  3. Resolve the centre mu from ske.me and ske.w with weighted_centre, which reads mean when the caller gave one.
  4. Form the deviation matrix Y. FullMoment takes X .- mu, and SemiMoment takes min.(X .- mu, 0).
  5. Delegate to _coskewness with Y, X, ske.mp and w, and return the pair it returns.

Arguments

  • ske: Coskewness estimator.

    • ske::Coskewness{<:Any, <:Any, <:FullMoment}: Coskewness estimator with FullMoment moment algorithm.
    • ske::Coskewness{<:Any, <:Any, <:SemiMoment}: Coskewness estimator with SemiMoment moment algorithm.
    • ske::Nothing: No-op, returns (nothing, nothing).
  • X: Data matrix (observations × assets).

  • dims: Dimension along which to perform the computation.

  • mean: Optional mean vector. If not provided, computed using the estimator's mean estimator.

  • kwargs...: Additional keyword arguments passed to the mean estimator.

Validation

  • dims is either 1 or 2.

Returns

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

Examples

julia> using StableRNGsjulia> rng = StableRNG(123456789);julia> X = randn(rng, 10, 3);julia> cskew, V = coskewness(Coskewness(), X);julia> cskew3×9 Matrix{Float64}: -0.329646    0.0782455   0.325842  …   0.325842  -0.250881   0.16769  0.0782455  -0.236104   -0.250881     -0.250881   0.266005   0.144546  0.325842   -0.250881    0.16769       0.16769    0.144546  -0.605589julia> V3×3 Matrix{Float64}:  0.513743   -0.0452078  -0.290893 -0.0452078   0.402765   -0.0372996 -0.290893   -0.0372996   0.837701

Related

source
coskewness(ske::WindowedCoskewness, X::MatNum; dims::Int = 1, mean = nothing, iv::Option{<:MatNum} = nothing, kwargs...)

Compute coskewness over a rolling or indexed observation window (matrix input).

This method selects a window of observations from X (and applies observation weights if specified), then delegates to the underlying coskewness estimator.

Algorithm

  1. Resolve the window and the observation weights with windowed_preamble, giving inner, a copy of ske.ske that carries the windowed weights, together with the windowed X and the windowed iv.
  2. Call coskewness on inner and the windowed X, and return its result. The inner estimator alone decides the value, so the window and the weights are the whole of this method's contribution.

Arguments

  • ske: Windowed coskewness estimator.
  • X: Data matrix of asset returns (observations × assets).
  • dims: Dimension along which to perform the computation.
  • mean: Optional pre-computed mean passed to the underlying estimator.
  • iv: Optional implied volatility matrix. Used if any internal covariance estimator is an instance of ImpliedVolatility.
  • kwargs...: Additional keyword arguments passed to the underlying estimator.

Returns

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

Related

source
coskewness(
    ske::Coskewness{<:Any, <:Any, <:FullMoment},
    state::CoskewnessPartialFitState
) -> Tuple{Any, Any}

Reads the coskewness tensor and its negative spectral skewness matrix out of a partial-fit state.

The state read-out of coskewness. It returns the pair the batch method returns, to machine precision, for the sample the state was fitted on.

Only the shape of that sample survives a partial fit, so the matrix processing estimator is handed a matrix of the right shape whose entries are zero. Of the shipped steps only :dn reads it, and it reads only size(X), so the two routes agree. A custom :alg step that reads the values of the sample must be run from the batch verb.

Algorithm

  1. Refuse a configuration the state no longer matches, with assert_partial_fittable. factory carries the state and replaces w, so an estimator that says weighted may hold a state fitted unweighted. The state stays on the estimator, so a caller who restores w = nothing reads it again.
  2. Divide the third accumulator by the observation count, giving the coskewness tensor.
  3. Reduce it with negative_spectral_coskewness, under ske.mp and the shape of the fitted sample.

Arguments

  • ske: Coskewness estimator with a FullMoment moment algorithm.
  • state: Partial-fit state written by partial_fit!.

Validation

  • ske.w is nothing and ske.me is an unweighted SimpleExpectedReturns. An ArgumentError is thrown otherwise.

Returns

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

Related

source
coskewness(
    ske::Coskewness,
    state::SampleBufferState
) -> Tuple{Any, Any}

Reads a coskewness out of a sample buffer, by refitting the batch verb over the observations the buffer holds.

The buffer read-out of coskewness, and the whole of the online form at this order. Neither the third nor the fourth co-moment folds exactly under a CoveragePolicy — an exact per-cell recursion needs the pairwise second co-moments over each triple's own observation set — so an Online wrapper seeds a SampleBufferState and the read-out refits from it. The answer is therefore bit-exact with the batch arm over the same rows, by construction rather than by arithmetic coincidence.

The buffer holds the observations verbatim, NaN included, so the available-case arm reads the same gaps from it that it would read from the caller's own matrix, and it holds the active mask that explains them beside them. With no active mask a gap is a holiday rather than a delisting, which is what coverage_valid_block states; with one, the read-out reads the delisting the fold was told about.

Arguments

  • ske: Coskewness estimator.
  • state: The sample buffer the wrapper seeded.

Returns

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

Related

source
coskewness(ske::Coskewness) -> Any

Reads the coskewness tensor and its negative spectral skewness matrix out of the state the estimator carries.

The one-argument forwarder of coskewness. It reads ske.cache, which partial_fit! writes, and refuses an estimator that has been shown no observation.

Algorithm

  1. Refuse the estimator whose cache is nothing.
  2. Forward to the state method of coskewness.

Arguments

  • ske: Coskewness estimator.

Validation

  • ske.cache is not nothing. An ArgumentError is thrown otherwise.

Returns

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

Related

source
coskewness(
    ske::CoskewnessEstimator,
    state::SampleBufferState
) -> Tuple{Any, Any}

Reads a coskewness tensor out of a SampleBufferState, by running the batch verb over the observations the buffer holds.

The buffer read-out arm of coskewness. An estimator wrapped in Online carries a buffer rather than a family state, so it takes no exact fold, and its estimate is whatever a batch fit over the rows the buffer holds gives — every observation folded so far when the buffer is uncapped, and the last max_history of them when it is capped. It runs no assert_partial_fittable check of its own: the batch verb answers configurations an incremental fold cannot, and a wrapper is how a caller reaches them. The buffer also holds the per-observation masks it was folded with, so the batch verb is given the mask that explains the rows and a wrapped estimator under a CoveragePolicy answers what an unwrapped one answers.

Arguments

  • ske: Coskewness estimator.
  • state: The buffer to read.

Returns

  • (cskew, V)::Tuple: The coskewness tensor of the observations the buffer holds, and its negative spectral shape.

Related

source
coskewness(
    ske::CoskewnessEstimator,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    pnl::Union{Nothing, AssetPanel};
    dims,
    kwargs...
) -> Tuple{Any, Any}

The coskewness root of the Asset Panel seam. It is the reduce-and-expand of Statistics.cov(ce::AbstractCovarianceEstimator, X::MatNum, pnl::Option{<:AssetPanel}; dims::Int = 1, kwargs...), which states the rule and its cost. The answer is a pair, so the tensor expands at the pair index and the negative spectral skewness matrix expands as a covariance-like matrix.

Related

source
coskewness(
    ske::Coskewness,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    pnl::Union{Nothing, AssetPanel};
    dims,
    kwargs...
) -> Tuple{Any, Any}

Asset Panel method of coskewness for a Coskewness, which routes on its cvg field with coverage_panel_moment. The answer is a pair, so the Coverage Universe seam frames the tensor at the pair index and the negative spectral skewness matrix as a covariance-like matrix.

Related

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