Base Risk Measures: private API
PortfolioOptimisers.AbstractBaseRiskMeasure — Type
abstract type AbstractBaseRiskMeasure <: AbstractEstimatorAbstract supertype for all risk measure estimators.
Defines the interface for risk measure types, which quantify portfolio risk using various statistical or econometric methods. All concrete risk measure types should subtype AbstractBaseRiskMeasure to ensure consistency and composability within the optimisation framework.
All concrete risk measures can be used as functors (callable structs) to compute their associated risk quantity.
Related
PortfolioOptimisers.NonOptimisationRiskMeasure — Type
abstract type NonOptimisationRiskMeasure <: AbstractBaseRiskMeasureAbstract supertype for risk measures that are not intended for use in portfolio optimisation routines.
These risk measures are typically used for analysis, reporting, or diagnostics, and are not designed to be included as objectives or constraints in optimisation problems. Subtype this when implementing a risk measure that should not be selectable by optimisation algorithms.
Related
PortfolioOptimisers.OptimisationRiskMeasure — Type
abstract type OptimisationRiskMeasure <: AbstractBaseRiskMeasureAbstract supertype for risk measures that are intended for use in portfolio optimisation routines.
All concrete risk measures that can be used as objectives or constraints in optimisation problems should subtype OptimisationRiskMeasure. This ensures compatibility with the optimisation framework and enables composability with other estimators and algorithms.
Related
PortfolioOptimisers.JuMPRiskMeasureSettings — Type
abstract type JuMPRiskMeasureSettings <: AbstractRiskMeasureSettingsAbstract supertype for risk measure settings used in JuMP-based optimisation routines.
All concrete settings types compatible with JuMP optimisation (e.g. RiskMeasureSettings, MaxRiskMeasureSettings) should subtype JuMPRiskMeasureSettings.
Related
PortfolioOptimisers.NonHierarchicalScalariser — Type
abstract type NonHierarchicalScalariser <: ScalariserAbstract supertype for scalarisation strategies that combine multiple risk measures into a single scalar value, and that every optimisation estimator can build.
Subtype NonHierarchicalScalariser when the aggregation has a convex JuMP form, so a JuMP optimiser can build it into the model as well as a clustering optimiser can compute it per cluster. JuMPOptimiser.sca is bounded here.
The split names a consumer, not a property of the number. It does not restrict the value-level readers, which admit all four scalarisers — see Scalariser.
Related
PortfolioOptimisers.HierarchicalScalariser — Type
abstract type HierarchicalScalariser <: ScalariserAbstract supertype for scalarisation strategies that combine multiple risk measures into a single scalar value, and that only the clustering optimisation estimators can build.
Subtype HierarchicalScalariser when the aggregation has no convex JuMP form. JuMPOptimiser.sca is bounded NonHierarchicalScalariser and refuses this half; the clustering optimisers compute each cluster's risk separately and accept it.
The split names a consumer, not a property of the number. At the value level the measures are evaluated first and the strategy combines plain numbers, so nothing is being built and all four scalarisers are admitted — see Scalariser.
Related
PortfolioOptimisers.DeferredQuantity — Type
const DeferredQuantity = Union{...}The dynamic half of a Deferred Quantity: an Estimator standing in a risk-measure slot where a prior-derived value goes. isa(x, DeferredQuantity) answers "is this slot deferred?" — the four moment-estimator families that compute one of the four deferrable quantities, plus AbstractPriorEstimator, which computes all of them at once.
The union exists because no supertype already answers the question: AbstractCovarianceEstimator is a StatsBase.CovarianceEstimator and not an AbstractEstimator.
Related
PortfolioOptimisers.MuSlot — Type
const MuSlot = Union{<:Num_VecNum_VecScalar, <:AbstractExpectedReturnsEstimator, <:AbstractPriorEstimator}Field bound for an expected-returns slot: the value itself, or the Estimator that computes it. See DeferredQuantity.
Related
PortfolioOptimisers.SigmaSlot — Type
const SigmaSlot = Union{<:MatNum, <:StatsBase.CovarianceEstimator, <:AbstractPriorEstimator}Field bound for a covariance slot: the matrix itself, or the Estimator that computes it. See DeferredQuantity.
Related
PortfolioOptimisers.KtSlot — Type
const KtSlot = Union{<:MatNum, <:CokurtosisEstimator, <:AbstractPriorEstimator}Field bound for a cokurtosis slot: the matrix itself, or the Estimator that computes it. See DeferredQuantity.
Related
PortfolioOptimisers.SkSlot — Type
const SkSlot = Union{<:MatNum, <:CoskewnessEstimator, <:AbstractPriorEstimator}Field bound for a coskewness slot: the matrix itself, or the Estimator that computes it. See DeferredQuantity.
Related
PortfolioOptimisers.VecBaseRM — Type
const VecBaseRM = AbstractVector{<:AbstractBaseRiskMeasure}Alias for an abstract vector of AbstractBaseRiskMeasure elements.
Related
PortfolioOptimisers.BaseRM_VecBaseRM — Type
const BaseRM_VecBaseRM = Union{<:AbstractBaseRiskMeasure, <:VecBaseRM}Argument bound for every value-level reader of a risk measure: one measure or several.
Mirrors JRE_VecJRE on the return side. A method bounded here serves a measure and a vector of them with one body, so the value-level surface needs one method per arity rather than two.
A vector is scalarised into one number by the sca keyword, which defaults to SumScalariser and is silently inert on a single measure.
Related
PortfolioOptimisers.VecOptRM — Type
const VecOptRM = AbstractVector{<:OptimisationRiskMeasure}Alias for an abstract vector of OptimisationRiskMeasure elements.
Related
PortfolioOptimisers.OptRM_VecOptRM — Type
const OptRM_VecOptRM = Union{<:OptimisationRiskMeasure, <:VecOptRM}Union type accepting a single OptimisationRiskMeasure or a vector of them.
Related
PortfolioOptimisers.VecRM — Type
const VecRM = AbstractVector{<:RiskMeasure}Alias for an abstract vector of RiskMeasure elements.
Related
PortfolioOptimisers.RM_VecRM — Type
const RM_VecRM = Union{<:RiskMeasure, <:VecRM}Union type accepting a single RiskMeasure or a vector of them.
Related
PortfolioOptimisers.RkRtBounds — Type
const RkRtBounds = Union{<:Num_VecNum, <:Frontier}Union type for risk-measure upper bound specifications.
Accepts either a scalar/vector numeric bound or a Frontier sweep configuration. Used in RiskMeasureSettings to set the upper bound field.
Related
PortfolioOptimisers.Front_NumVec — Type
const Front_NumVec = Union{<:VecNum, <:Frontier}Union type for frontier or numeric-vector specifications used internally for risk bounds.
Related
PortfolioOptimisers.RiskInputKind — Type
abstract type RiskInputKindAbstract supertype for the input-shape classification of a risk measure, used by expected_risk to decide what to feed a measure's functor.
Each concrete AbstractBaseRiskMeasure declares its kind via risk_input_kind. The three kinds correspond to the three functor call shapes:
NetReturnsInput:r(calc_net_returns(w, X, fees)).WeightsReturnsFeesInput:r(w, X, fees).WeightsInput:r(w).
Related
PortfolioOptimisers.NetReturnsInput — Type
struct NetReturnsInput <: RiskInputKindInput kind for risk measures whose expected risk is computed on net returns (returns after fees). The measure's functor is called as r(calc_net_returns(w, X, fees)).
Related
PortfolioOptimisers.WeightsReturnsFeesInput — Type
struct WeightsReturnsFeesInput <: RiskInputKindInput kind for risk measures whose expected risk depends on weights, the returns matrix, and fees. The measure's functor is called as r(w, X, fees).
Related
PortfolioOptimisers.WeightsInput — Type
struct WeightsInput <: RiskInputKindInput kind for risk measures whose expected risk depends only on portfolio weights. The measure's functor is called as r(w).
Related
PortfolioOptimisers.scalarise_combine — Function
scalarise_combine(op, a, b)Combine two scalarised risk values slot-wise.
Numbers combine directly via op, arrays elementwise, and tuples recursively per slot. Used by scalarise to accumulate values that may be scalars, vectors, or tuples mixing both.
Related
PortfolioOptimisers.scalarise_map — Function
scalarise_map(op, x)Apply op slot-wise to a scalarised risk value.
Numbers are transformed directly, arrays elementwise, and tuples recursively per slot.
Related
PortfolioOptimisers.scalarise_logsumexp — Function
scalarise_logsumexp(vs)Slot-wise log-sum-exp across a vector of scalarised risk values.
For a vector of numbers this is LogExpFunctions.logsumexp; for a vector of same-shaped arrays it is applied elementwise across the vector; for a vector of same-shaped tuples it recurses per slot.
Related
PortfolioOptimisers.nothing_scalar_array_selector — Function
nothing_scalar_array_selector(risk_variable::Nothing, prior_variable::Nothing)
nothing_scalar_array_selector(risk_variable::Num_ArrNum_VecScalar_DynWeights, ::Any)
nothing_scalar_array_selector(risk_variable::Nothing, prior_variable::Num_ArrNum_VecScalar_DynWeights)Function for selecting a non-nothing value when provided by a risk measure, or fall back to a value contained in a prior result
Arguments
risk_variable: The risk-side input.prior_variable: The prior-side input.
Returns
- If both inputs are
nothingreturnsnothing. - If
risk_variableis notnothing, returnsrisk_variable. - If
risk_variableisnothingandprior_variableis notnothing, returnsprior_variable.
PortfolioOptimisers.deferred_factors — Function
deferred_factors(pr::AbstractPriorResult)Return the factor returns matrix carried by prior result pr, or nothing when pr has no factor block. This is the only channel through which factors reach a risk-measure slot: no moment estimator takes an F, so a slot that must see factors has to hold an AbstractPriorEstimator.
Related
PortfolioOptimisers.fit_deferred_quantity — Function
fit_deferred_quantity(dq::DeferredQuantity, pr::AbstractPriorResult)Run a Deferred Quantity against the optimisation's own prior result and return what it produces: a moment estimator gives its quantity, an AbstractPriorEstimator gives a whole AbstractPriorResult.
The estimator sees pr.original_X — the returns the caller supplied, sliced by any port_opt_view the measure crossed. pr.w is threaded through factory, so a weighted prior replaces the estimator's own observation weights and an unweighted prior leaves them alone.
pr.original_X rather than pr.X, because the two differ on a factor route. FactorPrior, FactorBlackLittermanPrior and AugmentedBlackLittermanPrior all overwrite X with the reconstruction F * transpose(M) .+ transpose(b), which spans only the factors: it has rank size(F, 2), and it carries no residual. Fitting a covariance estimator on it returns a singular matrix whenever there are more assets than factors, and a prior estimator in a slot could not regress it against those same factors. Off a factor route original_X === X, so nothing moves.
A CokurtosisEstimator gives its tensor. A CoskewnessEstimator gives the pair (sk, V) together with the matrix-processing estimator that built V, as a named tuple — V is derived from sk and never travels on its own. See coskewness_processor.
mean is the centre the higher moment is taken about. deferred_centre supplies it, so that the resolved mu and the resolved kt or sk describe one distribution. nothing leaves the estimator to centre on its own me.
Algorithm
- Rebuild
dqagainst the prior's observation weights withfactory(dq, pr.w). The two co-moment arms bind the rebuilt estimator askteandske. - Run the rebuilt estimator on
pr.original_X, through the verb of its own family:Statistics.meanfor an expected-returns estimator,Statistics.covfor a covariance estimator,cokurtosisfor a cokurtosis estimator,coskewnessfor a coskewness estimator, andpriorfor a prior estimator, which also takesdeferred_factors(pr). - Pass
meanon to the two co-moment verbs, so the tensor is taken about the centre the caller named. - Return the quantity the verb gave. The coskewness arm returns the named tuple
(; sk, V, skmp)instead, becauseVcomes out of the same call andcoskewness_processornames the estimator that built it.
Returns
- The deferred quantity: a vector for an expected-returns estimator, a matrix for a covariance or a cokurtosis estimator, an
AbstractPriorResultfor a prior estimator, and the named tuple(; sk, V, skmp)for a coskewness estimator.
Related
PortfolioOptimisers.coskewness_processor — Function
coskewness_processor(ske::CoskewnessEstimator)Return the matrix-processing estimator that a CoskewnessEstimator uses to build V, or nothing when the estimator names none.
V = negative_spectral_coskewness(sk, X, mp), so building V always names a processor. When a coskewness estimator stands in a NegativeSkewness sk slot, that estimator's processor is the one that built the V it hands back, and the measure records it in place of its own mp so that a later rebuild uses the same one. This mirrors HighOrderPrior's skmp.
The CoskewnessEstimator interface does not require an mp field, so the default answers nothing and the measure keeps the processor it already holds. Declare a method for an estimator that names one.
Related
PortfolioOptimisers.deferred_centre — Function
deferred_centre(dq, pr::AbstractPriorResult)Return the centre that a Deferred Quantity in a kt or sk slot takes its moment about, or nothing when the estimator names none.
A higher moment is a moment about a centre, so the tensor and the centre are one pair of quantities out of one object. When the measure leaves mu unstated, the centre comes from the co-moment estimator's own me run on pr.original_X, is threaded back into the fit as mean =, and becomes the resolved mu. The measure then centres on exactly the vector its tensor was built about.
A stated mu wins and is threaded in its place. An AbstractPriorEstimator centres itself, so the answer is nothing and the centre is read off the prior result it produced instead.
The CokurtosisEstimator and CoskewnessEstimator interfaces do not require an me field, so the default answers nothing. Declare a method for an estimator that names one.
Related
PortfolioOptimisers.centring_target — Function
centring_target(mu)Turn a resolved mu into the per-asset centre that cokurtosis and coskewness take as mean =.
Those two subtract the centre from the T × N returns matrix before they build the tensor, so the centre is a row rather than a column. A VecScalar contributes its scalar to every asset, which is the per-asset image of the portfolio-level target dot(w, mu.v) + mu.s. nothing leaves the estimator to centre on its own me.
Related
PortfolioOptimisers.fit_deferred_moment — Function
fit_deferred_moment(dq, pr::AbstractPriorResult, centre)Run a Deferred Quantity that stands in a kt or sk slot, about the centre centre.
A co-moment estimator takes the centre as mean = (centring_target puts it in the shape the estimator wants), so the tensor is built about exactly the vector the measure will resolve mu to.
Every other occupant centres itself and has no channel to take one, so centre is dropped. That is the case of an AbstractPriorEstimator in the slot: it computes its own mu and its own tensor about that mu, and the centre is read back off the result it produced rather than pushed into it. A mu the caller stated alongside such a slot still wins as the measure's centring target, and the docstring's consistency warning is what covers the gap.
Algorithm
- Return
fit_deferred_quantity(dq, pr)whendqis neither aCokurtosisEstimatornor aCoskewnessEstimator. Such an occupant has no channel to take a centre, socentreis dropped. - Put
centreinto the row shape a co-moment estimator wants withcentring_target. - Run
fit_deferred_quantitywith that row asmean, and return what it gives.
Returns
- The moment the fit produced, taken about
centreon the two co-moment arms.
Related
PortfolioOptimisers.deferred_quantity — Function
deferred_quantity(fitted, key::Symbol)Read the quantity named by key off what fit_deferred_quantity produced.
A moment estimator produced the quantity itself, so it is returned and key is inert. A CoskewnessEstimator produced a named tuple, because sk and V come out of one call. An AbstractPriorEstimator produced a prior result, so key picks the one wanted from the several it computed.
Validation
- Throws an
ArgumentErrorwhenfittedis aNamedTuplecarrying nokey, and whenfittedis anAbstractPriorResultcarrying no propertykey. The slot named an estimator that computes something else, and a silentnothingwould reach the model builders as though the caller had stated none. This is the difference fromdeferred_derived_quantity, which answersnothingfor a key its fit does not carry.
Returns
- The quantity named by
key, or the whole fit when a moment estimator produced it.
Related
PortfolioOptimisers.fan_out_slot — Function
fan_out_slot(fitted, slot, key::Symbol)Fill one slot from a fan-out fit, unless the caller stated it.
A measure that carries two or more independently deferrable slots takes a pe instead of widening each slot. fit_deferred_quantity runs that estimator once, and this reads one quantity per slot off the single result it produced.
The precedence is the map's: a stated slot wins, pe fills the rest, and a slot that neither names is left nothing so the consumer's own prior fallback still applies.
A measure with exactly one deferrable slot takes no pe. It widens that slot instead, and a derived companion — chol with sigma, V with sk — travels with it out of the same fit rather than being fanned out separately.
Algorithm
- Return
slotunchanged when it is notnothing. A slot the caller stated wins over the fan-out. - Read the quantity named by
keyofffittedwithdeferred_quantity, and return it.
Returns
- The stated slot, or the quantity named by
key.
Related
PortfolioOptimisers.deferred_derived_quantity — Function
deferred_derived_quantity(fitted, key::Symbol)Read a derived quantity named by key off what fit_deferred_quantity produced — chol, the factorisation that travels with sigma; V, the negative spectral part that travels with sk; mu, the centre a higher moment was taken about; skmp, the processor that built V.
A prior result carries all four. A coskewness estimator produced a named tuple carrying V and skmp but no centre. Anything else produced only its own quantity, so the answer is nothing and the consumer keeps whatever fallback it already had. This is the difference from deferred_quantity, which refuses a key the fit does not carry.
Related
PortfolioOptimisers.resolve_slot — Function
resolve_slot(slot, key::Symbol, pr::AbstractPriorResult)Resolve one risk-measure slot against prior result pr and return a plain value.
A slot that holds a Deferred Quantity is run against pr and the quantity named by key is read back. Anything else — a stated value, nothing, a centring strategy — is returned unchanged, so the caller can apply the ordinary prior fallback (sel) on top.
This is the whole of the third state. The other two are unchanged: nothing still falls back to the prior's own field, and a stated value still wins.
Algorithm
- Return
slotunchanged when it is not aDeferredQuantity. A stated value,nothingand a centring strategy all take that arm. - Run the Deferred Quantity against
prwithfit_deferred_quantity, givingfitted. - Read the quantity named by
keyofffittedwithdeferred_quantity, and return it.
Returns
- A plain value: the slot's own occupant, or the quantity the fit produced.
Related
PortfolioOptimisers.deferred_slots — Function
deferred_slots(x)Declare the slots of x that may hold a Deferred Quantity, as a NamedTuple mapping each slot's name to its current value. The default is empty: a type with no deferrable slot needs no method.
This is the declaration both consumers read. assert_resolved_slots refuses a slot that a value-level entry point cannot resolve, and resolve_deferred_quantities derives its container recursion from it. A type that names its slots here needs no forwarding method of its own.
A slot that holds a child measure is declared here too. Both consumers recurse into whatever a slot holds, so a container names its children and each child names its own slots; nothing walks fields blindly. That matters because a risk measure holds Estimators that are not deferred slots — a variance estimator in ve, an uncertainty-set estimator in ucs — and a blind walk would refuse them.
A type that resolves a quantity of its own — a matrix out of a covariance estimator, a tensor and the centre it was taken about out of a co-moment estimator — declares a resolve_deferred_quantities method beside this one. That method is per type because slots that travel together must be resolved together, which no derivation can know. Declaring the slots without the method is refused at the first call rather than passed over.
Related
PortfolioOptimisers.functor_slots — Function
functor_slots(x)Declare the slots of x that its functor reads as they stand, as a NamedTuple mapping each slot's name to its current value. The default is empty: a type whose functor computes every moment from the returns it is handed needs no method.
assert_resolved_slots reads this beside deferred_slots. A slot named here that holds nothing is refused at a value-level entry point by name, because nothing on that route fills it and the functor would otherwise meet the nothing several frames down, as dot(w, nothing, w) inside LinearAlgebra.
A slot is named here only when nothing there has no value-level reading. The covariance a Variance contracts the weights with is one: the functor is dot(w, r.sigma, w), and a bare matrix cannot stand in. A moment measure's mu is not: nothing there means centre on the sample mean, which the functor computes from the returns, so LowOrderMoment declares no method. A slot that holds a child measure is not named here either — the child names its own, and assert_resolved_slots reaches it through deferred_slots. A container that reads a grandchild directly names it by the path the caller wrote, so a refusal reads VarianceSkewKurtosis.sk.sk.
Related
PortfolioOptimisers.resolve_calibration_slots — Method
resolve_calibration_slots(x, pr::AbstractPriorResult, slv = nothing)Resolve the slots that calibration_slots declared, and return them as a NamedTuple for resolve_deferred_quantities to rebuild from. The answer is empty when no slot held a Calibration Rule.
This is the derived half of the calibration channel, and it is the parallel of the container recursion in resolve_deferred_quantities. A type whose slots carry no order between them declares them once and writes no resolution: the declaration is the whole statement, and this method reads it.
A type whose slots do carry an order writes its own resolve_deferred_quantities method instead, and that method is more specific, so it wins. A CalibrationContext built from a sibling's resolved number is what an order looks like: the slot reads that number off the context, and no derivation can know which sibling or in which direction. That rule reaches the slots that read a sibling and no others.
A type whose slot key is not the field's name declares a method of this verb returning an empty NamedTuple, which takes it out of the derivation. The three regularisation keys are the one case that ships: val is one field under three quantities, and a derivation that read the field name would hand the rule the wrong key.
The effective observation weights and the effective solver are the two quantities a rule reads beyond the prior, and both are read off x by the names the library gives them everywhere: w holds the measure's own observation weights and slv its own solver. A type that carries the field settles it against the caller's with sel; a type that carries none reads nothing and sel then hands over what the caller gave. The read is skipped where no slot holds a role, so a container whose w names a child rather than a weight vector never reaches it.
The empty answer is what keeps a container's key out of the merge. A container names one child in both declarations, the child holds no role, and the answer is therefore empty rather than a restatement of the unresolved child.
Algorithm
- Read the slots
xdeclares withcalibration_slots, givingslots. - Return an empty
NamedTuplewhenis_calibration_ruleanswersfalsefor every entry ofslots. A stated number resolves to itself, so the walk is skipped. A stated number resolves to itself, so the walk would move nothing. - Read every field of
xintoprops, by index, which is the shaperebuild_with_slotsbuilds. A field read by name would name a field some type reaching this method does not carry. - Settle the effective observation weights
w, assel(props.w, pr.w)wherexcarries the field andsel(nothing, pr.w)where it does not. - Settle the effective solver
sv, assel(props.slv, slv)wherexcarries the field andsel(nothing, slv)where it does not. - Resolve every entry of
slotswithresolve_calibration_slot, giving each its own name as the key and threadingpr,wandsv.
Returns
slots::NamedTuple: The resolved calibration slots, and an emptyNamedTuplewhere none held a role.
Related
PortfolioOptimisers.resolve_deferred_quantities — Method
resolve_deferred_quantities(x, ) -> StandardDeviation
resolve_deferred_quantities(x, , ) -> StandardDeviation
Resolve every Deferred Quantity held by x against prior result pr, returning a struct of the same type whose deferred slots hold plain values.
This resolves the deferred state and nothing else. A slot left unstated stays nothing, so whichever fallback the consumer already applies — sel on the factory path, chol_sigma_selector and its siblings on the JuMP path — keeps working unchanged. The two paths are separate: a JuMP model builder reads the risk measure's slots directly and never calls factory, so both entry points resolve.
Given a prior result the rule has two halves. Container recursion is derived from deferred_slots, so a type that only holds children needs no method at all. A type that resolves a quantity of its own defines a method, which overrides the derived one. Writing that half per type — rather than per field — is what lets slots that travel together be resolved together: a deferred sigma supplies chol from the same fit, so the pair is never mixed across two sources.
slv is the effective solver, and it is what a Calibration Rule in the same struct reads. It carries the value the optimisation settled on, so a rule resolves against one solver on both routes. On the factory route the @cprop selection has already put that solver on the struct, so the argument stays at its default. On the JuMP route no selection runs, so set_risk_constraints! reads the solver off the estimator and threads it here. A type that carries a solver of its own settles it locally as sel(x.slv, slv), beside the observation weights it already settles that way, and a type that carries none gives its rules none on either route.
Algorithm
- Return
xunchanged. This method is the arm for a second argument that is not a prior result: with no prior in hand nothing can be fitted, so the deferred state travels on.
A more specific method dominates this one on a prior result: the one that deferred_slots derives for a container, and the hand-written one of a type that resolves a quantity of its own.
Related
resolve_deferred_quantities(x, pr::AbstractPriorResult, slv = nothing)Resolve the children that deferred_slots declared and the slots that calibration_slots declared, and return x itself when none of them changed.
This is the derived half of the resolution rule. A container declares its children once and both entry points follow: factory reaches them through @fprop, and the JuMP builders reach them through this method. Neither needs a forwarding method per container.
A type that resolves a quantity of its own overrides this with its own method, which is more specific. So the derivation carries container recursion alone, and never guesses how a matrix, a tensor or the centre a moment was taken about comes out of a fit.
Both channels end in one rebuild: a measure that carries both kinds of slot must not be rebuilt twice. resolve_calibration_slots states the calibration half and returns its resolved slots rather than a rebuilt object, and the two answers merge here. The deferred half merges last, so it wins a key both channels declare. A container names one child in both, and the child the recursion resolved is the one to keep.
slv is the effective solver, and the recursion threads it to every child. A container states no solver of its own, so it changes none: each child settles the one it was handed against the one it carries.
Algorithm
- Read the slots
xdeclares withdeferred_slots, givingslots. - Read the resolved calibration slots with
resolve_calibration_slots, givingcalibrated. - Return
xunchanged when both are empty. A type with neither kind of slot needs no method of its own. - Resolve every entry of
slotswithresolve_deferred_child, threadingprandslvto each, givingresolved. - Refuse a slot the recursion left unresolved with
assert_declared_slot_resolver. - Hand
merge(calibrated, resolved)torebuild_with_slots, which returnsxitself when no entry moved and a rebuilt copy when one did.
Returns
xitself when no slot moved, and a rebuilt copy ofxwhen one did.
Related
PortfolioOptimisers.resolve_deferred_child — Function
resolve_deferred_child(slot, pr::AbstractPriorResult, slv = nothing)Resolve one slot that deferred_slots declared, on behalf of the derived recursion in resolve_deferred_quantities.
A slot holds one of three things, and one rule covers all three. A child measure resolves through its own method. A vector of children resolves element by element, which is the rule factory_child already applies on the other path. Anything else — a stated value, nothing, a Deferred Quantity the enclosing type resolves itself — is returned unchanged.
The vector arm is bounded by the element type rather than by AbstractArray, so a matrix slot is a value and never a container of children.
slv is the effective solver, and it travels down to the child untouched. A child that carries a solver of its own settles it against this one; a child that carries none gives its rules none. So a container hands each child the same solver the enclosing measure was resolved against.
Related
PortfolioOptimisers.rebuild_with_slots — Function
rebuild_with_slots(x, slots::NamedTuple)Return x itself when every field named by slots already holds the value slots gives it, and a copy of x carrying those values when one of them moved.
This is the one rebuild of the resolution channel. The field list is derived from the type and the constructor is recovered from it, so nothing is written per type: a field added to the type joins the rebuild on its own, and no site can drop one. Every resolve_deferred_quantities method ends here, the derived recursion and the per-type overrides alike.
The call is positional, so the inner constructor runs and every guard the type states is re-applied to the rebuilt value. A Calibration Rule that returns a value the slot does not admit is therefore refused at fold time, by the guard a caller's own number meets.
Algorithm
- Read the type of
xintoT. - Read every field of
xinto the named tupleprops, in declaration order. - Return
xitself when every entry ofslotsis the value the field of that name already held, so the common case allocates nothing. - Merge
slotsoverprops, so a named slot carries its new value and every other field survives. - Call
T.name.wrapperpositionally on the merged values, so the inner constructor runs and re-applies every guard the type states.
Validation
- Throws whatever the inner constructor of
Tthrows for the merged values, so a slot that moved out of its admitted range is refused here.
Returns
xitself when no slot moved, and a value of the same type asxholding the values inslotswhen one did.
Related
PortfolioOptimisers.assert_declared_slot_resolver — Function
assert_declared_slot_resolver(x, slots::NamedTuple)Refuse a type that declares a deferrable slot and no way to resolve it.
slots is what the derived recursion produced. A Deferred Quantity that survives it names a type that declared the slot in deferred_slots and then wrote no resolve_deferred_quantities method, so the estimator would reach the model builders and be multiplied as though it were a matrix. The two declarations are paired; this is where the pair is enforced.
Algorithm
- Walk the pairs of
slots, giving each slot's namekeyand its occupantslot. - Refuse an occupant that is still a
DeferredQuantity. - Return
nothingonce the walk is spent.
Validation
- Throws an
ArgumentErrorwhen an entry ofslotsstill holds aDeferredQuantity. The message names the type, the slot and the method to declare.
Returns
nothing.
Related
PortfolioOptimisers.assert_resolved_slots — Function
assert_resolved_slots(x)Refuse a Deferred Quantity, or an empty slot the functor reads, that reached a value-level entry point, which has no prior result to resolve or fill it with.
expected_risk takes either a prior result or a plain returns matrix. Given the prior it resolves the measure through factory first. Given the matrix it cannot: that call has no pr.w to thread and no factor returns to reach, so resolving there would use a different rule than the settled one. So it refuses instead, naming the slot and the Estimator standing in it — without the refusal the failure lands several frames down, inside a kernel that expected a matrix.
The same door refuses a slot that holds nothing when the functor reads it as it stands. Variance() is built with sigma at nothing, and the prior route fills it through factory; the matrix route has nothing to fill it from, and dot(w, nothing, w) raised a bare MethodError inside LinearAlgebra that named neither the measure nor the slot (#1079). Which slots those are is declared by functor_slots, so a slot whose nothing has a value-level reading — a moment measure's mu, which then means the sample mean — is never refused. Resolving the slot from the matrix instead would pick an estimator the caller never named, and would make the matrix route and the prior route disagree whenever the prior is not empirical, so the door refuses by name and states the two ways out.
This is the shape HopCount and PathLength already use: the consumer resolves, the kernel refuses.
The slots come from deferred_slots and the check recurses into whatever they hold, so a container is covered by its children's declarations. A slot that holds a vector of children is recursed element by element, which is the rule resolve_deferred_child applies on the resolution path. Every slot of a concretely-typed measure has a concrete field type, so the test is a type-level one and a leaf measure compiles the whole check away. A container pays one small allocation per call for the recursion into its children.
The message names both types with nameof, not by printing the type. A printed type carries a module prefix whenever the name is not visible from Main, which is the case inside an isolated test worker and inside any module that imports the package qualified. Variance.sigma is the path the caller wrote, and the message must read the same in every process.
Algorithm
- Walk the pairs that
deferred_slotsdeclares forx, giving each slot's namekeyand its occupantslot, and refuse an occupant that holds aDeferredQuantity. - Recurse into every occupant, so a child measure's own slots are checked as well. A slot that holds a vector of children is walked element by element.
- Walk the pairs that
functor_slotsdeclares forx, and refuse an occupant that isnothing. - Return
nothingonce the three walks are spent.
The order is the precedence. A Deferred Quantity on x is named before anything below it, because its fit is what would fill the empty slots of the children — a VarianceSkewKurtosis whose pe is deferred has an empty sigma on its vr for that reason alone. A child is walked before the slots x reads off it, for the same reason one level down: a Skewness whose pe is deferred has an empty sk, and that is the slot the container reads.
Validation
- Throws an
ArgumentErrorwhen a slot ofx, or of any child the walk reaches, holds aDeferredQuantity. The message names the slot, the Estimator standing in it and the two ways out. - Throws an
IsNothingErrorwhen a slot thatfunctor_slotsdeclares forx, or for any child the walk reaches, holdsnothing. The message names the slot and the two ways out: a prior result that carries the quantity, or the quantity stated on the measure.
Returns
nothing.
Related
PortfolioOptimisers.sigma_chol_selector — Function
sigma_chol_selector(sigma, chol, pr::AbstractPriorResult)Apply the prior fallback to a covariance slot and its factorisation as a pair, so that the two never come from two different sources.
chol is a factorisation of sigma. Pairing a factor with a covariance matrix it does not factorise would let the model optimise one quantity while the functor evaluates another. So the pair falls back to the prior only when the measure names neither; a stated sigma with no factor keeps no factor, and the consumer derives the right one.
The Deferred-Quantity state never reaches here: resolve_deferred_quantities has already turned it into the pair the fit produced. Nor does a stated chol without a matrix sigma beside it — assert_derived_slot_has_source refuses both that and a chol stated beside a deferred sigma at construction, so the two arms below see only value states.
A stated chol is never rebuilt from sigma. Under a factor prior the factorisation is sparse and special, and a rebuild would throw that structure away.
Related
PortfolioOptimisers.assert_derived_slot_has_source — Function
assert_derived_slot_has_source(derived, source, dname::Symbol, sname::Symbol)Refuse a derived slot that was stated without a source the caller can see.
A derived slot is a function of its source, so the two are one pair out of one fit. Two states break that, and both are refused at construction:
- The source is unstated. The prior supplies it, so the caller's derived value would pair with a source the caller never saw.
- The source holds a Deferred Quantity. That fit supplies the pair, so the caller's derived value would be discarded, or worse, kept beside a source it does not describe.
chol is a factorisation of sigma, and V is the negative spectral part of sk. Both follow this rule, so a stated derived slot always means a stated source value.
Arguments
derived: The derived slot's occupant.source: The source slot's occupant.dname: Name of the derived slot, as the message prints it.sname: Name of the source slot, as the message prints it.
Validation
- Throws an
ArgumentErrorwhensourceholds aDeferredQuantityandderivedis notnothing. That fit supplies the pair. - Throws an
ArgumentErrorwhenderivedis notnothingandsourceisnothing. The prior would supply the source, and the caller's derived value would pair with a source the caller never saw.
Returns
nothing.
Related
PortfolioOptimisers.risk_measure_nothing_scalar_array_view — Function
risk_measure_nothing_scalar_array_view(risk_variable, prior_variable, i)Get a view of a risk measure's risk or prior variable for index i.
Internal helper for slicing scalar, array, or nothing risk/prior variables by index. Dispatches on the types of risk_variable and prior_variable.
Arguments
risk_variable: Risk variable (scalar, array, ornothing).prior_variable: Prior variable (array ornothing).i: Index or range to slice.
Validation
- Throws an
ArgumentErrorwhenrisk_variableandprior_variableare bothnothing. There is then nothing to slice, and the caller named no source.
Returns
- Sliced or unchanged value.
Related
PortfolioOptimisers.solver_selector — Function
solver_selector(risk_solvers, slv)Select the appropriate solver for a risk measure computation.
Returns the risk-measure-specific solver if provided, otherwise falls back to the optimiser-level solver. Neither is refused rather than answered with nothing: a JuMP model that carries no solver cannot be solved, and the refusal names that where the caller can act on it. sel never reaches the refusal, because a both-nothing call routes to nothing_scalar_array_selector on the operand types instead.
Arguments
risk_solvers: Risk-measure-specific solver(s) ornothing.slv: Optimiser-level solver(s) ornothing.
Validation
- Throws an
ArgumentErrorwhenrisk_solversandslvare bothnothing.
Returns
Slv_VecSlv: The selected solver or solvers.
Related
PortfolioOptimisers.bigger_is_better — Function
bigger_is_better(r::AbstractBaseRiskMeasure) -> BoolReturn whether a larger value of risk measure r is preferred over a smaller one.
The default implementation returns false (lower risk is better) for all AbstractBaseRiskMeasure subtypes. Ratio-based or return-like measures that should be maximised may override this method to return true.
Returns
Bool:trueif a higher value ofris preferred;falseotherwise.
Related
bigger_is_better(
rs::AbstractVector{<:AbstractBaseRiskMeasure}
) -> Bool
Return whether every element of rs shares the same polarity, and throw when they do not.
The scalarised value of a mixed vector such as [Variance(), ExpectedReturn()] has no defined orientation, so neither all nor any is correct. Both are wrong in silence, and the consumers are load-bearing: RankRule and QuantileRule take counts from each tail, and the flag decides which tail is best.
Validation
- Throws an
ArgumentErrorwhen the elements disagree.
Related
bigger_is_better(_::Skewness) -> Bool
Return true because higher skewness is preferred over lower skewness.
Related
PortfolioOptimisers.range_tails — Function
range_tails(r::RiskMeasure) -> @NamedTuple{loss, gain}Decompose a range risk measure into the two point measures it is the sum of.
A range measure is its base measure applied twice: once to the losses at level alpha, and once to the gains at the second level the range carries (beta, w2, …). The two point measures are the whole content of that statement, so a range declares them here once and both of its consumers read them from this one place:
set_range_risk_constraints!builds the model by callingset_risk_constraints!onlosswithloss = trueand ongainwithloss = false, then sums the two expressions.- The measure's own
r(x::VecNum)functor is the value-level twin of the same sum,loss(x) + gain(-x).
The tails carry RiskMeasureSettings(; rke = false): an upper bound and a risk-expression contribution belong to the range as a whole, which registers them once from the composite expression. A range that fuses its two tails into a shared formulation rather than duplicating one (OrderedWeightsArrayRange under ExactOrderedWeightsArray, ValueatRiskRange under DistributionValueatRisk) declares no tails, because there are no two sub-models to build.
There is no default. A measure that is not a range, or one that fuses, throws rather than returning a decomposition that does not describe it.
Arguments
r: Range risk measure.
Validation
- Throws an
ArgumentErrorwhenrdeclares no method of its own. A measure that is not a range, and one that fuses its two tails into a single formulation, both take that arm.
Returns
(; loss, gain): The loss-tail and gain-tail point measures.
Related
PortfolioOptimisers.supports_precomputed_returns — Method
supports_precomputed_returns(r::AbstractBaseRiskMeasure) -> Bool
supports_precomputed_returns(rk::RiskInputKind, r::AbstractBaseRiskMeasure) -> BoolWhether risk measure r has a well-defined precomputed-returns form — i.e. whether its expected risk can be evaluated on an already-reduced net-return series x alone, via the functor r(x::VecNum).
The contract is well-defined exactly when the measure's result is a function of the series alone:
NetReturnsInputmeasures (quantile / drawdown families): alwaystrue— their functor is the net-returns functor.- The moment family (
LowOrderMoment,HighOrderMoment,Skewness,Kurtosis,MedianAbsoluteDeviation,ThirdCentralMoment):trueiff its target is weight-independent (muisnothing, a scalar, or a centering function); a per-assetmu(VecNum/VecScalar) reduces asdot(w, mu)and needs the weights the series no longer carries, sofalse. WeightsInputmeasures, tracking measures, and variance-carrying composites (VarianceSkewKurtosis):false— "risk of a bare return series" is undefined for them.
This predicate is what makes the precomputed-returns contract safe. Because a WeightsInput measure's functor r(w) shares the r(::VecNum) signature with the contract, dispatch alone cannot distinguish weights from returns; expected_risk_from_returns consults this predicate and throws an explanatory error for ineligible measures rather than silently consuming the series as weights.
Related
PortfolioOptimisers.supports_precomputed_returns — Method
supports_precomputed_returns(
rs::AbstractVector{<:AbstractBaseRiskMeasure}
) -> Bool
Return whether every element of rs supports evaluation on a precomputed return series.
Answers by all, matching the && the two ratio composites already use. Note the polarity against its neighbour: needs_previous_weights on the same alias answers by any. Both are correct — "does the vector need it" is not "can the vector do it".
Related
PortfolioOptimisers.supports_precomputed_returns — Method
supports_precomputed_returns(_::NetReturnsInput, _) -> Bool
Return true: NetReturnsInput measures always support precomputed returns — their functor is the net-returns functor.
Related
PortfolioOptimisers.supports_precomputed_returns — Method
supports_precomputed_returns(_::WeightsInput, _) -> Bool
Return false: WeightsInput measures never support precomputed returns — their functor consumes portfolio weights, not a return series.
Related
PortfolioOptimisers.supports_precomputed_returns — Method
supports_precomputed_returns(
_::WeightsReturnsFeesInput,
r::AbstractBaseRiskMeasure
)
Erroring tripwire for WeightsReturnsFeesInput measures that have not declared supports_precomputed_returns at their own definition site. Every such measure must declare it there: moment measures as supports_precomputed_returns(r::T) = weight_independent_target(r.mu); weights-dependent measures (tracking, variance-carrying composites) as supports_precomputed_returns(::T) = false. Reaching this leaf means a measure forgot to declare it — throws an ArgumentError with instructions rather than silently mis-routing (and the completeness test in test_09c_risk_input_kind.jl turns that into a CI failure).
Validation
- Throws an
ArgumentErroron every call. Reaching this leaf means the measure declared nosupports_precomputed_returnsmethod of its own, and the message states the declaration to write.
Returns
- Nothing is returned. The method always raises.
Related
PortfolioOptimisers.weight_independent_target — Method
weight_independent_target(_::Nothing) -> Bool
Return true: a Nothing target is trivially weight-independent and can be evaluated on a bare return series.
Related
PortfolioOptimisers.weight_independent_target — Method
weight_independent_target(_::Number) -> Bool
Return true: a scalar target does not require portfolio weights and can be evaluated on a bare return series.
Related
PortfolioOptimisers.weight_independent_target — Method
weight_independent_target(_) -> Bool
Return false: the target type requires portfolio weights (e.g. a per-asset mu involves dot(w, mu)) and cannot be evaluated on a bare return series.
Related
PortfolioOptimisers._Frontier — Function
_Frontier(; N = 20, factor, bound)Build a Frontier whose factor and bound a formulation chose, rather than the caller.
The keyword constructor Frontier(; N, bound) pins factor to 1, because a caller states a bound in the units of the measure and knows nothing of the model's internal scaling. A formulation does know: it reads the caller's Frontier, keeps its N, and rebuilds it with the conversion its own risk expression needs. That is what variance_risk_bounds_val and second_moment_bound_val do.
This builds a Frontier, not the sweep values. The values are built later, in rebuild_risk_frontier, once the two corner portfolios are known: bound transforms each end of the span, factor multiplies both, and range divides the result into N levels.
Arguments
N: Number of sweep points (default 20).factor: Multiplier applied to both ends of the span, afterboundhas transformed them.bound:FrontierBoundEstimatorconverting the span into the units of the risk expression.
Returns
Frontier: The rebuilt sweep specification.
Related