Skip to content
18

The source files can be found in examples/.

Meta-optimisers

Every optimiser so far produces weights by solving one problem. Meta-optimisers instead orchestrate other optimisers: they split the problem up, solve the pieces with whatever estimator you like, and recombine the results. They are the package's answer to two practical worries — estimation error (a single fit on all assets is fragile) and modularity (you may want different rules for different parts of the universe).

PortfolioOptimisers provides three, all sharing the same inner/outer composition idea:

  • NestedClustered (NCO) — cluster the assets, run an inner optimiser inside each cluster, then an outer optimiser across the cluster aggregates.

  • Stacking — run several inner optimisers on the full universe, then stack their portfolios together with an outer optimiser (an ensemble).

  • SubsetResampling — repeatedly optimise on random subsets of the assets and average the resampled weights, à la bagging.

Because the inner and outer slots accept any optimisation estimator (including other meta-optimisers), these compose arbitrarily.

When to reach for this

Reach for a meta-optimiser when a single global fit feels too fragile or too monolithic: NCO when you trust the cluster structure and want a different rule within vs across groups, Stacking when you want to hedge model risk by ensembling several optimisers, and SubsetResampling when you want bagging-style robustness against the specific asset set and estimation noise. If a single optimiser already does what you need, prefer it — these add compute and configuration surface in exchange for robustness.

julia
using PortfolioOptimisers, PrettyTables, StableRNGs

resfmt = (v, i, j) -> begin
    if j == 1
        return v
    else
        return isa(v, Number) ? "$(round(v*100, digits=3)) %" : v
    end
end;

1. ReturnsResult data and shared ingredients

We use the same S&P 500 slice as the other optimiser examples, and precompute a prior, a clustering, and a solver to share across the meta-optimisers.

julia
using CSV, TimeSeries, DataFrames, Clarabel

X = TimeArray(CSV.File(joinpath(@__DIR__, "..", "SP500.csv.gz")); timestamp = :Date)[(end - 252):end]
rd = prices_to_returns(X)

slv = Solver(; name = :clarabel1, solver = Clarabel.Optimizer,
             settings = Dict("verbose" => false),
             check_sol = (; allow_local = true, allow_almost = true))
pr = prior(EmpiricalPrior(), rd)
clr = clusterise(ClustersEstimator(; alg = DBHT()), pr.X)
Clusters
  res ┼ Clustering.Hclust{Float64}([-1 -13; -7 -4; … ; 12 17; 10 18], [0.1, 0.1111111111111111, 0.125, 0.14285714285714285, 0.16666666666666666, 0.2, 0.25, 0.3333333333333333, 0.5, 1.0, 0.125, 0.14285714285714285, 0.16666666666666666, 0.2, 0.25, 0.3333333333333333, 0.5, 1.0, 2.0], [5, 20, 17, 3, 9, 6, 2, 1, 13, 7, 4, 19, 14, 10, 16, 18, 11, 8, 12, 15], :DBHT)
    S ┼ 20×20 Matrix{Float64}
    D ┼ 20×20 Matrix{Float64}
    P ┼ nothing
    k ┴ Int64: 4

A recurring pattern below illustrates the precomputed-result vs estimator distinction (see the MeanRisk objectives note). The inner optimiser is given the precomputed prior through its JuMPOptimiser (pe = pr) — fine, because the inner solves run on the real asset returns. The outer optimiser is deliberately not given a prior: it operates on the synthetic per-cluster (or stacked) returns the meta-optimiser builds internally, where a precomputed asset-level prior would be meaningless. The outer slot is an estimator-driven slot — it recomputes whatever statistics it needs from those synthetic returns at solve time, so here it only needs a solver. (Passing pe = pr to the outer optimiser would silently feed it the wrong, asset-level prior.)

