Skip to content
18

Hierarchical Equal Risk Contribution

PortfolioOptimisers.HierarchicalEqualRiskContribution Type
julia
struct HierarchicalEqualRiskContribution{__T_opt, __T_ri, __T_ro, __T_scai, __T_scao, __T_ex, __T_fb} <: ClusteringOptimisationEstimator

Hierarchical Equal Risk Contribution (HERC) portfolio optimiser.

HierarchicalEqualRiskContribution implements the Hierarchical Equal Risk Contribution algorithm. It clusters assets, then allocates weights so that each cluster contributes equally to total portfolio risk (using ro), and within each cluster, assets are weighted by inverse intra-cluster risk (using ri).

Fields

  • opt: Base hierarchical optimiser configuration.

  • ri: Inner risk measure.

  • ro: Outer risk measure.

  • scai: Inner scalariser.

  • scao: Outer scalariser.

  • ex: Parallel execution strategy.

  • fb: Fallback result or estimator.

Constructors

julia
HierarchicalEqualRiskContribution(;
    opt::HierarchicalOptimiser = HierarchicalOptimiser(),
    ri::TD{<:OptRM_VecOptRM} = Variance(),
    ro::TD{<:OptRM_VecOptRM} = ri,
    scai::TD{<:Scalariser} = SumScalariser(),
    scao::TD{<:Scalariser} = scai,
    ex::FLoops.Transducers.Executor = FLoops.ThreadedEx(),
    fb::TDO_Option{<:OptE_Opt} = nothing
) -> HierarchicalEqualRiskContribution

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 inner/outer risk measures, their scalarisers and the 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). The executor ex is execution control and stays static.

Validation

  • If ri or ro is a vector: !isempty(ri) / !isempty(ro).

  • 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.

  • ri: Recursively updated via factory.

  • ro: Recursively updated via factory.

  • fb: Recursively updated via factory.

Examples

julia
julia> HierarchicalEqualRiskContribution()
HierarchicalEqualRiskContribution
   opt ┼ HierarchicalOptimiser
       │       pe ┼ EmpiricalPrior
       │          │        ce ┼ PortfolioOptimisersCovariance
       │          │           │   ce ┼ Covariance
       │          │           │      │    me ┼ SimpleExpectedReturns
       │          │           │      │       │   w ┴ nothing
       │          │           │      │    ce ┼ GeneralCovariance
       │          │           │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)
       │          │           │      │       │    w ┴ nothing
       │          │           │      │   alg ┴ FullMoment()
       │          │           │   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
       │      cle ┼ ClustersEstimator
       │          │    ce ┼ PortfolioOptimisersCovariance
       │          │       │   ce ┼ Covariance
       │          │       │      │    me ┼ SimpleExpectedReturns
       │          │       │      │       │   w ┴ nothing
       │          │       │      │    ce ┼ GeneralCovariance
       │          │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)
       │          │       │      │       │    w ┴ nothing
       │          │       │      │   alg ┴ FullMoment()
       │          │       │   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
       │   cle_pr ┼ Bool: true
       │   strict ┴ Bool: false
    ri ┼ Variance
       │   settings ┼ RiskMeasureSettings
       │            │   scale ┼ Float64: 1.0
       │            │      ub ┼ nothing
       │            │     rke ┴ Bool: true
       │      sigma ┼ nothing
       │       chol ┼ nothing
       │         rc ┼ nothing
       │        alg ┴ SquaredSOCRiskExpr()
    ro ┼ Variance
       │   settings ┼ RiskMeasureSettings
       │            │   scale ┼ Float64: 1.0
       │            │      ub ┼ nothing
       │            │     rke ┴ Bool: true
       │      sigma ┼ nothing
       │       chol ┼ nothing
       │         rc ┼ nothing
       │        alg ┴ SquaredSOCRiskExpr()
  scai ┼ SumScalariser()
  scao ┼ SumScalariser()
    ex ┼ Transducers.ThreadedEx{@NamedTuple{}}: Transducers.ThreadedEx()
    fb ┴ nothing

Mathematical definition

Let K be the number of clusters. The inter-cluster (outer) step assigns equal risk contribution across all clusters using risk measure ρo:

wCk=ρ~o(Ck)1j=1Kρ~o(Cj)1.

Within each cluster Ck, the intra-cluster (inner) step assigns weights proportional to inverse intra-cluster risk ρi:

wiρ~i({i})1,iCk,iCkwi=wCk.

Where:

  • wCk: Weight allocated to cluster Ck.

  • ρ~o(Ck): Outer (inter-cluster) risk of cluster Ck.

  • ρ~i({i}): Inner (intra-cluster) risk of asset i.

  • K: Number of clusters.

  • wi: Final weight of asset i.

  • ρ~: Quasi-diagonal cluster portfolio risk.

Related

source
PortfolioOptimisers.herc_td_defaults Function
julia
herc_td_defaults(

) -> @NamedTuple{ri::Variance{RiskMeasureSettings{Float64, Nothing, Bool}, Nothing, Nothing, Nothing, SquaredSOCRiskExpr}, ro::Variance{RiskMeasureSettings{Float64, Nothing, Bool}, Nothing, Nothing, Nothing, SquaredSOCRiskExpr}, scai::SumScalariser, scao::SumScalariser}

Return the static defaults of the HierarchicalEqualRiskContribution 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
PortfolioOptimisers.needs_previous_weights Method
julia
needs_previous_weights(
    opt::HierarchicalEqualRiskContribution
) -> Any

