Threshold Constraints
PortfolioOptimisers.ThresholdEstimator — Type
struct ThresholdEstimator{__T_val, __T_key, __T_dval} <: AbstractConstraintEstimatorResolves a minimum-holding threshold written in asset or group names against a universe.
threshold_constraints turns it into a Threshold: every name is mapped to its indices in the universe key selects, and an unnamed asset takes dval, which defaults to no threshold. A threshold may also be a scalar, a vector, or an algorithmic rule such as UniformValues.
The universe key is a field here, and estimator_to_val resolves a nothing key to sets.xkey. RiskBudgetEstimator is the same shape with the key taken positionally by its verb instead. A dval of nothing is passed straight through and becomes zero(datatype), so an unnamed asset carries no threshold — where a risk budget's nothing default becomes the uniform share of its axis.
Fields
val: Asset-specific minimum-holding threshold value(s).
key: Key to specify the universe insets.dictthat names resolve against. Ifnothing, the key is taken fromsets.xkey— or, where the caller is written against another declared axis, from that axis' key.
dval: Default value for assets not specified inval.
Constructors
ThresholdEstimator(; val::EstValType{<:VectorAbstractEstimatorValueAlgorithm}, key::Option{<:AbstractString} = nothing, dval::Option{<:Number} = nothing) -> ThresholdEstimatorKeywords correspond to the struct's fields.
Validation
valanddvalare both validated withassert_nonempty_nonneg_finite_val, so a threshold is non-empty, non-negative and finite.- If
keyis notnothing, it is a non-empty string.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
val: Sliced to the selected indices viaport_opt_view.
Only a vector val is sliced. A val 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> ThresholdEstimator(; val = Dict("A" => 0.05, "B" => 0.1))ThresholdEstimator val ┼ Dict{String, Float64}: Dict("B" => 0.1, "A" => 0.05) key ┼ nothing dval ┴ nothingjulia> ThresholdEstimator(; val = "A" => 0.05)ThresholdEstimator val ┼ Pair{String, Float64}: "A" => 0.05 key ┼ nothing dval ┴ nothingjulia> ThresholdEstimator(; val = 0.05)ThresholdEstimator val ┼ Float64: 0.05 key ┼ nothing dval ┴ nothingjulia> ThresholdEstimator(; val = [0.05])ThresholdEstimator val ┼ Vector{Float64}: [0.05] key ┼ nothing dval ┴ nothingjulia> ThresholdEstimator(; val = UniformValues())ThresholdEstimator val ┼ UniformValues() key ┼ nothing dval ┴ nothingRelated
ThresholdEstValTypethreshold_constraintsAbstractConstraintEstimatorUniverseSetsport_opt_viewRiskBudgetEstimator: the same shape with the universe key taken positionally by its verb.
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.4.
PortfolioOptimisers.Threshold — Type
struct Threshold{__T_val} <: AbstractConstraintResultForces every held position to reach a minimum size, and drives anything smaller to zero.
The threshold is a scalar shared by every asset or a vector of one value per asset. It exists to keep a mixed-integer model from answering with a long tail of positions too small to trade. The source writes the same constraint over positive and negative trades against a reference portfolio; this library writes it over the position, so no reference portfolio enters it.
There is no upper bound on a threshold, and a threshold above the largest weight the budget admits makes the asset unholdable. On a three-asset long-only variance model with a cardinality cap of three, Threshold(0.15) held all three and Threshold(0.5) held two at exactly 0.5 each, while Threshold(1.5) returned an OptimisationFailure whose solver status is INFEASIBLE — the budget row forces the weights to sum to one, and no held weight can reach 1.5. Use a threshold above one to state that an asset must not be held only when the model has some other way to satisfy its budget.
Mathematical definition
The threshold is the lower half of a buy-in constraint, stated against the held binary:
\[\begin{align} \underset{\boldsymbol{w}}{\mathrm{opt}}\quad & \phi(\boldsymbol{w})\\ \textrm{s.t.}\quad & \ell_i z_i \leq w_i \leq u_i z_i\,,\quad \forall i = 1,\ldots,N\,,\\ & \boldsymbol{z} \in \{0, 1\}^{N}\,,\quad \boldsymbol{w} \in \mathcal{W}\,. \end{align}\]
Where:
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $\boldsymbol{z}$: Held binary, one entry per asset.
- $\ell_i$: Minimum-holding threshold for asset $i$, the
valfield. - $u_i$: Upper weight bound for asset $i$, from
WeightBounds. - $N$: Number of assets.
- $\phi$: Objective function of the optimiser.
- $\mathcal{W}$: Rest of the feasible set.
The binary carries both halves. Where $z_i = 0$ the two bounds collapse to $w_i = 0$; where $z_i = 1$ the position must reach $\ell_i$. A long and a short threshold are separate objects, each bound to its own side's binary.
A threshold above the corresponding $u_i$ admits only $z_i = 0$, because $\ell_i z_i \leq w_i \leq u_i z_i$ is infeasible for $z_i = 1$. So the pair of bounds carries the exclusion as well as the minimum, and no separate row states it.
Fields
val: Minimum-holding threshold(s) on the portfolio weights. A held position must reach its threshold; a position below it is driven to zero. The threshold binds the held weight, never the trade, so a reference portfolio does not enter it.
Constructors
Threshold( val::Num_VecNum) -> ThresholdThreshold(; val::Num_VecNum) -> ThresholdKeywords correspond to the struct's fields.
Validation
valis validated withassert_nonempty_nonneg_finite_val, so a threshold is non-empty, non-negative and finite. No upper bound is checked, and a value above one is admitted.
Both constructors run the same check, because the positional form is the inner one and the keyword form forwards to it.
View parameters
When port_opt_view is called on this type, the following @vprop-tagged fields are automatically subset to the selected indices:
val: Sliced to the selected indices viaport_opt_view.
Examples
julia> Threshold(0.05)Threshold val ┴ Float64: 0.05julia> Threshold([0.05, 0.1, 0.0])Threshold val ┴ Vector{Float64}: [0.05, 0.1, 0.0]Related
short_mip_threshold_constraintsmip_constraintsset_mip_constraints!ThresholdEstimatorthreshold_constraintsAbstractConstraintResultWeightBoundsport_opt_view
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.4.
PortfolioOptimisers.threshold_constraints — Function
threshold_constraints(t::Option{<:Threshold}, args...; kwargs...)Propagate or pass through buy-in threshold portfolio constraints.
threshold_constraints returns the input Threshold object or nothing unchanged. This method is used to propagate already constructed buy-in threshold constraints, enabling composability and uniform interface handling in constraint generation workflows.
It builds nothing and allocates nothing: the returned object is the same object, so threshold_constraints(t) === t holds. Accepting nothing is what lets VecOptBtE_Bt's broadcast carry an empty block through untouched.
Algorithm
- Return
t. The method reads none of its other arguments and none of its keywords.
Arguments
t: An existingThresholdobject ornothing.args...: Additional positional arguments (ignored).kwargs...: Additional keyword arguments (ignored).
Returns
bt::Option{<:Threshold}: The input constraint object, unchanged.
Examples
julia> threshold_constraints(Threshold(0.05))Threshold val ┴ Float64: 0.05julia> threshold_constraints(nothing)Related
threshold_constraints(t::ThresholdEstimator, sets::UniverseSets;
datatype::DataType = Float64, strict::Bool = false)Generate buy-in threshold portfolio constraints from a ThresholdEstimator and asset set.
threshold_constraints constructs a Threshold object representing minimum allocation thresholds for the assets in sets, using the specifications in t. Supports scalar, vector, dictionary, pair, or custom threshold types for flexible assignment and validation.
It does not normalise the resolved vector, unlike risk_budget_constraints's estimator branch. A threshold is a bound on one weight and not a share of a total, so scaling the vector changes the model.
Algorithm
- Resolve
t.valagainstsetswithestimator_to_val, against the universet.keyselects, orsets.xkey's universe whent.keyisnothing. The fill value for an asset thatt.valdoes not name ist.dval, orzero(datatype)whent.dvalisnothing, which is no threshold. - Wrap the resolved vector in a
Threshold, which checks that it is non-empty, non-negative and finite.
Arguments
t:ThresholdEstimatorspecifying asset-specific threshold values.sets:UniverseSetscontaining asset names or indices.datatype: Output data type for thresholds.strict: Iftrue, a name int.valthatsetsdoes not resolve throws; iffalse, it issues a warning and is skipped.
Validation
strictgoverns an unresolvable name, not an unnamed asset. A name int.valthat names neither an asset nor a group raisesArgumentErrorwhenstrictistrue, and issues a warning otherwise. An unnamed asset always takes the default of step 1 and never throws.- The resolved vector passes
assert_nonempty_nonneg_finite_valthrough theThresholdconstructor of step 2.
Returns
bt::Threshold: Object containing threshold values aligned withsets.
Examples
julia> sets = UniverseSets(; dict = Dict("nx" => ["A", "B", "C"]));julia> t = ThresholdEstimator(Dict("A" => 0.05, "B" => 0.1));julia> threshold_constraints(t, sets)Threshold val ┴ Vector{Float64}: [0.05, 0.1, 0.0]Related
threshold_constraints(t::VecOptBtE_Bt, sets::UniverseSets;
kwargs...)Broadcasts threshold_constraints over the vector.
Provides a uniform interface for processing multiple constraint estimators simultaneously. Each entry is resolved by the method its own type selects, so an estimator resolves, a Threshold passes through and a nothing stays nothing. The result is a VecOptBt of the same length and the same order, which is what a scenario-block target needs: entry i belongs to block i, so an empty block must survive as nothing rather than be dropped.
This is the difference from risk_budget_constraints, which has no vector method. The threshold targets :slt, :sst, :sglt and :sgst are in the set of routing targets that accumulate and hold a positional list, and a risk budget's target :rkb is not. RkbE_Rkb states the same decision from the other side.
Algorithm
- For each entry
tioft, in order, callthreshold_constraints(ti, sets; kwargs...). - Collect the results into a vector of the same length.
Arguments
t: AVecOptBtE_Bt, one entry per scenario or group block. An entry is aThresholdEstimator, aThresholdornothing.sets:UniverseSetscontaining asset names or indices.kwargs...: Additional keyword arguments forwarded to each entry's method.
Returns
bt::VecOptBt: One resolvedThresholdornothingper entry oft, in the order oft.
Examples
julia> sets = UniverseSets(; dict = Dict("nx" => ["A", "B", "C"]));julia> t = Union{Nothing, PortfolioOptimisers.BtE_Bt}[nothing, Threshold(0.05), ThresholdEstimator(; val = Dict("A" => 0.1))];julia> bt = threshold_constraints(t, sets);julia> length(bt), isnothing(bt[1]), bt[2].val, bt[3].val(3, true, 0.05, [0.1, 0.0, 0.0])Related
VecOptBtE_BtVecOptBtThresholdThresholdEstimatorthreshold_constraintsRkbE_Rkb: the risk budget family, which is singular by the same decision.
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).