Schur Complement Hierarchical Risk Parity: private API

PortfolioOptimisers.naive_portfolio_riskMethod
naive_portfolio_risk(r::Sd_Var, sigma::MatNum) -> Number

Compute the risk of the naive risk parity portfolio a covariance matrix implies.

The weights are the inverse diagonal of sigma, normalised to sum to one, for both risk measures. The measure changes only what is done with the quadratic form. sigma here is an augmented block, so its diagonal is not a plain asset variance.

Arguments

Returns

  • risk::Number: The portfolio risk under r.

Details

  • The weights are inverse variance, not inverse volatility, under both measures. This matches the naive risk parity allocation of HierarchicalRiskParity with a Variance measure, and is why gamma = 0 reproduces that optimiser exactly.

Related

source
PortfolioOptimisers.symmetric_step_up_matrixMethod
symmetric_step_up_matrix(n1::Integer, n2::Integer) -> AbstractMatrix

Build the matrix that carries a Schur complement between two halves of nearly equal size.

The augmentation subtracts a term shaped by the other half, so the result has to come back to the size of the half being augmented. This matrix is that map. It is the identity when the halves are equal, and it averages over every position an extra uniform row can take when one half is longer by one.

Arguments

  • n1: Size of the half being augmented, and the number of rows.
  • n2: Size of the other half, and the number of columns.

Validation

  • abs(n1 - n2) <= 1. A bisection produces halves that differ by at most one, so no other shape can reach this method.

Returns

  • m::AbstractMatrix: An n1 by n2 matrix. It is LinearAlgebra.I(n1) when n1 == n2.

Details

  • The n1 < n2 case is the transpose of the n1 > n2 case, scaled by n1 / n2.
  • Every row of the n1 > n2 matrix sums to one.

Related

source
PortfolioOptimisers.schur_augmentationMethod
schur_augmentation(A::MatNum, B::MatNum, C::MatNum, gamma::Number) -> MatNum

Augment one half's covariance block with the information in the cross block.

This is the $\hat{\mathbf{\Sigma}}_{11}$ of SchurComplementHierarchicalRiskParity's mathematical definition. It scales the Schur complement of C by gamma, carries the result back to the size of A with a symmetric_step_up_matrix, and symmetrises.

Arguments

  • A: Covariance block of the half being augmented.
  • B: Cross-covariance between the two halves, with A's assets along the rows.
  • C: Covariance block of the other half.
  • gamma: Interpolation parameter in [0, 1].

Returns

  • A_aug::MatNum: The augmented block, of the same size as A, and symmetric.

Details

  • A is returned untouched when gamma is zero, or when either half holds one asset. A one-asset half leaves the step-up matrix nothing to average over.
  • The result is not guaranteed positive definite. schur_complement_weights decides what to do about that, from SchurComplementParams.flag.

Related

source
PortfolioOptimisers.assert_schur_weightsMethod
assert_schur_weights(
    w::Union{Nothing, AbstractVector{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}},
    gamma::Number
)

Assert that schur_complement_weights produced a weight vector.

A SchurComplementParams with flag = false does not repair an augmented block that is not positive definite. It abandons the allocation and returns nothing instead, which is what the monotonic search needs. A caller that keeps the weights needs the reason in words.

Arguments

Validation

  • !isnothing(w).

Returns

  • nothing.

Related

source
PortfolioOptimisers.schur_complement_binary_searchMethod
schur_complement_binary_search(objective::Function, lgamma::Number, hgamma::Number,
                               lrisk::Number, tol::Number = 1e-4,
                               iter::Option{<:Integer} = nothing,
                               strict::Bool = false) -> Tuple

Bisect a bracket that holds the variance-minimising $\gamma$.

MonotonicSchurComplement's coarse scan hands over a bracket in which the portfolio variance stops falling. This method halves that bracket until it is narrower than tol, keeping the midpoint only when its variance beats both the current lower end and the point one tol below it.

