Base Risk Constraints: private API
PortfolioOptimisers.AbstractRiskSeriesAlgorithm — Type
abstract type AbstractRiskSeriesAlgorithm <: AbstractAlgorithmAbstract supertype for the series a conic risk measure reduces.
A conic tail measure is written once against a per-observation series of returns. The returns family reduces the net portfolio returns; the drawdown family reduces the negated drawdown path, which is the same series with one substitution. risk_series is the one place that substitution is made, so a builder is written once and each twin selects its series by passing the marker.
Related
PortfolioOptimisers.NonFRCJuMPOpt — Type
const NonFRCJuMPOpt = Union{<:MeanRisk, <:NearOptimalCentering, <:RiskBudgeting}Alias for JuMP optimisers that do not use factor risk contribution.
Matches MeanRisk, NearOptimalCentering, or RiskBudgeting. Used for dispatch in risk constraint generation functions that apply to these optimiser types but not to factor risk contribution.
Related
PortfolioOptimisers.NetReturnsRiskSeries — Type
struct NetReturnsRiskSeries <: AbstractRiskSeriesAlgorithmMarker selecting the net portfolio returns as the series a risk measure reduces.
This is the series of every returns-tail measure, and the only one that can be range-composed: the gain tail is the same series negated, which is what loss = false means in risk_series.
Related
PortfolioOptimisers.DrawdownRiskSeries — Type
struct DrawdownRiskSeries <: AbstractRiskSeriesAlgorithmMarker selecting the negated drawdown path as the series a risk measure reduces.
-dd[2:T+1] is the return-signed drawdown series, so a builder written against net returns encodes the drawdown twin without a single sign written by hand.
The drawdown series has no gain tail. A run-up is a different recurrence, not the negation of this one, so risk_series takes no loss keyword for this marker and no drawdown measure can be range-composed.
Related
PortfolioOptimisers.set_risk_constraints! — Function
set_risk_constraints!(
model::Model,
r::RiskMeasure,
opt::JuMPOptimisationEstimator,
pr::AbstractPriorResult,
pl::Union{Nothing, AbstractPhylogenyConstraintResult, AbstractVector{<:AbstractPhylogenyConstraintResult}},
fees::Union{Nothing, Fees};
...
)
set_risk_constraints!(
model::Model,
r::RiskMeasure,
opt::JuMPOptimisationEstimator,
pr::AbstractPriorResult,
pl::Union{Nothing, AbstractPhylogenyConstraintResult, AbstractVector{<:AbstractPhylogenyConstraintResult}},
fees::Union{Nothing, Fees},
b1::Union{Nothing, AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}};
kwargs...
)
Dispatch to index-aware set_risk_constraints! for a single risk measure or iterate over a vector of risk measures.
The single-measure overload calls set_risk_constraints!(model, 1, r, ...). The vector overload calls set_risk_constraints!(model, i, rs[i], ...) for each element.
The single-measure overload also drops the measure's scale through unit_scale_risk_measure. scale weights a measure inside an aggregate built from several measures, and one measure is not an aggregate, so the weight is inert. The vector overload keeps every element's scale.
Both overloads stamp the owning measure onto the :risk_frontier entries that measure registered (set_risk_frontier_owner!). This is the only depth at which the measure and its entries are both in hand.
Both overloads resolve the measure through resolve_deferred_quantities, and both thread the estimator's own solver into that call. This route calls no factory, so no selection has run and a measure that states no solver of its own still holds nothing. A Calibration Rule that reads the solver would see that nothing, while the same rule on the factory route sees the optimiser's. Threading opt.opt.slv is what makes the two routes resolve one measure against one solver.
Arguments
model::JuMP.Model: The JuMP optimisation model.r: ARiskMeasureinstance, orrsa vector of risk measures.opt::JuMPOptimisationEstimator: JuMP optimisation estimator.pr: Prior result.pl: Optional phylogeny constraints.fees: Optional fees structure.b1::Option{<:MatNum} = nothing: Factor loading matrix forFactorRiskContribution;nothingfor all other optimisers.
Returns
nothing.
Related
PortfolioOptimisers.risk_frontier_length — Function
risk_frontier_length(model::Model) -> Int64
Number of entries the :risk_frontier Model State registry holds, or 0 when it holds none.
Read before a risk measure builds its constraints, so that the entries it adds can be told from the entries its predecessors added.
Related
PortfolioOptimisers.set_risk_frontier_owner! — Function
set_risk_frontier_owner!(
model::Model,
first::Integer,
i::Integer
)
Stamp i as the owning risk measure of every :risk_frontier entry after position first.
rebuild_risk_frontier resolves a Frontier bound into a span of numbers, and it needs the risk measure the span belongs to. The registry is not parallel to the measure vector: a measure registers an entry only when its settings.ub is a Front_NumVec, and a memoised measure such as UlcerIndex registers one entry for every copy of itself in that vector. So the position of an entry does not name its measure, and the owner is recorded rather than derived. This mirrors the return side, whose :ret_frontier entry has carried its own term index from the start.
Arguments
model::JuMP.Model: The JuMP optimisation model.first::Integer: The registry length before measureibuilt its constraints, asrisk_frontier_lengthreads it.i::Integer: The position of the measure in the risk measure vector.
Returns
nothing.
Related
PortfolioOptimisers.set_risk_upper_bound! — Function
set_risk_upper_bound!(
::Model,
::JuMPOptimisationEstimator,
r_expr,
::Nothing,
key
)
set_risk_upper_bound!(
::Model,
::JuMPOptimisationEstimator,
r_expr,
::Nothing,
key,
flag::Bool
)
Add an upper-bound constraint on a risk expression to model.
The Nothing overload does nothing (no bound was requested). The Front_NumVec overload records the expression and its frontier bound vector in the :risk_frontier Model State entry for later use in Pareto frontier solves. That entry is (bound_var_key, bound_key) => (r_expr, ub, flag, owner), and owner is written here as 0. The measure that registered the entry is not known at this depth, so set_risk_frontier_owner! stamps it from the loop that enumerates the measures. The Number overload adds the constraint sc * (r_expr - ub * k) <= 0 directly to the model. The fall-through method emits a warning: a non-nothing bound with an optimiser outside NonFRCJuMPOpt is ignored, which would otherwise happen silently.
Arguments
model::JuMP.Model: The JuMP optimisation model.r_expr::JuMP.AbstractJuMPScalar: The risk JuMP expression to bound.ub: Upper bound; a scalar number or a frontier specification.key::Symbol: Symbol used to name the constraint in the model.flag::Bool: If true, sets upper bound; if false sets lower bound (default:true).
Returns
nothing.
Related
PortfolioOptimisers.set_risk_expression! — Function
set_risk_expression!(
model::Model,
r_expr::AbstractJuMPScalar,
scale::Number,
rke::Bool
)
Push a scaled risk expression onto the risk_vec array in model.
If rke is false the function does nothing. Otherwise it initialises risk_vec if needed and appends scale * r_expr.
Arguments
model::JuMP.Model: The JuMP optimisation model.r_expr::JuMP.AbstractJuMPScalar: The risk JuMP expression to add.scale::Number: Scaling factor applied to the expression.rke::Bool: Whenfalsethis method is a no-op.
Returns
nothing.
Related
PortfolioOptimisers.set_risk_bounds_and_expression! — Function
set_risk_bounds_and_expression!(
model::Model,
opt::RiskJuMPOptimisationEstimator,
r_expr::AbstractJuMPScalar,
settings::RiskMeasureSettings,
name::Symbol;
...
)
set_risk_bounds_and_expression!(
model::Model,
opt::RiskJuMPOptimisationEstimator,
r_expr::AbstractJuMPScalar,
settings::RiskMeasureSettings,
name::Symbol,
flag::Bool;
prefix
)
Apply an upper-bound constraint and register the risk expression for the objective.
Calls set_risk_upper_bound! with settings.ub and set_risk_expression! with settings.scale and settings.rke.
Arguments
model::JuMP.Model: The JuMP optimisation model.opt::RiskJuMPOptimisationEstimator: Risk-based optimisation estimator.r_expr::JuMP.AbstractJuMPScalar: Risk JuMP expression.settings::RiskMeasureSettings: Settings carrying upper bound, scale, andrkeflag.name: Bare Model State entry name seeding the derived bound keys (<name>_ub,<name>_ub_var). The key is resolved here rather than at the call site, so emitters never build a key by hand.i: Measure index, for per-measure entry names. The indexed method resolves the same key the emitter registered the risk expression under, so the bound keys and the entry key cannot drift apart.flag::Bool: If true, sets upper bound; if false sets lower bound (default:true).
Keyword arguments
prefix::Symbol: Model State namespace forname(default: empty, i.e. the bare key).
Returns
nothing.
Related
PortfolioOptimisers.set_range_risk_constraints! — Function
set_range_risk_constraints!(
model::Model,
i,
r::RiskMeasure,
name::Symbol,
opt::RiskJuMPOptimisationEstimator,
pr::AbstractPriorResult,
args...;
prefix,
kwargs...
) -> Any
Add a range risk measure's constraints to model by building its two tails and summing them.
A range measure is its base measure applied twice, so this builds it that way. It reads the two point measures from range_tails, calls set_risk_constraints! on the loss tail with loss = true and on the gain tail with loss = false, and registers the sum under key. The gain tail sees the negated net portfolio returns, which is the whole of what "the other tail" means and is what each base builder's loss keyword does.
The two tails carry rke = false and no upper bound, so only the composite expression reaches the objective and the bound. Each tail builds under its own measure index, composed by nested_index, so a two-tail model names its parts by the side they describe and a range nested in a range stays collision-free.
Arguments
model::JuMP.Model: The JuMP optimisation model.i: Constraint index for unique variable and constraint naming.r::RiskMeasure: The range risk measure.name::Symbol: Bare Model State entry name for the composite range expression.opt::RiskJuMPOptimisationEstimator: Risk-based optimisation estimator.pr::AbstractPriorResult: Prior result containing the returns matrixX.
Keyword arguments
prefix::Symbol: Model State namespace (default: empty, i.e. the bare key).
Returns
range_risk: The combinedloss + gainrisk expression added to the model.
Related
PortfolioOptimisers.set_drawdown_constraints! — Function
set_drawdown_constraints!(
model::Model,
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}};
prefix
) -> Any
Add portfolio drawdown tracking variables and constraints to model.
Creates the dd variable array (length T + 1) together with three constraints: cdd_start (initial drawdown is zero), cdd_geq_0 (drawdowns are non-negative), and cdd (drawdown recurrence relation). Returns the dd array; returns the existing one if already present in model.
Mathematical definition
Drawdown recurrence:
\[\begin{align} dd_0 &= 0\,, \\ dd_t &\geq 0\,, \\ dd_t &\geq dd_{t-1} - \hat{r}_t \quad \Leftrightarrow \quad dd_t &= \max_{s \leq t} V_s - V_t\,. \end{align}\]
Where:
- $dd_t$: Portfolio drawdown at time $t$.
- $\hat{r}_t$: Portfolio return at time $t$.
- $V_t$: Cumulative portfolio wealth at time $t$.
where $\hat{r}_t = \boldsymbol{x}_t^\intercal \boldsymbol{w}$ and $V_t = k + \sum_{s=1}^t \hat{r}_s$.
Arguments
model::JuMP.Model: The JuMP optimisation model.X::MatNum: Asset returns matrix (T × N).
Returns
dd: JuMP variable array of lengthT + 1tracking portfolio drawdowns.
Related
PortfolioOptimisers.risk_series — Function
risk_series(
model::Model,
::NetReturnsRiskSeries,
pr::AbstractPriorResult;
loss,
prefix
) -> Tuple{Any, Any}
Build the per-observation series a conic risk measure reduces, and its length.
The returns twin and the drawdown twin of a conic tail measure are one programme under the substitution net_X -> -dd[2:T+1]. This function is the one place that substitution is written, so each builder takes (series, T) and encodes both twins.
The series is signed as a return: a loss is a negative entry, on both markers. That is why the drawdown branch negates — dd is a non-negative loss path — and it is what lets one builder body serve both.
Arguments
model::JuMP.Model: The JuMP optimisation model.alg::NetReturnsRiskSeries: Reduce the net portfolio returns.pr::AbstractPriorResult: Prior result containing the returns matrixX.
Keyword arguments
loss::Bool:truebuilds the loss tail on the net portfolio returns,falsethe gain tail on their negation.prefix::Symbol: Model State namespace (default: empty, i.e. the bare key).
Returns
series: The per-observation return series, lengthT.T::Int: The number of observations.
Related
risk_series(
model::Model,
::DrawdownRiskSeries,
pr::AbstractPriorResult;
prefix
) -> Tuple{Any, Any}
Build the negated drawdown path a drawdown risk measure reduces, and its length.
Registers the drawdown variables through set_drawdown_constraints!, then returns -dd[2:T+1]. The negation is what makes the drawdown path a return series, so a builder written against net portfolio returns encodes the drawdown twin unchanged.
There is no loss keyword. A drawdown has no gain tail, so a caller that tries to compose a range from this series fails at the call site rather than silently building the loss tail twice.
Arguments
model::JuMP.Model: The JuMP optimisation model.alg::DrawdownRiskSeries: Reduce the negated drawdown path.pr::AbstractPriorResult: Prior result containing the returns matrixX.
Keyword arguments
prefix::Symbol: Model State namespace (default: empty, i.e. the bare key).
Returns
series: The negated drawdown path-dd[2:T+1], lengthT.T::Int: The number of observations.
Related
PortfolioOptimisers.prior_high_order_quantity — Function
prior_high_order_quantity(pr::AbstractPriorResult, key::Symbol)Read the high-order quantity named by key off prior result pr, or nothing when it carries none.
A HighOrderPrior carries kt, sk, V and the three vectorisation matrices. A LowOrderPrior carries none of them, and a HighOrderPrior fitted with only one of the two tensors leaves the other nothing, so the answer is a value or nothing rather than an error either way.
Related
PortfolioOptimisers.assert_high_order_quantity — Function
assert_high_order_quantity(q, pr::AbstractPriorResult, rm::Symbol, key::Symbol,
est::Symbol)Refuse a high-order risk measure whose tensor resolves neither on the measure nor on the prior.
q is what the measure holds in the slot named key, after resolve_deferred_quantities has run. The measure is buildable when either side supplies the quantity, so the gate is on the pair rather than on the type of pr: a caller who has told the measure how to build its own cokurtosis has already met the requirement, and a HighOrderPrior that computed neither tensor does not meet it.
The message names the three ways out: state the quantity, name a est or an AbstractPriorEstimator in the slot, or give the optimiser a prior estimator that computes one.
Related
PortfolioOptimisers.dup_elim_sum_selector — Function
dup_elim_sum_selector(pr::AbstractPriorResult, N::Integer)Select the duplication, elimination and summation matrices for N assets from prior result pr, rebuilding whichever of the three it does not carry.
dup_elim_sum_matrices is a pure function of the asset count, with no data in it, so the three matrices a HighOrderPrior carries are exactly the ones this rebuilds. That is what lets a measure holding its own tensor be built against a LowOrderPrior: the vectorisation matrices were the only other thing the kernel took from the prior.
One call builds all three, so the rebuild costs the same whether one of them is missing or all three are.
Related