The source files can be found in examples/.

Regularisation

This example shows regularisation, a penalty on a norm of the weights that the optimiser adds to the objective.

Section 2 gives every penalty coefficient as a number. Section 3 replaces the number with a calibration rule, an estimator that computes the coefficient from the sample. It also adds the three norm ceilings, l2c, lpc and linfc, which bound a norm of the weights in place of a penalty on it.

using PortfolioOptimisers, PrettyTablestsfmt = (v, i, j) -> begin    if j == 1        return Date(v)    else        return v    endend;resfmt = (v, i, j) -> begin    if j == 1        return v    else        return isa(v, Number) ? "$(round(v*100, digits=3)) %" : v    endend;mmtfmt = (v, i, j) -> begin    if i == j == 1        return v    else        return isa(v, Number) ? "$(round(v*100, digits=3)) %" : v    endend;summary_row = (data, j) -> begin    if j == 1        return "N/A"    else        return number_effective_assets(data[:, j])    endend
#11 (generic function with 1 method)

1. Data

The data is one year of S&P 500 prices. We fit an empirical prior, and give the optimiser seven Clarabel settings to try in turn.

using CSV, TimeSeries, DataFrames, ClarabelX = TimeArray(CSV.File(joinpath(@__DIR__, "..", "SP500.csv.gz")); timestamp = :Date)[(end - 252):end]pretty_table(X[(end - 5):end]; formatters = [tsfmt])rd = prices_to_returns(X)pr = prior(EmpiricalPrior(), rd)slv = [Solver(; name = :clarabel1, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel2, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.95),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel3, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.9),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel4, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.85),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel5, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.8),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel6, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.75),              check_sol = (; allow_local = true, allow_almost = true)),       Solver(; name = :clarabel7, solver = Clarabel.Optimizer,              settings = Dict("verbose" => false, "max_step_fraction" => 0.70),              check_sol = (; allow_local = true, allow_almost = true))];
┌────────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│  timestamp │    AAPL │     AMD │     BAC │     BBY │     CVX │      GE │      HD │     JNJ │     JPM │      KO │     LLY │     MRK │    MSFT │     PEP │     PFE │      PG │     RRC │     UNH │     WMT │     XOM │
│       Date │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │ Float64 │
├────────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ 2022-12-20 │ 131.916 │   65.05 │  31.729 │  77.371 │ 169.497 │  62.604 │ 310.342 │ 173.109 │ 127.844 │  61.841 │  357.55 │ 108.229 │  240.67 │ 178.765 │  49.754 │ 147.661 │   25.65 │ 516.245 │ 142.919 │ 104.964 │
│ 2022-12-21 │ 135.057 │   67.68 │  32.212 │  78.729 │  171.49 │   64.67 │ 314.798 │  175.09 │ 129.282 │  62.836 │ 365.872 │ 109.611 │ 243.287 │ 180.017 │  50.084 │ 149.015 │  26.574 │ 523.519 │  144.04 │ 106.312 │
│ 2022-12-22 │ 131.846 │   63.86 │  31.927 │  78.563 │ 168.918 │  63.727 │ 311.604 │  174.45 │ 127.814 │  62.383 │ 363.187 │ 109.739 │ 237.077 │ 178.627 │  50.065 │ 149.359 │  25.232 │ 523.072 │ 142.354 │ 104.168 │
│ 2022-12-23 │ 131.477 │   64.52 │  32.005 │  79.432 │  174.14 │  63.742 │ 314.177 │ 174.893 │ 128.421 │  62.855 │ 365.762 │  110.35 │ 237.614 │ 179.781 │  50.249 │ 149.781 │  26.226 │  527.26 │ 142.641 │ 106.922 │
│ 2022-12-27 │ 129.652 │   63.27 │  32.065 │   79.93 │ 176.329 │  64.561 │ 314.985 │ 174.844 │ 128.871 │   63.24 │  362.76 │ 110.607 │ 235.852 │  180.58 │   49.57 │ 151.086 │  26.375 │ 527.935 │ 142.681 │ 108.408 │
│ 2022-12-28 │ 125.674 │   62.57 │  32.301 │  78.279 │ 173.728 │  63.883 │  311.22 │ 174.085 │ 129.575 │  62.609 │ 363.098 │ 109.581 │ 233.434 │ 179.278 │   49.25 │ 149.133 │  24.497 │ 524.422 │ 140.181 │ 106.627 │
└────────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

