Coskewness
PortfolioOptimisers.CoskewnessEstimator — Type
abstract type CoskewnessEstimator <: AbstractEstimatorAbstract 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 matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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 tensorassets x assets².V::MatNum: Processed coskewness matrixassets 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 vectorobservations × 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 thei-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
PortfolioOptimisers.Coskewness — Type
struct Coskewness{__T_me, __T_mp, __T_alg, __T_w, __T_cvg, __T_cache} <: CoskewnessEstimatorEstimates 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 vectorobservations × 1, or a concrete subtype ofDynamicAbstractWeights. Ifnothing, the computation is unweighted.
cvg: OptionalCoveragePolicy.nothingis 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 isNaNthroughout 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 whereadmitssays so.
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
Coskewness(; me::AbstractExpectedReturnsEstimator = SimpleExpectedReturns(), mp::AbstractMatrixProcessingEstimator = MatrixProcessing(), alg::AbstractMomentAlgorithm = FullMoment(), w::Option{<:ObsWeights} = nothing, cvg::Option{<:CoveragePolicy} = nothing, cache::Option{<:AbstractPartialFitState} = nothing) -> CoskewnessKeywords correspond to the struct's fields.
Validation
- If
wis notnothing,!isempty(w).
Propagated parameters
When factory is called on this type, the following @fprop-tagged fields are automatically propagated:
me: Recursively updated viafactory.w: Replaced with the incomingObsWeights.cache: Carried unchanged viafactory.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
me: Recursively viewed viaport_opt_view.cache: Sliced to the selected assets viaport_opt_view.
Observation weight parameters
When obs_weights_view is called on this type, the following fields are automatically indexed to the selected observations:
me: Recursively indexed viaobs_weights_view.w: Indexed to the selected observations viaobs_weights_view.cache: Dropped viaobs_weights_view, because no slice of a state exists on the observation axis.
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 ┴ nothingRelated
CoskewnessEstimatorAbstractExpectedReturnsEstimatorAbstractMatrixProcessingEstimatorAbstractMomentAlgorithmnegative_spectral_coskewnessfactoryport_opt_viewobs_weights_view
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).
PortfolioOptimisers.coskewness — Function
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
- Orient
Xto observations × assets withdims_oriented, which validatesdims. - Resolve the observation weights
wfromske.wwithget_observation_weights. - Resolve the centre
mufromske.meandske.wwithweighted_centre, which readsmeanwhen the caller gave one. - Form the deviation matrix
Y.FullMomenttakesX .- mu, andSemiMomenttakesmin.(X .- mu, 0). - Delegate to
_coskewnesswithY,X,ske.mpandw, and return the pair it returns.
Arguments
ske: Coskewness estimator.ske::Coskewness{<:Any, <:Any, <:FullMoment}: Coskewness estimator withFullMomentmoment algorithm.ske::Coskewness{<:Any, <:Any, <:SemiMoment}: Coskewness estimator withSemiMomentmoment 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
dimsis either1or2.
Returns
cskew::MatNum: Coskewness tensorassets x assets².V::MatNum: Processed coskewness matrixassets 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.837701Related
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
- Resolve the window and the observation weights with
windowed_preamble, givinginner, a copy ofske.skethat carries the windowed weights, together with the windowedXand the windowediv. - Call
coskewnessoninnerand the windowedX, 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 ofImpliedVolatility.kwargs...: Additional keyword arguments passed to the underlying estimator.
Returns
cskew::MatNum: Coskewness tensorassets x assets².V::MatNum: Processed coskewness matrixassets x assets.
Related
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
- Refuse a configuration the state no longer matches, with
assert_partial_fittable.factorycarries the state and replacesw, so an estimator that says weighted may hold a state fitted unweighted. The state stays on the estimator, so a caller who restoresw = nothingreads it again. - Divide the third accumulator by the observation count, giving the coskewness tensor.
- Reduce it with
negative_spectral_coskewness, underske.mpand the shape of the fitted sample.
Arguments
ske: Coskewness estimator with aFullMomentmoment algorithm.state: Partial-fit state written bypartial_fit!.
Validation
ske.wisnothingandske.meis an unweightedSimpleExpectedReturns. AnArgumentErroris thrown otherwise.
Returns
cskew::MatNum: Coskewness tensorassets x assets².V::MatNum: Processed coskewness matrixassets x assets.
Related
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 tensorassets x assets².V::MatNum: Processed coskewness matrixassets x assets.
Related
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
- Refuse the estimator whose
cacheisnothing. - Forward to the state method of
coskewness.
Arguments
ske: Coskewness estimator.
Validation
ske.cacheis notnothing. AnArgumentErroris thrown otherwise.
Returns
cskew::MatNum: Coskewness tensorassets x assets².V::MatNum: Processed coskewness matrixassets x assets.
Related
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
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
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
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).