No Risk: private API
PortfolioOptimisers.norisk_flag — Function
norisk_flag(r) -> Bool
Return true when r is a NoRisk, or a collection holding one.
The quantifier is any, not all, and it is earned on the hierarchical path. HierarchicalRiskParity and HierarchicalEqualRiskContribution allocate by inverse risk, so a single NoRisk can drive a divisor to zero even with a real measure beside it. NoRisk's functor returns zero, so its unitary risks are all zero; HRP then forms alpha = 1 - lrisk / (lrisk + rrisk) and HERC forms inv.(risk) ./ sum(inv.(risk)).
Measured on r = [NoRisk(), Variance()] over a 200×4 return matrix:
| Scalariser | HRP alpha | HERC inner weights |
|---|---|---|
SumScalariser | finite | NaN |
MinScalariser | NaN | NaN |
MaxScalariser | finite | finite |
LogSumExpScalariser | finite, near 0.5 | NaN |
Only MaxScalariser escapes on both, being the one reduction that can discard the zero term. HERC fails under three of the four: inv(0) is Inf and Inf / Inf is NaN, and that NaN is produced inside the per-measure closure, so every scalariser that combines the terms propagates it. HRP fails only under MinScalariser, where the aggregate is itself zero and alpha becomes 0 / 0; under LogSumExpScalariser it stays finite but collapses towards 0.5, an uninformative split rather than an error.
settings.rke is inert on the hierarchical path, so a measure's type is the only signal available there. An all quantifier would admit every NaN row above.
This is not the right quantifier for the JuMP axis, where the question is whether the :risk expression is identically zero. See zero_risk_expression_flag.
Related
PortfolioOptimisers.zero_risk_expression_flag — Function
zero_risk_expression_flag(r) -> Bool
Return true when the model's :risk expression is identically zero.
The degeneracy guard tests the state of the expression, not the type of the term. Two routes reach that state, and this predicate is the disjunction of both:
- A
NoRiskmeasure is present. This isnorisk_flag, whose quantifier isany— earned on the hierarchical path, where oneNoRiskzeroes a divisor under three of the four scalarisers, and documented there. On this axisanyis wider than the state it names:[NoRisk(), Variance()]leaves:risknon-zero, yet the predicate returnstrue, so the pair is refused underMinimumRiskandMaximumRatiowith a cause string that overstates the case. The refusal is kept because the configuration is vestigial — aNoRiskbeside a real measure buys nothing, which is the opposite of whatNoRiskexists for — but it is a type test surviving inside a state test. - Every measure carries
settings.rke = false, soset_risk_expression!pushes nothing. The quantifier here isall, because one included measure leaves the expression non-zero.
The halves compose only because route 1's any subsumes the mixed case that a composed test would otherwise miss. The exact mirror of the return axis is all(isa NoRisk || !rke), which agrees with this predicate everywhere except [NoRisk(), Variance()], where the fused form is the accurate one. The return axis must fuse because it has no such subsuming half; see zero_return_expression_flag.
all on the state route keeps a constraint-only measure expressible, which is the risk side of the term set_return_bounds! supports on the return side:
r = [Variance(), Variance(; settings = RiskMeasureSettings(; rke = false, ub = u))]The second measure binds a ub without entering the objective, and this predicate does not refuse it.
Related
PortfolioOptimisers.zero_risk_expression_cause — Function
zero_risk_expression_cause(r) -> String
Name the route by which the risk expression became identically zero, for an error message.
Related
PortfolioOptimisers.assert_risk_measure_required — Function
assert_risk_measure_required(r, T::Symbol; flag)
Assert that r gives a non-zero risk expression, for optimisers built around one.
A zero risk expression is only coherent in MeanRisk, under an objective that never consults it. Every other risk-taking optimiser is its risk measure — a risk budget with nothing to budget, a risk contribution that is always zero, a clustering optimiser dividing by a zero risk — so they reject it rather than return a degenerate answer.
flag selects the predicate, because the two families of caller do not see the same routes:
- The JuMP optimisers pass
zero_risk_expression_flag, which covers aNoRiskmeasure and every measure carryingsettings.rke = false. HierarchicalRiskParityandHierarchicalEqualRiskContributionkeep the defaultnorisk_flag. They never reach the JuMP risk builders, sorkeis inert for them, and widening the predicate would refuse a configuration that solves correctly today.
T names the calling optimiser, for the error message. TimeDependent schedules are skipped here and reached instead through assert_time_dependent_substitution.
Related
PortfolioOptimisers.assert_no_risk_objective_compatibility — Function
assert_no_risk_objective_compatibility(r, obj)
Assert that a zero risk expression is paired with an objective that ignores risk.
Rejects a zero :risk under MinimumRisk — whose objective would be identically zero, so the solver could return any feasible portfolio, silently — and under MaximumRatio, whose risk-normalisation constraint would go vacuous and leave the model unbounded.
The criterion is zero_risk_expression_flag, so the guard covers both routes to a zero expression: a NoRisk measure, and every measure carrying settings.rke = false. The second route shipped unguarded since the inclusion flag was introduced.
Called from MeanRisk's constructor. TimeDependent schedules are skipped here and reached instead through assert_time_dependent_substitution, which re-runs the constructor on each scheduled entry.
Related