Arguments

  • objective: Takes a $\gamma$ and returns (w, risk). risk is typemax when the allocation fails.
  • lgamma: Lower end of the bracket, and the incumbent.
  • hgamma: Upper end of the bracket.
  • lrisk: The variance already measured at lgamma. It is the value a midpoint must beat.
  • tol: Width at which the bracket is narrow enough, and the step used for the one-sided derivative test.
  • iter: Iteration budget. nothing derives one from the bracket and tol.
  • strict: Whether a bracket that never narrows to tol raises rather than warns.

Returns

  • (w, gamma)::Tuple: The weight vector of the best $\gamma$ seen, and that $\gamma$.

Details

  • The returned w is the weight vector of the last midpoint evaluated, which is the incumbent's whenever the loop ends by narrowing the bracket.
  • Failing to narrow within iter is reported through strict_diagnostic, so the search returns its incumbent rather than failing.

Related

source
PortfolioOptimisers.schur_complement_weightsFunction
schur_complement_weights(pr::AbstractPriorResult, items::VecVecInt,
                         wb::WeightBounds, params::SchurComplementParams,
                         gamma::Option{<:Number} = nothing) -> Tuple

Run the Schur complement recursion at one value of $\gamma$.

Splits items in half repeatedly, augments each half's covariance block with schur_augmentation, and divides the weight between the halves in inverse proportion to their augmented risks. This method takes a NonMonotonicSchurComplement; the MonotonicSchurComplement method searches over $\gamma$ and delegates here.

Arguments

  • pr: Prior result. Its covariance seeds the recursion and its X sizes the weight vector.
  • items: The leaf orders to split. The recursion starts from one entry, the whole dendrogram order.
  • wb: Resolved weight bounds, which clamp each split factor.
  • params: The bundle's parameters. gamma, pdm, flag and r are all read here.
  • gamma: A value that overrides params.gamma, or nothing to use the field. The monotonic search passes each candidate this way.

Returns

  • (w, gamma, r)::Tuple: The unnormalised weight vector, the $\gamma$ the recursion ran at, and the risk measure resolved by factory. w is nothing when params.flag is false and an augmented block was not positive definite.

Details

  • The running covariance matrix is updated in place: each augmented block is written back, so a later split sees the earlier augmentation. A half holding one asset is left unaugmented.
  • With params.flag true, params.pdm repairs an augmented block that is not positive definite, and a failed repair raises an ArgumentError naming gamma.
  • With params.flag false, no repair happens and the whole allocation is abandoned. The monotonic search wants that; assert_schur_weights turns it into a message for anyone else.

Related

source
PortfolioOptimisers.schur_complement_weightsMethod
schur_complement_weights(
    pr::AbstractPriorResult,
    items::AbstractVector{<:AbstractVector{<:Integer}},
    wb::WeightBounds,
    params::SchurComplementParams{<:Any, <:Any, <:Any, <:MonotonicSchurComplement}
) -> Tuple{Any, Any, Any}

Search $[0, \gamma]$ for the variance-minimising Schur complement parameter, then allocate at it.

Scans params.alg.N values across the range and stops at the first one whose portfolio variance is no lower than its predecessor. It then bisects the bracket around that turning point with schur_complement_binary_search. When the variance is still falling at the top of the range, that top value is used. Every evaluation delegates to the NonMonotonicSchurComplement method with the positive-definite repair off, so a $\gamma$ that fails scores an infinite variance.

Related

source
PortfolioOptimisers.schur_complement_hrp_td_defaultsFunction
schur_complement_hrp_td_defaults(

) -> @NamedTuple{params::SchurComplementParams{Variance{RiskMeasureSettings{Float64, Nothing, Bool}, Nothing, Nothing, Nothing, SquaredSOCRiskExpr}, Float64, Posdef{UnionAll, @NamedTuple{}}, MonotonicSchurComplement{Int64, Float64, Nothing, Bool}, Bool}}

Return the static defaults of the SchurComplementHierarchicalRiskParity fields that may hold a TimeDependent.

Shared by the constructor's test-substitution pass and time_dependent_field_defaults, so the fold-less value of a field is declared once. Fields whose static default is nothing are omitted.

Related

source