Regularisation Constraints: private API

PortfolioOptimisers.AbstractRegularisationEstimatorType
abstract type AbstractRegularisationEstimator <: AbstractEstimator

Abstract supertype for all portfolio weight regularisation estimators.

A regularisation term penalises the norm of the weight vector, which is the Lagrangian form of a constraint on that norm.

Related

References

  • [131] V. DeMiguel, L. Garlappi, F. J. Nogales and R. Uppal. A Generalized Approach to Portfolio Optimization: Improving Performance by Constraining Portfolio Norms. Management Science 55, 798–812 (2009).
source
PortfolioOptimisers.set_l1_regularisation!Function
set_l1_regularisation!(model::JuMP.Model, l1_val::Number)
set_l1_regularisation!(args...)

Add an L1 regularisation penalty to the objective function.

The penalty is applied as given, so l1_val must be commensurate with the scale of the objective it is added to.

Mathematical definition

\[\begin{align} \text{penalty} &= \lambda_1 \lVert \boldsymbol{w} \rVert_1\,. \end{align}\]

Where:

  • $\lambda_1$: L1 regularisation coefficient.
  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • l1_val::Number: L1 regularisation coefficient.

Returns

  • nothing.

Details

  • l1_val::Number: Introduces the auxiliary variable t_l1, bounds it with [t_l1; w] in MOI.NormOneCone, and adds l1_val * t_l1 to the objective penalty via add_to_objective_penalty!.
  • args...: No-op, used when no L1 regularisation is configured.

Related

source
PortfolioOptimisers._set_l2_regularisation!Function
_set_l2_regularisation!(model::JuMP.Model, i::Integer, w::VecNum,
                        l2::L2Regularisation{<:Any, <:SOCRiskExpr}, sc::Number)
_set_l2_regularisation!(model::JuMP.Model, i::Integer, w::VecNum,
                        l2::L2Regularisation{<:Any, <:SquaredSOCRiskExpr}, sc::Number)
_set_l2_regularisation!(model::JuMP.Model, i::Integer, w::VecNum,
                        l2::L2Regularisation{<:Any, <:QuadRiskExpr}, args...)
_set_l2_regularisation!(model::JuMP.Model, i::Integer, w::VecNum,
                        l2::L2Regularisation{<:Any, <:RSOCRiskExpr}, sc::Number)

Add the i-th L2 regularisation penalty to the objective, in the formulation given by l2.alg.

The coefficient l2.val is applied as given, so it must be consistent with the formulation it multiplies: SOCRiskExpr penalises $\lVert \boldsymbol{w} \rVert_2$, whereas SquaredSOCRiskExpr, QuadRiskExpr and RSOCRiskExpr penalise $\lVert \boldsymbol{w} \rVert_2^2$. The same val therefore does not produce the same penalty across formulations.

QuadRiskExpr yields a quadratic objective term rather than a conic one; add_to_objective_penalty! promotes an affine penalty expression to a JuMP.QuadExpr to accommodate it.

Mathematical definition

\[\begin{align} \text{penalty} &= \lambda_2 \lVert \boldsymbol{w} \rVert_2\,, \\ \text{penalty} &= \lambda_2 \lVert \boldsymbol{w} \rVert_2^2\,. \end{align}\]

Where:

  • $\lambda_2$: L2 regularisation coefficient, the val field of l2.
  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • i::Integer: Index of the term, used to suffix the variables and constraints it creates so that multiple terms do not collide.
  • w::VecNum: Portfolio weights variable.
  • l2::L2Regularisation: The L2 regularisation term.
  • sc::Number: Constraint scale factor.

Returns

  • nothing.

Details

  • l2::L2Regularisation{<:Any, <:SOCRiskExpr}: Introduces t_l2_i, constrains [t_l2_i; w] in SecondOrderCone so that t_l2_i >= norm(w, 2), and penalises val * t_l2_i.
  • l2::L2Regularisation{<:Any, <:SquaredSOCRiskExpr}: Introduces t_l2_i, constrains [t_l2_i; w] in SecondOrderCone so that t_l2_i >= norm(w, 2), and penalises val * t_l2_i^2.
  • l2::L2Regularisation{<:Any, <:QuadRiskExpr}: Introduces no auxiliary variable, and penalises val * dot(w, w) directly.
  • l2::L2Regularisation{<:Any, <:RSOCRiskExpr}: Introduces t_l2_i, constrains [t_l2_i; 0.5; w] in RotatedSecondOrderCone so that t_l2_i >= norm(w, 2)^2, and penalises val * t_l2_i.

Related

source
PortfolioOptimisers.set_l2_regularisation!Function
set_l2_regularisation!(model::JuMP.Model, l2s::L2Reg_VecL2Reg)
set_l2_regularisation!(args...)

