Weight bounds constraints
PortfolioOptimisers.WeightBounds — Type
struct WeightBounds{__T_lb, __T_ub} <: AbstractConstraintResultBounds every portfolio weight between a lower and an upper limit.
A bound is a scalar shared by every asset, a vector of one limit per asset, or nothing for no limit in that direction. The bounds also serve the mixed-integer builders, which read them as the big-M that links a weight to its held indicator.
Mathematical definition
\[\begin{align} \boldsymbol{l} \leq \boldsymbol{w} \leq \boldsymbol{u}\,. \end{align}\]
Where:
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $\boldsymbol{l}$: Lower bound vector $N \times 1$. An entry of $-\infty$ leaves that weight unbounded below, and an entry of $+\infty$ admits no weight at all.
- $\boldsymbol{u}$: Upper bound vector $N \times 1$. An entry of $+\infty$ leaves that weight unbounded above, and an entry of $-\infty$ admits no weight at all.
- $N$: Number of assets.
A scalar bound is the case in which every entry of the vector holds the same value, and a nothing bound is the case in which every entry of that side is the infinity that leaves the weight free.
Fields
lb: Lower bound.
ub: Upper bound.
Constructors
WeightBounds( lb::Option{<:Num_VecNum}, ub::Option{<:Num_VecNum}) -> WeightBoundsWeightBounds(; lb::Option{<:Num_VecNum} = 0.0, ub::Option{<:Num_VecNum} = 1.0) -> WeightBoundsKeywords correspond to the struct's fields. Only the keyword form carries the defaults, and it forwards to the positional form, so both run the same checks.
Validation
validate_bounds runs on the pair, and the pair of types decides which of the checks below runs.
- A vector bound is non-empty,
IsEmptyErrorotherwise. - Two vector bounds have the same length,
DimensionMismatchotherwise. lbis not aboveub, entry by entry where both are vectors,DomainErrorotherwise.- A
nothingon one side leaves the other side's non-emptiness as the only check, because no comparison is possible. - Two
nothingbounds are checked by nothing. - Two infinite scalar bounds of the same sign pass, because the comparison holds.
WeightBounds(; lb = Inf, ub = Inf)andWeightBounds(; lb = -Inf, ub = -Inf)are both admitted, and both admit no weight.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
lb: Sliced to the selected indices viaport_opt_view.ub: Sliced to the selected indices viaport_opt_view.
A field that weight_bounds_constraints expanded to a constant range is sliced as any other vector is, and the slice is a view of that range.
Examples
julia> WeightBounds(0.0, 1.0)WeightBounds lb ┼ Float64: 0.0 ub ┴ Float64: 1.0julia> WeightBounds([0.0, 0.1], [0.8, 1.0])WeightBounds lb ┼ Vector{Float64}: [0.0, 0.1] ub ┴ Vector{Float64}: [0.8, 1.0]Related
w_neg_flagw_finite_flagset_weight_constraints!set_linear_weight_constraints!WeightBoundsEstimatorweight_bounds_constraintsvalidate_bounds: the seven methods that carry this type's checks.port_opt_view
PortfolioOptimisers.UniformValues — Type
struct UniformValues <: VectorAbstractEstimatorValueAlgorithmFills every entry of a value vector with 1/N, where N is the number of assets in the universe.
The same value is produced whatever slot the algorithm sits in. lb = UniformValues() floors every weight at the equal-weight level and ub = UniformValues() caps every weight there. Neither slot is a special case in estimator_to_val.
Mathematical definition
\[\begin{align} v_i &= \frac{1}{N}\,, \quad i = 1,\, \ldots,\, N\,. \end{align}\]
Where:
- $v_i$: Entry $i$ of the value vector.
- $N$: Number of assets.
The entries sum to one, so the vector is the equal-weight portfolio whenever the slot it fills is a set of weights.
Examples
julia> sets = UniverseSets(; dict = Dict("nx" => ["A", "B", "C"]));julia> PortfolioOptimisers.estimator_to_val(UniformValues(), sets)StepRangeLen(0.3333333333333333, 0.0, 3)Related
PortfolioOptimisers.WeightBoundsEstimator — Type
struct WeightBoundsEstimator{__T_lb, __T_ub, __T_dlb, __T_dub} <: AbstractConstraintEstimatorResolves weight bounds written in asset or group names against a universe.
weight_bounds_constraints turns it into a WeightBounds: every name is mapped to its indices in the universe, and an unnamed asset takes dlb or dub. A bound may also be a scalar, a vector, or an algorithmic rule such as UniformValues. A dlb or a dub left at nothing is filled at resolution time, with zero(datatype) on the lower side and one(datatype) on the upper side.
Fields
lb: Lower bound.
ub: Upper bound.
dlb: Default lower bound.
dub: Default upper bound.
Constructors
WeightBoundsEstimator(; lb::Option{<:EstValType{<:VectorAbstractEstimatorValueAlgorithm}} = 0.0, ub::Option{<:EstValType{<:VectorAbstractEstimatorValueAlgorithm}} = 1.0, dlb::Option{<:Number} = nothing, dub::Option{<:Number} = nothing) -> WeightBoundsEstimatorKeywords correspond to the struct's fields.
Validation
- If
lborubis aAbstractDictorAbstractVector, it must be non-empty,IsEmptyErrorotherwise. - Two vector bounds must have the same length,
DimensionMismatchotherwise, throughvalidate_bounds. - Where both bounds are numbers or vectors of numbers,
lbis not aboveub, throughvalidate_bounds,DomainErrorotherwise. - Where one side is a
AbstractDict, aPairor an algorithmic rule, the two sides are not compared here, because neither side is resolved yet.weight_bounds_constraintsbuilds aWeightBoundsfrom the resolved pair, and that constructor compares them. - If neither
dlbnordubisnothing,dlb <= dub,DomainErrorotherwise.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
lb: Sliced to the selected indices viaport_opt_view.ub: Sliced to the selected indices viaport_opt_view.
Only a vector bound is sliced. A bound that is a scalar, a Dict, a Pair or an algorithmic rule is not indexed by asset, so a view passes it through untouched and it resolves against the viewed universe when the estimator runs.
Examples
julia> WeightBoundsEstimator(; lb = Dict("A" => 0.1, "B" => 0.2), ub = Dict("A" => 0.8, "B" => 0.9))WeightBoundsEstimator lb ┼ Dict{String, Float64}: Dict("B" => 0.2, "A" => 0.1) ub ┼ Dict{String, Float64}: Dict("B" => 0.9, "A" => 0.8) dlb ┼ nothing dub ┴ nothingjulia> WeightBoundsEstimator(; lb = UniformValues(), ub = nothing)WeightBoundsEstimator lb ┼ UniformValues() ub ┼ nothing dlb ┼ nothing dub ┴ nothingRelated
WeightBoundsweight_bounds_constraintsvalidate_bounds: the checks this constructor runs on a pair of numbers or a pair of vectors.port_opt_view
PortfolioOptimisers.weight_bounds_constraints — Function
weight_bounds_constraints(wb::WeightBoundsEstimator, sets::UniverseSets; strict::Bool = false,
datatype::DataType = Float64, kwargs...)Generate portfolio weight bounds constraints from a WeightBoundsEstimator and asset set.
weight_bounds_constraints constructs a WeightBounds object representing lower and upper portfolio weight bounds for the assets in sets, using the specifications in wb. A bound may be a scalar, a vector, a dictionary, a pair, or an algorithmic rule, so one estimator carries both a universe-wide limit and an asset-specific one. A bound that is nothing states no limit in that direction, and survives the resolution as nothing.
Algorithm
- Resolve the lower side with
estimator_to_val, givinglb. The fill value for an asset the estimator does not name iswb.dlb, orzero(datatype)whenwb.dlbisnothing. - Resolve the upper side the same way, giving
ub. The fill value iswb.dub, orone(datatype)whenwb.dubisnothing. - Build
WeightBounds(; lb = lb, ub = ub). That constructor runsvalidate_boundson the resolved pair, which is where aAbstractDictbound on one side and a scalar on the other are first compared.
Arguments
wb:WeightBoundsEstimatorspecifying lower and upper bounds.sets:UniverseSetscontaining asset names or indices.strict: Iftrue, enforces strict matching between assets and bounds (throws error on mismatch); iffalse, issues a warning.datatype: Output data type for bounds.kwargs...: Additional keyword arguments passed to bound extraction routines.
Validation
- The resolved pair passes
validate_bounds, through theWeightBoundsconstructor of step 3. A resolvedlbabove a resolvedubraisesDomainErrorthere, and a resolved vector of the wrong length raisesDimensionMismatchinsideestimator_to_val.
Returns
wb::WeightBounds: Object containing lower and upper bounds aligned withsets.
Examples
julia> sets = UniverseSets(; dict = Dict("nx" => ["A", "B", "C"]));julia> wb = WeightBoundsEstimator(; lb = Dict("A" => 0.1, "B" => 0.2), ub = 1.0);julia> weight_bounds_constraints(wb, sets)WeightBounds lb ┼ Vector{Float64}: [0.1, 0.2, 0.0] ub ┴ Float64: 1.0julia> wb = WeightBoundsEstimator(; lb = Dict("A" => 0.1), ub = Dict("A" => 0.5), dlb = 0.02, dub = 0.7);julia> weight_bounds_constraints(wb, sets)WeightBounds lb ┼ Vector{Float64}: [0.1, 0.02, 0.02] ub ┴ Vector{Float64}: [0.5, 0.7, 0.7]Related
weight_bounds_constraints(wb::WeightBounds{<:Any, <:Any}, args...; N::Integer = 0,
datatype::DataType = Float64, kwargs...)Expand portfolio weight bounds constraints from a WeightBounds object to length N.
weight_bounds_constraints expands a scalar, nothing or infinite bound to a vector or range of length N using weight_bounds_constraints_side, so every bound reaches the model per asset. A vector bound is passed through unchanged, and its length is checked against N.
N is required in practice. The default N = 0 builds two empty bound vectors, and WeightBounds's own validation then throws IsEmptyError: lb cannot be empty.
Algorithm
- Expand the lower side with
weight_bounds_constraints_side(wb.lb, N, -Inf), givinglb. The free bound is-Infindatatype, so a side with no bound lands in the type of the data rather than inFloat64. - Expand the upper side with
weight_bounds_constraints_side(wb.ub, N, Inf), givingub, with the free boundInfindatatype. - Build
WeightBounds(; lb = lb, ub = ub), which validates the expanded pair.
Arguments
wb:WeightBoundsobject containing lower and upper bounds.args...: Additional positional arguments (ignored).N: Number of assets, the length of the expansion.datatype: Type of the free bound a side with no bound expands to.kwargs...: Additional keyword arguments (ignored).
Validation
- A vector bound has length
N,DimensionMismatchotherwise, throughweight_bounds_constraints_side. - The expanded pair passes
validate_bounds, through theWeightBoundsconstructor of step 3. With the defaultN = 0both sides expand to an empty vector and that constructor raisesIsEmptyError.
Returns
wb::WeightBounds: Expanded bounds object.
Examples
julia> weight_bounds_constraints(WeightBounds(0.0, 1.0); N = 3)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)julia> weight_bounds_constraints(WeightBounds([0.1, 0.2, 0.3], 1.0); N = 3)WeightBounds lb ┼ Vector{Float64}: [0.1, 0.2, 0.3] ub ┴ StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}: StepRangeLen(1.0, 0.0, 3)Related
weight_bounds_constraints(wb::WeightBounds{<:VecNum, <:VecNum}, args...; N::Integer = 0,
kwargs...)Propagate asset-specific portfolio weight bounds constraints from a WeightBounds object with vector bounds.
weight_bounds_constraints returns the input WeightBounds object unchanged when both lower and upper bounds are provided as vectors. This method is used to propagate explicit per-asset bounds in constraint generation workflows, ensuring that asset-specific constraints are preserved.
Algorithm
- When
Nis not zero, checklength(wb.lb) == N. The constructor ofwbalready held the two sides to one length. - Return
wb.
Arguments
wb:WeightBoundsobject with vector lower and upper bounds.args...: Additional positional arguments (ignored).N: Number of assets. A zero states that the caller knows no asset count, and then the length is not checked.kwargs...: Additional keyword arguments (ignored).
Validation
iszero(N) || length(wb.lb) == N,DimensionMismatchotherwise.
Returns
wb::WeightBounds: The input bounds object, unchanged.
Examples
julia> weight_bounds_constraints(WeightBounds([0.1, 0.2, 0.3], [0.8, 0.9, 1.0]))WeightBounds lb ┼ Vector{Float64}: [0.1, 0.2, 0.3] ub ┴ Vector{Float64}: [0.8, 0.9, 1.0]Related
weight_bounds_constraints(wb::Nothing, args...; N::Integer = 0,
datatype::DataType = Float64, kwargs...)Generate unconstrained portfolio weight bounds when no bounds are specified.
weight_bounds_constraints returns a WeightBounds object with lower bounds set to -Inf and upper bounds set to Inf for all assets when wb is nothing. N is required in practice. The default N = 0 builds two empty vectors, and WeightBounds's own validation then throws IsEmptyError: lb cannot be empty.
Algorithm
- Fill both sides to length
N,-Infon the lower side andInfon the upper side, both indatatype, so the free bounds land in the type of the data rather than inFloat64. - Build
WeightBounds(; lb = lb, ub = ub), which validates the pair.
Arguments
wb::Nothing: Indicates no constraint for portfolio weights.args...: Additional positional arguments (ignored).N::Integer: Number of assets, the length of the two bound vectors.datatype: Type of the two free bounds.kwargs...: Additional keyword arguments (ignored).
Validation
- The pair passes
validate_bounds, through theWeightBoundsconstructor of step 2. With the defaultN = 0both sides are empty and that constructor raisesIsEmptyError.
Returns
wb::WeightBounds: Object with unconstrained lower and upper bounds.
Examples
julia> weight_bounds_constraints(nothing; N = 3)WeightBounds lb ┼ Vector{Float64}: [-Inf, -Inf, -Inf] ub ┴ Vector{Float64}: [Inf, Inf, Inf]Related