julia
jopti = JuMPOptimiser(; pe = pr, slv = slv)
jopto = JuMPOptimiser(; slv = slv)
JuMPOptimiser
       pe ┼ EmpiricalPrior
          │        ce ┼ PortfolioOptimisersCovariance
          │           │   ce ┼ Covariance
          │           │      │    me ┼ SimpleExpectedReturns
          │           │      │       │   w ┴ nothing
          │           │      │    ce ┼ GeneralCovariance
          │           │      │       │   ce ┼ SimpleCovariance: 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
      slv ┼ Solver
          │          name ┼ Symbol: :clarabel1
          │        solver ┼ UnionAll: Clarabel.MOIwrapper.Optimizer
          │      settings ┼ Dict{String, Bool}: Dict{String, Bool}("verbose" => 0)
          │     check_sol ┼ @NamedTuple{allow_local::Bool, allow_almost::Bool}: (allow_local = true, allow_almost = true)
          │   add_bridges ┴ Bool: true
       wb ┼ WeightBounds
          │   lb ┼ Float64: 0.0
          │   ub ┴ Float64: 1.0
      bgt ┼ Float64: 1.0
     sbgt ┼ nothing
       lt ┼ nothing
       st ┼ nothing
     lcse ┼ nothing
      cte ┼ nothing
   gcarde ┼ nothing
  sgcarde ┼ nothing
     smtx ┼ nothing
    sgmtx ┼ nothing
      slt ┼ nothing
      sst ┼ nothing
     sglt ┼ nothing
     sgst ┼ nothing
       tn ┼ nothing
     fees ┼ nothing
     sets ┼ nothing
       tr ┼ nothing
      ple ┼ nothing
      ret ┼ ArithmeticReturn
          │   ucs ┼ nothing
          │    lb ┼ nothing
          │    mu ┴ nothing
      sca ┼ SumScalariser()
     ccnt ┼ nothing
     cobj ┼ nothing
       sc ┼ Int64: 1
       so ┼ Int64: 1
       ss ┼ nothing
     card ┼ nothing
    scard ┼ nothing
      wn2 ┼ nothing
      wnp ┼ nothing
    wninf ┼ nothing
       l1 ┼ nothing
       l2 ┼ nothing
     linf ┼ nothing
       lp ┼ nothing
      brt ┼ Bool: false
   cle_pr ┼ Bool: true
   strict ┴ Bool: false

For a reference point we also compute a plain minimum-variance MeanRisk over the whole universe.

julia
res_bench = optimise(MeanRisk(; obj = MinimumRisk(),
                              opt = JuMPOptimiser(; pe = pr, slv = slv)))
MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │      f_mu ┼ nothing
     │           │           │   f_sigma ┼ nothing
     │           │           │       f_w ┴ nothing
     │           │        wb ┼ WeightBounds
     │           │           │   lb ┼ 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
     │           │           │   ub ┴ 20-element StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
     │           │        lt ┼ nothing
     │           │        st ┼ nothing
     │           │      lcsr ┼ nothing
     │           │       ctr ┼ nothing
     │           │    gcardr ┼ nothing
     │           │   sgcardr ┼ nothing
     │           │      smtx ┼ nothing
     │           │     sgmtx ┼ nothing
     │           │       slt ┼ nothing
     │           │       sst ┼ nothing
     │           │      sglt ┼ nothing
     │           │      sgst ┼ nothing
     │           │        tn ┼ nothing
     │           │      fees ┼ nothing
     │           │       plr ┼ nothing
     │           │       ret ┼ ArithmeticReturn
     │           │           │   ucs ┼ nothing
     │           │           │    lb ┼ nothing
     │           │           │    mu ┴ 20-element Vector{Float64}
     │   retcode ┼ OptimisationSuccess
     │           │   res ┴ Dict{Any, Any}: Dict{Any, Any}()
     │       sol ┼ JuMPOptimisationSolution
     │           │   w ┴ 20-element Vector{Float64}
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 21
     │           │ ├ num_constraints: 4
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 1
     │           │ │ └ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub
  fb ┴ nothing

2. Nested clustered optimisation (NCO)

NCO solves a minimum-variance problem inside each cluster, collapses each cluster to a single synthetic asset, then solves a second minimum-variance problem across the clusters. The inner and outer optimisers are independent — here both are MeanRisk, but either could be a risk-budgeting, hierarchical, or naive estimator.

julia
res_nco = optimise(NestedClustered(; pe = pr, cle = clr,
                                   opti = MeanRisk(; obj = MinimumRisk(), opt = jopti),
                                   opto = MeanRisk(; obj = MinimumRisk(), opt = jopto)), rd)