Add one or more L2 regularisation penalties to the objective function.

Each L2Regularisation carries its own coefficient and cone formulation, so several may be combined in a single model. The penalties are applied as given, so each coefficient must be commensurate with the scale of the objective it is added to, and with the formulation used to express it.

Mathematical definition

\[\begin{align} \text{penalty} &= \sum_{i} \lambda_{2,\, i} \lVert \boldsymbol{w} \rVert_2\,. \end{align}\]

Where:

  • $\lambda_{2,\, i}$: L2 regularisation coefficient of the $i$-th term.
  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • l2s::L2Reg_VecL2Reg: One or more L2 regularisation terms.

Returns

  • nothing.

Details

  • l2s::L2Reg_VecL2Reg: Dispatches each term to _set_l2_regularisation!, which selects the cone formulation from the term's alg field. Variables and constraints are suffixed by the term's index, so terms do not collide.
  • args...: No-op, used when no L2 regularisation is configured.

Related

source
PortfolioOptimisers.set_lp_regularisation!Function
set_lp_regularisation!(model::JuMP.Model, lps::LpReg_VecLpReg)
set_lp_regularisation!(args...)

Add one or more Lp regularisation penalties to the objective function.

Each LpRegularisation carries its own norm order and coefficient, so several may be combined in a single model. The penalties are applied as given, so each coefficient must be commensurate with the scale of the objective it is added to.

The same estimator also specifies a p-norm constraint when it is placed in the lpc field of JuMPOptimiser instead of the lp field, in which case its val field is an upper bound on the p-norm rather than a coefficient. See set_weight_norm_p_constraints!.

Mathematical definition

\[\begin{align} \text{penalty} &= \sum_{i} \lambda_{p,\, i} \lVert \boldsymbol{w} \rVert_{p_i}\,. \end{align}\]

Where:

  • $\lambda_{p,\, i}$: Regularisation coefficient of the $i$-th term, its val field.
  • $p_i$: Norm order of the $i$-th term, its p field.
  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • lps::LpReg_VecLpReg: One or more Lp regularisation terms.

Returns

  • nothing.

Details

  • lps::LpReg_VecLpReg: For each term, introduces the auxiliary variables t_lp_i and r_lp_i, bounds $\lVert \boldsymbol{w} \rVert_{p_i}$ above by t_lp_i with a set of MOI.PowerCone constraints, and adds val * t_lp_i to the objective penalty via add_to_objective_penalty!. Variables and constraints are suffixed by the term's index, so terms do not collide.
  • args...: No-op, used when no Lp regularisation is configured.

Related

source
PortfolioOptimisers.set_linf_regularisation!Function
set_linf_regularisation!(model::JuMP.Model, linf::Number)
set_linf_regularisation!(args...)

Add an L∞ regularisation penalty to the objective function.

The penalty is applied as given, so linf must be commensurate with the scale of the objective it is added to and with the range of values the weights can take.

Mathematical definition

\[\begin{align} \text{penalty} &= \lambda_\infty \lVert \boldsymbol{w} \rVert_\infty\,. \end{align}\]

Where:

  • $\lambda_\infty$: L∞ regularisation coefficient.
  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • linf::Number: L∞ regularisation coefficient.

Returns

  • nothing.

Details

  • linf::Number: Introduces the auxiliary variable t_linf, bounds it with [t_linf; w] in MOI.NormInfinityCone, and adds linf * t_linf to the objective penalty via add_to_objective_penalty!.
  • args...: No-op, used when no L∞ regularisation is configured.

Related

source
PortfolioOptimisers.squared_norm_radius_msgFunction
squared_norm_radius_msg(alg) -> String

Build the refusal message of assert_ambiguity_radius_formulation, naming the formulation that was paired with a rule.

The message is written once and every refusing method reads it, so the two formulations that refuse a radius today, and any that a later type adds, all say the same thing. The formulation and the rule family are named with nameof, not printed, for the reason assert_calibrated_slots states: a printed type carries a module prefix wherever the name is not visible from Main.

Arguments

  • alg: The formulation that penalises the squared norm.

Returns

  • msg::String: The refusal message.

Related

source
PortfolioOptimisers.assert_ambiguity_radius_formulationFunction
assert_ambiguity_radius_formulation(val, alg) -> Nothing

Refuse an ambiguity radius that was placed beside a formulation which penalises the squared norm.

L2Regularisation emits val * norm(w, 2) under SOCRiskExpr, and val * norm(w, 2)^2 under the three others. Only the first is the Wasserstein form, so only the first reads val as a radius. A plain number stays legal with every formulation, because a number is whatever coefficient the caller meant it to be. A rule of the radius family states that the number is a radius, and that statement is false beside a squared penalty.

