JuMP model setup: private API
PortfolioOptimisers.process_model — Function
process_model(model::JuMP.Model, retcode::OptimisationSuccess)
process_model(model::JuMP.Model, retcode::OptimisationFailure)Read the portfolio weights of a solved JuMP model into a JuMPOptimisationSolution.
After a success the weights are the solved w divided by the solved k. A ratio objective solves for $k \boldsymbol{w}$, so the division gives weights on the scale of the budget. When k solves to zero the weights are the solved w unchanged. After a failure the weights are a vector of NaN, one for each weight, in the value type of the model.
Arguments
model::JuMP.Model: The optimised JuMP model.retcode: The return code,OptimisationSuccessorOptimisationFailure, that selects the method.
Returns
sol::JuMPOptimisationSolution: The solution that holds the weights.
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}}
Solve the JuMP model with each solver of opt.opt.slv in turn, until one gives an acceptable solution.
A solution is acceptable when JuMP reports it solved and feasible under the check_sol keywords of the solver, every weight is finite, and at least one weight is not zero. The fallback of the optimiser is not tried here.
Algorithm
- For each solver of
opt.opt.slv, in order:- Attach the solver to
model. When that raises, record the error under the name of the solver and go to the next solver. - Set the attributes of the solver from its
settings. - Solve. When that raises, record the error and go to the next solver.
- Check the solution with
JuMP.assert_is_solved_and_feasible, and read the weights. When every weight is finite and one weight is not zero, stop the loop. - Otherwise record the settings, the error of the check if there is one, and the solution summary under the name of the solver.
- Attach the solver to
- When a solver stopped the loop, the return code is an
OptimisationSuccess. Otherwise it is anOptimisationFailure, and a warning points to itsres. - Read the weights with
process_model.
Returns
(retcode, sol): The return code and theJuMPOptimisationSolution. Theresfield of the return code is aDictfrom the name of each solver that failed to what went wrong. The solver that succeeds adds no entry, so theresof a success on the first solver is empty.
Related
PortfolioOptimisers.set_initial_w! — Function
set_initial_w!(args...)
set_initial_w!(w::VecNum, wi::VecNum)Set the start values of the weight variables of the JuMP model.
The method for two vectors sets the start value of each weight variable to the matching entry of wi. Every other argument, nothing or a vector of weight vectors among them, reaches the method that does nothing.
Arguments
w::VecNum: Vector of JuMP weight variables.wi::VecNum: Vector of start values.
Validation
length(wi) == length(w). Otherwise aDimensionMismatchnames both lengths.
Returns
nothing.
Related
PortfolioOptimisers.set_w! — Function
set_w!(model::JuMP.Model, X::MatNum, wi::Option{<:VecNum_VecVecNum})Register the portfolio weight variables in the JuMP model, and set their start values from wi.
A vector wi sets one start value for each weight through set_initial_w!. A vector of weight vectors, which a NearOptimalCentering sweep passes as its anchors, and nothing set no start value.
JuMP formulation
Variables
w: $\boldsymbol{w}$, created free, with one entry for each column ofX.
Where:
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix, $T \times N$. The function reads only its number of columns.wi: Start values of the weights, ornothing.
Returns
nothing.
Related
PortfolioOptimisers.set_portfolio_returns! — Function
set_portfolio_returns!(model::JuMP.Model, X::MatNum; prefix::Symbol = Symbol(""))Register the portfolio return series $\mathbf{X} \boldsymbol{w}$ in the JuMP model, and return it.
A second call returns the registered series and builds nothing.
JuMP formulation
Variables
w: $\boldsymbol{w}$, read underprefixwithget_w.
Expressions
Xunderprefix: $\mathbf{X} \boldsymbol{w}$, a $T \times 1$ vector.
Where:
- $\mathbf{X}$: Returns matrix $T \times N$.
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix.prefix::Symbol: Model State namespace of the build. The empty default gives the bare key.
Returns
- The portfolio return series.
Related
PortfolioOptimisers.set_net_portfolio_returns! — Function
set_net_portfolio_returns!(model::JuMP.Model, X::MatNum; prefix::Symbol = Symbol(""))Register the portfolio return series net of fees in the JuMP model, and return it.
The model's :fees expression holds the per period terms l, s, tn and lq. They are rates for each period, so each observation pays them. The model's :one_time_fees expression holds the one-off terms fl, fs and flq, which the holding period pays one time, and :fee_fa names the clock they fall on. A nothing or FirstObservationFees clock takes them from the first observation alone, and an AmortisedFees clock spreads them evenly over the observation count of the fit. charge_fees states the same rule for values, and charge_one_time_fees applies it here. A second call returns the registered series and builds nothing. :fees and :one_time_fees are shared entries, so a nested build reads them under the bare key.
Algorithm
- When the series is registered under
prefix, return it. - Build the portfolio return series with
set_portfolio_returns!, givingXe. - When the model holds neither
:feesnor:one_time_fees, registerXeas the net series and return it. - Subtract
:fees, or zero when the model holds none, from each entry ofXe, givingnet. - When the model holds
:one_time_feesandnetis not empty, charge them on the clock:fee_fawithcharge_one_time_fees, over the countget_T. - Register
netand return it.
JuMP formulation
Expressions
net_Xunderprefix: $\hat{r}_t = \boldsymbol{x}_t^\intercal \boldsymbol{w} - f_r - c_t f_o$.
Where:
- $\boldsymbol{x}_t$: Asset returns for observation $t$, the $t$-th row of the returns matrix.
- $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
- $f_r$: Per period fee, the model's
:feesexpression. Each observation of the net return series pays it in full. It is zero when the model holds none. - $f_o$: One-off fee, the model's
:one_time_feesexpression. The holding period pays it one time. It is zero when the model holds none. - $c_t$: Charge fraction of observation $t$, the part of $f_o$ that the observation pays. Under a
nothingorFirstObservationFeesclock $c_1 = 1$ and $c_t = 0$ for $t > 1$. Under anAmortisedFeesclock $c_t = 1 / T$. - $T$: Number of observations.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix.prefix::Symbol: Model State namespace of the build. The empty default gives the bare key.
Returns
- The net portfolio return series.
Related
PortfolioOptimisers.set_asset_returns_plus_one! — Function
set_asset_returns_plus_one!(model::JuMP.Model, X::MatNum; prefix::Symbol = Symbol(""))Register the gross asset returns $\mathbf{X} + 1$ in the JuMP model, and return them.
The value is the constant matrix X .+ 1, not a JuMP expression. The distributionally robust conditional value at risk measures the transport cost of its Wasserstein ambiguity ball against it. The gain side of that measure passes -X under a prefix of its own. A second call returns the registered matrix and builds nothing.
JuMP formulation
Expressions
Xap1underprefix: $\mathbf{X} + 1$, entry by entry.
Where:
- $\mathbf{X}$: Returns matrix $T \times N$.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix.prefix::Symbol: Model State namespace of the build. The empty default gives the bare key.
Returns
- The gross asset returns matrix.
Related
PortfolioOptimisers.set_portfolio_drawdowns_plus_one! — Function
set_portfolio_drawdowns_plus_one!(model::JuMP.Model, X::MatNum; prefix::Symbol = Symbol(""))Register the drawdowns of each asset plus one in the JuMP model, and return them.
absolute_drawdown_arr takes the drawdown of each column of X on the cumulative sum of its returns, with the running peak seeded at zero. The value is a constant matrix, not a JuMP expression. The distributionally robust conditional drawdown at risk measures the transport cost of its Wasserstein ambiguity ball against it. A second call returns the registered matrix and builds nothing.
Mathematical definition
\[\begin{align} c_{t,i} &= \sum_{s=1}^{t} x_{s,i}\,, \\ D_{t,i} &= 1 + c_{t,i} - \max\left(0,\, \max_{s \leq t} c_{s,i}\right)\,. \end{align}\]
Where:
- $x_{s,i}$: The return of asset $i$ at observation $s$, an entry of
X. - $c_{t,i}$: The cumulative sum of the returns of asset $i$ up to observation $t$.
- $D_{t,i}$: The drawdown of asset $i$ at observation $t$, plus one. It is at most one.
JuMP formulation
Expressions
ddap1underprefix: the matrix of $D_{t,i}$, $T \times N$.
Where:
- $T$: Number of observations.
- $N$: Number of assets.
Arguments
model::JuMP.Model: JuMP optimisation model.X::MatNum: Asset returns matrix, $T \times N$.prefix::Symbol: Model State namespace of the build. The empty default gives the bare key.
Returns
- The matrix of asset drawdowns plus one.
Related
PortfolioOptimisers.set_risk_constraints! — Function
set_risk_constraints!(model::JuMP.Model, i, r, opt, pr, args...; kwargs...)Add the variables, expressions and rows of the risk measure r to the JuMP model.
This is the declaration of the generic function. Each risk measure defines a method in the file of its constraints. i is the index of the measure in its vector, and it keeps the entries of two measures of one type apart.
Related