2. Regularised portfolios

The coefficient that suits a problem depends on the data and on the norm, and it changes as the data changes. You can search for it with cross-validation, or compute it from the sample with a calibration rule, as section 3 shows. This section sets up and solves regularised problems, and does not search for the best value.

We use the same coefficient, 4e-4, for every regularisation, so you can compare them.

  • L1 regularisation, also called Lasso, adds a penalty proportional to the sum of the absolute values of the weights. It pushes small weights to zero, so the portfolio holds fewer assets.
  • L2 regularisation, L2Regularisation, adds a penalty proportional to the 2-norm of the weights, the square root of the sum of their squares. It penalises large weights and spreads the portfolio over more assets. Other values of its alg penalise the square of the 2-norm, which is the Ridge penalty.
  • Lp regularisation, LpRegularisation, adds a penalty proportional to the p-norm of the weights, for a finite p greater than 1.
  • L-Inf regularisation adds a penalty proportional to the largest absolute weight. It limits the size of the largest position.

2.1 Efficient frontier

For each regularisation we compute an efficient frontier of 50 portfolios, with weights between -1 and 1, a short budget of 1 and a budget of 1.

opts = [JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1,                      ret = ArithmeticReturn(;                                             settings = JuMPReturnsSettings(;                                                                            lb = Frontier(;                                                                                          N = 50)))),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      ret = ArithmeticReturn(;                                             settings = JuMPReturnsSettings(;                                                                            lb = Frontier(;                                                                                          N = 50))),                      bgt = 1, l1 = 4e-4),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      ret = ArithmeticReturn(;                                             settings = JuMPReturnsSettings(;                                                                            lb = Frontier(;                                                                                          N = 50))),                      bgt = 1, l2 = L2Regularisation(; val = 4e-4)),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      ret = ArithmeticReturn(;                                             settings = JuMPReturnsSettings(;                                                                            lb = Frontier(;                                                                                          N = 50))),                      bgt = 1, lp = LpRegularisation(; p = 5, val = 4e-4)),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      ret = ArithmeticReturn(;                                             settings = JuMPReturnsSettings(;                                                                            lb = Frontier(;                                                                                          N = 50))),                      bgt = 1, linf = 4e-4)]nocs = [MeanRisk(; opt = opt) for opt in opts]ress = optimise.(nocs)
5-element Vector{MeanRiskResult{JuMPOptimisationResult{ProcessedJuMPOptimiserAttributes{LowOrderPrior{Matrix{Float64}, Nothing, Vector{Float64}, Matrix{Float64}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing}, WeightBounds{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, ArithmeticReturn{JuMPReturnsSettings{Float64, Frontier{Int64, Int64, LinearBound}, Bool, Bool, Bool}, Nothing, Vector{Float64}, EigenFallbackSquareRoot}, SumScalariser, Nothing}, Vector{OptimisationReturnCode}, Vector{JuMPOptimisationSolution}, Model}, Variance{RiskMeasureSettings{Float64, Nothing, Bool}, Matrix{Float64}, Nothing, Nothing, SquaredSOCRiskExpr, EigenFallbackSquareRoot, SquareRootRatio, Nothing}, Nothing}}:
 MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 50
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   retcode ┼ 50-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 50-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 62
     │           │ ├ num_constraints: 50
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 3
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ │ ├ VariableRef in MOI.GreaterThan{Float64}: 40
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :T, :bgt, :cdev_soc_1, :decomposition_contract, :dev_1, :k, :lbgt, :lw, :obj_expr, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sbgt, :sc, :so, :sw, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_lw, :w_sw, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

 MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 50
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   retcode ┼ 50-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 50-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 63
     │           │ ├ num_constraints: 51
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 3
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.NormOneCone: 1
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ │ ├ VariableRef in MOI.GreaterThan{Float64}: 40
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :T, :bgt, :cdev_soc_1, :cl1_noc, :decomposition_contract, :dev_1, :k, :l1, :lbgt, :lw, :obj_expr, :op, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sbgt, :sc, :so, :sw, :t_l1, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_lw, :w_sw, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

 MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 50
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   retcode ┼ 50-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 50-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 63
     │           │ ├ num_constraints: 51
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 3
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 2
     │           │ │ ├ VariableRef in MOI.GreaterThan{Float64}: 40
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :T, :bgt, :cdev_soc_1, :cl2_soc_1, :decomposition_contract, :dev_1, :k, :l2_1, :lbgt, :lw, :obj_expr, :op, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sbgt, :sc, :so, :sw, :t_l2_1, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_lw, :w_sw, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

 MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 50
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   retcode ┼ 50-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 50-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 83
     │           │ ├ num_constraints: 71
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 4
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ │ ├ Vector{AffExpr} in MOI.PowerCone{Float64}: 20
     │           │ │ ├ VariableRef in MOI.GreaterThan{Float64}: 40
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :T, :bgt, :cdev_soc_1, :clp_1, :cslp_1, :decomposition_contract, :dev_1, :k, :lbgt, :lp_1, :lw, :obj_expr, :op, :r_lp_1, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sbgt, :sc, :so, :sw, :t_lp_1, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_lw, :w_sw, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

 MeanRiskResult
  jr ┼ JuMPOptimisationResult
     │        pa ┼ ProcessedJuMPOptimiserAttributes
     │           │        pr ┼ LowOrderPrior
     │           │           │         X ┼ 252×20 Matrix{Float64}
     │           │           │       o_X ┼ nothing
     │           │           │        mu ┼ 20-element Vector{Float64}
     │           │           │     sigma ┼ 20×20 Matrix{Float64}
     │           │           │      chol ┼ nothing
     │           │           │         w ┼ nothing
     │           │           │       ens ┼ nothing
     │           │           │       kld ┼ nothing
     │           │           │        ow ┼ nothing
     │           │           │        rr ┼ nothing
     │           │           │       fpr ┼ nothing
     │           │           │     x_src ┼ nothing
     │           │           │   ss_gaps ┼ nothing
     │           │           │        pc ┴ 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
     │           │           │   settings ┼ JuMPReturnsSettings
     │           │           │            │   scale ┼ Float64: 1.0
     │           │           │            │      lb ┼ Frontier
     │           │           │            │         │        N ┼ Int64: 50
     │           │           │            │         │   factor ┼ Int64: 1
     │           │           │            │         │    bound ┴ LinearBound()
     │           │           │            │     rte ┼ Bool: true
     │           │           │            │     fee ┼ Bool: true
     │           │           │            │     mic ┴ Bool: true
     │           │           │        ucs ┼ nothing
     │           │           │         mu ┼ 20-element Vector{Float64}
     │           │           │   mtx_sqrt ┴ EigenFallbackSquareRoot()
     │           │       sca ┼ SumScalariser()
     │           │      imsk ┴ nothing
     │   retcode ┼ 50-element Vector{OptimisationSuccess}
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │           │ OptimisationSuccess ⋯
     │       sol ┼ 50-element Vector{JuMPOptimisationSolution}
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │           │ JuMPOptimisationSolution ⋯
     │     model ┼ A JuMP Model
     │           │ ├ solver: Clarabel
     │           │ ├ objective_sense: MIN_SENSE
     │           │ │ └ objective_function_type: QuadExpr
     │           │ ├ num_variables: 63
     │           │ ├ num_constraints: 51
     │           │ │ ├ AffExpr in MOI.EqualTo{Float64}: 3
     │           │ │ ├ AffExpr in MOI.GreaterThan{Float64}: 1
     │           │ │ ├ Vector{AffExpr} in MOI.Nonnegatives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.Nonpositives: 2
     │           │ │ ├ Vector{AffExpr} in MOI.NormInfinityCone: 1
     │           │ │ ├ Vector{AffExpr} in MOI.SecondOrderCone: 1
     │           │ │ ├ VariableRef in MOI.GreaterThan{Float64}: 40
     │           │ │ └ VariableRef in MOI.Parameter{Float64}: 1
     │           │ └ Names registered in the model
     │           │   └ :G, :T, :bgt, :cdev_soc_1, :clinf_nic, :decomposition_contract, :dev_1, :k, :lbgt, :linf, :lw, :obj_expr, :op, :ret, :ret_1, :ret_frontier, :ret_lb_1, :ret_lb_var_1, :ret_vec, :risk, :risk_minimised, :risk_vec, :sbgt, :sc, :so, :sw, :t_linf, :variance_flag, :variance_risk_1, :w, :w_gross_ub, :w_lb, :w_lw, :w_sw, :w_ub
   r ┼ Variance
     │    settings ┼ RiskMeasureSettings
     │             │   scale ┼ Float64: 1.0
     │             │      ub ┼ nothing
     │             │     rke ┴ Bool: true
     │       sigma ┼ 20×20 Matrix{Float64}
     │        chol ┼ nothing
     │          rc ┼ nothing
     │         alg ┼ SquaredSOCRiskExpr()
     │    mtx_sqrt ┼ EigenFallbackSquareRoot()
     │   ratio_alg ┼ SquareRootRatio()
     │      corner ┴ nothing
  fb ┴ nothing

For each regularisation we plot the weights along the frontier, and the frontier itself in the plane of variance and return, coloured by the ratio of return to risk.

using StatsPlots, GraphRecipesr = Variance()
Variance
   settings ┼ RiskMeasureSettings
            │   scale ┼ Float64: 1.0
            │      ub ┼ nothing
            │     rke ┴ Bool: true
      sigma ┼ nothing
       chol ┼ nothing
         rc ┼ nothing
        alg ┼ SquaredSOCRiskExpr()
   mtx_sqrt ┼ EigenFallbackSquareRoot()
  ratio_alg ┼ SquareRootRatio()
     corner ┴ nothing

Weights along the frontier with no regularisation.

plot_stacked_area_composition(ress[1].w, rd.nx;                              kwargs = (; xlabel = "Portfolios", ylabel = "Weight",                                        title = "No regularisation", legend = :outerright))
Example block output

Frontier with no regularisation.

plot_measures(ress[1].w, pr; x = r, y = ExpectedReturn(; rt = ress[1].ret),              c = ExpectedReturnRiskRatio(; rt = ress[1].ret, rk = r, rf = 4.2 / 100 / 252),              title = "No regularisation", xlabel = "Variance",              ylabel = "Arithmetic Return", colorbar_title = "\nReturn/Risk Ratio",              right_margin = 6Plots.mm)
Example block output

Weights with L1 regularisation. Fewer assets hold a weight than with no regularisation.

plot_stacked_area_composition(ress[2].w, rd.nx;                              kwargs = (; xlabel = "Portfolios", ylabel = "Weight",                                        title = "L1 regularisation", legend = :outerright))
Example block output

Frontier with L1 regularisation. It is not smooth, because assets enter and leave the portfolio along it.

plot_measures(ress[2].w, pr; x = r, y = ExpectedReturn(; rt = ress[2].ret),              c = ExpectedReturnRiskRatio(; rt = ress[1].ret, rk = r, rf = 4.2 / 100 / 252),              title = "L1 regularisation", xlabel = "Variance",              ylabel = "Arithmetic Return", colorbar_title = "\nReturn/Risk Ratio",              right_margin = 6Plots.mm)
Example block output

Weights with L2 regularisation. The budget fixes the sum of the weights, so the 2-norm is smallest when every weight equals the mean weight. The penalty pulls the weights toward it and spreads the portfolio over more assets.

plot_stacked_area_composition(ress[3].w, rd.nx;                              kwargs = (; xlabel = "Portfolios", ylabel = "Weight",                                        title = "L2 regularisation", legend = :outerright))
Example block output

Frontier with L2 regularisation.

plot_measures(ress[3].w, pr; x = r, y = ExpectedReturn(; rt = ress[3].ret),              c = ExpectedReturnRiskRatio(; rt = ress[1].ret, rk = r, rf = 4.2 / 100 / 252),              title = "L2 regularisation", xlabel = "Variance",              ylabel = "Arithmetic Return", colorbar_title = "\nReturn/Risk Ratio",              right_margin = 6Plots.mm)
Example block output

Weights with Lp regularisation, p = 5. As p grows, the p-norm comes closer to the largest absolute weight, which is the norm of the L-Inf penalty.

plot_stacked_area_composition(ress[4].w, rd.nx;                              kwargs = (; xlabel = "Portfolios", ylabel = "Weight",                                        title = "Lp (p = 5) regularisation",                                        legend = :outerright))
Example block output

Frontier with Lp regularisation, p = 5.

plot_measures(ress[4].w, pr; x = r, y = ExpectedReturn(; rt = ress[4].ret),              c = ExpectedReturnRiskRatio(; rt = ress[1].ret, rk = r, rf = 4.2 / 100 / 252),              title = "Lp (p = 5) regularisation", xlabel = "Variance",              ylabel = "Arithmetic Return", colorbar_title = "\nReturn/Risk Ratio",              right_margin = 6Plots.mm)
Example block output

Weights with L-Inf regularisation.

plot_stacked_area_composition(ress[5].w, rd.nx;                              kwargs = (; xlabel = "Portfolios", ylabel = "Weight",                                        title = "L-Inf regularisation",                                        legend = :outerright))
Example block output

Frontier with L-Inf regularisation.

plot_measures(ress[5].w, pr; x = r, y = ExpectedReturn(; rt = ress[5].ret),              c = ExpectedReturnRiskRatio(; rt = ress[1].ret, rk = r, rf = 4.2 / 100 / 252),              title = "L-Inf regularisation", xlabel = "Variance",              ylabel = "Arithmetic Return", colorbar_title = "\nReturn/Risk Ratio",              right_margin = 6Plots.mm)
Example block output

2.2 Minimum risk portfolios

We now solve only the minimum risk portfolio of each regularisation. The table prints the weights, and its last row is the number of effective assets of each portfolio.

opts = [JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1, l1 = 4e-4),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1, l2 = L2Regularisation(; val = 4e-4)),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1, lp = LpRegularisation(; p = 5, val = 4e-4)),        JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                      bgt = 1, linf = 4e-4)]nocs = [MeanRisk(; opt = opt) for opt in opts]ress = optimise.(nocs)pretty_table(DataFrame(:Assets => rd.nx, :No_Reg => ress[1].w, :L1 => ress[2].w,                       :L2 => ress[3].w, :L5 => ress[4].w, :LInf => ress[5].w);             formatters = [resfmt], summary_rows = [summary_row],             summary_row_labels = ["Effective assets"])
┌──────────────────┬────────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│                  │ Assets │    No_Reg │       L1 │       L2 │       L5 │     LInf │
│                  │ String │   Float64 │  Float64 │  Float64 │  Float64 │  Float64 │
├──────────────────┼────────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│                  │   AAPL │ -11.866 % │    0.0 % │  1.136 % │ -3.026 % │ -5.751 % │
│                  │    AMD │   -1.55 % │    0.0 % │ -2.963 % │ -6.029 % │ -7.437 % │
│                  │    BAC │   -5.46 % │    0.0 % │  3.488 % │   4.28 % │  1.181 % │
│                  │    BBY │  -3.234 % │    0.0 % │  1.035 % │ -1.015 % │ -1.935 % │
│                  │    CVX │   9.487 % │  7.415 % │   6.57 % │  7.553 % │  7.879 % │
│                  │     GE │   5.203 % │  0.823 % │  3.871 % │  5.501 % │  6.838 % │
│                  │     HD │   3.621 % │    0.0 % │  4.194 % │  6.143 % │  7.879 % │
│                  │    JNJ │  40.958 % │ 36.981 % │ 10.221 % │  9.019 % │  7.879 % │
│                  │    JPM │   7.738 % │  0.762 % │  4.252 % │  5.446 % │  7.879 % │
│                  │     KO │    12.0 % │ 11.098 % │  7.952 % │  8.217 % │  7.879 % │
│                  │    LLY │  -7.707 % │    0.0 % │  5.655 % │  6.507 % │  7.878 % │
│                  │    MRK │  19.792 % │ 17.462 % │  9.638 % │  8.719 % │  7.879 % │
│                  │   MSFT │   8.105 % │    0.0 % │  2.536 % │  4.917 % │  7.071 % │
│                  │    PEP │    13.7 % │  8.969 % │  7.992 % │  8.234 % │  7.879 % │
│                  │    PFE │  -4.513 % │    0.0 % │  5.999 % │  6.673 % │  7.879 % │
│                  │     PG │   1.787 % │  2.399 % │  7.873 % │  8.145 % │  7.879 % │
│                  │    RRC │   -1.68 % │    0.0 % │  0.153 % │   -1.8 % │ -2.388 % │
│                  │    UNH │  -1.188 % │    0.0 % │  5.939 % │  6.862 % │  7.879 % │
│                  │    WMT │   9.161 % │  9.356 % │  8.205 % │   8.32 % │  7.879 % │
│                  │    XOM │   5.649 % │  4.734 % │  6.254 % │  7.334 % │  7.879 % │
├──────────────────┼────────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ Effective assets │    N/A │   3.28601 │  4.88263 │  13.8946 │  11.5881 │    9.971 │
└──────────────────┴────────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Against No_Reg, the L1 penalty sets several weights to zero. The L2, Lp and L-Inf penalties make the weights more even, and the last row shows that each of them gives more effective assets than L1. How much a penalty changes the weights depends on its size against the rest of the objective, which here is the risk. The penalty is the coefficient times the norm of the weights.

