Orthogonal Uncertainty Sets

PortfolioOptimisers.AbstractOrthogonalScalingType
abstract type AbstractOrthogonalScaling <: AbstractAlgorithm

Abstract supertype of the scalings that size each direction inside the Orthogonal Subspace.

A member states the $r \times r$ matrix $\mathbf{\Lambda}$ of the mean set, whose square root maps the ball's coordinates onto the subspace basis. The scaling changes the shape of the set and not its support: every member confines the set to the same subspace, and the radius reads the same rank degrees of freedom.

Interfaces

orthogonal_scaling

  • orthogonal_scaling(scaling::AbstractOrthogonalScaling, G::MatNum, rr::AbstractLoadingsRegressionResult) -> MatNum: Returns $\mathbf{\Lambda}$, rank × rank, symmetric and positive semi-definite.

Related

source
PortfolioOptimisers.AbstractOrthogonalityMetricType
abstract type AbstractOrthogonalityMetric <: AbstractAlgorithm

Abstract supertype of the cross-sectional weightings that name a weight source on a fitted loadings block.

A member of this family selects a weight vector a fitted loadings block already carries, and computes none. It is the marker-family form the library uses for a fixed choice among stored quantities, as class does on NormBallUncertaintySet. Its sibling AbstractCrossSectionalWeightsAlgorithm is the other side of the pair: that family computes the cross-sectional weights inside the prior's own fit, and this one names which of the fit's outputs to read back.

Two readers resolve a member to two different shapes, both over the same field of the block. orthogonality_weights reads the weights of the latest observation, one entry per asset, for an uncertainty set whose geometry serves the next decision. cs_diagnostic_weights reads the whole observation history, one row per observation, for a diagnostic that scores every observation instead of only the latest.

Interfaces

orthogonality_weights

  • orthogonality_weights(metric::AbstractOrthogonalityMetric, rr::AbstractLoadingsRegressionResult) -> Option{<:VecNum}: Returns the weights of the latest observation, or nothing when the member is the unweighted one.

cs_diagnostic_weights

  • cs_diagnostic_weights(metric::AbstractOrthogonalityMetric, csfm::CrossSectionalFactorModel) -> Option{<:MatNum}: Returns the whole observation history the member names, or nothing when the member is the unweighted one.

Related

source
PortfolioOptimisers.BenchmarkWeightMetricType
struct BenchmarkWeightMetric <: AbstractOrthogonalityMetric

Names the benchmark weights as the cross-sectional weight source.

A block's bw field carries the benchmark weight of every asset at every observation, and a reader takes its latest row for an uncertainty set or its whole history for a diagnostic. A block fitted per asset over the observations carries no such field and refuses.

The metric needs every investable asset to have entered the latest fit. An asset can be in the Investable Mask and outside the estimation universe of the latest cross-section: its return is finite, so the prior states a moment for it, and it is dropped from the fit, so its benchmark weight is zero there. A zero weight cannot be inverted into a metric, and an uncertainty set built on this metric refuses the block. On a point-in-time Asset Panel whose latest cross-section does not cover the whole Investable Mask, use InverseIdiosyncraticVarianceMetric or IdentityMetric, which read no weight history.

Examples

julia> BenchmarkWeightMetric()BenchmarkWeightMetric()

Related

source
PortfolioOptimisers.RegressionWeightMetricType
struct RegressionWeightMetric <: AbstractOrthogonalityMetric

Names the regression weights as the cross-sectional weight source.

A block's rw field carries the weight the Cross-Sectional Regression gave every asset at every observation, and a reader takes its latest row for an uncertainty set or its whole history for a diagnostic. A block fitted per asset over the observations carries no such field and refuses.

The metric needs every investable asset to have entered the latest fit. An asset can be in the Investable Mask and outside the estimation universe of the latest cross-section: its return is finite, so the prior states a moment for it, and it is dropped from the fit, so its regression weight is zero there. A zero weight cannot be inverted into a metric, and an uncertainty set built on this metric refuses the block. On a point-in-time Asset Panel whose latest cross-section does not cover the whole Investable Mask, use InverseIdiosyncraticVarianceMetric or IdentityMetric, which read no weight history.

Examples

julia> RegressionWeightMetric()RegressionWeightMetric()

Related

