Weight bounds constraints

PortfolioOptimisers.WeightBoundsType
struct WeightBounds{__T_lb, __T_ub} <: AbstractConstraintResult

Bounds 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) -> WeightBounds

Keywords 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, IsEmptyError otherwise.
  • Two vector bounds have the same length, DimensionMismatch otherwise.
  • lb is not above ub, entry by entry where both are vectors, DomainError otherwise.
  • A nothing on one side leaves the other side's non-emptiness as the only check, because no comparison is possible.
  • Two nothing bounds are checked by nothing.
  • Two infinite scalar bounds of the same sign pass, because the comparison holds. WeightBounds(; lb = Inf, ub = Inf) and WeightBounds(; 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:

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

source
PortfolioOptimisers.UniformValuesType
struct UniformValues <: VectorAbstractEstimatorValueAlgorithm

Fills 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

source
PortfolioOptimisers.WeightBoundsEstimatorType
struct WeightBoundsEstimator{__T_lb, __T_ub, __T_dlb, __T_dub} <: AbstractConstraintEstimator

Resolves 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) -> WeightBoundsEstimator

Keywords correspond to the struct's fields.

Validation

  • If lb or ub is a AbstractDict or AbstractVector, it must be non-empty, IsEmptyError otherwise.
  • Two vector bounds must have the same length, DimensionMismatch otherwise, through validate_bounds.
  • Where both bounds are numbers or vectors of numbers, lb is not above ub, through validate_bounds, DomainError otherwise.
  • Where one side is a AbstractDict, a Pair or an algorithmic rule, the two sides are not compared here, because neither side is resolved yet. weight_bounds_constraints builds a WeightBounds from the resolved pair, and that constructor compares them.
  • If neither dlb nor dub is nothing, dlb <= dub, DomainError otherwise.

View parameters

When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:

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 ┴ nothing

Related

source
PortfolioOptimisers.weight_bounds_constraintsFunction
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

  1. Resolve the lower side with estimator_to_val, giving lb. The fill value for an asset the estimator does not name is wb.dlb, or zero(datatype) when wb.dlb is nothing.
  2. Resolve the upper side the same way, giving ub. The fill value is wb.dub, or one(datatype) when wb.dub is nothing.
  3. Build WeightBounds(; lb = lb, ub = ub). That constructor runs validate_bounds on the resolved pair, which is where a AbstractDict bound on one side and a scalar on the other are first compared.

Arguments

  • wb: WeightBoundsEstimator specifying lower and upper bounds.
  • sets: UniverseSets containing asset names or indices.
  • strict: If true, enforces strict matching between assets and bounds (throws error on mismatch); if false, 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 the WeightBounds constructor of step 3. A resolved lb above a resolved ub raises DomainError there, and a resolved vector of the wrong length raises DimensionMismatch inside estimator_to_val.

Returns

  • wb::WeightBounds: Object containing lower and upper bounds aligned with sets.

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

source
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

  1. Expand the lower side with weight_bounds_constraints_side(wb.lb, N, -Inf), giving lb. The free bound is -Inf in datatype, so a side with no bound lands in the type of the data rather than in Float64.
  2. Expand the upper side with weight_bounds_constraints_side(wb.ub, N, Inf), giving ub, with the free bound Inf in datatype.
  3. Build WeightBounds(; lb = lb, ub = ub), which validates the expanded pair.

Arguments

  • wb: WeightBounds object 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, DimensionMismatch otherwise, through weight_bounds_constraints_side.
  • The expanded pair passes validate_bounds, through the WeightBounds constructor of step 3. With the default N = 0 both sides expand to an empty vector and that constructor raises IsEmptyError.

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

source
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

  1. When N is not zero, check length(wb.lb) == N. The constructor of wb already held the two sides to one length.
  2. Return wb.

Arguments

  • wb: WeightBounds object 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, DimensionMismatch otherwise.

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

source
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

  1. Fill both sides to length N, -Inf on the lower side and Inf on the upper side, both in datatype, so the free bounds land in the type of the data rather than in Float64.
  2. 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 the WeightBounds constructor of step 2. With the default N = 0 both sides are empty and that constructor raises IsEmptyError.

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

source