The number of effective assets, number_effective_assets, is 1/(w ⋅ w). It measures how concentrated the weights are, and it is not a count of the weights that are not zero. A portfolio that holds fewer assets can still have more effective assets, if its weights are more even. A larger number of effective assets means a less concentrated portfolio.

To combine penalties, give more than one of the regularisation keywords to the JuMPOptimiser. For example, an L1 and an L2 penalty together ask for fewer assets and for more even weights. Each penalty you add is one more coefficient to choose.

3. A rule in place of a penalty value

Each penalty coefficient of section 2 can be a calibration rule in place of a number. A calibration rule is an estimator that computes the coefficient from the prior of the sample it gets. l1 and linf take the rule directly, and L2Regularisation and LpRegularisation take it in their val. The optimiser computes the coefficient when it builds the model. In a cross-validation, each fold therefore gets a coefficient from its own training sample, and the rest of the optimiser stays the same. The calibration example uses a rule in a risk measure. This section covers the four penalties and the three norm ceilings.

The four penalty coefficients are ambiguity radii. Take a ball of return distributions around the sample, with distance measured in the dual of the penalised norm. The worst case of the objective over that ball is the objective plus a penalty on the norm of the weights, and the coefficient of the penalty is the radius of the ball. The penalties therefore take the rules that compute a radius.

