Orthogonal Uncertainty Sets: private API

PortfolioOptimisers.latest_orthogonality_weightsFunction
latest_orthogonality_weights(::Nothing, name::Symbol, rr::AbstractLoadingsRegressionResult)
latest_orthogonality_weights(w::MatNum, name::Symbol, ::AbstractLoadingsRegressionResult)

Last row of a cross-sectional weight history, checked as a metric.

Algorithm

  1. On a nothing history, throw. The block declares the field and this fit left it unset, which the message says.
  2. On a history, take its last row, the weights of the latest observation. The uncertainty set is built for the next decision, so it reads the newest cross-section and not an average of the sample.

Arguments

  • w: Weight history, observations × assets, or nothing.
  • name: Name of the field the history came from, which the refusals quote.
  • rr: Fitted loadings block, quoted by the refusals.

Validation

  • !isempty(w), else an IsEmptyError.
  • Every entry of the last row is finite and > 0, else a DomainError. A weight of zero excluded its asset from the fit, and an excluded asset gives the metric a singular direction. The case is not a point-in-time gap the Investable Mask removes: an asset can be investable, with a finite return and a stated moment, and still sit outside the estimation universe of the latest cross-section, so its weight is zero while its loadings and variance are finite. The two metrics that read no weight history, InverseIdiosyncraticVarianceMetric and IdentityMetric, are the way round it.

Returns

  • w::VecNum: Weights of the latest observation, one entry per asset.

Related

source
PortfolioOptimisers.cs_diagnostic_weight_historyFunction
cs_diagnostic_weight_history(A::Nothing, name::AbstractString, what::AbstractString)
cs_diagnostic_weight_history(A::MatNum, name::AbstractString, what::AbstractString)

Return a weight history of a factor model block, or refuse an absent one by name.

Arguments

  • A: The field of the block the metric names, or nothing.
  • name: Name of that field, which the refusal states.
  • what: What the field holds, which the refusal states.

Returns

  • A::MatNum: The history.

Related

source
PortfolioOptimisers.orthogonal_factor_spanFunction
orthogonal_factor_span(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult
) -> Tuple{Any, Any, Any}

Weighted factor span of the prior's loadings block, the geometry both sets are built from.

Algorithm

  1. Refuse when pr.rr is nothing. The set reads the loadings off the prior result, and a prior that fitted no factor model carries none.
  2. Read the effective loadings rr.L, which reads back as rr.M when no Factor Family was re-based, so a re-based model is already reduced to a full-rank basis here. Refuse a row that is not finite: a singular value decomposition of such a row is not a span but a LAPACK error. The fit never meets the NaN rows a point-in-time Asset Panel writes outside the Investable Mask, because the optimiser's builders hand it the prior reduced to that mask and the standalone verbs reduce to it first through investable_ucs_reduction. A non-finite row that reaches here therefore sits on an asset the prior calls investable, whose mu and variance are finite while its loadings are not, and the message says so.
  3. Read the cross-sectional weights through orthogonality_weights and take their element-wise square root, or leave a nothing.
  4. Scale the rows of the loadings by that square root, take a thin LinearAlgebra.svd, and count the singular values above maximum(size) * eps * s[1], the tolerance LinearAlgebra.rank applies. Keep that many left singular vectors.

Step 4 counts the rank after the family re-basis of step 2, because the selected universe can still leave the exposures numerically dependent, and a dependent direction that survives would widen the span the penalty spares.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the optimisation is solving on.

Validation

  • !isnothing(pr.rr), else an IsNothingError naming the field and the estimator that returned no block.
  • all(isfinite, rr.L), else an IsNonFiniteError counting the assets whose loadings are not finite. Every such asset is inside the Investable Mask of pr, because the verbs that call this fit reduce to the mask first.

Returns

  • rr::AbstractLoadingsRegressionResult: The loadings block the span came from.
  • w_sqrt::Option{<:VecNum}: Element-wise square root of the metric, or nothing on IdentityMetric.
  • Q::MatNum: Orthonormal basis of the weighted factor span, $N \times r_{\mathbf{B}}$.