The check is a set of methods rather than a list of types in one body, so a formulation that a later change adds refuses a radius by adding one method here. The permissive fallback is the first method, and each refusing method names one formulation of the family.

Algorithm

  1. The pair is anything but a radius rule beside a squared formulation: return nothing.
  2. A radius rule stands beside a VarianceFormulation, which covers QuadRiskExpr and SquaredSOCRiskExpr: refuse.
  3. A radius rule stands beside an RSOCRiskExpr: refuse.

Arguments

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_penalty_coefficient_roleFunction
assert_penalty_coefficient_role(x) -> Nothing

Refuse a norm-ceiling rule that was placed in a slot which reads its number as a penalty coefficient.

The val field of LpRegularisation is the one dual-use slot in the library. JuMPOptimiser's lp field adds val * norm(w, p) to the objective, where val is an ambiguity radius, and its lpc field bounds norm(w, p) <= val * k, where val is a norm ceiling. One field cannot carry two bounds, so Num_AmbRadNormCeilCal admits both rule families and the slot's owner settles which reading is legal.

This is the penalty half. A rule of the norm-ceiling family states that the number is a ceiling, and a ceiling has no reading as a penalty coefficient: its reciprocal is a floor on the effective number of assets, which says nothing about how strongly the objective should shrink the weights. A plain number stays legal on both routes, because a number is whatever quantity the caller meant it to be.

The check is a set of methods rather than a list of types in one body, on the same terms as assert_ambiguity_radius_formulation. It runs in JuMPOptimiser's constructor, where the caller wrote the field, and again in factory for a term that reached the objective by another route.

Algorithm

  1. The occupant is anything but a norm-ceiling role: return nothing.
  2. The occupant is an LpRegularisation: check its val.
  3. The occupant is a vector of them: check each.
  4. The occupant is an AbstractNormCeilingCalibrationAlgorithm: refuse.

Arguments

  • x: A regularisation term, a vector of them, or the occupant of a val slot.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_norm_ceiling_roleFunction
assert_norm_ceiling_role(x) -> Nothing

Refuse an ambiguity-radius rule that was placed in a slot which reads its number as a norm ceiling.

This is the norm-constraint half of the pair assert_penalty_coefficient_role opens, and it carries that method's reading unchanged. A rule of the ambiguity-radius family states that the number is a radius, the coefficient of a norm penalty in the objective. JuMPOptimiser.lpc bounds the norm instead, so the statement is false there.

Algorithm

  1. The occupant is anything but a radius role: return nothing.
  2. The occupant is an LpRegularisation: check its val.
  3. The occupant is a vector of them: check each.
  4. The occupant is an AbstractAmbiguityRadiusCalibrationAlgorithm: refuse.

Arguments

  • x: A norm-constraint term, a vector of them, or the occupant of a val slot.

Returns

  • nothing.

Related

source
PortfolioOptimisers.norm_ceiling_factoryFunction
norm_ceiling_factory(x, pr::AbstractPriorResult, slv = nothing)

Resolve the norm ceiling in val against prior result pr, and return an LpRegularisation holding the number.

It is the norm-constraint counterpart of factory on the same type, and it is a second verb because the two routes read one field as two quantities. A factory call on the lpc field would refuse the ceiling rule that belongs there, and would resolve the rule under :lpreg_val, the key of the penalty slot.

The two verbs differ in the guard and in the key, and in nothing else. Each refuses the family that has no reading on its own route, this one through assert_norm_ceiling_role. Each states the term's own norm order in the CalibrationContext it resolves the slot against, because one rule placed in lp or in lpc serves every term and each term carries its own p.

The fallback carries its argument through unchanged, which is the route nothing takes. A term whose val is already a number is returned by identity instead, because the resolution gives back the number it holds.

Algorithm

  1. The argument is neither a term nor a vector of them: return it unchanged.
  2. The argument is a vector of terms: resolve each of them, and return the vector of the results.
  3. The argument is one term: refuse a radius rule with assert_norm_ceiling_role.
  4. State the term's own p in a CalibrationContext, then resolve the slot under the key :lpc against it, giving val.
  5. Pair the declaration with this resolver through assert_declared_calibration_resolver, which refuses the declared slot when the resolution above did not reach it.
  6. val is the number the term already holds: return the term itself. Otherwise rebuild the term through the keyword constructor.

Arguments

  • x: The norm-constraint term, or a vector of them.
  • pr: Prior result the rule reads.
  • slv: Effective solver, or nothing.

Returns

  • The term, or the vector of terms, with each val holding a number.

Related

source

References

[131]
V. DeMiguel, L. Garlappi, F. J. Nogales and R. Uppal. A Generalized Approach to Portfolio Optimization: Improving Performance by Constraining Portfolio Norms. Management Science 55, 798–812 (2009).