The three norm ceilings, l2c, lpc and linfc, bound a norm of the weights in place of a penalty on it. A ceiling is not a radius, so the ceilings take a different family of rules. A ceiling on a norm of the weights sets a floor on an effective number of assets, as section 3.3 shows.

3.1 A rule whose value depends on the penalty

DualNormRadius returns the sampling error of the mean returns, measured in the dual of the norm that the penalty uses. It therefore gives a different number for each penalty, where the other radius rules of the library give the same number for every penalty. We call the rule with the key of each penalty and print the four radii.

numfmt = (v, i, j) -> begin    return isa(v, AbstractFloat) ? round(v; sigdigits = 4) : vend;dnr = DualNormRadius(; confidence = 0.95)lp5 = CalibrationContext(; p = 5)radius_table = DataFrame(:slot => ["l1", "linf", "l2, val", "lp, val, p = 5"],                         :penalised_norm => ["1", "Inf", "2", "5"],                         :ground_metric => ["Inf", "1", "2", "1.25"],                         :radius => [dnr(:l1, pr, nothing, nothing, CalibrationContext()),                                     dnr(:linf, pr, nothing, nothing, CalibrationContext()),                                     dnr(:l2reg_val, pr, nothing, nothing, CalibrationContext()),                                     dnr(:lpreg_val, pr, nothing, nothing, lp5)])pretty_table(radius_table; formatters = [numfmt])
┌────────────────┬────────────────┬───────────────┬──────────┐
│           slot │ penalised_norm │ ground_metric │   radius │
│         String │         String │        String │  Float64 │
├────────────────┼────────────────┼───────────────┼──────────┤
│             l1 │              1 │           Inf │   0.0041 │
│           linf │            Inf │             1 │  0.04173 │
│        l2, val │              2 │             2 │ 0.009974 │
│ lp, val, p = 5 │              5 │          1.25 │   0.0233 │
└────────────────┴────────────────┴───────────────┴──────────┘

