Base JuMP Optimisation: private API
PortfolioOptimisers.BaseJuMPOptimisationEstimator — Type
abstract type BaseJuMPOptimisationEstimator <: BaseOptimisationEstimatorAbstract supertype for base JuMP-based portfolio optimisation estimators.
These are configuration-level types (e.g., JuMPOptimiser) that define the optimisation problem setup for JuMP-based optimisers.
Related
PortfolioOptimisers.JuMPOptimisationEstimator — Type
abstract type JuMPOptimisationEstimator <: NonFiniteAllocationOptimisationEstimatorAbstract supertype for JuMP-based portfolio optimisation estimators.
JuMP optimisers formulate and solve portfolio optimisation problems using mathematical programming via the JuMP.jl framework.
Related
PortfolioOptimisers.RiskJuMPOptimisationEstimator — Type
abstract type RiskJuMPOptimisationEstimator <: JuMPOptimisationEstimatorAbstract supertype for risk-based JuMP portfolio optimisation estimators.
Subtype RiskJuMPOptimisationEstimator to implement optimisers that minimise or constrain risk measures as the primary objective.
Related
PortfolioOptimisers.ObjectiveFunction — Type
abstract type ObjectiveFunction <: AbstractEstimatorAbstract supertype for portfolio objective functions.
Subtype ObjectiveFunction to implement portfolio optimisation objectives such as minimum risk, maximum return, or maximum Sharpe ratio.
The four concrete children are the source's four classic objective functions, one per subsection.
Related
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 8.2.
PortfolioOptimisers.JuMPReturnsEstimator — Type
abstract type JuMPReturnsEstimator <: AbstractEstimatorAbstract supertype for JuMP-based returns estimators used in optimisation models.
JuMPReturnsEstimator types define how expected returns are incorporated into JuMP models.
The two children are the source's two return definitions: the arithmetic return of Section 8.1.1 and the geometric return of Section 8.1.2.
Related
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 8.1.
PortfolioOptimisers.JuMPConstraintEstimator — Type
abstract type JuMPConstraintEstimator <: AbstractConstraintEstimatorAbstract supertype for JuMP constraint estimators.
The extension point for user-defined constraints and objectives. Rather than subtyping this directly, subtype one of the two purpose-built children and implement its one contract method:
CustomJuMPConstraint⇒ implementadd_custom_constraint!, supply viaJuMPOptimiser'sccnt.CustomJuMPObjective⇒ implementadd_custom_objective_term!, supply viaJuMPOptimiser'scobj.
(The objective child subtypes AbstractEstimator directly — it is grouped here as the sibling extension point, not by type hierarchy.)
Related
PortfolioOptimisers.JuMPConstr_VecJuMPConstr — Type
const JuMPConstr_VecJuMPConstr = Union{<:CustomJuMPConstraint, <:VecJuMPConstr}Alias for a single JuMP constraint estimator or a vector of them.
Related
PortfolioOptimisers.JuMPObj_VecJuMPObj — Type
const JuMPObj_VecJuMPObj = Union{<:CustomJuMPObjective, <:VecJuMPObj}Alias for a single JuMP objective estimator or a vector of them.
Related
PortfolioOptimisers.VecJuMPOptSol — Type
const VecJuMPOptSol = AbstractVector{<:JuMPOptimisationSolution}Alias for a vector of JuMP optimisation solutions.
Related
PortfolioOptimisers.JuMPOptSol_VecJuMPOptSol — Type
const JuMPOptSol_VecJuMPOptSol = Union{<:JuMPOptimisationSolution, <:VecJuMPOptSol}Alias for a single JuMP optimisation solution or a vector of them.
Related
PortfolioOptimisers.BaseJuMPOptimisationResult — Type
abstract type BaseJuMPOptimisationResult <: AbstractResultAbstract supertype for the embedded JuMP optimisation result core.
Mirrors BaseJuMPOptimisationEstimator: the factored-out struct holding the fields common to every JuMP-based optimisation result lives on this branch and is not part of the optimisation result hierarchy. The concrete core is JuMPOptimisationResult.
Related
PortfolioOptimisers.RiskJuMPOptimisationResult — Type
abstract type RiskJuMPOptimisationResult <: NonFiniteAllocationOptimisationResultAbstract supertype for JuMP-based continuous optimisation results that carry a risk measure.
One of the two JuMP result halves; mirrors RiskJuMPOptimisationEstimator. The sibling half is NonRiskJuMPOptimisationResult, for the JuMP results that carry no risk measure at all. Concrete subtypes embed a JuMPOptimisationResult as their first field (jr) and add only their unique fields plus the trailing fb. Every subtype carries a resolved r; a JuMP result with no r belongs on the sibling branch. The default getproperty resolves unique fields directly and delegates everything else (including :w and the pa fall-through) to jr; types with composed sub-result fields override it to forward into those first.
Related
PortfolioOptimisers.NonRiskJuMPOptimisationResult — Type
abstract type NonRiskJuMPOptimisationResult <: NonFiniteAllocationOptimisationResultAbstract supertype for JuMP-based continuous optimisation results that carry no risk measure.
The sibling half of RiskJuMPOptimisationResult. A relaxed risk budgeting run builds its constraints straight from pr.sigma and never resolves a measure, so its result has no r to carry. Splitting the branch keeps r mandatory on the risk half instead of optional on a shared type. Concrete subtypes follow the same shape: an embedded JuMPOptimisationResult jr first, their unique fields, then the trailing fb.
Related
PortfolioOptimisers.RJR_NRJR — Type
const RJR_NRJR = Union{<:RiskJuMPOptimisationResult, <:NonRiskJuMPOptimisationResult}Union of both JuMP result halves.
The default getproperty and propertynames are bound here, not on either half alone. MeanRiskResult and NearOptimalCenteringResult declare no @forward_properties rule and depend on that default for res.w, so a half without it would silently cost the next measure-less leaf its property forwarding.
Related
PortfolioOptimisers.AbstractDecompositionContract — Type
abstract type AbstractDecompositionContractAbstract supertype for the head's decomposition contract: how model[:w] relates to the long/short parts model[:lw] and model[:sw].
Heads build that relationship in one of two incompatible ways, and a builder that pins the decomposition needs to know which, because the two need different constraints to become exact. The head declares its own with set_decomposition_contract!; builders read it back with decomposition_contract and dispatch.
Related
PortfolioOptimisers.SHARED_STATE — Constant
SHARED_STATEThe Model State entries deliberately shared bare across a nested risk build.
The complement of Per-Build Risk State: an entry belongs here iff it is not a function of the weights being optimised and not a build-scoped presence flag, so the inner and outer builds want the same object and prefixing it would break sharing rather than protect it. shared_get and friends validate against this set, so the classification is enforced at run time rather than only by the seam-lock test.
Each grouping records why those entries are shared. Adding a name here is a claim that a nested build may safely see the enclosing build's copy — check that claim before adding.
PortfolioOptimisers.WeightsFromParts — Type
struct WeightsFromParts <: AbstractDecompositionContractThe head defines the weights from the parts: w = lw - sw is an identity, lw and sw being the primitive variables. Declared by set_rb_mip_w!.
Because the identity always holds, forcing the long-xor-short sign pattern is enough to pin the decomposition: with sw = 0 the identity leaves lw == w, and lw >= 0 makes that max(w, 0). No slack remains to close.
Related
PortfolioOptimisers.PartsBoundWeights — Type
struct PartsBoundWeights <: AbstractDecompositionContractThe head defines the parts as bounds on the weights: lw >= w, sw >= -w, lw, sw >= 0, w being the primitive variable. Declared by set_weight_constraints!.
The parts are only upper bounds on the true long/short exposures, so every budget built on them (bgt, sbgt, gbgt) bounds the realised exposure rather than pinning it. Forcing the sign pattern does not change that — the slack survives it — so pinning the decomposition under this contract needs two further constraints to close it.
Related
PortfolioOptimisers.is_time_dependent — Method
is_time_dependent(opt::JuMPOptimisationEstimator) -> Any
Return true if the estimator's own problem-definition fields, the inner JuMP optimiser, or the fallback carry time-dependent constraints.
PortfolioOptimisers.reset_time_dependent_estimator — Method
reset_time_dependent_estimator(
opt::JuMPOptimisationEstimator
) -> Any
Replace time-dependent constraints with their static defaults, both on the estimator's own fields and by recursing into the inner JuMP optimiser and fallback.
PortfolioOptimisers.process_model — Function
process_model(model, retcode)Extract the solution from an optimised JuMP model based on the return code.
On success, extracts the optimised weights from the model. On failure, returns an empty solution.
Arguments
model: Optimised JuMP model.retcode: Optimisation return code (OptimisationSuccessorOptimisationFailure).
Returns
- Solution object.
Related
PortfolioOptimisers.optimise_JuMP_model! — Function
optimise_JuMP_model!(model::JuMP.Model, slv::Slv_VecSlv)Attempt to optimise a JuMP model using one or more configured solvers.
Tries each solver in order, applying settings and checking for solution feasibility. Returns a JuMPResult with trial errors and success status.
Arguments
model: JuMP model to optimise.slv: SingleSolveror vector ofSolverobjects.
Returns
res::JuMPResult: Result object containing trial errors and success flag.
Details
- For each solver, sets the optimizer and attributes, runs
JuMP.optimize!, and checks solution feasibility. - If a solver fails at one of the three guarded stages, records the error under the solver's
nameand tries the next. - Stops at the first successful solution, and leaves no
trialsentry for it.
Three stages are guarded: JuMP.set_optimizer, JuMP.optimize! and JuMP.assert_is_solved_and_feasible. set_solver_attributes is not. A solver attribute the backend refuses throws straight out of this function, so no trial is recorded and no later solver is tried. This is deliberate: a misspelled attribute is a configuration error, not a solver failure, and swallowing it would silently drop a setting the caller asked for.
Give each solver of a vector its own name. The name is the trials key, the default is "" for every solver, and a later failure overwrites an earlier one under the same key. Measured on two solvers that both fail at set_optimizer: trials holds one entry with the default names and two with distinct names.
Related
optimise_JuMP_model!(
model::Model,
opt::JuMPOptimisationEstimator
) -> Tuple{Union{OptimisationFailure{Dict{Any, Any}}, OptimisationSuccess{Dict{Any, Any}}}, JuMPOptimisationSolution{<:AbstractArray{var"#s137", N}} where {var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
optimise_JuMP_model!(
model::Model,
opt::JuMPOptimisationEstimator,
datatype::DataType
) -> Tuple{Union{OptimisationFailure{Dict{Any, Any}}, OptimisationSuccess{Dict{Any, Any}}}, JuMPOptimisationSolution{<:AbstractArray{var"#s137", N}} where {var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
Attempt to solve the JuMP model using each solver in opt.opt.slv in order.
Tries each solver sequentially, checking feasibility and finite non-zero weights. Returns a (retcode, solution) tuple where retcode is OptimisationSuccess or OptimisationFailure and solution is a JuMPOptimisationSolution.
Related
PortfolioOptimisers.set_model_scales! — Function
set_model_scales!(model::JuMP.Model, sc::Number, so::Number)Register constraint scale sc and objective scale so as named expressions in the JuMP model.
The positional order is sc first, matching every head, which passes opt.sc, opt.so straight out of its JuMPOptimiser.
Arguments
model::JuMP.Model: JuMP optimisation model.sc::Number: Constraint scale factor, read back byget_constraint_scale.so::Number: Objective scale factor, read back byget_objective_scale.
Returns
nothing.
Related
PortfolioOptimisers.set_model_observations! — Function
set_model_observations!(model::JuMP.Model, T::Integer)Register the observation count of the fit as the named entry model[:T].
The sibling of set_model_scales!, and every head calls it in the same place, before any builder runs. The count is a model-wide singleton: one fit produces one returns matrix, and its row count is the holding period every builder measures against. Registering it here rather than inside a builder means a reader may rely on it whatever the model carries, and get_T reads it back.
The row count of a fold, of a benchmark, or of a stacked meta-optimisation panel is a different number. A site that means one of those keeps its own size(..., 1).
Arguments
model::JuMP.Model: JuMP optimisation model.T::Integer: Observation count of the fit, read back byget_T.
Returns
nothing.
Related
PortfolioOptimisers.set_initial_w! — Function
set_initial_w!(args...)
set_initial_w!(w::VecNum, wi::VecNum)Set initial (warm-start) values for portfolio weight variables in the JuMP model.
The no-op fallback does nothing when wi is not provided. The two-argument method sets JuMP start values for each weight variable.
Arguments
w::VecNum: Vector of JuMP weight variables.wi::VecNum: Vector of initial weight values.
Returns
nothing.
Related
PortfolioOptimisers.set_w! — Function
set_w!(model::JuMP.Model, X::MatNum, wi::Option{<:VecNum_VecVecNum})Create portfolio weight variables in the JuMP model and optionally set initial values.
Registers a vector of weight variables w of length size(X, 2) in the model. If wi is provided, sets the initial values via set_initial_w!.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix (shape: observations × assets).wi: Optional initial weight values.
Returns
nothing.
Related
PortfolioOptimisers.set_portfolio_returns! — Function
set_portfolio_returns!(model::JuMP.Model, X::MatNum)Compute and register portfolio returns expression X * w in the JuMP model.
If the expression already exists in the model, returns it directly (idempotent).
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix.
Returns
- The portfolio returns expression.
Related
PortfolioOptimisers.set_net_portfolio_returns! — Function
set_net_portfolio_returns!(model::JuMP.Model, X::MatNum)Compute and register net portfolio returns (after fees) in the JuMP model.
Calls set_portfolio_returns! and subtracts the fees if any are registered. The model's :fees expression holds the per period terms l, s and tn, which are rates per period, so it is subtracted from every observation. The model's :one_time_fees expression holds the two fixed terms, which are charged one time for the whole holding period, and :fee_fa names the clock they fall on: a nothing or FirstObservationFees clock subtracts them from the first observation alone, and an AmortisedFees spreads them over the observation count of the fit. That is the rule charge_fees states at the value level, and charge_one_time_fees applies it here.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix.
Returns
- The net portfolio returns expression.
Related
PortfolioOptimisers.set_asset_returns_plus_one! — Function
set_asset_returns_plus_one!(model::JuMP.Model, X::MatNum)Compute and register portfolio asset gross returns X .+ 1 in the JuMP model.
Used in drawdown and logarithmic return computations.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns expression.
Returns
- The gross asset returns expression
X .+ 1.
Related
PortfolioOptimisers.set_asset_neg_returns_plus_one! — Function
set_asset_neg_returns_plus_one!(model::JuMP.Model, X::MatNum)Compute and register negative asset gross returns -X .+ 1 in the JuMP model.
Used in drawdown and logarithmic return computations.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns expression.
Returns
- The negative gross asset returns expression
-X .+ 1.
Related
PortfolioOptimisers.set_portfolio_drawdowns_plus_one! — Function
set_portfolio_drawdowns_plus_one!(model::JuMP.Model, X::MatNum)Compute and register absolute drawdowns plus one in the JuMP model.
Computes absolute_drawdown_arr(X) .+ 1 and registers it in the model.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Portfolio returns expression.
Returns
- The drawdowns-plus-one expression.
Related
PortfolioOptimisers.set_risk_constraints! — Function
set_risk_constraints!(model, r, X, T, ...) -> nothingSet risk constraints in the JuMP model for a given risk measure.
Generic function stub; concrete methods are defined in constraint and risk measure files. Each method configures the appropriate risk constraint expressions for a given risk measure type r.
Related
PortfolioOptimisers.has_Xap1 — Function
has_Xap1(model::JuMP.Model, prefix::Symbol = Symbol(""))Return true if the gross portfolio returns model[Symbol(prefix, :Xap1)] have been registered (via set_asset_returns_plus_one!).
Related
PortfolioOptimisers.get_ret — Function
get_ret(model::JuMP.Model)Return the portfolio expected-return expression model[:ret].
Asserts the return expression has been registered; errors otherwise.
Related
PortfolioOptimisers.get_net_X — Function
get_net_X(model::JuMP.Model, prefix::Symbol = Symbol(""))Return the net portfolio returns expression model[Symbol(prefix, :net_X)].
Asserts it has been registered (via set_net_portfolio_returns!); errors otherwise.
Related
PortfolioOptimisers.get_ddap1 — Function
get_ddap1(model::JuMP.Model, prefix::Symbol = Symbol(""))Return the drawdowns-plus-one expression model[Symbol(prefix, :ddap1)].
Asserts it has been registered (via set_portfolio_drawdowns_plus_one!); errors otherwise.
Related
PortfolioOptimisers.get_objective_scale — Function
get_objective_scale(model::JuMP.Model)Return the objective scale expression model[:so].
Asserts the scale has been registered (via set_model_scales!); errors otherwise.
Related
PortfolioOptimisers.get_T — Function
get_T(model::JuMP.Model)Return the observation count of the fit, model[:T].
Asserts the count has been registered (via set_model_observations!); errors otherwise.
This is the row count of the fit's own returns matrix. It is not the row count of a fold, of a tracking benchmark, or of a stacked meta-optimisation panel; a site that means one of those reads its own matrix instead.
Related
PortfolioOptimisers.set_unit_budget! — Function
set_unit_budget!(model::JuMP.Model)Record that the head normalised the model's budget scale to unit.
A head declares this when its own constraints make the formulation scale-invariant, so downstream builders may substitute the literal 1 for the homogenisation variable k. RiskBudgeting is the only such head: its log-barrier normalisation pins the scale, and the weights are renormalised after the solve. Note that k remains a free variable under this declaration — it is the budget scale that is unit, not k that is constant.
Related
PortfolioOptimisers.is_unit_budget — Function
is_unit_budget(model::JuMP.Model)Return whether the head normalised the budget scale to unit (see set_unit_budget!).
Related
PortfolioOptimisers.effective_k — Function
effective_k(model::JuMP.Model)Return the budget scale a builder should use: 1 under a unit budget, else model[:k].
Builders that multiply a bound by the budget want this rather than get_k, so a scale-invariant head is honoured without each builder re-deriving that fact for itself.
Related
PortfolioOptimisers.set_decomposition_contract! — Function
set_decomposition_contract!(model::JuMP.Model, dc::AbstractDecompositionContract)Record how the head related model[:w] to model[:lw]/model[:sw].
The first declaration wins: a head may run both builders (the mixed-integer RiskBudgeting head calls set_rb_mip_w!, then hands the same lw/sw to set_weight_constraints!, which re-states them as bounds). The identity is the stronger statement and still holds, so the bounds must not overwrite it.
Related
PortfolioOptimisers.decomposition_contract — Function
decomposition_contract(model::JuMP.Model)Return the head's decomposition contract, or nothing when no head declared one.
nothing means the model has no short side — the weights are their own long part, lw is an alias for w and there is no sw, so there is no decomposition to pin.
Related
PortfolioOptimisers.get_Xap1 — Function
get_Xap1(model::JuMP.Model, prefix::Symbol = Symbol(""))Return the gross portfolio returns expression model[Symbol(prefix, :Xap1)] (X .+ 1).
Asserts it has been registered (via set_asset_returns_plus_one!); errors otherwise.
Related
PortfolioOptimisers.has_ddap1 — Function
has_ddap1(model::JuMP.Model, prefix::Symbol = Symbol(""))Return true if the drawdowns-plus-one model[Symbol(prefix, :ddap1)] have been registered (via set_portfolio_drawdowns_plus_one!).
Related
PortfolioOptimisers.has_net_X — Function
has_net_X(model::JuMP.Model, prefix::Symbol = Symbol(""))Return true if the net portfolio returns model[Symbol(prefix, :net_X)] have been registered (via set_net_portfolio_returns!).
Related
PortfolioOptimisers.get_X — Function
get_X(model::JuMP.Model, prefix::Symbol = Symbol(""))Return the portfolio returns expression model[Symbol(prefix, :X)].
Asserts it has been registered (via set_portfolio_returns!); errors otherwise.
Related
PortfolioOptimisers.get_risk — Function
get_risk(model::JuMP.Model)Return the scalarised portfolio risk expression model[:risk].
Asserts the risk expression has been registered (via scalarise_risk_expression!); errors otherwise.
Related
PortfolioOptimisers.get_dd — Function
get_dd(model::JuMP.Model, prefix::Symbol = Symbol(""))Return the cumulative-drawdown variables model[Symbol(prefix, :dd)].
Asserts they have been registered (via set_drawdown_constraints!); errors otherwise.
Related
PortfolioOptimisers.has_X — Function
has_X(model::JuMP.Model, prefix::Symbol = Symbol(""))Return true if the portfolio returns model[Symbol(prefix, :X)] have been registered (via set_portfolio_returns!).
Related
PortfolioOptimisers.has_dd — Function
has_dd(model::JuMP.Model, prefix::Symbol = Symbol(""))Return true if the cumulative-drawdown variables model[Symbol(prefix, :dd)] have been registered (via set_drawdown_constraints!).
Related
PortfolioOptimisers.assert_shared_state — Function
assert_shared_state(name::Symbol)Assert name is a sanctioned bare Model State entry.
Guards the shared_get family so that reaching for a per-build entry without a prefix fails loudly at the call site, rather than silently aliasing the enclosing build's copy — the regression class that broke IndependentVariableTracking.
PortfolioOptimisers.shared_set! — Function
shared_set!(model::JuMP.Model, name::Symbol, val)Register val as the sanctioned bare Model State entry name and return it.
The unprefixed counterpart of state_set!, for entries on SHARED_STATE.
Related
PortfolioOptimisers.shared_has — Function
shared_has(model::JuMP.Model, name::Symbol)Return true if the sanctioned bare Model State entry name is registered.
PortfolioOptimisers.frontier_point_count — Function
frontier_point_count(front::Frontier)
frontier_point_count(front::VecNum)Number of sweep points one frontier bound asks for.
A Frontier states its count in N; a stated vector of bounds states it in its length. Both shapes are admissible in :ret_frontier and :risk_frontier (see Front_NumVec), and at Model Assembly time a Frontier has not yet been resolved into its range, so the count is read from the shape rather than from a materialised vector.
Related
PortfolioOptimisers.frontier_sweep_points — Function
frontier_sweep_points(model::JuMP.Model)Count the solves the model's frontier sweep runs, and the factors that make up the count.
The sweep is a product: every swept return term and every swept risk measure joins the same Iterators.product, so k bounds of N points each cost N^k full solves rather than k * N. This reads both frontier registries — :ret_frontier and :risk_frontier — and multiplies their per-entry counts together.
The product is accumulated as a BigInt, so it is exact and cannot overflow into a value that would pass a cap it should fail.
Returns
(total, factors): the total number of sweep points, and abound_key => countpair per swept entry, in registration order (return terms first).
Related
PortfolioOptimisers.assert_frontier_sweep_cap — Function
assert_frontier_sweep_cap(model::JuMP.Model)Assert the total frontier sweep does not exceed the active max_frontier ceiling.
Frontier's constructor caps the N of one bound; nothing there sees the product, so k bounds at the ceiling cost max_frontier^k solves and no guard fires. This is the guard, and it runs at Model Assembly — the point at which both frontier registries are complete and no sweep solve has started yet.
Every sweep point runs a full inner optimise_JuMP_model! solve, so the product is the compute-exhaustion sink max_frontier exists to bound (see RESOURCE_LIMITS). The cap applies to the risk side and the return side alike.
Returns
nothing.
Throws
DomainErrorif the product exceedsRESOURCE_LIMITS[].max_frontier. The message names the product, the factors that made it, and the knob that raises the ceiling.
Related
PortfolioOptimisers.frontier_axis — Function
frontier_axis(frontier::VecPair)Turn one resolved frontier registry into the sweep axis it stands for.
Both registries — :ret_frontier and :risk_frontier — hold (bound_var_key, bound_key) => (expr, points, …) entries, and both are swept as a product across their own entries: two swept risk measures of N points each cost N^2 solves on the risk axis alone. This is that product, in two halves — the keys of the bound parameters, and the values to write into them — so set_frontier_point! can zip one against the other.
Arguments
frontier::VecPair: A resolved frontier registry. Every entry's bound is already a vector of sweep points.
Returns
(keys, points): Two product iterators of equal length.
Related
PortfolioOptimisers.set_ret_frontier_parameters! — Function
set_ret_frontier_parameters!(model::JuMP.Model, ret_frontier::VecPair)Register one parameter and one lower-bound constraint per swept return term.
Each term's bound binds on that term's own expression, so the return side is a product across terms rather than a single ladder. The bound is homogenised by k, exactly as the scalar bound in set_return_bounds! is.
Arguments
model::JuMP.Model: The JuMP optimisation model.ret_frontier::VecPair: The resolved:ret_frontierregistry.
Returns
- The return sweep axis, as
frontier_axisbuilds it.
Related
PortfolioOptimisers.set_risk_frontier_parameters! — Function
set_risk_frontier_parameters!(model::JuMP.Model, risk_frontier::VecPair)Register one parameter and one bound constraint per swept risk measure.
The twin of set_ret_frontier_parameters!, and the one place the risk side's two extra pieces are stated: the polarity d, which flips the inequality for a measure whose bigger value is better, and the homogenisation k, which the scalar bound in set_risk_upper_bound! also applies. k is the literal 1 under every head whose objective is fixed — NearOptimalCentering minimises a barrier, so its head registers k = 1 and the factor is a no-op there — and the ratio variable under MaximumRatio. Reading it here rather than at each call site is what keeps the two heads from drifting apart.
Arguments
model::JuMP.Model: The JuMP optimisation model.risk_frontier::VecPair: The resolved:risk_frontierregistry.
Returns
- The risk sweep axis, as
frontier_axisbuilds it.
Related
PortfolioOptimisers.frontier_sweep_axes — Function
frontier_sweep_axes(ret_axis, risk_axis)Join the two sweep axes into the flat sequence of sweep points.
The risk axis varies fastest, so the flat order is return-outer and risk-inner. That order is load-bearing rather than cosmetic: NearOptimalCentering solves its anchor portfolios as one MeanRisk sweep over the same two frontiers, and pairs anchor i with sweep point i. Stating the order once here is what keeps the two sweeps aligned. Either axis may be nothing, which means that side is not swept. If both axes are nothing, the sweep is one point that writes nothing.
Arguments
ret_axis: The return axis fromset_ret_frontier_parameters!, ornothing.risk_axis: The risk axis fromset_risk_frontier_parameters!, ornothing.
Returns
- An iterator of sweep points. Each point is a tuple of
(keys, bounds)pairs, one per swept axis, and itslengthis the number of solves the sweep runs.
Related
PortfolioOptimisers.set_frontier_point! — Function
set_frontier_point!(model::JuMP.Model, point::Tuple)Write one sweep point's bounds into the frontier parameters.
Arguments
model::JuMP.Model: The JuMP optimisation model.point::Tuple: One element of afrontier_sweep_axesiterator. An empty tuple writes nothing, which is the sweep that has no frontier at all.
Returns
nothing.
Related
PortfolioOptimisers.frontier_sweep! — Function
frontier_sweep!(point!, model, opt, ::Type{T}, points)
frontier_sweep!(point!, model, opt, ::Type{T}, n::Integer)
frontier_sweep!(model, opt, ::Type{T}, points)Solve one model per sweep point and collect the outcomes.
The collect tail every frontier sweep shares. The model is assembled once and its objective is set once; a sweep point changes only parameter values, so no constraint is rebuilt between solves. point! is the per-optimiser hook, called with the flat 1-based index of the point after its bounds are written — NearOptimalCentering uses it to move noc_rk and noc_rt onto that point's anchor, and MeanRisk needs no hook at all.
The n::Integer method sweeps n points with no frontier bound to write, which is the unconstrained NearOptimalCentering sweep over a vector of anchors.
Arguments
point!: Hook of one argument, the flat index of the sweep point. Defaults to a no-op.model::JuMP.Model: The JuMP optimisation model.opt::JuMPOptimisationEstimator: The optimiser, foroptimise_JuMP_model!.::Type{T}: Element type of the returns matrix.points: Afrontier_sweep_axesiterator, or the point countn.
Returns
(retcodes, sols): One entry per sweep point, in flat sweep order.
Related
PortfolioOptimisers.state_key — Function
state_key(prefix::Symbol, name::Symbol)
state_key(prefix::Symbol, name::Symbol, i)Resolve the Model State key for entry name under prefix, optionally at measure index i.
Internal to the Model State interface: the single place the two namespacing conventions are spelled. A Model State key is disambiguated on two axes, and both are resolved here:
prefixseparates one build from another, so a nested risk build cannot collide with the build that encloses it.iseparates one measure instance from another inside a single build, so twoConditionalValueatRiskmeasures in the same vector get their own scratch entries.
Keeping both here is what lets the seam-lock test assert that no emitter builds a key by hand — emitters reach Model State through state_get, state_has, state_set! and state_build!.
Neither axis carries a delimiter, so composition is not injective: (:te_dr_, 11) and (:te_dr_1, 1) both give :te_dr_11. The spelling is kept — a delimiter would move every top-level key a caller reads — and the collision is caught where it does harm, by assert_state_key_free at registration.
Related
PortfolioOptimisers.assert_state_key_free — Function
assert_state_key_free(model::JuMP.Model, key::Symbol)Assert Model State key key is not registered yet, so a write cannot replace an entry.
Neither axis of state_key is separated by a delimiter, so key composition is not injective: a name that ends in a digit and a low index compose the same Symbol as a shorter name and a higher index — state_key(p, :te_dr_, 11) == state_key(p, :te_dr_1, 1). Without this guard the second write wins, the model carries one entry where the build expected two, and a constraint binds the wrong variable. That is a wrong answer, not a crash, so the registration verb fails closed instead.
A delimiter was rejected as the fix: it would move every top-level key spelling (state_key(Symbol(""), :ret_, 1) is :ret_1, a key callers read), and it would still let one emitter overwrite another's entry under a key both spell correctly. The guard closes both. Re-registration under one key has no legitimate reading either: the build-once case is state_build!, which returns the existing entry untouched, and the flag case is mark_state!, which is idempotent.
Returns
nothing.
Throws
ArgumentErrorifkeyis already registered. The message names the key and the two verbs that do accept a repeat.
Related
PortfolioOptimisers.state_set! — Function
state_set!(model::JuMP.Model, prefix::Symbol, name::Symbol, val)
state_set!(model::JuMP.Model, prefix::Symbol, name::Symbol, i, val)Register val in the model under the prefixed Model State key and return it.
A nested risk build (e.g. risk tracking) passes a non-empty prefix so the shared infrastructure entries it creates (:X, :net_X, :W, :dd, …) do not collide with the outer model's; the default empty prefix reproduces the bare key.
The indexed method registers per-measure scratch (:cvar_risk_, :z_cvar_, …) at measure index i, so two instances of the same measure in one build get their own entries. Both disambiguators are resolved by state_key.
Registration is fresh: the composed key must be free, because key composition is not injective and a replaced entry is a wrong answer rather than an error (assert_state_key_free). Reuse is the other two verbs' job.
Throws
ArgumentErrorif the composed key is already registered.
Related
PortfolioOptimisers.state_has — Function
state_has(model::JuMP.Model, prefix::Symbol, name::Symbol)
state_has(model::JuMP.Model, prefix::Symbol, name::Symbol, i)Return true if Model State entry name is registered under prefix, at index i if given.
Related
PortfolioOptimisers.state_get — Function
state_get(model::JuMP.Model, prefix::Symbol, name::Symbol)
state_get(model::JuMP.Model, prefix::Symbol, name::Symbol, i)Return Model State entry name under prefix, asserting it has been registered.
Prefer a named accessor (get_X, get_net_X, get_dd, …) where one exists: those name the builder that produces the entry, so an out-of-order read reports which builder to call instead of a generic missing-entry error.
The indexed method reads per-measure scratch registered at measure index i.
Related
PortfolioOptimisers.state_build! — Function
state_build!(f, model::JuMP.Model, prefix::Symbol, name::Symbol)
state_build!(f, model::JuMP.Model, prefix::Symbol, name::Symbol, i)Return Model State entry name under prefix, building it with f() exactly once.
The memoise-on-prefixed-key idiom shared by every risk and constraint emitter: if the entry is already registered — an earlier measure in the same build produced it, or an outer build already did — it is returned untouched; otherwise f() runs and its value is registered under the prefixed key. Companion entries created inside f register with state_set!.
Because the key is resolved here rather than at the call site, a Model State entry added in future participates in the prefix discipline with no further work. That is what closes a residual hole an earlier, more permissive design left open.
Related
PortfolioOptimisers.mark_state! — Function
mark_state!(model::JuMP.Model, prefix::Symbol, name::Symbol)Record that this build has name present, idempotently.
A build-scoped presence flag: name carries no value beyond its own existence, and readers test it with state_has rather than reading it. Marking under prefix is what keeps a nested build's flags out of the enclosing build — the second half of Per-Build Risk State, the half that is not weight-dependent.
Related
PortfolioOptimisers.nested_prefix — Function
nested_prefix(prefix::Symbol, tag::Symbol)
nested_prefix(prefix::Symbol, tag::Symbol, i)Compose the Model State namespace a nested build threads down its own spine.
Distinct from a Model State key: this produces a prefix, not an entry name, so a nested build's entries cannot alias the enclosing build's. tag names the nesting kind (:tr_iv_, :tr_dv_, :te_ir_, :te_dr_, :gain_) and the optional i disambiguates the measure index, which is what makes tracking-nested-in-tracking collision-free.
Related
PortfolioOptimisers.nested_index — Function
nested_index(tag::Symbol, i)Compose the Model State measure index a sub-measure build threads down.
The twin of nested_prefix on the other disambiguating axis. A composite measure that builds its parts in the same build — GenericValueatRiskRange over its loss and gain sides — separates the parts by index rather than by namespace, because they share the build's infrastructure entries and must not each rebuild them. tag names the part (:loss_, :gain_), and the composition nests, so a range inside a range stays collision-free.
Distinct from a Model State key: this produces an index, not an entry name.
Related
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).