Frontier sweep: private API

PortfolioOptimisers.frontier_point_count — Function
frontier_point_count(front::Frontier)
frontier_point_count(front::VecNum)

Return the number of sweep points that one frontier bound asks for.

A Frontier states the count in its field N, and a vector of bounds states it in its length. The registries :ret_frontier and :risk_frontier accept both forms (see Front_NumVec). During Model Assembly a Frontier is not yet resolved into its points, so the count comes from the form of the bound and not from a vector of points.

Related

source
PortfolioOptimisers.frontier_sweep_points — Function
frontier_sweep_points(model::JuMP.Model)

Count the solves of the frontier sweep of the model, and return the factors of the count.

Every swept return term and every swept risk measure joins one product, so k bounds of N points each cost $N^k$ solves and not $k N$.

Algorithm

  1. For each registry, :ret_frontier first and :risk_frontier second, that the model holds, push one bound_key => count pair per entry onto factors. The count is frontier_point_count of the bound of the entry.
  2. Multiply the counts as BigInt values, starting from one, giving total. The product is exact, so an overflow cannot give a value that passes a cap that it must fail.

Returns

  • (total, factors): The total number of sweep points, a BigInt, and the vector of bound_key => count pairs in the order of step 1. A model with no frontier gives (1, []).

Related

source
PortfolioOptimisers.assert_frontier_sweep_cap — Function
assert_frontier_sweep_cap(model::JuMP.Model)

Check that the total number of points of the frontier sweep does not exceed the max_frontier ceiling.

The constructor of Frontier caps the N of one bound and does not see the product, so k bounds at the ceiling cost $\mathrm{max\_frontier}^k$ solves. This check caps the product. It runs during Model Assembly, when both registries are complete and no solve of the sweep has started.

Each point runs a full solve with optimise_JuMP_model!, so the product is the cost that max_frontier bounds (see RESOURCE_LIMITS). The cap applies to the return side and the risk side alike.

Algorithm

  1. Count the points with frontier_sweep_points, giving total and factors.
  2. Return when factors is empty, because the model sweeps no frontier.
  3. Read the ceiling cap from RESOURCE_LIMITS[].max_frontier.
  4. Check that total <= cap.

Validation

  • total <= RESOURCE_LIMITS[].max_frontier. Otherwise a DomainError names the product, the factors that make it, and how to raise the ceiling.

Returns

  • nothing.

Related

source
PortfolioOptimisers.frontier_axis — Function
frontier_axis(frontier::VecPair)

Turn one resolved frontier registry into the sweep axis that it defines.

Each registry, :ret_frontier or :risk_frontier, holds entries of the form (bound_var_key, bound_key) => (expr, points, …). The entries of one registry form a product, so two swept risk measures of N points each cost $N^2$ solves on the risk axis alone. The axis is that product in two parts: the keys of the bound parameters, and the values to write into them.

Algorithm

  1. For each entry, pair the key bound_var_key, repeated once per point, with the vector of points.
  2. Take the product of the repeated keys over the entries, giving keys, and the product of the points over the entries, giving points.

Arguments

  • frontier::VecPair: A resolved frontier registry. The bound of each entry is a vector of sweep points.

Returns

  • (keys, points): Two product iterators of equal length. set_frontier_point! reads them in pairs.

Related

source
PortfolioOptimisers.set_ret_frontier_parameters! — Function
set_ret_frontier_parameters!(model::JuMP.Model, ret_frontier::VecPair)

Register one parameter and one floor row for each swept return term.

The bound of each term acts on the expression of that term, so the return side is a product over the terms and not one ladder. k multiplies the bound, as it multiplies the scalar bound of set_return_bounds!.

JuMP formulation

Variables

  • bound_var_key of each entry: a new parameter $b_j$, with the value zero until set_frontier_point! writes a point into it.
  • k: read from the model.

Constraints

  • bound_key of each entry: $s_c \left(\mathrm{ret}_j - b_j k\right) \geq 0$.

Where:

  • $\mathrm{ret}_j$: The return expression of the $j$-th swept return term, the first value of the entry.
  • $b_j$: The return floor of the $j$-th swept return term, a parameter.
  • $s_c$: Constraint scale. It multiplies both sides of a row, so a positive value leaves the feasible set unchanged.
  • $k$: Budget scaling / homogenisation variable.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • ret_frontier::VecPair: The resolved :ret_frontier registry.