source
PortfolioOptimisers.InverseIdiosyncraticVarianceMetricType
struct InverseIdiosyncraticVarianceMetric <: AbstractOrthogonalityMetric

Names the inverse of the idiosyncratic variances as the cross-sectional weight source, the default.

An asset whose returns the factors explain well carries a large weight. The variances come from idiosyncratic_variances on the loadings block, which reads esigma, and a non-positive variance refuses.

Examples

julia> InverseIdiosyncraticVarianceMetric()InverseIdiosyncraticVarianceMetric()

Related

source
PortfolioOptimisers.IdentityMetricType
struct IdentityMetric <: AbstractOrthogonalityMetric

Names no weight source: every asset carries the same weight.

It is the one member that reads nothing off the loadings block, so it serves a block that carries neither a weight history nor an idiosyncratic variance. Both readers answer nothing on this member, which their caller takes as a vector of ones.

Examples

julia> IdentityMetric()IdentityMetric()

Related

source
PortfolioOptimisers.IdiosyncraticVarianceScalingType
struct IdiosyncraticVarianceScaling <: AbstractOrthogonalScaling

Sizes each direction of the Orthogonal Subspace by the idiosyncratic covariance projected onto it.

$\mathbf{\Lambda} = \mathbf{G}^{\intercal}\mathbf{D}\mathbf{G}$, with $\mathbf{D}$ the idiosyncratic covariance the loadings block carries. A direction the factors leave noisy is then given more uncertainty than a quiet one, where IdentityScaling gives both the same. The block must carry an idiosyncratic covariance, so an unset esigma refuses.

Examples

julia> IdiosyncraticVarianceScaling()IdiosyncraticVarianceScaling()

Related

source
PortfolioOptimisers.OrthogonalUncertaintySetType
struct OrthogonalUncertaintySet{__T_q, __T_method, __T_scaling, __T_kappa, __T_metric} <: AbstractPriorUncertaintySetEstimator

Fits both uncertainty sets from the factor model of the optimisation's own prior, confined to the directions the factors do not span.

The estimator reads the loadings block rr of the Prior Result it is handed, and never fits a prior of its own. It is the one member of AbstractPriorUncertaintySetEstimator, and the two JuMP builders pass the reduced prior beside the returns so that the sets and the moments they correct are fitted on one object.

One estimator answers both axes. The weighted loadings, their thin singular value decomposition and the numerical rank are computed once, and ucs returns the pair. The mean axis gets a NormBallUncertaintySet whose geometry map spans the Orthogonal Subspace, and the covariance axis gets a CompactCovarianceUncertaintySet whose basis is the weighted factor span itself. The two are complementary: a portfolio inside the span pays nothing on either axis, and one outside it pays on both.

The point estimates are unchanged. The mean set is centred on pr.mu and the covariance set carries pr.sigma, so nothing is shrunk in the prior. The correction is a portfolio-dependent worst case that grows with the exposure to the subspace, which is what an optimiser that over-allocates to unspanned directions needs.

Mathematical definition

\[\begin{align} \mathbf{B}_{\mathbf{W}} &= \mathbf{W}^{1/2}\mathbf{B}\,, \quad \mathbf{Q} = \operatorname{svd}_{r_{\mathbf{B}}}(\mathbf{B}_{\mathbf{W}})\,, \\ \mathbf{A} &= \mathbf{W}^{-1/2}\left(\mathbf{I} - \mathbf{Q}\mathbf{Q}^{\intercal}\right)\,, \quad \mathbf{G} = \operatorname{qr}(\mathbf{A}\mathbf{V}_{+})\,, \\ \mathbf{L} &= \mathbf{G}\mathbf{\Lambda}^{1/2}\,, \quad \kappa_{\boldsymbol{\mu}} = \sqrt{\chi^{2,\,-1}_{r}(1 - q)}\,, \\ \mathbf{C} &= \mathbf{W}^{-1/2}\,. \end{align}\]

