Skip to content
18

Nested Clustered

PortfolioOptimisers.NestedClusteredResult Type
julia
struct NestedClusteredResult{__T_pr, __T_clr, __T_wb, __T_fees, __T_resi, __T_reso, __T_cv, __T_retcode, __T_w, __T_fb} <: NonJuMPOptimisationResult

Result type for Nested Clustered Optimisation.

Fields

  • pr: Prior result.

  • clr: Clusters result.

  • wb: Weight bounds.

  • fees: Fees estimator or result.

  • resi: Inner optimisation results.

  • reso: Outer optimisation results.

  • cv: Cross-validation estimator.

  • retcode: Optimisation return code.

  • w: Final aggregated portfolio weights.

  • fb: Fallback result or estimator.

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(res::NonFiniteAllocationOptimisationResult, fb::Option{<:OptE_Opt})

Rebuild a continuous optimisation result with an updated fallback optimiser fb.

Every optimisation result carries fb as its last field, so the generic rebuild copies all fields unchanged except the trailing fb. Concrete result types may override this method when rebuilding requires more than swapping fb.

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.NestedClustered Type
julia
struct NestedClustered{__T_pe, __T_cle, __T_wb, __T_fees, __T_sets, __T_opti, __T_opto, __T_cv, __T_wf, __T_ex, __T_fb, __T_brt, __T_cle_pr, __T_strict} <: ClusteringOptimisationEstimator

Nested Clustered Optimisation (NCO) portfolio optimiser.

NestedClustered implements the Nested Clustered Optimisation algorithm. It first clusters assets, then solves a within-cluster (inner) optimisation for each cluster independently, and finally solves an across-cluster (outer) optimisation to combine the cluster portfolios into a final portfolio.

Fields

  • pe: Prior estimator.

  • cle: Clusters estimator.

  • wb: Weight bounds estimator or weight bounds.

  • fees: Fees estimator.

  • sets: Sets used to map estimator values to features.

  • opti: Inner optimiser.

  • opto: Outer optimiser.

  • cv: Cross-validation estimator.

  • wf: Weight finaliser.

  • ex: Parallel execution strategy.

  • fb: Fallback result or estimator.

  • brt: Whether to use bootstrap returns.

  • cle_pr: Whether to pass the prior result to the clustering estimator.

  • strict: Whether to strictly enforce weight bounds.

Constructors

julia
NestedClustered(;
    pe::TD{<:PrE_Pr} = EmpiricalPrior(),
    cle::TD{<:ClE_Cl} = ClustersEstimator(),
    wb::TD_Option{<:WbE_Wb} = WeightBounds(),
    fees::TD_Option{<:FeesE_Fees} = nothing,
    sets::TD_Option{<:AssetSets} = nothing,
    opti::OptE_TD,
    opto::OptE_TD,
    cv::Option{<:OptimisationCrossValidation} = nothing,
    wf::TD{<:WeightFinaliser} = IterativeWeightFinaliser(),
    ex::FLoops.Transducers.Executor = FLoops.ThreadedEx(),
    fb::TDO_Option{<:OptE_Opt} = nothing,
    brt::Bool = false,
    cle_pr::Bool = true,
    strict::Bool = false
) -> NestedClustered

Keywords correspond to the struct's fields.

Time-dependent fields

pe, cle, wb, fees, sets, wf, opti, opto and fb may hold a TimeDependent per-fold schedule. opto and fb are bind = :outermost only — no inner fold loop consumes them. opti additionally admits bind = :nearest: the inner cross-validation is entered per cluster (cross_val_predict(opti, …; cols = cl)), so the field itself is the inner fold loop's entry point and a :nearest schedule is consumed there, per cluster. A :nearest opti schedule requires an explicit default and cv !== nothing at construction (see assert_nearest_optimiser_schedule) because the per-cluster optimise leg always resolves it fold-lessly to its default. cv itself stays static: it is the inner fold loop, not part of the per-fold problem definition, and the :nearest construction checks must be able to inspect it.

Schedule entries for opti/opto must be estimators, like the static fields: a vector schedule holding a precomputed result is rejected at construction by the entry substitution pass.