NestedClusteredResult
       pr ┼ LowOrderPrior
          │         X ┼ 252×20 Matrix{Float64}
          │        mu ┼ 20-element Vector{Float64}
          │     sigma ┼ 20×20 Matrix{Float64}
          │      chol ┼ nothing
          │         w ┼ nothing
          │       ens ┼ nothing
          │       kld ┼ nothing
          │        ow ┼ nothing
          │        rr ┼ nothing
          │      f_mu ┼ nothing
          │   f_sigma ┼ nothing
          │       f_w ┴ nothing
      clr ┼ Clusters
          │   res ┼ Clustering.Hclust{Float64}([-1 -13; -7 -4; … ; 12 17; 10 18], [0.1, 0.1111111111111111, 0.125, 0.14285714285714285, 0.16666666666666666, 0.2, 0.25, 0.3333333333333333, 0.5, 1.0, 0.125, 0.14285714285714285, 0.16666666666666666, 0.2, 0.25, 0.3333333333333333, 0.5, 1.0, 2.0], [5, 20, 17, 3, 9, 6, 2, 1, 13, 7, 4, 19, 14, 10, 16, 18, 11, 8, 12, 15], :DBHT)
          │     S ┼ 20×20 Matrix{Float64}
          │     D ┼ 20×20 Matrix{Float64}
          │     P ┼ nothing
          │     k ┴ Int64: 4
       wb ┼ WeightBounds
          │   lb ┼ Vector{Float64}: [-Inf, -Inf, -Inf, -Inf]
          │   ub ┴ Vector{Float64}: [Inf, Inf, Inf, Inf]
     fees ┼ nothing
     resi ┼ 4-element Vector{MeanRiskResult}
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
     reso ┼ MeanRiskResult
          │   jr ┼ JuMPOptimisationResult
          │      │        pa ┼ ProcessedJuMPOptimiserAttributes
          │      │           │        pr ┼ LowOrderPrior
          │      │           │           │         X ┼ 252×4 Matrix{Float64}
          │      │           │           │        mu ┼ Vector{Float64}: [-0.000570395477285726, 0.0021114548119627085, 0.0005954569768867652, 0.0003887564267948336]
          │      │           │           │     sigma ┼ 4×4 Matrix{Float64}
          │      │           │           │      chol ┼ nothing
          │      │           │           │         w ┼ nothing
          │      │           │           │       ens ┼ nothing
          │      │           │           │       kld ┼ nothing
          │      │           │           │        ow ┼ nothing
          │      │           │           │        rr ┼ nothing
          │      │           │           │      f_mu ┼ nothing
          │      │           │           │   f_sigma ┼ nothing
          │      │           │           │       f_w ┴ nothing
          │      │           │        wb ┼ WeightBounds
          │      │           │           │   lb ┼ StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: StepRangeLen(0.0, 0.0, 4)
          │      │           │           │   ub ┴ StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: StepRangeLen(1.0, 0.0, 4)
          │      │           │        lt ┼ nothing
          │      │           │        st ┼ nothing
          │      │           │      lcsr ┼ nothing
          │      │           │       ctr ┼ nothing
          │      │           │    gcardr ┼ nothing
          │      │           │   sgcardr ┼ nothing
          │      │           │      smtx ┼ nothing
          │      │           │     sgmtx ┼ nothing
          │      │           │       slt ┼ nothing
          │      │           │       sst ┼ nothing
          │      │           │      sglt ┼ nothing
          │      │           │      sgst ┼ nothing
          │      │           │        tn ┼ nothing
          │      │           │      fees ┼ nothing
          │      │           │       plr ┼ nothing
          │      │           │       ret ┼ ArithmeticReturn
          │      │           │           │   ucs ┼ nothing
          │      │           │           │    lb ┼ nothing
          │      │           │           │    mu ┴ Vector{Float64}: [-0.000570395477285726, 0.0021114548119627085, 0.0005954569768867652, 0.0003887564267948336]
          │      │   retcode ┼ OptimisationSuccess
          │      │           │   res ┴ Dict{Any, Any}: Dict{Any, Any}()
          │      │       sol ┼ JuMPOptimisationSolution
          │      │           │   w ┴ Vector{Float64}: [7.224724138864844e-7, 0.13660786446523465, 0.6133269234160356, 0.25006448964631584]
          │      │     model ┼ A JuMP Model
          │      │           │ ├ solver: Clarabel
          │      │           │ ├ objective_sense: MIN_SENSE
          │      │           │ │ └ objective_function_type: QuadExpr
          │      │           │ ├ num_variables: 5
          │      │           │ ├ num_constraints: 4
          │      │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 1
          │      │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 1
          │      │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 1
          │      │           │ │ └ Vector{AffExpr} in MOI.SecondOrderCone: 1
          │      │           │ └ Names registered in the model
          │      │           │   └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub
          │   fb ┴ nothing
       cv ┼ nothing
  retcode ┼ OptimisationSuccess
          │   res ┴ nothing
        w ┼ 20-element Vector{Float64}
       fb ┴ nothing

3. Stacking

Stacking runs a list of inner optimisers on the full universe — here a min-variance MeanRisk, a HierarchicalRiskParity, and a naive InverseVolatility — then combines their portfolios with an outer optimiser. The result is an ensemble that hedges the model risk of any single rule.