Where:

  • $\mathbf{B}$: Effective loadings, $N \times K$, reduced to a full-rank basis when a Factor Family was re-based.
  • $\mathbf{W} = \operatorname{diag}(\boldsymbol{w})$: Cross-sectional metric the AbstractOrthogonalityMetric names, the identity on IdentityMetric.
  • $\mathbf{Q}$: Left singular vectors of the weighted loadings kept by the numerical rank $r_{\mathbf{B}}$, an orthonormal basis of the weighted factor span.
  • $\mathbf{A}$: Orthogonal projector, mapped back through the metric.
  • $\mathbf{V}_{+}$: Eigenvectors of $\mathbf{A}^{\intercal}\mathbf{A}$ that carry its $N - r_{\mathbf{B}}$ largest eigenvalues, which is the rank of the projector.
  • $\mathbf{G}$: Orthonormal basis of the Orthogonal Subspace, $N \times r$.
  • $\mathbf{\Lambda}$: Scaling the AbstractOrthogonalScaling names, $r \times r$.
  • $\mathbf{L}$: Geometry map of the mean set.
  • $\kappa_{\boldsymbol{\mu}}$: Radius of the mean set, at $r$ degrees of freedom.
  • $\mathbf{C}$: Diagonal metric square root of the covariance set.

The rank of the mean set is the dimension of the Orthogonal Subspace, not the number of assets, because a flat set is a confidence region of its own subspace and not of the ambient space. A model whose loadings span the whole cross-section leaves $r = 0$, a radius of zero, and no correction at all.

Fields

  • q: Confidence level that sizes the uncertainty set (0 < q < 1). A smaller q is more demanding and yields a larger, more conservative set (wider box intervals / larger ellipsoid radius); a larger q gives a tighter set closer to the point estimate.
  • method: Ellipsoidal uncertainty set estimation method.
  • scaling: Scaling of the mean set inside the Orthogonal Subspace. It changes the shape of the set and not the subspace the set lives in.
  • kappa: Radius $\kappa \geq 0$ of the covariance set, the multiplier of its quadratic penalty, and 0 leaves the nominal variance. It is a size the caller states, a rule of AbstractCompactRadiusAlgorithm that computes one from the sample and the span, or a value a search picks: kappa is a plain field, so "ucs.kappa" is a lens path a GridSearchCrossValidation or a RandomisedSearchCrossValidation grid ranges over, and a grid may hold rules beside numbers.
  • metric: Cross-sectional weighting under which the factor span is taken. It fixes the geometry of both sets, because both read one span.

Constructors

OrthogonalUncertaintySet(;    q::Number = 0.05,    method::Num_UcSK = ChiSqKUncertaintyAlgorithm(),    scaling::AbstractOrthogonalScaling = IdentityScaling(),    kappa::Num_CptRad = 1.0,    metric::AbstractOrthogonalityMetric = InverseIdiosyncraticVarianceMetric()) -> OrthogonalUncertaintySet

Keywords correspond to the struct's fields.

Validation

  • 0 < q < 1.
  • If kappa is a number: isfinite(kappa) and kappa >= 0. A rule is checked where its number lands, by the constructor of CompactCovarianceUncertaintySet.

Examples

julia> OrthogonalUncertaintySet()OrthogonalUncertaintySet        q ┼ Float64: 0.05   method ┼ ChiSqKUncertaintyAlgorithm()  scaling ┼ IdentityScaling()    kappa ┼ Float64: 1.0   metric ┴ InverseIdiosyncraticVarianceMetric()

Related

References

  • [94] D. P. Palomar. Portfolio Optimization: Theory and Application (Cambridge University Press, 2025). Chapter 14.
  • [89] D. Goldfarb and G. Iyengar. Robust Portfolio Selection Problems. Mathematics of Operations Research 28, 1–38 (2003). Section 5.
  • [88] A. Ben-Tal and A. Nemirovski. Robust Convex Optimization. Mathematics of Operations Research 23, 769–805 (1998). Section 3.
source
PortfolioOptimisers.orthogonal_scalingFunction
orthogonal_scaling(::IdentityScaling, G::MatNum, ::AbstractLoadingsRegressionResult)
orthogonal_scaling(::IdiosyncraticVarianceScaling, G::MatNum, rr::AbstractLoadingsRegressionResult)

Scaling $\\mathbf{\\Lambda}$ of the mean set inside the Orthogonal Subspace.

Mathematical definition

\[\begin{align} \mathbf{\Lambda}_{\mathrm{id}} &= \mathbf{I}_{r}\,, \\ \mathbf{\Lambda}_{\mathrm{idio}} &= \mathbf{G}^{\intercal}\mathbf{D}\mathbf{G}\,. \end{align}\]

