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

Returns

  • sol::JuMPOptimisationSolution: The solution that holds the weights.

Related

source
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: Single Solver or vector of Solver objects.

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 name and tries the next.
  • Stops at the first successful solution, and leaves no trials entry 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

source
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

  1. For each solver of opt.opt.slv, in order:
    1. Attach the solver to model. When that raises, record the error under the name of the solver and go to the next solver.
    2. Set the attributes of the solver from its settings.
    3. Solve. When that raises, record the error and go to the next solver.
    4. 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.
    5. Otherwise record the settings, the error of the check if there is one, and the solution summary under the name of the solver.
  2. When a solver stopped the loop, the return code is an OptimisationSuccess. Otherwise it is an OptimisationFailure, and a warning points to its res.
  3. Read the weights with process_model.

Returns

  • (retcode, sol): The return code and the JuMPOptimisationSolution. The res field of the return code is a Dict from the name of each solver that failed to what went wrong. The solver that succeeds adds no entry, so the res of a success on the first solver is empty.

Related

source
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 a DimensionMismatch names both lengths.

Returns

  • nothing.

Related

source
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 of X.

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, or nothing.

Returns

  • nothing.

Related

source
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 under prefix with get_w.

Expressions

  • X under prefix: $\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

source
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

  1. When the series is registered under prefix, return it.
  2. Build the portfolio return series with set_portfolio_returns!, giving Xe.
  3. When the model holds neither :fees nor :one_time_fees, register Xe as the net series and return it.
  4. Subtract :fees, or zero when the model holds none, from each entry of Xe, giving net.
  5. When the model holds :one_time_fees and net is not empty, charge them on the clock :fee_fa with charge_one_time_fees, over the count get_T.
  6. Register net and return it.

JuMP formulation

Expressions

  • net_X under prefix: $\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 :fees expression. 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_fees expression. 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 nothing or FirstObservationFees clock $c_1 = 1$ and $c_t = 0$ for $t > 1$. Under an AmortisedFees clock $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

source
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

  • Xap1 under prefix: $\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

source
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

  • ddap1 under prefix: 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

source
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

source