Related

source
PortfolioOptimisers.orthogonal_mu_setFunction
orthogonal_mu_set(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult,
    rr::AbstractLoadingsRegressionResult,
    w_sqrt::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    Q::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> 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}}

Builds the mean NormBallUncertaintySet on the Orthogonal Subspace, from a span already taken.

Algorithm

  1. Form the orthogonal projector I - Q * Q' and divide its rows by the metric square root, giving $\mathbf{A}$, the projector read back in the asset coordinates.
  2. Take the symmetric eigendecomposition of A' * A and keep the trailing N - size(Q, 2) eigenvectors. The count is exact: Q is orthonormal, so I - Q * Q' has rank N - size(Q, 2), the metric scaling is an invertible diagonal, and LinearAlgebra.eigen on a Symmetric orders the eigenvalues from small to large. No tolerance decides the rank here: an eigenvalue tolerance can sit close enough to the eigenvalue it must cut that the rule flips with the reduction order of the machine and states a subspace one dimension too wide. Step 4 of orthogonal_factor_span still reads a tolerance, because the rank of the loadings is a property of the data and not of a projector.
  3. Orthonormalise A * V₊ with a reduced LinearAlgebra.qr, giving G, and read the dimension r of the Orthogonal Subspace off its columns.
  4. When r is 0, return the set with a radius of zero and a map of one zero column. The map keeps a column because the type admits a rank-zero map and a consumer that reads a size finds one either way, and the zero radius leaves the nominal mean.
  5. Otherwise take the scaling $\mathbf{\Lambda}$ through orthogonal_scaling, form L = G * sqrt(Λ) with a symmetric square root, and size the radius with k_norm_ball at r degrees of freedom.
  6. Carry pr.mu into val, so a set fitted on one prior and handed to another optimisation carries the centre its geometry was calibrated on.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the span came from.
  • rr: Loadings block the span came from.
  • w_sqrt: Element-wise square root of the metric, or nothing.
  • Q: Orthonormal basis of the weighted factor span.

Returns

  • ucs::NormBallUncertaintySet: Mean set on the Orthogonal Subspace, of order 2.

Related

source
PortfolioOptimisers.orthogonal_sigma_setFunction
orthogonal_sigma_set(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult,
    rr::AbstractLoadingsRegressionResult,
    w_sqrt::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    Q::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> 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})}}
orthogonal_sigma_set(
    ue::OrthogonalUncertaintySet,
    pr::AbstractPriorResult,
    rr::AbstractLoadingsRegressionResult,
    w_sqrt::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    Q::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    rd
) -> 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})}}

Builds the covariance CompactCovarianceUncertaintySet on the Orthogonal Subspace, from a span already taken.

Algorithm

  1. Take the element-wise inverse of the metric square root as the diagonal metric $\mathbf{C}$, or a vector of ones on IdentityMetric.
  2. Hand the weighted factor span Q to the set as the basis it spares. A rank of zero leaves a basis with no column, which the type admits and which leaves the penalty on every direction.
  3. Settle the radius with k_compact and carry pr.sigma as the nominal covariance. A stated number passes through unchanged; a rule of AbstractCompactRadiusAlgorithm is handed the confidence level, the metric, the prior result, the loadings block, C and Q, which is everything a radius of this set can be sized from.

The set spares the span and penalises its complement, which is the same subspace the mean set lives in. The two axes are therefore built from one decomposition, and the estimator computes it once.

Arguments

  • ue: Orthogonal uncertainty set estimator.
  • pr: Prior result the span came from.
  • rr: Loadings block the span came from.
  • w_sqrt: Element-wise square root of the metric, or nothing.
  • Q: Orthonormal basis of the weighted factor span.
  • rd: Returns data the set was fitted beside, or nothing. Only a VarianceFraction holding an optimiser reads it.

Returns

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

Related

source