Schur Complement Hierarchical Risk Parity: private API
PortfolioOptimisers.SchurComplementAlgorithm — Type
abstract type SchurComplementAlgorithm <: AbstractAlgorithmAbstract supertype for the algorithms that choose the Schur complement parameter $\gamma$.
A subtype decides what SchurComplementParams.gamma means: the value to use, or the upper end of a range to search.
Related
PortfolioOptimisers.Sd_Var — Type
const Sd_Var = Union{<:StandardDeviation, <:Variance}Alias for a standard deviation or variance risk measure.
Used in the Schur Complement HRP to accept either risk measure type for computing naive portfolio risk.
Related
PortfolioOptimisers.VecScP — Type
const VecScP = AbstractVector{<:SchurComplementParams}Alias for a vector of Schur complement parameters.
Represents a collection of SchurComplementParams objects, used when different cluster levels have different Schur complement configurations.
Related
PortfolioOptimisers.ScP_VecScP — Type
const ScP_VecScP = Union{<:SchurComplementParams, <:VecScP}Alias for a single or vector of Schur complement parameters.
Matches either a single SchurComplementParams or a vector of them (VecScP).
Related
PortfolioOptimisers.naive_portfolio_risk — Method
naive_portfolio_risk(r::Sd_Var, sigma::MatNum) -> NumberCompute 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
r: Risk measure.Variancereturns the quadratic form;StandardDeviationreturns its square root.sigma: Covariance matrix, usually the output ofschur_augmentation.
Returns
risk::Number: The portfolio risk underr.
Details
- The weights are inverse variance, not inverse volatility, under both measures. This matches the naive risk parity allocation of
HierarchicalRiskParitywith aVariancemeasure, and is whygamma = 0reproduces that optimiser exactly.
Related
PortfolioOptimisers.symmetric_step_up_matrix — Method
symmetric_step_up_matrix(n1::Integer, n2::Integer) -> AbstractMatrixBuild 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: Ann1byn2matrix. It isLinearAlgebra.I(n1)whenn1 == n2.
Details
- The
n1 < n2case is the transpose of then1 > n2case, scaled byn1 / n2. - Every row of the
n1 > n2matrix sums to one.
Related
PortfolioOptimisers.schur_augmentation — Method
schur_augmentation(A::MatNum, B::MatNum, C::MatNum, gamma::Number) -> MatNumAugment 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, withA'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 asA, and symmetric.
Details
Ais returned untouched whengammais 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_weightsdecides what to do about that, fromSchurComplementParams.flag.
Related
PortfolioOptimisers.assert_schur_weights — Method
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
w: Weight vector returned byschur_complement_weights, ornothing.gamma: The value ofgammathe allocation ran with.
Validation
!isnothing(w).
Returns
nothing.
Related
PortfolioOptimisers.schur_complement_binary_search — Method
schur_complement_binary_search(objective::Function, lgamma::Number, hgamma::Number,
lrisk::Number, tol::Number = 1e-4,
iter::Option{<:Integer} = nothing,
strict::Bool = false) -> TupleBisect 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).riskistypemaxwhen the allocation fails.lgamma: Lower end of the bracket, and the incumbent.hgamma: Upper end of the bracket.lrisk: The variance already measured atlgamma. 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.nothingderives one from the bracket andtol.strict: Whether a bracket that never narrows totolraises rather than warns.
Returns
(w, gamma)::Tuple: The weight vector of the best $\gamma$ seen, and that $\gamma$.
Details
- The returned
wis 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
iteris reported throughstrict_diagnostic, so the search returns its incumbent rather than failing.
Related
PortfolioOptimisers.schur_complement_weights — Function
schur_complement_weights(pr::AbstractPriorResult, items::VecVecInt,
wb::WeightBounds, params::SchurComplementParams,
gamma::Option{<:Number} = nothing) -> TupleRun 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 itsXsizes 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,flagandrare all read here.gamma: A value that overridesparams.gamma, ornothingto 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 byfactory.wisnothingwhenparams.flagisfalseand 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.flagtrue,params.pdmrepairs an augmented block that is not positive definite, and a failed repair raises anArgumentErrornaminggamma. - With
params.flagfalse, no repair happens and the whole allocation is abandoned. The monotonic search wants that;assert_schur_weightsturns it into a message for anyone else.
Related
PortfolioOptimisers.schur_complement_weights — Method
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
PortfolioOptimisers.schur_complement_hrp_td_defaults — Function
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