Compare the l1 and linf rows. The rule, the sample and the confidence level are the same, but the two coefficients are about an order of magnitude apart. The L1 penalty uses the ∞-norm of the error, and the L-Inf penalty uses its 1-norm. The lp row needs the p of the penalty, because its dual order q = p / (p - 1) depends on it. When the optimiser builds the model, it gives the rule this p in a CalibrationContext. Here we call the rule ourselves, so we pass lp5, a context with p = 5.

Next we give the rule to l1 of one optimiser, and the number that it computes to l1 of a second optimiser. Both use a confidence level of 0.95, so both models get the same coefficient. The cell prints the largest difference between the two sets of weights.

l1_rule = JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1),                        sbgt = 1, bgt = 1, l1 = DualNormRadius())l1_num = JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                       bgt = 1, l1 = dnr(:l1, pr, nothing, nothing, CalibrationContext()))res_rule = optimise(MeanRisk(; opt = l1_rule))res_num = optimise(MeanRisk(; opt = l1_num))println("largest weight difference = $(maximum(abs, res_rule.w - res_num.w))")
largest weight difference = 0.0

3.2 Radii that fall as the sample grows

RateRadius returns c / sqrt(T), so the radius falls at the rate of the error of a sample mean. DimensionalRateRadius falls as T to the power -1 / max(N, 2), where N is the number of assets. Over a wide universe this is much slower than the power -1 / 2. We compute both rules on three windows of the same price history, of 252, 630 and 1260 days.