Where:

  • $\mathbf{G}$: Orthonormal basis of the Orthogonal Subspace, $N \times r$.
  • $\mathbf{D}$: Idiosyncratic covariance the loadings block carries, $N \times N$, diagonal when the block stores a variance vector.
  • $r$: Dimension of the subspace.

Algorithm

  1. On IdentityScaling, return the $r \times r$ identity as a dense matrix, so the caller's square root and the radius read one type on both routes.
  2. On IdiosyncraticVarianceScaling, read rr.esigma, form $\mathbf{G}^{\intercal}\mathbf{D}\mathbf{G}$ and symmetrise it. A stored variance vector is used as a diagonal without materialising the $N \times N$ matrix.

Arguments

  • scaling: Orthogonal scaling.
  • G: Orthonormal basis of the Orthogonal Subspace, $N \times r$.
  • rr: Fitted loadings block.

Validation

Returns

  • lambda::MatNum: $r \times r$ scaling, symmetric and positive semi-definite.

Related

source
PortfolioOptimisers.orthogonality_weightsFunction
orthogonality_weights(::IdentityMetric, ::AbstractLoadingsRegressionResult)
orthogonality_weights(::InverseIdiosyncraticVarianceMetric, rr::AbstractLoadingsRegressionResult)
orthogonality_weights(::BenchmarkWeightMetric, rr::AbstractLoadingsRegressionResult)
orthogonality_weights(::RegressionWeightMetric, rr::AbstractLoadingsRegressionResult)
orthogonality_weights(::BenchmarkWeightMetric, rr::CrossSectionalFactorModel)
orthogonality_weights(::RegressionWeightMetric, rr::CrossSectionalFactorModel)

Cross-sectional weight vector the AbstractOrthogonalityMetric names, read off a fitted loadings block.

Algorithm

  1. On IdentityMetric, return nothing. The caller reads a nothing as a vector of ones and skips both the scaling of the loadings and the division of the projector, so the unweighted route costs no arithmetic.
  2. On InverseIdiosyncraticVarianceMetric, take idiosyncratic_variances(rr) and return its element-wise inverse.
  3. On BenchmarkWeightMetric and RegressionWeightMetric over a CrossSectionalFactorModel, take the last row of bw or of rw, the weights of the latest observation.
  4. On the same two metrics over any other loadings block, throw. A block fitted per asset over the observations carries no cross-sectional weight history, so the metric has nothing to read.

Arguments

  • metric: Orthogonality metric.
  • rr: Fitted loadings block.

Validation

  • On InverseIdiosyncraticVarianceMetric: every idiosyncratic variance is finite and > 0, else a DomainError. idiosyncratic_variances itself throws when the block carries none.
  • On a weight history: the history is non-empty, and every weight of its last row is finite and > 0, else an IsEmptyError or a DomainError.
  • On a weight metric over a block that carries no history: an IsNothingError naming the field.

Returns

  • w::Option{<:VecNum}: Weight vector of length $N$, or nothing on IdentityMetric.

Related

source
PortfolioOptimisers.cs_diagnostic_weightsFunction
cs_diagnostic_weights(weighting::IdentityMetric, csfm::CrossSectionalFactorModel)
cs_diagnostic_weights(weighting::BenchmarkWeightMetric, csfm::CrossSectionalFactorModel)
cs_diagnostic_weights(weighting::RegressionWeightMetric, csfm::CrossSectionalFactorModel)
cs_diagnostic_weights(weighting::InverseIdiosyncraticVarianceMetric, csfm::CrossSectionalFactorModel)

Return the cross-sectional weight history an AbstractOrthogonalityMetric names, over the whole observation axis of a factor model block.

orthogonality_weights reads the same family off the same block and answers with the weights of the latest observation, which is what an uncertainty set needs. A diagnostic scores every observation, so it needs the whole history, and this verb is that reading of the family.

Arguments

  • weighting: The metric that names the history.
  • csfm: A cross-sectional factor model block.

Validation

  • The field the metric names is not nothing, else an IsNothingError naming it is raised.

Returns

  • u::Option{<:MatNum}: observations × assets, and nothing under IdentityMetric, which the diagnostics read as equal weights.

Related