Return whether the HierarchicalEqualRiskContribution requires previous portfolio weights.

Returns true if any of the base optimiser, inner/outer risk measures, or fallback require previous weights.

Related

source
PortfolioOptimisers.factory Method
julia
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

Arguments

  • a: Indicates no object should be constructed.

  • args...: Arbitrary positional arguments (ignored).

  • kwargs...: Arbitrary keyword arguments (ignored).

Returns

  • a: The input unchanged.

Examples

julia
julia> factory(nothing, 1, 2; x = 3)

julia> factory(MeanValue())
MeanValue
  w ┴ nothing

Related

source
julia
factory(
    opt::Union{NonFiniteAllocationOptimisationEstimator, NonFiniteAllocationOptimisationResult},
    _
) -> SubsetResamplingResult

Return opt unchanged.

Default pass-through factory for optimisation estimators and results. Overridden for estimators that carry parameters requiring update at each optimisation step.

Related

source
PortfolioOptimisers.port_opt_view Method
julia
port_opt_view(
    hec::HierarchicalEqualRiskContribution,
    i,
    X::AbstractMatrix{<:Union{var"#s29", var"#s28"} where {var"#s29"<:Number, var"#s28"<:AbstractJuMPScalar}},
    args...
) -> HierarchicalEqualRiskContribution{HierarchicalOptimiser{__T_pe, __T_cle, __T_slv, __T_wb, __T_fees, __T_sets, __T_wf, __T_brt, __T_cle_pr, __T_strict}, _A, _B, _C, _D, <:Transducers.Executor} where {__T_pe, __T_cle, __T_slv, __T_wb, __T_fees, __T_sets, __T_wf, __T_brt, __T_cle_pr, __T_strict, _A, _B, _C, _D}

Return a view of HierarchicalEqualRiskContribution hec sliced to asset indices i.

Related

source
PortfolioOptimisers.herc_scalarised_risk_o! Function
julia
herc_scalarised_risk_o!(scalariser, wk, roku, rkbo, cl, ros, X, fees)

Compute and accumulate the scalarised outer (inter-cluster) HERC risk in-place.

Updates rkbo with inverse-risk weights for cluster cl and accumulates the scaled risk contribution using the given scalariser strategy.

Arguments

  • scalariser: Scalarisation strategy (SumScalariser, MaxScalariser, MinScalariser, or LogSumExpScalariser).

  • wk: Cluster weight vector.

  • roku: Unitary outer risk vector or matrix.

  • rkbo: Outer risk buffer vector (modified in-place).

  • cl: Cluster asset indices.

  • ros: Vector of outer risk measures.

  • X: Return matrix.

  • fees: Optional fees.

Returns

  • Scalarised outer cluster risk scalar.

Related

source
PortfolioOptimisers.herc_scalarised_risk_i! Function
julia
herc_scalarised_risk_i!(scalariser, wk, riku, cl, ris, X, fees)

Compute the scalarised inner (intra-cluster) HERC risk for cluster cl.

Aggregates inner risk measures across the assets in cluster cl using the given scalariser, returning the per-asset risk vector used for intra-cluster weight allocation.

Arguments

Returns

  • Per-asset inner risk vector for assets in cl.

Related

source
PortfolioOptimisers.herc_unitary_risks_i! Function
julia
herc_unitary_risks_i!(wk, rku, i, r, X, fees)

Return the unitary risk vector for inner measure i of a HERC scalarised risk computation.

When rku is a vector it is a shared buffer and the unitary risks for measure r are recomputed into it. When rku is a matrix, the unitary risks are computed into column i, caching them per measure for reuse by herc_scalarised_risk_o! when the inner and outer measures are identical.

Related

source
PortfolioOptimisers.herc_unitary_risks_o! Function
julia
herc_unitary_risks_o!(wk, rku, i, r, X, fees)

Return the unitary risk vector for outer measure i of a HERC scalarised risk computation.

When rku is a vector it is a shared buffer and the unitary risks for measure r are recomputed into it. When rku is a matrix, column i already caches measure i's unitary risks (filled by herc_scalarised_risk_i! when the inner and outer measures are identical) and is returned as-is.

Related

source
PortfolioOptimisers.herc_risk Function
julia
herc_risk(hec, pr, cls)

Compute per-cluster risk contributions for HERC weight allocation.

Evaluates the inner and outer risk measures for all clusters in cls, returning the risk arrays needed to allocate intra- and inter-cluster weights.

Arguments

  • hec: HierarchicalEqualRiskContribution optimiser instance.

  • pr: Prior result containing asset moments and return data.

  • cls: Vector of vectors of asset indices per cluster.

Returns

  • (riku, roku): Inner and outer per-asset risk arrays.

Related

source
PortfolioOptimisers.optimise Function
julia
optimise(hec::HierarchicalEqualRiskContribution{
                 <:Any, <:Any, <:Any, <:Any, <:Any, <:Any, Nothing
             },
        rd::ReturnsResult = ReturnsResult(); dims::Int = 1,
        branchorder::Symbol = :optimal, kwargs...) -> HierarchicalResult

Run the Hierarchical Equal Risk Contribution portfolio optimisation.

Arguments

  • hec: The hierarchical equal risk contribution optimiser to use.

  • rd: The returns result to use. If isa(hec.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.

  • branchorder: The branch order to use for the clusterisation, this optimisation can use non-optimal branch orders, which make the clustering faster but the dendrogram won't be as nice.

  • kwargs: Additional keyword arguments passed to the optimisation function.

Related

source