Hierarchical Risk Parity

PortfolioOptimisers.HierarchicalRiskParityType
struct HierarchicalRiskParity{__T_opt, __T_r, __T_sca, __T_fb} <: ClusteringOptimisationEstimator

Allocates weights by recursively bisecting the dendrogram's leaf order and splitting each part's weight in inverse proportion to the risk of its two halves.

This is the Hierarchical Risk Parity algorithm. It clusters the assets, orders the leaves, then walks that order down to the individual assets. It solves no optimisation problem, so it accepts a risk measure that is not convex.

Mathematical definition

Every asset starts at $w_i = 1$. The algorithm splits the dendrogram's leaf order in half, then splits each half in half, until every part holds one asset. At each split it divides the part's weight between the two halves $C_1$ and $C_2$:

\[\begin{align} \tilde{w}_i(C) &= \frac{\rho(\{i\})^{-1}}{\sum_{j \in C} \rho(\{j\})^{-1}} \quad \forall\, i \in C\,,\\ \tilde{\rho}(C) &= \rho\left(\tilde{\boldsymbol{w}}(C)\right)\,,\\ \alpha &= \frac{\tilde{\rho}(C_2)}{\tilde{\rho}(C_1) + \tilde{\rho}(C_2)}\,,\\ \boldsymbol{w}_{C_1} &\leftarrow \alpha \, \boldsymbol{w}_{C_1}\,,\\ \boldsymbol{w}_{C_2} &\leftarrow (1 - \alpha) \, \boldsymbol{w}_{C_2}\,. \end{align}\]

Where:

  • $\rho$: Risk measure r, resolved by factory against the prior.
  • $\rho(\{i\})$: Risk of asset $i$ held alone, from unitary_expected_risks.
  • $\tilde{\boldsymbol{w}}(C)$: Naive risk parity weights inside part $C$, zero outside it.
  • $\tilde{\rho}(C)$: Risk of that naive risk parity sub-portfolio.
  • $\alpha$: Fraction of the part's weight that goes to $C_1$.
  • $C_1$, $C_2$: The two halves of the part being split, in leaf order.

Two steps follow the recursion. The split factor $\alpha$ is first clamped so that neither half can leave the resolved weight bounds, see split_factor_weight_constraints. The final vector is then normalised to sum to one and passed to the weight finaliser wf. When r is a vector, hrp_scalarised_risk combines the measures with sca before the split factor is formed.

Fields

  • opt: Base hierarchical optimiser configuration.
  • r: Risk measure or vector of risk measures.
  • sca: Scalariser for combining multiple risk measures.
  • fb: Fallback result or estimator.

Constructors

HierarchicalRiskParity(;    opt::HierarchicalOptimiser = HierarchicalOptimiser(),    r::TD{<:OptRM_VecOptRM} = Variance(),    sca::TD{<:Scalariser} = SumScalariser(),    fb::TDO_Option{<:OptE_Opt} = nothing) -> HierarchicalRiskParity

Keywords correspond to the struct's fields. Fields typed TD or TDO_Option may hold a TimeDependent per-fold schedule instead of a static value: the risk measure, scalariser and fallback are problem definition, so a cross-validation fold loop resolves them per fold, and a fold-less optimise runs with each at its static default (nothing for fb).

Validation

  • If r is a vector: !isempty(r).
  • fb schedules: bind !== :nearest.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • opt: Recursively updated via factory.
  • r: Recursively updated via factory.
  • fb: Recursively updated via factory.

View parameters

HierarchicalRiskParity defines its own port_opt_view method rather than deriving one from field tags.

  • The method reads the returns matrix X as its third argument. When opt.pe already holds a prior result, the method replaces X with opt.pe.X, so the children are viewed against the prior's own observations rather than the caller's matrix.
  • r recurses through port_opt_view with that matrix. opt recurses with the index alone, because a HierarchicalOptimiser holds no risk measure and therefore needs no returns.
  • sca and fb are carried through unchanged.

Examples