X_all = TimeArray(CSV.File(joinpath(@__DIR__, "..", "SP500.csv.gz")); timestamp = :Date)rate_table = DataFrame()for T in (252, 630, 1260)    pr_T = prior(EmpiricalPrior(), prices_to_returns(X_all[(end - T):end]))    push!(rate_table,          (; T = size(pr_T.X, 1), N = size(pr_T.X, 2),           rate = RateRadius(; c = 0.02)(:l1, pr_T, nothing, nothing, CalibrationContext()),           dimensional = DimensionalRateRadius(; confidence = 0.95)(:l1, pr_T, nothing,                                                                    nothing,                                                                    CalibrationContext())))endpretty_table(rate_table; formatters = [numfmt])
┌───────┬───────┬───────────┬─────────────┐
│     T │     N │      rate │ dimensional │
│ Int64 │ Int64 │   Float64 │     Float64 │
├───────┼───────┼───────────┼─────────────┤
│   252 │    20 │   0.00126 │     0.01613 │
│   630 │    20 │ 0.0007968 │     0.01441 │
│  1260 │    20 │ 0.0005634 │     0.01537 │
└───────┴───────┴───────────┴─────────────┘

The rate radius falls by the square root of the ratio of the window lengths. The dimensional radius changes little, and it does not always fall. With 20 assets its power is -1 / 20, so a window five times longer lowers it by less than 8%. The rule also multiplies by a scale that it computes from each window, the mean standard deviation of the assets, and that scale causes the rest of the change.