Validation

  • opto must pass assert_external_optimiser and assert_special_nco_requirements (schedules delegate to their entries and default).

  • If opti !== opto: opti must pass assert_internal_optimiser and assert_special_nco_requirements.

  • If cv is provided: opti must also pass assert_external_optimiser and assert_special_nco_requirements.

  • opto and fb schedules: bind !== :nearest. A bind = :nearest opti schedule: explicit default and cv !== nothing.

Mathematical definition

Let clusters C1,,CK partition the N assets. The NCO algorithm: 2. Inner: for each cluster k, solve wCk=opti(XCk) (sub-portfolio weights within Ck).

  1. Outer: form a T×K synthetic returns matrix from cluster portfolios and solve a=opto(Xcluster) (allocation across clusters).

  2. Combine: wi=akwCk,i for iCk.

Propagated parameters

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

  • fees: Recursively updated via factory.

  • opti: Recursively updated via factory.

  • opto: Recursively updated via factory.

  • fb: Recursively updated via factory.

Related

source
PortfolioOptimisers.nested_clustered_td_defaults Function
julia
nested_clustered_td_defaults(

) -> @NamedTuple{pe::EmpiricalPrior{PortfolioOptimisersCovariance{Covariance{SimpleExpectedReturns{Nothing}, GeneralCovariance{SimpleCovariance, Nothing}, FullMoment}, MatrixProcessing{Posdef{UnionAll, @NamedTuple{}}, Nothing, Nothing, Nothing, NTuple{4, Symbol}}}, SimpleExpectedReturns{Nothing}, Nothing}, cle::ClustersEstimator{PortfolioOptimisersCovariance{Covariance{SimpleExpectedReturns{Nothing}, GeneralCovariance{SimpleCovariance, Nothing}, FullMoment}, MatrixProcessing{Posdef{UnionAll, @NamedTuple{}}, Nothing, Nothing, Nothing, NTuple{4, Symbol}}}, Distance{Nothing, CanonicalDistance}, HClustAlgorithm{Symbol}, OptimalNumberClusters{Nothing, SecondOrderDifference{StandardisedValue{MeanValue{Nothing}, StdValue{Nothing, Bool}}}}}, opti::NoDefault, opto::NoDefault, wf::IterativeWeightFinaliser{Int64}}

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

Shared by the constructor's test-substitution pass and time_dependent_field_defaults. The optimiser-valued fields opti and opto are required and have no static default, so they are marked NoDefault: a schedule there must carry its own default to be usable outside a fold loop. pe, cle and wf reset to their keyword defaults; fields whose static default is nothing (wb, fees, sets, fb) are omitted.

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(
    nco::NestedClustered,
    i,
    X::AbstractMatrix{<:Union{var"#s29", var"#s28"} where {var"#s29"<:Number, var"#s28"<:AbstractJuMPScalar}},
    args...
) -> NestedClustered{_A, _B, _C, _D, _E, _F, _G, _H, _I, var"#s179", _J, Bool, Bool, Bool} where {_A, _B, _C, _D, _E, _F, _G, _H, _I, var"#s179"<:Transducers.Executor, _J}

Return a cluster-sliced copy of NestedClustered for asset index set i and returns matrix X.

source
PortfolioOptimisers.predict_outer_nco_estimator_returns Function
julia
predict_outer_nco_estimator_returns(
    nco::NestedClustered,
    rd::ReturnsResult,
    pr::AbstractPriorResult,
    fees::Option{<:Fees},
    wi::MatNum,
    resi::VecOpt,
    cls::VecVecInt
)

Predict outer portfolio returns for NestedClustered optimisation. Overload this using nco.cv for custom cross-validation prediction.

source
PortfolioOptimisers.optimise Method
julia
optimise(nco::NestedClustered{<:Any, <:Any, <:Any, <:Any, <:Any, <:Any, <:Any,
                  <:Any, <:Any, <:Any, Nothing
              }, rd::ReturnsResult;
         dims::Int = 1, branchorder::Symbol = :optimal, str_names::Bool = false,
         save::Bool = true, kwargs...) -> NestedClusteredResult

Run the Nested Clustered Optimisation portfolio optimisation.