julia> HierarchicalRiskParity()HierarchicalRiskParity  opt ┼ HierarchicalOptimiser      │       pe ┼ EmpiricalPrior      │          │           ce ┼ PortfolioOptimisersCovariance      │          │              │   ce ┼ Covariance      │          │              │      │    me ┼ SimpleExpectedReturns      │          │              │      │       │   w ┴ nothing      │          │              │      │    ce ┼ GeneralCovariance      │          │              │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)      │          │              │      │       │    w ┴ nothing      │          │              │      │   alg ┼ FullMoment()      │          │              │      │     w ┴ nothing      │          │              │   mp ┼ MatrixProcessing      │          │              │      │     pdm ┼ Posdef      │          │              │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton      │          │              │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()      │          │              │      │      dn ┼ nothing      │          │              │      │      dt ┼ nothing      │          │              │      │     alg ┼ nothing      │          │              │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)      │          │           me ┼ SimpleExpectedReturns      │          │              │   w ┴ nothing      │          │      horizon ┼ nothing      │          │   fill_limit ┴ nothing      │      cle ┼ ClustersEstimator      │          │    ce ┼ PortfolioOptimisersCovariance      │          │       │   ce ┼ Covariance      │          │       │      │    me ┼ SimpleExpectedReturns      │          │       │      │       │   w ┴ nothing      │          │       │      │    ce ┼ GeneralCovariance      │          │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)      │          │       │      │       │    w ┴ nothing      │          │       │      │   alg ┼ FullMoment()      │          │       │      │     w ┴ nothing      │          │       │   mp ┼ MatrixProcessing      │          │       │      │     pdm ┼ Posdef      │          │       │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton      │          │       │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()      │          │       │      │      dn ┼ nothing      │          │       │      │      dt ┼ nothing      │          │       │      │     alg ┼ nothing      │          │       │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)      │          │    de ┼ Distance      │          │       │   power ┼ nothing      │          │       │     alg ┴ CanonicalDistance()      │          │   alg ┼ HClustAlgorithm      │          │       │   linkage ┴ Symbol: :ward      │          │   onc ┼ OptimalNumberClusters      │          │       │   max_k ┼ nothing      │          │       │     alg ┼ SecondOrderDifference      │          │       │         │   alg ┼ StandardisedValue      │          │       │         │       │   mv ┼ MeanValue      │          │       │         │       │      │   w ┴ nothing      │          │       │         │       │   sv ┼ StdValue      │          │       │         │       │      │           w ┼ nothing      │          │       │         │       │      │   corrected ┴ Bool: true      │      slv ┼ nothing      │       wb ┼ WeightBounds      │          │   lb ┼ Float64: 0.0      │          │   ub ┴ Float64: 1.0      │     fees ┼ nothing      │     sets ┼ nothing      │       wf ┼ IterativeWeightFinaliser      │          │   iter ┴ Int64: 100      │      brt ┼ Bool: false      │    x_src ┼ Symbol: :prior      │   strict ┴ Bool: false    r ┼ Variance      │   settings ┼ RiskMeasureSettings      │            │   scale ┼ Float64: 1.0      │            │      ub ┼ nothing      │            │     rke ┴ Bool: true      │      sigma ┼ nothing      │       chol ┼ nothing      │         rc ┼ nothing      │        alg ┴ SquaredSOCRiskExpr()  sca ┼ SumScalariser()   fb ┴ nothing

Related

References

  • [120] M. López de Prado. Building diversified portfolios that outperform out of sample. The Journal of Portfolio Management 42, 59–69 (2016).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 12.1.
source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    hrp::HierarchicalRiskParity,
    i,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    args...
) -> HierarchicalRiskParity{HierarchicalOptimiser{__T_pe, __T_cle, __T_slv, __T_wb, __T_fees, __T_sets, __T_wf, __T_brt, __T_x_src, __T_strict, __T_cache}} where {__T_pe, __T_cle, __T_slv, __T_wb, __T_fees, __T_sets, __T_wf, __T_brt, __T_x_src, __T_strict, __T_cache}

Return a view of HierarchicalRiskParity hrp sliced to asset indices i.

Related

source
PortfolioOptimisers.optimiseMethod
optimise(hrp::HierarchicalRiskParity{<:Any, <:Any, <:Any, <:Nothing},
         rd::ReturnsResult; dims::Int = 1, kwargs...) -> HierarchicalRiskParityResult

Run the Hierarchical Risk Parity portfolio optimisation.

Arguments

  • hrp: The hierarchical risk parity optimiser to use.
  • rd: The returns result to use. If isa(hrp.opt.pe, AbstractPriorResult), rd is not necessary if doing a standalone optimisation, but may be required/desired by fallbacks and/or clusterisation.
  • dims: The dimension along which observations advance in time.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Details

Unlike HierarchicalEqualRiskContribution and NestedClustered, this optimiser accepts no branchorder keyword. Recursive bisection allocates by splitting the dendrogram's leaf permutation, so that permutation is the algorithm's input rather than a presentation detail, and the clusterisation always runs with the optimal ordering. A branchorder passed here is absorbed by kwargs and ignored.

Validation

  • No field in the tree of hrp holds an Online. An ArgumentError naming the field is thrown otherwise, through assert_batch_entry: a plain optimise is a batch fit, and a wrapper resolves only at the warm-up of the fold loop's online arm.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[120]
M. López de Prado. Building diversified portfolios that outperform out of sample. The Journal of Portfolio Management 42, 59–69 (2016).