julia
res_stk = optimise(Stacking(; pe = pr,
                            opti = [MeanRisk(; opt = jopti),
                                    HierarchicalRiskParity(;
                                                           opt = HierarchicalOptimiser(;
                                                                                       pe = pr)),
                                    InverseVolatility(; pe = pr)],
                            opto = MeanRisk(; obj = MinimumRisk(), opt = jopto)), rd)
StackingResult
       pr ┼ LowOrderPrior
          │         X ┼ 252×20 Matrix{Float64}
          │        mu ┼ 20-element Vector{Float64}
          │     sigma ┼ 20×20 Matrix{Float64}
          │      chol ┼ nothing
          │         w ┼ nothing
          │       ens ┼ nothing
          │       kld ┼ nothing
          │        ow ┼ nothing
          │        rr ┼ nothing
          │      f_mu ┼ nothing
          │   f_sigma ┼ nothing
          │       f_w ┴ nothing
       wb ┼ WeightBounds
          │   lb ┼ Vector{Float64}: [-Inf, -Inf, -Inf]
          │   ub ┴ Vector{Float64}: [Inf, Inf, Inf]
     fees ┼ nothing
     resi ┼ 3-element Vector{NonFiniteAllocationOptimisationResult}
          │ MeanRiskResult ⋯
          │ HierarchicalResult ⋯
          │ NaiveOptimisationResult ⋯
     reso ┼ MeanRiskResult
          │   jr ┼ JuMPOptimisationResult
          │      │        pa ┼ ProcessedJuMPOptimiserAttributes
          │      │           │        pr ┼ LowOrderPrior
          │      │           │           │         X ┼ 252×3 Matrix{Float64}
          │      │           │           │        mu ┼ Vector{Float64}: [0.0007720709986441823, 0.0003452652641646169, 0.00024546841241466886]
          │      │           │           │     sigma ┼ 3×3 Matrix{Float64}
          │      │           │           │      chol ┼ nothing
          │      │           │           │         w ┼ nothing
          │      │           │           │       ens ┼ nothing
          │      │           │           │       kld ┼ nothing
          │      │           │           │        ow ┼ nothing
          │      │           │           │        rr ┼ nothing
          │      │           │           │      f_mu ┼ nothing
          │      │           │           │   f_sigma ┼ nothing
          │      │           │           │       f_w ┴ nothing
          │      │           │        wb ┼ WeightBounds
          │      │           │           │   lb ┼ StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: StepRangeLen(0.0, 0.0, 3)
          │      │           │           │   ub ┴ StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: StepRangeLen(1.0, 0.0, 3)
          │      │           │        lt ┼ nothing
          │      │           │        st ┼ nothing
          │      │           │      lcsr ┼ nothing
          │      │           │       ctr ┼ nothing
          │      │           │    gcardr ┼ nothing
          │      │           │   sgcardr ┼ nothing
          │      │           │      smtx ┼ nothing
          │      │           │     sgmtx ┼ nothing
          │      │           │       slt ┼ nothing
          │      │           │       sst ┼ nothing
          │      │           │      sglt ┼ nothing
          │      │           │      sgst ┼ nothing
          │      │           │        tn ┼ nothing
          │      │           │      fees ┼ nothing
          │      │           │       plr ┼ nothing
          │      │           │       ret ┼ ArithmeticReturn
          │      │           │           │   ucs ┼ nothing
          │      │           │           │    lb ┼ nothing
          │      │           │           │    mu ┴ Vector{Float64}: [0.0007720709986441823, 0.0003452652641646169, 0.00024546841241466886]
          │      │   retcode ┼ OptimisationSuccess
          │      │           │   res ┴ Dict{Any, Any}: Dict{Any, Any}()
          │      │       sol ┼ JuMPOptimisationSolution
          │      │           │   w ┴ Vector{Float64}: [0.9999999015969727, 5.634352147282784e-8, 4.2059505865632995e-8]
          │      │     model ┼ A JuMP Model
          │      │           │ ├ solver: Clarabel
          │      │           │ ├ objective_sense: MIN_SENSE
          │      │           │ │ └ objective_function_type: QuadExpr
          │      │           │ ├ num_variables: 4
          │      │           │ ├ num_constraints: 4
          │      │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 1
          │      │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 1
          │      │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 1
          │      │           │ │ └ Vector{AffExpr} in MOI.SecondOrderCone: 1
          │      │           │ └ Names registered in the model
          │      │           │   └ :G, :bgt, :dev_1, :dev_1_soc, :k, :lw, :obj_expr, :ret, :risk, :risk_vec, :sc, :so, :variance_flag, :variance_risk_1, :w, :w_lb, :w_ub
          │   fb ┴ nothing
       cv ┼ nothing
  retcode ┼ OptimisationSuccess
          │   res ┴ nothing
        w ┼ 20-element Vector{Float64}
       fb ┴ nothing