3.3 The three norm ceilings

EffectiveAssetFloor is the one rule for the ceilings. You give it a fraction of the universe, and it returns the ceiling on the norm that keeps the effective number of assets at or above m = fraction * N. The effective number of assets of order p is (sum(abs.(w) .^ p))^(1 / (1 - p)). The ceiling is m^(1 / p - 1), which is 1 / sqrt(m) for p = 2 and 1 / m for p = Inf.

The order p comes from the constraint, not from the rule. l2c uses 2, lpc uses the p of its LpRegularisation, and linfc uses Inf. When we call the rule ourselves, we pass the order in a CalibrationContext, as we did for the lp radius. We print the size of the universe, the floor and the three ceilings.

N = size(pr.X, 2)println("universe = $N assets, floor = $(0.5 * N) effective assets")eaf = EffectiveAssetFloor(; fraction = 0.5)ceiling_table = DataFrame(:slot => ["l2c", "lpc, p = 5", "linfc"],                          :norm_order => ["2", "5", "Inf"],                          :ceiling =>                              [eaf(:l2c, pr, nothing, nothing, CalibrationContext(; p = 2)),                               eaf(:lpc, pr, nothing, nothing, CalibrationContext(; p = 5)),                               eaf(:linfc, pr, nothing, nothing,                                   CalibrationContext(; p = Inf))])pretty_table(ceiling_table; formatters = [numfmt])
universe = 20 assets, floor = 10.0 effective assets
┌────────────┬────────────┬─────────┐
│       slot │ norm_order │ ceiling │
│     String │     String │ Float64 │
├────────────┼────────────┼─────────┤
│        l2c │          2 │  0.3162 │
│ lpc, p = 5 │          5 │  0.1585 │
│      linfc │        Inf │     0.1 │
└────────────┴────────────┴─────────┘