Arguments

  • nco: The nested clustered optimiser to use.

  • rd: The returns result to use.

  • dims: The dimension along which observations advance in time.

  • branchorder: Passed to the inner and outer optimisers. If this optimiser uses hierarchical clustering, this applies to the clusterisation. The branch order to use for the clusterisation.

  • str_names: Passed to the inner and outer optimisers. Whether to use string names for the assets in the optimisation.

  • save: Passed to the inner and outer optimisers. Whether to save the JuMP model in the optimisation result.

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

Related

source
PortfolioOptimisers.needs_previous_weights Method
julia
needs_previous_weights(opt::NestedClustered) -> Any

Return true if any sub-estimator of opt requires previous portfolio weights (fees, inner optimiser, outer optimiser, or fallback).

source
PortfolioOptimisers.is_time_dependent Method
julia
is_time_dependent(opt::NestedClustered) -> Any

Return true if the inner optimiser, outer optimiser, or fallback carries time-dependent constraints.

source
PortfolioOptimisers.reset_time_dependent_estimator Method
julia
reset_time_dependent_estimator(
    opt::NestedClustered
) -> NestedClustered

Replace this meta-optimiser's own time-dependent fields with their static defaults.

Deliberately does not recurse into the wrapped optimisers: a standalone meta solve consumes inner per-fold schedules through its inner cross-validation leg, and its fold-less full-window inner solves reset themselves at their own _optimise seam. Only the meta's own fields (applied to the combined weights, resolved by an outer fold loop when one exists) are inert here. A bind = :nearest schedule in a field the meta hands across its own inner fold loop (see inner_fold_fields) is likewise left in place — resetting it here would replace it with its default before the inner cross-validation ever saw it.

source
PortfolioOptimisers.assert_rc_pl Method
julia
assert_rc_pl(opt)

Assert that the optimiser does not use phylogeny risk contribution for NCO outer optimisation.

Checks that factor risk contribution optimisers do not use phylogeny-based constraints when used as the outer optimiser in NCO.

Arguments

  • opt: Optimisation estimator.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_external_optimiser Method
julia
assert_external_optimiser(opt)

Assert that the outer optimiser is valid for use in NCO.

Checks that the outer optimiser does not use pre-computed prior results, regression results, or unsupported variance/phylogeny risk contribution configurations.

Arguments

  • opt: Outer optimisation estimator.

Returns

  • nothing on success; throws ArgCheck error otherwise.

Related

source
PortfolioOptimisers.RiskBudgetingOptimiser Type
julia
const RiskBudgetingOptimiser = Union{<:RiskBudgeting, <:RelaxedRiskBudgeting}

Alias for risk budgeting JuMP optimisers.

Matches either RiskBudgeting or RelaxedRiskBudgeting. Used for dispatch in NCO validation and constraint generation.

Related

source
PortfolioOptimisers.assert_rc_variance Function
julia
assert_rc_variance(opt)

Assert that the optimiser does not use variance risk contribution for NCO outer optimisation.

Checks that risk budgeting-based JuMP optimisers do not use variance for risk contribution when used as the outer optimiser in NCO.

Arguments

  • opt: Optimisation estimator.

Returns

  • nothing.

Related

source
PortfolioOptimisers._update_asset_sets Function
julia
_update_asset_sets(
    nco::NestedClustered,
    rdo::ReturnsResult
) -> Any

Align the outer optimiser's asset sets with the synthetic universe produced by the inner optimisations.

The outer optimiser of a NestedClustered does not see the original assets. It sees one synthetic asset per cluster, whose names are carried by the outer returns result rdo. An outer optimiser configured with AssetSets built over the original universe would therefore resolve its constraints against the wrong names, so the sets are rebuilt over the cluster names before the outer solve.

Arguments

  • nco::NestedClustered: The nested clustered optimiser.

  • rdo::ReturnsResult: Outer returns result, whose nx holds the cluster names.

Returns

  • nco::NestedClustered: Instance with the outer optimiser's asset sets rebuilt over rdo.nx, or nco unchanged when it has no outer asset sets, or they already match.

Details

  • Handles both shapes of outer optimiser: one that nests its own optimiser (nco.opto.opt.sets) and one that carries the sets directly (nco.opto.sets).

  • The dictionary is copied before being reset, so the caller's AssetSets is not mutated.

Related

source