4. Subset resampling

SubsetResampling draws repeated random subsets of the assets, optimises each one, and averages the resampled weights — bagging for portfolios. We draw 10 subsets of 70% of the assets with a fixed RNG/seed so the result is reproducible.

julia
res_ssr = optimise(SubsetResampling(; pe = pr,
                                    opt = MeanRisk(; obj = MinimumRisk(),
                                                   opt = JuMPOptimiser(; slv = slv)),
                                    subset_size = 0.7, n_subsets = 10, rng = StableRNG(123),
                                    seed = 42), rd)
SubsetResamplingResult
       pr ┼ LowOrderPrior
          │         X ┼ 252×20 Matrix{Float64}
          │        mu ┼ 20-element Vector{Float64}
          │     sigma ┼ 20×20 Matrix{Float64}
          │      chol ┼ nothing
          │         w ┼ nothing
          │       ens ┼ nothing
          │       kld ┼ nothing
          │        ow ┼ nothing
          │        rr ┼ nothing
          │      f_mu ┼ nothing
          │   f_sigma ┼ nothing
          │       f_w ┴ nothing
       wb ┼ WeightBounds
          │   lb ┼ 20-element Vector{Float64}
          │   ub ┴ 20-element Vector{Float64}
     fees ┼ nothing
     ress ┼ 10-element Vector{MeanRiskResult}
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
          │ MeanRiskResult ⋯
      idx ┼ 14×10 Matrix{Int64}
  retcode ┼ OptimisationSuccess
          │   res ┴ nothing
        w ┼ 20-element Vector{Float64}
       fb ┴ nothing

5. Comparing the allocations

All four portfolios target minimum variance, but reach it through very different machinery. NCO and Stacking tend to spread weight more than the plain fit, and SubsetResampling smooths it further by averaging over universes.

julia
pretty_table(DataFrame(; :assets => rd.nx, :MinVar => res_bench.w, :NCO => res_nco.w,
                       :Stacking => res_stk.w, :SubsetResampling => res_ssr.w);
             formatters = [resfmt])
┌────────┬──────────┬──────────┬──────────┬──────────────────┐
 assets    MinVar       NCO  Stacking  SubsetResampling 
 String   Float64   Float64   Float64           Float64 
├────────┼──────────┼──────────┼──────────┼──────────────────┤
│   AAPL │    0.0 % │    0.0 % │    0.0 % │            0.0 % │
│    AMD │    0.0 % │    0.0 % │    0.0 % │            0.0 % │
│    BAC │    0.0 % │    0.0 % │    0.0 % │            0.0 % │
│    BBY │    0.0 % │    0.0 % │    0.0 % │            0.0 % │
│    CVX │  7.432 % │ 10.376 % │  7.432 % │          6.786 % │
│     GE │  0.806 % │    0.0 % │  0.806 % │          0.856 % │
│     HD │    0.0 % │    0.0 % │    0.0 % │          0.677 % │
│    JNJ │ 36.974 % │ 31.364 % │ 36.974 % │         20.456 % │
│    JPM │  0.749 % │    0.0 % │  0.749 % │          1.915 % │
│     KO │ 11.161 % │ 10.028 % │ 11.161 % │          13.75 % │
│    LLY │    0.0 % │    0.0 % │    0.0 % │          0.735 % │
│    MRK │ 17.467 % │ 15.876 % │ 17.467 % │         19.192 % │
│   MSFT │    0.0 % │    0.0 % │    0.0 % │            0.0 % │
│    PEP │  8.978 % │ 10.115 % │  8.978 % │          10.81 % │
│    PFE │    0.0 % │    0.0 % │    0.0 % │           2.41 % │
│      ⋮ │        ⋮ │        ⋮ │        ⋮ │                ⋮ │
└────────┴──────────┴──────────┴──────────┴──────────────────┘
                                                5 rows omitted

6. Visualising the compositions

The stacked-bar composition makes the diversifying effect of the meta-optimisers visible against the plain minimum-variance benchmark.

Composition of the benchmark and the three meta-optimisers.

julia
using StatsPlots, GraphRecipes
plot_stacked_bar_composition([res_bench, res_nco, res_stk, res_ssr], rd)


This page was generated using Literate.jl.