We give the same rule to the three ceilings of three optimisers, and compare them with an optimiser that has no ceiling. For each portfolio we print the effective number of assets of order 2, and of the order that its ceiling uses.

ceil_rule = EffectiveAssetFloor(; fraction = 0.5)copts = [JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                       bgt = 1),         JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                       bgt = 1, l2c = ceil_rule),         JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                       bgt = 1, lpc = LpRegularisation(; p = 5, val = ceil_rule)),         JuMPOptimiser(; pe = pr, slv = slv, wb = WeightBounds(; lb = -1, ub = 1), sbgt = 1,                       bgt = 1, linfc = ceil_rule)]cress = [optimise(MeanRisk(; opt = opt)) for opt in copts]n_eff_p(w, p) = isinf(p) ? inv(maximum(abs, w)) : sum(abs.(w) .^ p)^inv(1 - p)ceiling_orders = [2, 2, 5, Inf]effective_table = DataFrame(:ceiling => ["none", "l2c", "lpc, p = 5", "linfc"],                            :p => ceiling_orders,                            :n_eff_2 => [number_effective_assets(r.w) for r in cress],                            :n_eff_p =>                                [n_eff_p(r.w, p) for (r, p) in zip(cress, ceiling_orders)])pretty_table(effective_table; formatters = [numfmt])
┌────────────┬─────────┬─────────┬─────────┐
│    ceiling │       p │ n_eff_2 │ n_eff_p │
│     String │ Float64 │ Float64 │ Float64 │
├────────────┼─────────┼─────────┼─────────┤
│       none │     2.0 │   3.286 │   3.286 │
│        l2c │     2.0 │    10.0 │    10.0 │
│ lpc, p = 5 │     5.0 │   9.157 │    10.0 │
│      linfc │     Inf │   8.571 │    10.0 │
└────────────┴─────────┴─────────┴─────────┘

The n_eff_p column is the effective number of assets of the order that each ceiling uses. Compare it with the floor printed above. The n_eff_2 column is number_effective_assets, which uses order 2 on every row. The two columns are equal on the first two rows, where the order is 2, and differ on the other two. A value of n_eff_2 below the floor on the lpc or linfc row does not mean that the ceiling failed, because that ceiling bounds a different order.

If the context names no order, the rule throws an error, and the message names the three ceilings that give one.

try    eaf(:l2c, pr, nothing, nothing, CalibrationContext())catch e    println(sprint(showerror, e))end
ArgumentError: `CalibrationContext.p` is `nothing` while the rule in `l2c` is being resolved. A ceiling is read against one norm order, the order belongs to the constraint, and each constraint site states it in the context. Place the rule in `l2c`, `lpc` or `linfc`, or state `p` in the context.

This page was generated using Literate.jl.