source
PortfolioOptimisers.ucsMethod
ucs(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult;
    rd,
    kwargs...
) -> Tuple{Union{NormBallUncertaintySet{var"#s185", <:AbstractMatrix{var"#s137"}, Int64, MuUncertaintySetClass, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, NormBallUncertaintySet{var"#s185", var"#s1851", Int64, MuUncertaintySetClass, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}, Union{CompactCovarianceUncertaintySet{var"#s185", var"#s1851", <:AbstractMatrix{var"#s137"}, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, CompactCovarianceUncertaintySet{var"#s185", var"#s1851", var"#s1852", <:AbstractMatrix{var"#s137"}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1852"<:AbstractMatrix{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}}}

Fits both uncertainty sets of an OrthogonalUncertaintySet from the prior result in one pass.

Algorithm

  1. Reduce the prior result, and the returns data beside it, to the Investable Mask with investable_ucs_reduction. Inside an optimiser the result arrives already reduced and the step is a passthrough; standalone, on a prior fitted on a point-in-time Asset Panel, it takes the view the optimiser would have taken.
  2. Take the weighted factor span once with orthogonal_factor_span.
  3. Build the mean set with orthogonal_mu_set and the covariance set with orthogonal_sigma_set, both from that span.
  4. Write each set back onto the full universe with expand_investable_ucs, so a set fitted standalone is over the same assets the prior is, with a zero row on every asset outside the mask, and a view of it at the mask recovers the reduced fit.

A caller that needs one axis alone calls mu_ucs or sigma_ucs, which take the same span and build one set.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the optimisation is solving on.
  • rd: Returns data the set is being fitted beside, or nothing. Only a VarianceFraction holding an optimiser reads it, and the three-argument forms of ucs fill it in.
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • mu_ucs::NormBallUncertaintySet: Mean set on the Orthogonal Subspace.
  • sigma_ucs::CompactCovarianceUncertaintySet: Covariance set that spares the factor span.

Related

source
PortfolioOptimisers.mu_ucsMethod
mu_ucs(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult;
    kwargs...
) -> Union{NormBallUncertaintySet{var"#s185", <:AbstractMatrix{var"#s137"}, Int64, MuUncertaintySetClass, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, NormBallUncertaintySet{var"#s185", var"#s1851", Int64, MuUncertaintySetClass, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}

Fits the mean uncertainty set of an OrthogonalUncertaintySet from the prior result.

Algorithm

  1. Reduce the prior result to the Investable Mask with investable_ucs_reduction, a passthrough on a result that arrived reduced.
  2. Take the weighted factor span with orthogonal_factor_span.
  3. Build the mean set with orthogonal_mu_set.
  4. Write it back onto the full universe with expand_investable_ucs.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the optimisation is solving on.
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • ucs::NormBallUncertaintySet: Mean set on the Orthogonal Subspace.

Related

source
PortfolioOptimisers.sigma_ucsMethod
sigma_ucs(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult;
    rd,
    kwargs...
) -> Union{CompactCovarianceUncertaintySet{var"#s185", var"#s1851", <:AbstractMatrix{var"#s137"}, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, CompactCovarianceUncertaintySet{var"#s185", var"#s1851", var"#s1852", <:AbstractMatrix{var"#s137"}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1852"<:AbstractMatrix{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}}

Fits the covariance uncertainty set of an OrthogonalUncertaintySet from the prior result.

Algorithm

  1. Reduce the prior result, and the returns data beside it, to the Investable Mask with investable_ucs_reduction, a passthrough on a result that arrived reduced.
  2. Take the weighted factor span with orthogonal_factor_span.
  3. Build the covariance set with orthogonal_sigma_set.
  4. Write it back onto the full universe with expand_investable_ucs.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the optimisation is solving on.
  • rd: Returns data the set is being fitted beside, or nothing. Only a VarianceFraction holding an optimiser reads it, and the three-argument forms of sigma_ucs fill it in.
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • ucs::CompactCovarianceUncertaintySet: Covariance set that spares the factor span.

Related

source

References

[88]
A. Ben-Tal and A. Nemirovski. Robust Convex Optimization. Mathematics of Operations Research 23, 769–805 (1998).
[89]
D. Goldfarb and G. Iyengar. Robust Portfolio Selection Problems. Mathematics of Operations Research 28, 1–38 (2003).
[94]
D. P. Palomar. Portfolio Optimization: Theory and Application (Cambridge University Press, 2025).