Returns

Related

source
PortfolioOptimisers.set_risk_frontier_parameters! — Function
set_risk_frontier_parameters!(model::JuMP.Model, risk_frontier::VecPair)

Register one parameter and one bound row for each swept risk measure.

It is the twin of set_ret_frontier_parameters!. The risk side adds the direction $d$ of the bound, which turns the ceiling into a floor for a measure whose flag is false. k multiplies the bound, as it multiplies the scalar bound of set_risk_upper_bound!. Two heads call this builder. Under MeanRisk, k is the ratio variable for a MaximumRatio objective and the number 1 for any other objective. NearOptimalCentering minimises a barrier and registers k = 1, so the factor has no effect there. One builder for both heads keeps the two from drifting apart.

JuMP formulation

Variables

  • bound_var_key of each entry: a new parameter $u_j$, with the value zero until set_frontier_point! writes a point into it.
  • k: read from the model.

Constraints

  • bound_key of each entry: $d_j s_c \left(R_j - u_j k\right) \leq 0$. With $d_j = 1$ the row is the ceiling $R_j \leq u_j k$, and with $d_j = -1$ it is the floor $R_j \geq u_j k$.

Where:

  • $R_j$: The risk expression of the $j$-th swept risk measure, the first value of the entry.
  • $u_j$: The bound of the $j$-th swept risk measure, a parameter.
  • $d_j$: $1$ when the flag of the entry, its third value, is true, and $-1$ otherwise.
  • $s_c$: Constraint scale. It multiplies both sides of a row, so a positive value leaves the feasible set unchanged.
  • $k$: Budget scaling / homogenisation variable.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • risk_frontier::VecPair: The resolved :risk_frontier registry.

Returns

Related

source
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 changes fastest, so the flat order runs over the risk points inside each return point. NearOptimalCentering solves its anchor portfolios as one MeanRisk sweep over the same two frontiers and pairs anchor i with sweep point i, so this one statement of the order keeps the two sweeps aligned. An axis that is nothing is not swept. When both are nothing, the sweep is one point that writes nothing.

Arguments

Returns

  • An iterator of sweep points. Each point is a tuple of (keys, bounds) pairs, one for each swept axis, and its length is the number of solves that the sweep runs.

Related

source
PortfolioOptimisers.set_frontier_point! — Function
set_frontier_point!(model::JuMP.Model, point::Tuple)

Write the bounds of one sweep point into the frontier parameters.

Algorithm

  1. For each (keys, bounds) pair of point, and for each key and bound of the pair, set the value of the parameter model[key] to bound.

Arguments

  • model::JuMP.Model: The JuMP optimisation model.
  • point::Tuple: One element of a frontier_sweep_axes iterator. The empty tuple, the one point of a sweep with no frontier, writes nothing.

Returns

  • nothing.

Related

source
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 the model once for each sweep point and collect the outcomes.

Every frontier sweep ends in this loop. The head builds the model once and sets its objective once. A sweep point changes only the values of the parameters, so the loop builds no constraint between two solves. point! is the hook of the optimiser. The loop calls it with the flat index of the point, from one, after it writes the bounds of the point. NearOptimalCentering uses it to move noc_rk and noc_rt to the anchor of that point, and MeanRisk passes no hook.

The method with n::Integer sweeps n points that write no bound. It is the NearOptimalCentering sweep over a vector of anchors with no frontier.

Algorithm

  1. For each sweep point, with its flat index i:
    1. Write the bounds of the point with set_frontier_point!.
    2. Call point!(i).
    3. Solve the model with optimise_JuMP_model!, giving retcode and sol.
    4. Push retcode onto retcodes and sol onto sols.

Arguments

  • point!: Hook with one argument, the flat index of the sweep point. The method without it passes a hook that does nothing.
  • model::JuMP.Model: The JuMP optimisation model.
  • opt::JuMPOptimisationEstimator: The optimiser, for optimise_JuMP_model!.
  • ::Type{T}: Element type of the returns matrix.
  • points: A frontier_sweep_axes iterator, or the point count n.

Returns

  • (retcodes, sols): One entry for each sweep point, in the flat order of the sweep.

Related

source