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
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
- For each registry,
:ret_frontierfirst and:risk_frontiersecond, that the model holds, push onebound_key => countpair per entry ontofactors. The count isfrontier_point_countof the bound of the entry. - Multiply the counts as
BigIntvalues, starting from one, givingtotal. 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, aBigInt, and the vector ofbound_key => countpairs in the order of step 1. A model with no frontier gives(1, []).
Related
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
- Count the points with
frontier_sweep_points, givingtotalandfactors. - Return when
factorsis empty, because the model sweeps no frontier. - Read the ceiling
capfromRESOURCE_LIMITS[].max_frontier. - Check that
total <= cap.
Validation
total <= RESOURCE_LIMITS[].max_frontier. Otherwise aDomainErrornames the product, the factors that make it, and how to raise the ceiling.
Returns
nothing.
Related
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
- For each entry, pair the key
bound_var_key, repeated once per point, with the vector of points. - Take the product of the repeated keys over the entries, giving
keys, and the product of the points over the entries, givingpoints.
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
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_keyof each entry: a new parameter $b_j$, with the value zero untilset_frontier_point!writes a point into it.k: read from the model.
Constraints
bound_keyof 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_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 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_keyof each entry: a new parameter $u_j$, with the value zero untilset_frontier_point!writes a point into it.k: read from the model.
Constraints
bound_keyof 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_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 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
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 for each swept axis, and itslengthis the number of solves that the sweep runs.
Related
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
- For each
(keys, bounds)pair ofpoint, and for eachkeyandboundof the pair, set the value of the parametermodel[key]tobound.
Arguments
model::JuMP.Model: The JuMP optimisation model.point::Tuple: One element of afrontier_sweep_axesiterator. The empty tuple, the one point of a sweep with no frontier, writes nothing.
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 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
- For each sweep point, with its flat index
i:- Write the bounds of the point with
set_frontier_point!. - Call
point!(i). - Solve the model with
optimise_JuMP_model!, givingretcodeandsol. - Push
retcodeontoretcodesandsolontosols.
- Write the bounds of the point with
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, foroptimise_JuMP_model!.::Type{T}: Element type of the returns matrix.points: Afrontier_sweep_axesiterator, or the point countn.
Returns
(retcodes, sols): One entry for each sweep point, in the flat order of the sweep.
Related