Tools: private API
Utility functions
We strive to be as type-stable, inferrable, and immutable as possible in order to improve robustness, performance, and correctness. These functions help us achieve these goals.
PortfolioOptimisers.PROP_TAG_NAMES — Constant
PROP_TAG_NAMESThe propagation tag set of @propagatable, as data.
One entry per field tag. The recognition layer is derived from this tuple rather than spelled out per tag: the macro names (PROP_TAG_MACRO_NAMES), the lookup (prop_tag), the gate (is_prop_tag_call), the peeler (peel_prop_tags) and the parser (propagatable_parse_body) all read it. A new propagation channel is one row here, one branch in prop_tag_expr, one entry in PROP_TAG_CHANNELS and one stub macro; check_prop_tag_macros refuses to load the module when a row lacks any of the three.
Related
PortfolioOptimisers.PROP_TAG_MACRO_NAMES — Constant
PROP_TAG_MACRO_NAMESThe macro name of every tag of PROP_TAG_NAMES, in the same order.
Derived from the tag names, so a row of the table carries no second spelling. prop_tag matches a :macrocall head against this tuple.
Algorithm
- Map each tag of
PROP_TAG_NAMEStoSymbol("@", tag), which is the name Julia gives that tag's macro.
The map preserves the order, so the $k$-th entry here is the macro name of the $k$-th tag there. prop_tag and check_prop_tag_macros both walk the two tuples together, and that pairing is what the shared order guarantees.
Related
PortfolioOptimisers.PROP_TAG_CHANNELS — Constant
PROP_TAG_CHANNELSThe propagation channels of @propagatable, with their tag precedence as data.
One entry per generated method. Each entry has two tuples of tags:
gate: the tags that make@propagatableemit the method at all.precedence: the order in which a field's tags are consulted. The first tag of this tuple that the field carries decides the field's transform; the rest are ignored for that channel.
The factory channel prefers @fprop over @wprop. The prior channel prefers @pprop, then @cprop, then @wprop, then @fprop, so @pprop wins over @fprop on one field. The precedence used to live in two hand-written if/elseif chains that no comment linked; it is now read by prop_channel_pairs for every channel.
A tag means what its channel says it means. The obs channel reads the same @wprop and @fprop tags as factory and gives them different transforms: factory replaces a @wprop field with an incoming ObsWeights value, while obs indexes the value already there. This is why prop_tag_expr takes the channel as well as the tag. It also means a weights field opts into obs_weights_view by carrying @wprop, with no second tag to write and no second tag to forget.
Related
PortfolioOptimisers.PROPAGATABLE_CONTRACTS — Constant
PROPAGATABLE_CONTRACTSEvery type declared with @propagatable, paired with its @pprop-tagged field names.
One entry is appended by the macro itself, immediately after the struct it declares, so the list is complete by the time the module finishes loading — including types declared in external packages. check_propagatable_contracts is what reads it.
Related
PortfolioOptimisers.concrete_typed_array_if_abstract — Function
concrete_typed_array_if_abstract(A::AbstractArray) -> AbstractArrayNarrow the element type of A with concrete_typed_array, but only when that element type is abstract.
The generic vector methods of factory and port_opt_view rebuild a vector field element by element. A comprehension over a heterogeneous vector infers an abstract element type, which costs a dynamic dispatch at every later use. This is the opt-in narrowing step for the families that want the concrete element type back.
Algorithm
- Test
eltype(A)withisabstracttype. - The element type is abstract: return
concrete_typed_arrayofA, which copies. - The element type is concrete: return
Aitself, which copies nothing.
Arguments
A: The rebuilt array.
Returns
A: Unchanged ifeltype(A)is concrete, elseconcrete_typed_arrayofA.
Examples
julia> PortfolioOptimisers.concrete_typed_array_if_abstract([1, 2, 3])3-element Vector{Int64}: 1 2 3julia> PortfolioOptimisers.concrete_typed_array_if_abstract(Any[1, 2.0])2-element Vector{Union{Float64, Int64}}: 1 2.0Related
PortfolioOptimisers.get_window — Function
get_window(window::Option{<:Colon}, args...) -> Option{<:Colon}
get_window(window::Integer, X::MatNum, dims::Int = 1) -> VecInt
get_window(window::Integer, X::VecNum, args...) -> VecInt
get_window(window::VecInt, args...) -> VecIntGet the observation window index range for a data array.
Algorithm
The type of window names the rule, and each rule is one step.
windowisnothingor aColon: returnColon(), which selects every observation.windowis an integer: readstart, the first index ofXalong the observation axis, andstop, its last index. Return the rangemax(start, stop - window + 1):stop, which is the lastwindowobservations.windowis a vector of integers: returnwindowitself, so the caller states the observations directly.
Step 2 clamps the lower end at start, so a window larger than the number of observations gives every observation rather than an error. The observation axis of a matrix is dims, and a vector carries one axis, so its method drops dims.
Arguments
window: Observation window. An integer selects the lastwindowobservations, and a vector of indices selects those observations.::Option{<:Colon}: ReturnsColon().::Integer: Returns the lastwindowobservations. This operation is safe, so it doesn't error ifwindowis larger than the number of observations.::VecInt: Returns thewindowargument.
X: Data matrix or vector.dims: Dimension along which to perform the computation.
Returns
window::Option{Union{Colon, <:VecInt}}: The window index range.
Related
PortfolioOptimisers.prop_tag — Function
prop_tag(x) -> Union{Nothing, Symbol}
Return the tag of PROP_TAG_NAMES that x names, or nothing.
x is the head of a :macrocall node. It is a bare Symbol in a struct body written by hand, and a GlobalRef once another macro has expanded around it, so both spellings resolve. A name outside PROP_TAG_MACRO_NAMES gives nothing, which is how the callers tell a tag from any other macro; no tag falls through to another tag.
Algorithm
- Read
namefromx. AGlobalRefgives itsnamefield, aSymbolgives itself, and any other value returnsnothingat once. - Walk
PROP_TAG_NAMESandPROP_TAG_MACRO_NAMEStogether. Return the tag whose macro name is identical toname. - No macro name matches: return
nothing.
The comparison is === on a Symbol, so a macro whose name merely resembles a tag never matches.
Arguments
x: The first argument of a:macrocallexpression.
Returns
tag::Symbol: The tag name, without the@.nothing: Ifxnames no tag.
Related
PortfolioOptimisers.is_prop_tag_call — Function
is_prop_tag_call(x) -> Bool
Return true if x is a macro call to any tag of PROP_TAG_NAMES.
Used by peel_prop_tags and propagatable_parse_body to detect tagged fields in a struct body.
Algorithm
- Return
truewhen all three hold:xis anExpr, its head is:macrocall, andprop_tagof its first argument is notnothing. - Return
falseotherwise.
Arguments
x: Any expression appearing in a struct body.
Related
PortfolioOptimisers.prop_tag_expr — Function
prop_tag_expr(
channel::Symbol,
tag::Symbol,
fname::Symbol,
xf,
mod::Module,
thread
) -> Expr
Return the expression that a tag substitutes for one field, inside a generated method.
This is the name → field transform half of the tag table: one branch per tag of PROP_TAG_NAMES, read by prop_channel_pairs for every channel. A tag of the table with no branch here errors, so it cannot silently take another tag's transform.
Algorithm
The channel is read first, and then the tag, because one tag has two transforms.
channelis:obs:tagis:wprop: returnnothing_scalar_array_getindex(xf, thread...). The field is the weights, so it is indexed to the selected observations. Indexing keeps theAbstractWeightssubtype, which a view would not.tagis:fprop: returnobs_weights_view(xf, thread...). The field is a composed child, so the verb recurses into it.
channelis any other channel:tagis:fprop: returnfactory_child(xf, thread..., args...; kwargs...).tagis:vprop: returnport_opt_view(xf, thread..., args...). This channel forwards no keywords.tagis:pprop: returnsel(xf, getproperty(pr, fname)), which is why the field name is an argument. The prior result supplies the property of the same name.tagis:cprop: returnsel(xf, _ctx(args...)), which reads the context out of the threaded arguments rather than the prior.tagis:wprop: return_wprop(xf, args...; kwargs...), which replaces the field with an incomingObsWeights.
- No branch matched: raise an error naming the tag and the channel, and ask for a branch here.
Steps 1.1 and 2.5 are the same tag with two transforms, so the channel decides what @wprop means. Every emitted name is qualified against mod, because the expansion is escaped into the caller's module.
Arguments
tag::Symbol: A tag ofPROP_TAG_NAMES.fname::Symbol: The field name, needed by@ppropto name the prior property.xf: The expression that reads the field off the incoming struct.mod::Module: The module that defines@propagatable. Every emitted name is qualified against it, because the expansion is escaped into the caller.thread: Extra positional arguments the channel threads beforeargs....
Returns
expr::Expr: The value of the field in the generated constructor call.
Related
PortfolioOptimisers.prop_channel_active — Function
prop_channel_active(
channel::Symbol,
tagged::AbstractDict
) -> Any
Return true if a channel of PROP_TAG_CHANNELS must emit a method.
A channel is active when at least one field carries a tag of the channel's gate.
Algorithm
- Read the
gatetuple of the channel fromPROP_TAG_CHANNELS. - Return
truewhen at least one tag ofgatehas a non-empty entry intagged, andfalseotherwise.
The precedence tuple is not read here, so a tag that a channel consults but does not gate on never makes that channel emit a method on its own. The obs channel gates on @wprop alone and consults @fprop, so a type carrying @fprop and no @wprop gains no obs method.
Arguments
channel::Symbol: A channel name ofPROP_TAG_CHANNELS.tagged::AbstractDict: Tag name to the field names that carry it, frompropagatable_parse_body.
Related
PortfolioOptimisers.prop_channel_pairs — Function
prop_channel_pairs(
channel::Symbol,
tagged::AbstractDict,
all_fields::AbstractVector{Symbol},
obj::Symbol,
mod::Module,
thread
) -> Vector{Any}
Return the keyword pairs of the constructor call that one channel generates.
Every declared field gets one pair, in declaration order. The field's tags are consulted in the channel's precedence order; the first match gives the value through prop_tag_expr, and a field carrying no tag of the channel is passed through unchanged.
Algorithm
- Read the
precedencetuple of the channel fromPROP_TAG_CHANNELS. - For each field name
fnameofall_fields, in declaration order:- Build
xf, the expressionobj.fnamethat reads the field off the incoming struct. - Find
idx, the position of the first tag ofprecedencethatfnamecarries. - When
idxisnothing, the field carries no tag of this channel: the value isxfitself. - Otherwise the value is
prop_tag_exprof that tag, in this channel. - Push
Expr(:kw, fname, value)ontopairs.
- Build
- Return
pairs.
Step 2.2 is where the precedence decides one field's transform. A field carrying @pprop and @fprop takes the @pprop transform on the prior channel and the @fprop transform on the factory channel, because the two channels order the tags differently.
Arguments
channel::Symbol: A channel name ofPROP_TAG_CHANNELS.tagged::AbstractDict: Tag name to the field names that carry it.all_fields::AbstractVector{Symbol}: Every declared field, in declaration order.obj::Symbol: The struct the generated method reads the fields off.mod::Module: The module that defines@propagatable.thread: Extra positional arguments the channel threads beforeargs....
Returns
pairs::Vector{Any}: OneExpr(:kw, field, value)per declared field.
Related
PortfolioOptimisers.check_prop_tag_macros — Function
check_prop_tag_macros()
check_prop_tag_macros(tags)
check_prop_tag_macros(tags, macro_names)
check_prop_tag_macros(tags, macro_names, channels)
check_prop_tag_macros(
tags,
macro_names,
channels,
mod::Module
)
Check that every tag of PROP_TAG_NAMES is complete.
A tag row is complete when it has a stub macro, a channel of PROP_TAG_CHANNELS that names it, and a field transform in prop_tag_expr for every channel that names it. A row that lacks one of the three is a tag that parses but never propagates, which is the failure the table exists to stop. The per-channel probe is what catches a tag added to a second channel without a transform there, now that a tag means what its channel says it means. All the violations are collected and reported together.
Runs once at the end of the module. Throws an ArgumentError listing every violation, so the package refuses to precompile rather than shipping a dead tag.
The three tables are arguments, and each one defaults to the table the module ships. The shipped tables are complete, so the call the module makes never reports a violation; a caller that passes a table of its own drives each of the three clauses and reads the message it gives.
Algorithm
- Make
violations, an empty vector of strings. - For each tag of
PROP_TAG_NAMES, with its macro name fromPROP_TAG_MACRO_NAMES:- The macro name is not defined in this module: push a message that the tag declares no stub macro.
- The tag appears in the
precedenceof no channel ofPROP_TAG_CHANNELS: push a message that the tag appears in no channel. - For each channel whose
precedencenames the tag, callprop_tag_exprwith the probe name:probe. When that call raises, push a message naming the tag and the channel.
violationsis not empty: throw anArgumentErrorlisting every one of them.- Return
nothing.
Step 2.3 probes each channel that names the tag, not the tag alone. This is what catches a tag added to a second channel with no transform there, which the whole-tag probe of an earlier design let through.
Arguments
tags: The tag names to check.macro_names: The stub macro name of each tag, in the order oftags.channels: The channel table whoseprecedencetuples are read.mod::Module: The module the stub macros are looked up in, and the module that qualifies the namesprop_tag_expremits.
Returns
nothing: Every row oftagsis complete.
Related
PortfolioOptimisers.is_doc_macro — Function
is_doc_macro(x) -> Union{Missing, Bool}
Return true if x is a reference to Julia's @doc macro (bare Symbol or GlobalRef).
Used by propagatable_parse_body to recognise docstring-prefixed fields in a struct body.
Algorithm
- Return
truewhenxis aGlobalRefwhosenameisSymbol("@doc"). - Return
truewhenxis equal toSymbol("@doc"). - Return
falseotherwise.
Both spellings are needed for the same reason prop_tag needs both: a struct body written by hand carries the bare Symbol, and one that another macro has already expanded carries the GlobalRef.
Related
PortfolioOptimisers._ctx — Function
_ctx(args...)Locate the lone threaded optimiser context value (a solver, Slv_VecSlv) in the variadic tail of a prior factory call, returning nothing if none is present. Emitted by the @cprop tag as the source argument to sel. The tuple scan is unrolled by the compiler, so it is type-stable and allocation-free.
Related
PortfolioOptimisers._wprop — Function
_wprop(
field,
args...;
kwargs...
) -> Union{DynamicAbstractWeights, AbstractWeights}
Resolve the new value of a @wprop-tagged observation-weights field during factory propagation.
When an ObsWeights argument is threaded through factory, the field is replaced by those weights; otherwise the existing field value is kept. This is distinct from factory_child (used by @fprop), which recurses into sub-estimators and leaves nothing/non-estimator values unchanged — a weights slot must not be confused with an optional sub-estimator that happens to be nothing.
Algorithm
The method that Julia selects is the algorithm, and the selection reads args..., never the field.
- The first threaded positional argument is an
ObsWeights: return that value, whatever the field held. - No such argument is threaded: return
fieldunchanged.
The field's own value never selects the branch, so a field holding nothing and a field holding weights are both replaced by an incoming ObsWeights, and both are kept when none is threaded.
Related
PortfolioOptimisers.resolve_deferred_quantities — Function
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
resolve_deferred_quantities(
r::Variance,
pr::AbstractPriorResult
) -> Variance
resolve_deferred_quantities(
r::Variance,
pr::AbstractPriorResult,
) -> Variance
Resolve a Deferred Quantity in Variance's sigma slot against prior result pr.
sigma and chol travel together, so both come from the same fit. A stated chol never reaches here: assert_derived_slot_has_source refuses it beside a deferred sigma at construction. A covariance estimator produces no factorisation, so chol becomes nothing and the consumer derives it from the resolved sigma. A prior estimator produces both, which is how a factor prior's sparse factorisation reaches the slot intact.
Related
resolve_deferred_quantities(
r::StandardDeviation,
pr::AbstractPriorResult
) -> StandardDeviation
resolve_deferred_quantities(
r::StandardDeviation,
pr::AbstractPriorResult,
) -> StandardDeviation
Resolve a Deferred Quantity in StandardDeviation's sigma slot against prior result pr. sigma and chol come from the same fit — see resolve_deferred_quantities(r::Variance, pr::AbstractPriorResult).
Related
resolve_deferred_quantities(
r::UncertaintySetVariance,
pr::AbstractPriorResult
) -> UncertaintySetVariance
resolve_deferred_quantities(
r::UncertaintySetVariance,
pr::AbstractPriorResult,
) -> UncertaintySetVariance
Resolve a Deferred Quantity in UncertaintySetVariance's sigma slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.
Related
resolve_deferred_quantities(
r::LowOrderMoment,
pr::AbstractPriorResult
) -> LowOrderMoment
resolve_deferred_quantities(
r::LowOrderMoment,
pr::AbstractPriorResult,
) -> LowOrderMoment
Resolve a Deferred Quantity in LowOrderMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.
Related
resolve_deferred_quantities(
r::HighOrderMoment,
pr::AbstractPriorResult
) -> HighOrderMoment
resolve_deferred_quantities(
r::HighOrderMoment,
pr::AbstractPriorResult,
) -> HighOrderMoment
Resolve a Deferred Quantity in HighOrderMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.
Related
resolve_deferred_quantities(
r::Kurtosis,
pr::AbstractPriorResult
) -> Kurtosis
resolve_deferred_quantities(
r::Kurtosis,
pr::AbstractPriorResult,
) -> Kurtosis
Resolve every Deferred Quantity held by Kurtosis r against prior result pr.
Three passes, in order.
- A deferred
muresolves on its own. - A deferred
ktresolves next, and it carries the centre with it.ktis a moment about a centre, so the two are one pair of quantities out of one object: whenmuis still unstated,deferred_centrereads it off the cokurtosis estimator's ownme, threads it into the fit asmean =, and it becomes the resolvedmu. A statedmuwins and is threaded in its place. AnAbstractPriorEstimatorcentres itself, so the centre is read back off the prior result it produced. pefans out into whatever both passes leftnothing.
A deferred slot therefore wins over pe, which is the map's precedence rule one level down. kt lives on a HighOrderPrior, so a prior estimator named in kt or pe must compute one.
Related
resolve_deferred_quantities(
r::NegativeSkewness,
pr::AbstractPriorResult
) -> NegativeSkewness
resolve_deferred_quantities(
r::NegativeSkewness,
pr::AbstractPriorResult,
) -> NegativeSkewness
Resolve a Deferred Quantity in NegativeSkewness's sk slot against prior result pr.
sk and V travel together, so both come from the same fit. V = negative_spectral_coskewness(sk, X, mp) is never a function of sk alone, so the fit's own processor builds it and is recorded in mp in place of the one the measure held. A CoskewnessEstimator supplies it through coskewness_processor; an AbstractPriorEstimator supplies it as the prior result's skmp, which is the field HighOrderPrior already carries for exactly this reason.
Recording it keeps the windowed rebuild in port_opt_view on the same processor that built the V it replaces. V is never rebuilt from a resolved sk: under a factor prior the negative spectral part is special, and a rebuild would throw that structure away. This is the sigma/chol rule on the sk/V pair.
The measure carries one deferrable slot, so there is no fan-out to make and it takes no pe. A coskewness estimator needs only a returns matrix, so sk resolves against a LowOrderPrior as readily as against a HighOrderPrior.
Related
resolve_deferred_quantities(
alg::DistributionValueatRisk,
pr::AbstractPriorResult
) -> DistributionValueatRisk
resolve_deferred_quantities(
alg::DistributionValueatRisk,
pr::AbstractPriorResult,
) -> DistributionValueatRisk
Resolve every Deferred Quantity held by DistributionValueatRisk alg against prior result pr.
The measure carries three prior-derived fields, and mu and sigma are independent of each other, so it takes a pe: one fit fills every slot the caller left unstated. chol is derived from sigma and travels with it — a sigma that names its own estimator supplies the factorisation from that same fit, and never from the pe's.
Related
resolve_deferred_quantities(
x::ValueatRisk,
pr::AbstractPriorResult
) -> ValueatRisk
resolve_deferred_quantities(
x::ValueatRisk,
pr::AbstractPriorResult,
slv
) -> ValueatRisk
Resolve the significance level alpha of a ValueatRisk against prior result pr, and resolve the formulation alg beside it.
alpha takes a Calibration Rule in place of the number, so it resolves here. The rebuild goes through rebuild_with_slots, and the inner constructor it calls re-runs 0 < alpha < 1 on the calibrated number: a rule that returns a value the slot does not admit is refused at fold time, by the guard a caller's own number meets.
This method is more specific than the derived recursion, so it takes over the alg slot that deferred_slots declares. It resolves that slot through resolve_deferred_child, which is the verb the derivation would have used.
The effective observation weights are computed locally as sel(x.w, pr.w) and threaded to the rule. The measure carries no solver of its own, so it hands the rule the one it was given.
Related
resolve_deferred_quantities(
x::ValueatRiskRange,
pr::AbstractPriorResult
) -> ValueatRiskRange
resolve_deferred_quantities(
x::ValueatRiskRange,
pr::AbstractPriorResult,
slv
) -> ValueatRiskRange
Resolve the two significance levels of a ValueatRiskRange against prior result pr, and resolve the formulation alg beside them.
Each tail carries its own slot and its own bound, so a stated tail rule and a stated head rule resolve independently. beta defaults to alpha, so a rule stated on the loss side alone reaches both ends: the rule states the method and the slot states the end. The rebuild goes through rebuild_with_slots, whose positional call runs the inner constructor and re-runs both range checks on the calibrated numbers.
This method is more specific than the derived recursion, so it takes over the alg slot that deferred_slots declares, through resolve_deferred_child.
Related
resolve_deferred_quantities(
x::DistributionallyRobustConditionalValueatRisk,
pr::AbstractPriorResult
) -> DistributionallyRobustConditionalValueatRisk
resolve_deferred_quantities(
x::DistributionallyRobustConditionalValueatRisk,
pr::AbstractPriorResult,
slv
) -> DistributionallyRobustConditionalValueatRisk
Resolve the significance level alpha, the ambiguity radius r and the tail weight l of a DistributionallyRobustConditionalValueatRisk against prior result pr.
All three slots take a Calibration Rule in place of the number, so all three resolve here. The struct is rebuilt through rebuild_with_slots, and the inner constructor it calls is what re-runs the positivity check on the calibrated number: a rule that returns a value the slot does not admit is refused at fold time, by the same guard a caller's own number meets.
alpha resolves first, because the tail weight reads it. TailTermParity prices a tail term at the measure's own significance level, so alpha and l are a travelling pair and the number reaches the l slot in its CalibrationContext. A stated number, a plain function and a rule that reads no sibling all ignore the field, so the order costs nothing where no rule reads a sibling. The radius reads neither of the two, so its own order is free.
The effective observation weights are computed locally as sel(r.w, pr.w) and threaded to the rule, so a rule that reads a weighted sample size sees the weights the optimisation settled on. The measure carries no solver, so the rule receives none. That holds on both routes: the third argument carries the effective solver for a measure that has a slot for one, and this measure has none.
The series both slots price travels in the same context. It is the returns, which is the default calibration_series states, so this site names what the default context already holds. It is written all the same, for the reason every site writes it: the marker belongs to the measure, and no rule carries one of its own to be corrected.
A measure whose two slots both hold numbers is returned unchanged, so the common case allocates nothing.
Related
resolve_deferred_quantities(
x::DistributionallyRobustConditionalValueatRiskRange,
pr::AbstractPriorResult
) -> DistributionallyRobustConditionalValueatRiskRange
resolve_deferred_quantities(
x::DistributionallyRobustConditionalValueatRiskRange,
pr::AbstractPriorResult,
slv
) -> DistributionallyRobustConditionalValueatRiskRange
Resolve the two ambiguity radii and the two tail weights of a DistributionallyRobustConditionalValueatRiskRange against prior result pr.
Each tail keeps its own pair, so four slots resolve here. It carries the reading of the scalar measure's method unchanged: the rebuild re-runs every positivity check, the effective observation weights are computed locally, and a measure whose four slots all hold numbers is returned unchanged.
Each end's tail weight reads that end's own probability. alpha and beta resolve first, and each is stated in the context of the tail weight beside it: l_a reads alpha and l_b reads beta. A skewed sample therefore resolves the two tail weights to two different numbers, which is the whole point of TailTermParity on a Range measure. The two radii read neither probability, so the four remaining slots resolve in one pass.
A radius names no end of the distribution, so a rule placed in the loss-side pair and the same rule placed in the gain-side pair resolve independently, and neither ambiguity slot defaults from the other.
Both ends price one series, which is the returns, so the same marker stands in the context of all four slots. The series is a property of the measure and not of an end, where the significance level is a property of the end.
Related
resolve_deferred_quantities(
x::DistributionallyRobustConditionalDrawdownatRisk,
pr::AbstractPriorResult
) -> DistributionallyRobustConditionalDrawdownatRisk
resolve_deferred_quantities(
x::DistributionallyRobustConditionalDrawdownatRisk,
pr::AbstractPriorResult,
slv
) -> DistributionallyRobustConditionalDrawdownatRisk
Resolve the significance level alpha, the ambiguity radius r and the tail weight l of a DistributionallyRobustConditionalDrawdownatRisk against prior result pr.
It carries the reading of resolve_deferred_quantities on the value-at-risk measure unchanged, alpha first and the tail weight reading it off its own CalibrationContext. A drawdown series holds one entry per observation, so a rule reads the same sample size here as it does there.
The series does not carry over, and the context is what says so. This measure prices the absolute drawdown series, so calibration_series states AbsoluteDrawdownSeries and the marker travels beside alpha to both ambiguity slots. TailTermParity then prices the mean drawdown of each column against the $\mathrm{CDaR}_{\alpha}$ of that column, and the radius rules read the error scale off the drawdown sample. The keys :l and :r name this measure's slots and the value-at-risk twin's slots alike, so nothing else could have told a rule which quantity it stands in front of.
Related
resolve_deferred_quantities(
x::RelativisticValueatRisk,
pr::AbstractPriorResult
) -> RelativisticValueatRisk
resolve_deferred_quantities(
x::RelativisticValueatRisk,
pr::AbstractPriorResult,
slv
) -> RelativisticValueatRisk
Resolve the significance level alpha and the deformation parameter kappa of a RelativisticValueatRisk against prior result pr.
alpha and kappa are a travelling pair: EntropyBudget reads the significance level of its sibling slot. So alpha resolves first, and the number it produced is stated in the CalibrationContext of the kappa slot before that slot is resolved. A stated number, a plain function and a rule that reads no sibling all ignore the field, so the order costs nothing where no rule reads a sibling.
The series this measure prices travels in the same context. It is the returns, which is the default calibration_series states, so this site names what the default context already holds. It is written all the same, for the reason every site writes it: the marker belongs to the measure, and no rule carries one of its own to be corrected.
The solver is settled once, as sel(x.slv, slv), and handed to both rules, so a rule may call RRM itself. The rebuild goes through rebuild_with_slots, whose positional call runs the inner constructor and re-runs both range checks on the calibrated numbers.
Related
resolve_deferred_quantities(
x::RelativisticValueatRiskRange,
pr::AbstractPriorResult
) -> RelativisticValueatRiskRange
resolve_deferred_quantities(
x::RelativisticValueatRiskRange,
pr::AbstractPriorResult,
slv
) -> RelativisticValueatRiskRange
Resolve the two significance levels and the two deformation parameters of a RelativisticValueatRiskRange against prior result pr.
Each end carries a travelling pair of its own: kappa_a reads alpha and kappa_b reads beta. The gain-side pair defaults to the loss-side pair, beta to alpha and kappa_b to kappa_a, so a pair stated on the loss side alone reaches both ends. The resolution runs the pair of the loss side and then the pair of the gain side, and neither side reads the other's number. That is the pairing range_tails builds and the functor evaluates.
The four slots carry four different bounds, so a rule of the wrong end or the wrong family is refused at construction. The solver is settled once and handed to all four rules.
Both ends price one series, which is the returns, so the same marker stands in the context of both kappa slots. The series is a property of the measure and not of an end, where the significance level is a property of the end.
Related
resolve_deferred_quantities(
x::RelativisticDrawdownatRisk,
pr::AbstractPriorResult
) -> RelativisticDrawdownatRisk
resolve_deferred_quantities(
x::RelativisticDrawdownatRisk,
pr::AbstractPriorResult,
slv
) -> RelativisticDrawdownatRisk
Resolve the significance level alpha and the deformation parameter kappa of a RelativisticDrawdownatRisk against prior result pr.
It carries the reading of resolve_deferred_quantities on the value-at-risk twin unchanged: alpha resolves first and reaches the kappa slot in its CalibrationContext. The drawdown series has one entry per row of the sample, so a rule reads the same sample size here as it does there.
The series does not carry over, and the context is what says so. This measure prices the absolute drawdown series of the portfolio, so calibration_series states AbsoluteDrawdownSeries and the marker travels beside alpha. A rule that reads the shape of a series then reads the drawdown series of each column of the sample, in place of the columns themselves, and the alpha it reads is the level of that same drawdown series. The key :kappa names this slot and the twin's slot alike, so nothing else could have told the rule which quantity it stands in front of.
Related
resolve_deferred_quantities(
x::RelativeRelativisticDrawdownatRisk,
pr::AbstractPriorResult
) -> RelativeRelativisticDrawdownatRisk
resolve_deferred_quantities(
x::RelativeRelativisticDrawdownatRisk,
pr::AbstractPriorResult,
slv
) -> RelativeRelativisticDrawdownatRisk
Resolve the significance level alpha and the deformation parameter kappa of a RelativeRelativisticDrawdownatRisk against prior result pr.
The measure is a hierarchical one, so it reaches no JuMP model and the factory route is its only resolution. The travelling pair is resolved in the order the absolute twin states.
The series is the twin's reading in its own units: this measure compounds the path, so calibration_series states RelativeDrawdownSeries and the context carries it. The two markers name two different series of the same column, and a rule that reads the shape of a series answers differently on each.
Related
resolve_deferred_quantities(
x::ComposedFunction{typeof(reverse), <:AbstractOrderedWeightsArrayFunction},
pr::AbstractPriorResult
) -> ComposedFunction{typeof(reverse)}
resolve_deferred_quantities(
x::ComposedFunction{typeof(reverse), <:AbstractOrderedWeightsArrayFunction},
pr::AbstractPriorResult,
slv
) -> ComposedFunction{typeof(reverse)}
Resolve the weight builder inside the reversal that OrderedWeightsArrayRange wraps its w2 in.
The Range constructor stores w2 as reverse ∘ w2 whenever the caller has not already reversed it, so the object the w2 slot holds is a composition and the builder a rule sits in is its inner half. Without this method the recursion would stop at the composition and a rule in the gain-side builder would never resolve, while the same rule in w1 did.
The composition is rebuilt around the resolved half, so the reversal survives. The bound names reverse and an AbstractOrderedWeightsArrayFunction, so no other composition reaches it.
Related
resolve_deferred_quantities(
r::Skewness,
pr::AbstractPriorResult
) -> Skewness
resolve_deferred_quantities(
r::Skewness,
pr::AbstractPriorResult,
) -> Skewness
Resolve every Deferred Quantity held by Skewness r against prior result pr.
Three passes, in order.
- A deferred
muresolves on its own. - A deferred
skresolves next, and it carries the centre with it.skis a moment about a centre, so the two are one pair of quantities out of one object: whenmuis still unstated,deferred_centrereads it off the coskewness estimator's ownme, threads it into the fit asmean =, and it becomes the resolvedmu. A statedmuwins and is threaded in its place. AnAbstractPriorEstimatorcentres itself, so the centre is read back off the prior result it produced. pefans out into whatever both passes leftnothing.
A deferred slot therefore wins over pe, which is the map's precedence rule one level down. The measure reads no V, so only the sk half of the coskewness pair is kept — see NegativeSkewness for the half that needs both.
Related
resolve_deferred_quantities(
r::VarianceSkewKurtosis,
pr::AbstractPriorResult
) -> VarianceSkewKurtosis
resolve_deferred_quantities(
r::VarianceSkewKurtosis,
pr::AbstractPriorResult,
slv
) -> VarianceSkewKurtosis
Resolve every Deferred Quantity held by VarianceSkewKurtosis r against prior result pr.
Two levels, in order. Each child resolves whatever it holds of its own, then the container's pe fans out into every child slot still unstated — sigma and its chol on vr, sk and mu on sk, kt and mu on kt — all from one fit.
A child that names its own quantity keeps it. This is the map's precedence rule applied one level down, and it treats a deferred child slot exactly as it already treats a stated one.
The composed measure adds a variance, a skewness and a kurtosis term, so a caller who wants the three to describe one distribution names pe on the container and states nothing on the children.
Related
resolve_deferred_quantities(
r::MedianAbsoluteDeviation,
pr::AbstractPriorResult
) -> Any
resolve_deferred_quantities(
r::MedianAbsoluteDeviation,
pr::AbstractPriorResult,
) -> Any
Resolve a Deferred Quantity in MedianAbsoluteDeviation's mu slot against prior result pr.
This is the only thing factory does to mu. The field is tagged @vprop and not @pprop, so the prior never fills it — a bare MedianAbsoluteDeviation() inside a JuMPOptimiser keeps median-centring rather than silently taking pr.mu.
Related
resolve_deferred_quantities(
r::ThirdCentralMoment,
pr::AbstractPriorResult
) -> ThirdCentralMoment
resolve_deferred_quantities(
r::ThirdCentralMoment,
pr::AbstractPriorResult,
) -> ThirdCentralMoment
Resolve a Deferred Quantity in ThirdCentralMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.
Related
resolve_deferred_quantities(
rt::ArithmeticReturn,
pr::AbstractPriorResult
) -> ArithmeticReturn
resolve_deferred_quantities(
rt::ArithmeticReturn,
pr::AbstractPriorResult,
) -> ArithmeticReturn
Resolve a Deferred Quantity in ArithmeticReturn's mu slot against prior result pr. The estimator carries one prior-derived slot, so the slot itself admits the Estimator and there is no fan-out to make.
Every JuMP path reaches this through factory, which processed_jump_optimiser_attributes calls on opt.ret before any model is built. A return term needs no second entry point, unlike a risk measure.
Related
PortfolioOptimisers.sel — Function
sel(risk_variable, source_variable)Unified risk-measure selector emitted by the @pprop/@cprop tags. Prefers the risk-measure value risk_variable when present, otherwise falls back to source_variable (a prior moment for @pprop, or a threaded optimiser value for @cprop). Dispatches on operand types to the appropriate leaf selector and inlines to zero cost:
- solvers (
Slv_VecSlv) →solver_selector - uncertainty sets (
UcSE_UcS) →ucs_selector - a Deferred Quantity or a Calibration Rule → kept, because a slot the caller filled with the method that computes the value is a stated slot
- everything else (moments) →
nothing_scalar_array_selector
The Deferred-Quantity arm exists because @propagatable runs the selection before resolve_deferred_quantities, so a @pprop slot that admits a Deferred Quantity reaches here still holding one. The prior must not fill such a slot: the caller stated the method, and the resolution that follows replaces it with the value that method produced. The same reading covers a Calibration Rule.
Note: the solver_selector both-nothing "cannot solve" error is not reachable through sel (both-nothing routes to the moment selector and returns nothing); the JuMPOptimiser solver-required invariant makes that case unreachable in the pipeline.
Related
PortfolioOptimisers.extract_field_name — Function
extract_field_name(expr) -> Any
Extract the field name Symbol from a bare field or field::Type expression.
Errors with a descriptive message when expr is neither a bare Symbol nor a field::Type annotation, since only those forms are valid after @fprop.
Algorithm
expris aSymbol: return it.expris anExprwhose head is:(::): return its first argument, which is the field name.expris anything else: raise an error naming the expression.
Step 3 is what separates this function from try_field_name, which returns nothing in the same case. A tag states that the node is a field, so a node that is not one is a defect in the struct body and not a node to skip.
Arguments
expr: ASymbol, anExprwith head:(::), or any other expression (triggers an error).
Returns
name::Symbol: The field name.
Related
PortfolioOptimisers.propagatable_find_struct — Function
propagatable_find_struct(
expr
) -> Union{Tuple{Expr, typeof(identity)}, Tuple{Expr, Union{PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, typeof(identity)}, PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, rebuild}} where rebuild}}}
Recursively unwrap macro call chains to locate the innermost :struct node.
Returns (struct_node, rebuild_fn) where rebuild_fn(new_struct) reconstructs the original macro chain with new_struct in place of the original struct. This allows @propagatable to inject modified struct definitions back into arbitrary macro wrappers such as @concrete.
Algorithm
expris not anExpr: raise an error naming its type.exprhas head:struct: returnexpritself andidentity, which rebuilds nothing.exprhas head:macrocall: takeinner, its last argument, and call this function again on it. That call givesstruct_nodeandrebuild. Readprefix, every argument ofexprexcept the last. Returnstruct_nodeand the functions -> Expr(:macrocall, prefix..., rebuild(s)).exprhas any other head: raise an error naming the head.
Step 3 rebuilds the chain from the inside out, so a struct wrapped in several macros comes back wrapped in the same macros, in the same order, with the same arguments. The prefix carries the macro's own arguments and its LineNumberNode, so nothing of the call is lost.
Arguments
expr: A:structexpression or a:macrocallexpression wrapping one.
Returns
struct_node::Expr: The innermost:structexpression.rebuild_fn::Function: A function that, given a replacement:struct, returns the full macro chain with the replacement in place of the original.
Related
PortfolioOptimisers.propagatable_bare_name — Function
propagatable_bare_name(n) -> Symbol
Extract the plain struct name Symbol from a potentially parameterised or supertype-constrained name expression.
Handles the forms Name, Name{T, ...}, and Name{T, ...} <: SuperType by recursively peeling :curly and :<: wrappers until a bare Symbol is reached.
Algorithm
nis aSymbol: return it.nhas head:curly: call this function again on its first argument, which drops the type parameters.nhas head:<:: call this function again on its first argument, which drops the supertype.nis anything else: raise an error naming the expression.
Steps 2 and 3 compose, so Name{T} <: Super peels the supertype first and then the parameters.
Arguments
n: ASymbol, or anExprwith head:curlyor:<:.
Returns
name::Symbol: The plain struct name.
Related
PortfolioOptimisers.try_field_name — Function
try_field_name(expr) -> Any
Return the field name Symbol for a plain field declaration, or nothing for non-field nodes.
Recognises bare Symbol fields and field::Type annotations. Returns nothing for LineNumberNodes, inner constructors, and any other expression that does not declare a single named field.
Algorithm
expris aSymbol: return it.exprhas head:(::)and its first argument is aSymbol: return that argument.expris anything else: returnnothing.
Step 2 tests the first argument as well as the head, which extract_field_name does not. A node such as ::Type, which annotates no name, therefore gives nothing here and reaches step 3.
Arguments
expr: Any expression appearing in a struct body.
Returns
name::Symbol: The field name, ifexpris a plain field declaration.nothing: Ifexpris not a plain field declaration.
Related
PortfolioOptimisers.peel_prop_tags — Function
peel_prop_tags(expr) -> Tuple{Set{Symbol}, Any}
Peel any stack of tag macrocalls off a field expression, recording which tags were present.
Tags may be stacked in either order (@pprop @fprop field), which parses as nested :macrocall nodes; this unwraps them all and returns the bare field expression. Each tag is looked up with prop_tag, so an untagged macro stops the peel and no tag is reached by falling through the others.
Algorithm
- Make
tags, an emptySet{Symbol}. - While
is_prop_tag_callofexprholds, pushprop_tagof its first argument ontotags, and replaceexprwith its last argument, which is the expression the tag wraps. - Return
tagsand the peeledexpr.
tags is a set, so a tag written twice on one field is recorded once. The loop stops at the first node that is not a tag call, so a non-tag macro between two tags hides the tags below it.
Arguments
expr: A field expression, with or without tag macrocalls around it.
Returns
tags::Set{Symbol}: The tags ofPROP_TAG_NAMESthatexprcarries.stripped: The field expression with all tags removed.
Related
PortfolioOptimisers.propagatable_parse_body — Function
propagatable_parse_body(
body
) -> Tuple{Dict{Symbol, Vector{Symbol}}, Vector{Symbol}, Expr}
Walk a struct body, collecting the tagged field names (and all field names) and stripping the tags from the body.
Handles bare tagged fields (@fprop field, …), stacked tags (@pprop @fprop field, in any order), and docstring-prefixed forms ("doc" \n @fprop field). Non-field nodes (line numbers, inner constructors) are carried through unchanged. The tags are the rows of PROP_TAG_NAMES, so a new tag needs no change here.
Algorithm
- Make
tagged, one empty vector per tag ofPROP_TAG_NAMES;all_fields, an empty vector; andnew_args, an empty vector for the stripped body. - For each node
argof the struct body, in declaration order, take one of three branches:argis a@docmacrocall, which is how a documented field parses. Peel the tags offinner, its last argument.- The field carries at least one tag: record the field name under each of its tags and in
all_fields, then push a rebuilt@docnode whose last argument is the stripped field. - The field carries no tag: record its name in
all_fieldswhentry_field_namefinds one, and pushargunchanged.
- The field carries at least one tag: record the field name under each of its tags and in
argis a tag macrocall with no docstring: peel the tags, record the field name under each of them and inall_fields, and push the stripped field expression.argis anything else — aLineNumberNode, an untagged field, an inner constructor: record its name inall_fieldswhentry_field_namefinds one, and pushargunchanged.
- Return
tagged,all_fields, and the new body as one:blockexpression.
all_fields holds every declared field, tagged or not, and it is what makes the generated constructor call name every keyword. The returned body carries no tag, so the wrapped macros and Julia itself never see one.
Arguments
body::Expr: The:blockexpression forming the struct body.
Returns
tagged::Dict{Symbol, Vector{Symbol}}: One entry per tag ofPROP_TAG_NAMES, holding the names of the fields that carry it, in declaration order.all_fields::Vector{Symbol}: Names of every declared field (tagged or not).new_body::Expr: The struct body with all tags stripped.
Related
PortfolioOptimisers.propagatable_register! — Function
propagatable_register!(
T::Type,
pprops::Tuple{Vararg{Symbol}}
)
Record type T and its @pprop-tagged field names in PROPAGATABLE_CONTRACTS.
Called by @propagatable at the declaration itself. It only records: the outer keyword constructor is written below the struct, so it does not exist yet and cannot be checked here. check_propagatable_contracts does the checking once the module is complete.
Algorithm
- Push the pair
(T, pprops)ontoPROPAGATABLE_CONTRACTS. - Return
nothing.
T is @nospecialized, so one method serves every registered type and the registration costs no compilation.
Related
PortfolioOptimisers.propagatable_keywords — Function
propagatable_keywords(T::Type) -> Vector{Symbol}
Return the keyword names accepted by the outer constructors of T, unioned over its methods.
A kwargs... slurp is dropped rather than counted. A slurp accepts field = value and then discards it, which is the silent failure this check exists to catch, so it must not satisfy the contract.
Algorithm
- Make
kws, an empty vector of symbols. - For each method
mof the constructorT, appendBase.kwarg_decl(m)tokws. The union runs over every outer constructor, so a keyword that any one of them names counts. - Remove the repeats from
kws. - Remove every name whose string ends in
..., which is howBase.kwarg_declreports a slurp. - Return
kws.
Step 4 is the whole point of the function. A constructor that carries kwargs... reports the slurp as a keyword name, and counting it would let every field satisfy the contract.
Related
PortfolioOptimisers.propagatable_contract_violations — Function
propagatable_contract_violations(
T::Type,
pprops,
pool
) -> Vector{String}
Return the broken clauses of one type's @propagatable contract, as messages.
Two clauses are checked, and both are properties of the code the macro emits:
- Every field name is a keyword of the outer constructor. Each generated method rebuilds the struct with
StructName(; field = …)over all fields, tagged or not, so one field that the keyword constructor does not name is aMethodErrorat the firstfactoryorport_opt_viewcall. - Every
@ppropfield is a property of a prior result. The generatedfactory(x, pr::AbstractPriorResult, args...)readsgetproperty(pr, :field), so a name absent fromprior_result_property_poolthrows when a prior is threaded.
The messages carry a suggest_declared_key suggestion, so a transposed or mistyped field name names its intended neighbour.
Algorithm
- Make
msgs, an empty vector of strings. - Read
kws, the keywords of the outer constructors ofT, withpropagatable_keywords. - For each field name of
Tthat is absent fromkws, push a message naming the type, the field and thesuggest_declared_keysuggestion drawn fromkws. - For each name of
ppropsthat is absent frompool, push a message naming the type, the field and the suggestion drawn frompool. - Return
msgs.
Every clause is collected, and none stops the walk, so one call reports every violation of one type at once. An empty result means that the type's contract holds.
Related
PortfolioOptimisers.check_propagatable_contracts — Function
check_propagatable_contracts()
check_propagatable_contracts(contracts)
check_propagatable_contracts(contracts, pool)
Check the @propagatable contract of every type in PROPAGATABLE_CONTRACTS.
Runs once at the end of the module, so the contract behind every generated method is enforced where the structs are declared rather than at the first factory call. The violations of every type are collected and reported together, because a run that stops at the first one hides the rest. See propagatable_contract_violations for the two clauses.
Throws an ArgumentError listing every violation; the package refuses to precompile rather than shipping a type whose generated methods throw on first use. A package that declares its own @propagatable types calls this at the end of its own module to get the same guarantee.
Algorithm
- Read
pool, the property names that a prior result can carry, withprior_result_property_pool. - Make
msgs, an empty vector of strings. - For each pair
(T, pprops)ofPROPAGATABLE_CONTRACTS, append the messages thatpropagatable_contract_violationsreports for that type. msgsis not empty: throw anArgumentErrornaming the count and listing every message.- Return
nothing.
Step 3 collects and never stops, so one run reports the violations of every registered type. The registry is filled by propagatable_register! at each declaration, so this function must run after the last one, which is why the module calls it at its end.
Both the registry and the pool are arguments, and each defaults to the value the module ships. A caller that passes a registry of its own reads the message a broken contract gives, without registering a broken type.
Arguments
contracts: The pairs of a type and its@pprop-tagged field names to check.pool: The property names a prior result can carry.
Returns
nothing: Every pair ofcontractssatisfies the contract.
Related
PortfolioOptimisers.forward_nonnothing — Function
forward_nonnothing(v, _::Type{T}, pathstr, nodestr) -> Any
Guard one intermediate node of a @forward_properties nested path.
Return v unchanged when it is not nothing; otherwise throw a PropertyPathError naming the receiver type T, the full declared path pathstr, and the nodestr node that resolved to nothing. Called once per intermediate hop in the descent generated for a depth-≥2 locator.
Algorithm
visnothing: throw aPropertyPathErrorwhose message namespathstr, the typeTand the nodenodestr.vis anything else: returnv.
The guard runs on the intermediate hops only, so a path whose last hop gives nothing returns that nothing rather than raising. That is deliberate: an absent leaf is a value, and an absent intermediate is a path that cannot be walked.
Related
PortfolioOptimisers.forward_flatten_path — Function
forward_flatten_path(expr) -> Vector{Symbol}
Flatten a @forward_properties locator into its path of field symbols.
A bare identifier a becomes [:a]; a dotted expression a.b.c becomes [:a, :b, :c]. Any other expression raises an error.
Algorithm
expris aSymbol: return the one-element vector holding it.expris anExprwith head:.and two arguments:- Read
leaf, its second argument, and unwrap aQuoteNodeto its value. leafis not aSymbol: raise an error naming the leaf.- Call this function again on the first argument, and append
leafto the result.
- Read
expris anything else: raise an error naming the expression.
The recursion of step 2.3 is what makes the path any depth: a.b.c parses as (a.b).c, so the walk descends to the bare name and rebuilds the path from the left.
Related
PortfolioOptimisers.forward_walk_expr — Function
forward_walk_expr(
path,
struct_name,
broadcast::Bool
) -> Expr
Build the expression that descends a @forward_properties path (a vector of field symbols) on the receiver x, returning the value at the path.
A depth-1 path is a single getfield. A depth-≥2 path descends hop by hop, guarding every intermediate with forward_nonnothing (keyed on the receiver type struct_name) so a nothing node throws a path-naming PropertyPathError. When broadcast is true, the final hop maps over the penultimate value if it is an AbstractVector (the scalar-or-vector solution case), otherwise it is a plain access.
Algorithm
- The path holds one name: return
getfield(x, name)and stop.getfieldis used rather thangetproperty, so the generatedBase.getpropertynever re-enters itself. - Build
pathstr, the whole path joined by dots, for the error message. - Start
stmtswith__v = getfield(x, first_name). - For each further hop
kof the path:- Push
__v = forward_nonnothing(__v, struct_name, pathstr, nodestr), wherenodestrnames the part of the path walked so far. kis the last hop andbroadcastistrue: push an assignment that reads the leaf withgetproperty.when__vis anAbstractVector, and withgetpropertyotherwise.- Otherwise: push
__v = getproperty(__v, leaf).
- Push
- Push
__vas the value of the block. - Return the statements wrapped in a
letblock, so__vnever escapes into the caller.
Step 4.1 runs before every hop after the first, so the guard covers each intermediate exactly once and never the leaf. Only the leaf of step 4.2 broadcasts, so an intermediate vector is still a path error rather than a silent map.
Related
Mathematical functions
PortfolioOptimisers.jl makes use of various mathematical operators, some of which are generic to support the variety of inputs supported by the library.
PortfolioOptimisers.:⊗ — Function
⊗(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}Tensor product of two arrays. Returns a matrix of size (length(A), length(B)) where each element is the product of elements from A and B.
Mathematical definition
\[\begin{align} (\boldsymbol{a} \otimes \boldsymbol{b})_{ij} &= a_{i} b_{j}\,. \end{align}\]
Where:
- $\boldsymbol{a}$: Vectorised first array
A, of length $n$. - $\boldsymbol{b}$: Vectorised second array
B, of length $m$. - $a_{i}$, $b_{j}$: Entries of $\boldsymbol{a}$ and $\boldsymbol{b}$ in linear index order.
The result is the outer product $\boldsymbol{a} \boldsymbol{b}^\intercal$, an $n \times m$ matrix. A and B may carry any shape, because both are read in linear index order.
Arguments
A::ArrNum: First array.B::ArrNum: Second array.
Examples
julia> PortfolioOptimisers.:⊗([1, 2], [3, 4])2×2 Matrix{Int64}: 3 4 6 8Related
PortfolioOptimisers.:⊙ — Function
⊙(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
⊙(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
⊙(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
⊙(A, B) -> promote_type(eltype(A), eltype(B))Elementwise (Hadamard) multiplication.
Mathematical definition
\[\begin{align} (\boldsymbol{a} \odot \boldsymbol{b})_{i} &= a_{i} b_{i}\,, \\ (\boldsymbol{a} \odot \beta)_{i} &= a_{i} \beta\,, \\ (\alpha \odot \boldsymbol{b})_{i} &= \alpha b_{i}\,, \\ \alpha \odot \beta &= \alpha \beta\,. \end{align}\]
Where:
- $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
- $\alpha$, $\beta$: Scalar operands.
- $i$: Linear index, $i = 1,\ldots,n$.
Both array operands must carry the same length. A scalar operand multiplies every entry of the array operand.
Arguments
A: First operand (array or scalar).B: Second operand (array or scalar).
Examples
julia> PortfolioOptimisers.:⊙([1, 2], [3, 4])2-element Vector{Int64}: 3 8julia> PortfolioOptimisers.:⊙([1, 2], 2)2-element Vector{Int64}: 2 4julia> PortfolioOptimisers.:⊙(2, [3, 4])2-element Vector{Int64}: 6 8julia> PortfolioOptimisers.:⊙(2, 3)6Related
PortfolioOptimisers.:⊘ — Function
⊘(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
⊘(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
⊘(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
⊘(A, B) -> promote_type(eltype(A), eltype(B))Elementwise (Hadamard) division.
Mathematical definition
\[\begin{align} (\boldsymbol{a} \oslash \boldsymbol{b})_{i} &= \frac{a_{i}}{b_{i}}\,, \\ (\boldsymbol{a} \oslash \beta)_{i} &= \frac{a_{i}}{\beta}\,, \\ (\alpha \oslash \boldsymbol{b})_{i} &= \frac{\alpha}{b_{i}}\,, \\ \alpha \oslash \beta &= \frac{\alpha}{\beta}\,. \end{align}\]
Where:
- $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
- $\alpha$, $\beta$: Scalar operands.
- $i$: Linear index, $i = 1,\ldots,n$.
Both array operands must carry the same length. The division is not guarded, so a zero divisor gives an infinity or a NaN.
Arguments
A: Dividend (array or scalar).B: Divisor (array or scalar).
Examples
julia> PortfolioOptimisers.:⊘([4, 9], [2, 3])2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.:⊘([4, 6], 2)2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.:⊘(8, [2, 4])2-element Vector{Float64}: 4.0 2.0julia> PortfolioOptimisers.:⊘(8, 2)4.0Related
PortfolioOptimisers.:⊕ — Function
⊕(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
⊕(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
⊕(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
⊕(A, B) -> promote_type(eltype(A), eltype(B))Elementwise (Hadamard) addition.
Mathematical definition
\[\begin{align} (\boldsymbol{a} \oplus \boldsymbol{b})_{i} &= a_{i} + b_{i}\,, \\ (\boldsymbol{a} \oplus \beta)_{i} &= a_{i} + \beta\,, \\ (\alpha \oplus \boldsymbol{b})_{i} &= \alpha + b_{i}\,, \\ \alpha \oplus \beta &= \alpha + \beta\,. \end{align}\]
Where:
- $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
- $\alpha$, $\beta$: Scalar operands.
- $i$: Linear index, $i = 1,\ldots,n$.
Both array operands must carry the same length. A scalar operand is added to every entry of the array operand, which the built-in + refuses.
Arguments
A: First summand (array or scalar).B: Second summand (array or scalar).
Examples
julia> PortfolioOptimisers.:⊕([1, 2], [3, 4])2-element Vector{Int64}: 4 6julia> PortfolioOptimisers.:⊕([1, 2], 2)2-element Vector{Int64}: 3 4julia> PortfolioOptimisers.:⊕(2, [3, 4])2-element Vector{Int64}: 5 6julia> PortfolioOptimisers.:⊕(2, 3)5Related
PortfolioOptimisers.:⊖ — Function
⊖(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
⊖(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
⊖(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
⊖(A, B) -> promote_type(eltype(A), eltype(B))Elementwise (Hadamard) subtraction.
Mathematical definition
\[\begin{align} (\boldsymbol{a} \ominus \boldsymbol{b})_{i} &= a_{i} - b_{i}\,, \\ (\boldsymbol{a} \ominus \beta)_{i} &= a_{i} - \beta\,, \\ (\alpha \ominus \boldsymbol{b})_{i} &= \alpha - b_{i}\,, \\ \alpha \ominus \beta &= \alpha - \beta\,. \end{align}\]
Where:
- $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
- $\alpha$, $\beta$: Scalar operands.
- $i$: Linear index, $i = 1,\ldots,n$.
Both array operands must carry the same length. A scalar operand is subtracted from every entry of the array operand, which the built-in - refuses.
Arguments
A: Minuend (array or scalar).B: Subtrahend (array or scalar).
Examples
julia> PortfolioOptimisers.:⊖([4, 6], [1, 2])2-element Vector{Int64}: 3 4julia> PortfolioOptimisers.:⊖([4, 6], 2)2-element Vector{Int64}: 2 4julia> PortfolioOptimisers.:⊖(8, [2, 4])2-element Vector{Int64}: 6 4julia> PortfolioOptimisers.:⊖(8, 2)6Related
PortfolioOptimisers.dot_scalar — Function
dot_scalar(a::Union{<:Number, <:JuMP.AbstractJuMPScalar}, b::VecNum) -> Number
dot_scalar(a::VecNum, b::Union{<:Number, <:JuMP.AbstractJuMPScalar}) -> Number
dot_scalar(a::VecNum, b::VecNum) -> NumberEfficient scalar and vector dot product utility.
- If one argument is a
Union{<:Number, <:JuMP.AbstractJuMPScalar}and the other anVecNum, returns the scalar times the sum of the vector. - If both arguments are
VecNums, returns theirdotproduct.
Mathematical definition
\[\begin{align} \mathrm{dot\_scalar}(\alpha, \boldsymbol{b}) &= \alpha \sum_{i=1}^{n} b_{i}\,, \\ \mathrm{dot\_scalar}(\boldsymbol{a}, \beta) &= \beta \sum_{i=1}^{n} a_{i}\,, \\ \mathrm{dot\_scalar}(\boldsymbol{a}, \boldsymbol{b}) &= \boldsymbol{a}^\intercal \boldsymbol{b}\,. \end{align}\]
Where:
- $\alpha$, $\beta$: Scalar operand, a number or a
JuMPscalar. - $\boldsymbol{a}$, $\boldsymbol{b}$: Vector operand of length $n$.
The first two forms are the dot product of the vector with a constant vector of value $\alpha$, so the scalar stands for a uniform vector. The sum replaces that constant vector, so no $n$-length array is built.
Arguments
a: First operand, a scalar or a vector.b: Second operand, a scalar or a vector.
Returns
res::Number: The resulting scalar.
Examples
julia> PortfolioOptimisers.dot_scalar(2.0, [1.0, 2.0, 3.0])12.0julia> PortfolioOptimisers.dot_scalar([1.0, 2.0, 3.0], 2.0)12.0julia> PortfolioOptimisers.dot_scalar([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])32.0Related
View functions
NestedClustered optimisations need to index the asset universe in order to produce the inner optimisations. These indexing operations are implemented as views, indexing, and custom index generators.
PortfolioOptimisers.nothing_scalar_array_view — Function
nothing_scalar_array_view(
x::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict,
<:AbstractEstimatorValueAlgorithm,
<:DynamicAbstractWeights, <:AbstractEstimator, <:AbstractAlgorithm,
<:StatsBase.CovarianceEstimator},
::Any
) -> x
nothing_scalar_array_view(x::AbstractVector, i) -> view(x, i)
nothing_scalar_array_view(x::VecScalar, i) -> VecScalar(; v = view(x.v, i), s = x.s)
nothing_scalar_array_view(x::AbstractMatrix, i) -> view(x, i, i)
nothing_scalar_array_view(
x::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}},
i
) -> [nothing_scalar_array_view(xi, i) for xi in x]Utility for safely viewing into possibly nothing, scalar, or array values.
Algorithm
The method that Julia selects is the algorithm. Each step is one method, and no method allocates a copy of the data.
xcarries no asset axis, because it isnothing, a scalar, a pair, a dictionary, a value algorithm, a set of dynamic weights, an estimator, an algorithm or aStatsBase.CovarianceEstimator: returnxitself.xis a vector: returnview(x, i), one entry per selected asset.xis aVecScalar: return a newVecScalarwhose vector part isview(x.v, i)and whose scalar partx.sis carried through. The scalar part carries no asset axis.xis a matrix: returnview(x, i, i), which selects the same index on both axes. This is the rule for a square per-asset matrix, such as a covariance matrix or a similarity matrix. A matrix whose two axes are different needsnothing_scalar_array_view_odd_orderinstead.xis a vector of vectors, matrices orVecScalars: apply step 2, 3 or 4 to each element, and collect the views into a new vector. The outer vector is rebuilt, so its own length is unchanged. The vector's element type selects this step, and it must be a subtype of theUnionthe signature names. A vector holding both a vector and a matrix has the element typeArray{T}, which is a subtype of neitherAbstractVectornorAbstractMatrix, so it resolves on step 2 and the index selects the elements of the outer vector.
Arguments
x: Input value.i: Index or indices to view.
Returns
x: Input value.::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict, <:AbstractEstimatorValueAlgorithm, <:DynamicAbstractWeights, <:AbstractEstimator, <:AbstractAlgorithm, <:StatsBase.CovarianceEstimator}: Returnsxunchanged.::AbstractVector: Returnsview(x, i).::VecScalar: ReturnsVecScalar(; v = view(x.v, i), s = x.s).::AbstractMatrix: Returnsview(x, i, i).::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of views for each element inx.
Examples
julia> PortfolioOptimisers.nothing_scalar_array_view(nothing, 1:2)julia> PortfolioOptimisers.nothing_scalar_array_view(3.0, 1:2)3.0julia> PortfolioOptimisers.nothing_scalar_array_view([1.0, 2.0, 3.0], 2:3)2-element view(::Vector{Float64}, 2:3) with eltype Float64: 2.0 3.0julia> PortfolioOptimisers.nothing_scalar_array_view([[1, 2], [3, 4]], 1)2-element Vector{SubArray{Int64, 0, Vector{Int64}, Tuple{Int64}, true}}: fill(1) fill(3)Related
nothing_scalar_array_view(
x::MedianCenteringFunction,
_
) -> MedianCenteringFunction
Return the MedianCenteringFunction x unchanged.
Identity pass-through: centering functions are not sliced by asset index.
Related
nothing_scalar_array_view(
td::TimeDependent,
i
) -> TimeDependent
Slice a TimeDependent schedule of scalar-or-array field values (warm starts, initial weights) to asset indices i.
Vector schedules slice each per-fold entry, and the default when one is set; callable schedules pass through — they see the sliced universe via their fold context's rd.
Related
PortfolioOptimisers.nothing_scalar_array_view_odd_order — Function
nothing_scalar_array_view_odd_order(::Nothing, i, j)
nothing_scalar_array_view_odd_order(x::AbstractMatrix, i, j) -> view(x, i, j)Utility for safely viewing into possibly nothing or array values with two indices.
- If
xisnothing, returnsnothing. - Otherwise, returns
view(x, i, j).
Algorithm
xisnothing: returnnothing.xis a matrix: returnview(x, i, j), which selectsion the row axis andjon the column axis.
The two axes take different indices, which is what separates this verb from nothing_scalar_array_view. An odd-order co-moment matrix is $N \times N^{k}$ for an odd order $k$, so the row index selects assets and the column index selects the tuples of assets that the columns hold. The caller supplies j, and this verb does not derive it; fourth_moment_index_generator is the counterpart that builds such a column index.
Arguments
x: Input value.i,j: Indices to view.
Returns
- The corresponding view or
nothing.
Examples
julia> PortfolioOptimisers.nothing_scalar_array_view_odd_order(nothing, 1, 2)julia> PortfolioOptimisers.nothing_scalar_array_view_odd_order([1 2; 3 4], 1, 2)0-dimensional view(::Matrix{Int64}, 1, 2) with eltype Int64:2Related
PortfolioOptimisers.nothing_scalar_array_getindex — Function
nothing_scalar_array_getindex(
x::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict,
<:AbstractEstimatorValueAlgorithm,
<:DynamicAbstractWeights},
::Any
) -> x
nothing_scalar_array_getindex(x::AbstractVector, i) -> x[i]
nothing_scalar_array_getindex(x::VecScalar, i) -> VecScalar(; v = x.v[i], s = x.s)
nothing_scalar_array_getindex(x::AbstractMatrix, i) -> x[i, i]
nothing_scalar_array_getindex(
x::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}},
i
) -> [nothing_scalar_array_getindex(xi, i) for xi in x]Utility for safely viewing into possibly nothing, scalar, or array values.
Algorithm
The method that Julia selects is the algorithm. It is the copying twin of nothing_scalar_array_view: every step returns a new array rather than a view.
xcarries no asset axis, because it isnothing, a scalar, a pair, a dictionary, a value algorithm or a set of dynamic weights: returnxitself.xis a vector: returnx[i], a new vector with one entry per selected asset.xis aVecScalar: return a newVecScalarwhose vector part isx.v[i]and whose scalar partx.sis carried through.xis a matrix: returnx[i, i], which selects the same index on both axes. This is the rule for a square per-asset matrix. A matrix whose two axes are different needsnothing_scalar_array_getindex_odd_orderinstead.xis a vector of vectors, matrices orVecScalars: apply step 2, 3 or 4 to each element, and collect the results into a new vector. The vector's element type selects this step, and it must be a subtype of theUnionthe signature names. A vector holding both a vector and a matrix has the element typeArray{T}, which is a subtype of neitherAbstractVectornorAbstractMatrix, so it resolves on step 2 and the index selects the elements of the outer vector.
The type list of step 1 is shorter than the one nothing_scalar_array_view carries: an estimator, an algorithm and a StatsBase.CovarianceEstimator reach the view verb and not this one, because only the view verb is the leaf of port_opt_view.
Arguments
x: Input value.i: Index or indices to view.
Returns
x: Input value.::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict, <:AbstractEstimatorValueAlgorithm, <:DynamicAbstractWeights}: Returnsxunchanged.::AbstractVector: Returnsx[i].::VecScalar: ReturnsVecScalar(; v = x.v[i], s = x.s).::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of elements indexed byi.::AbstractMatrix: Returnsx[i, i].
Examples
julia> PortfolioOptimisers.nothing_scalar_array_getindex(nothing, 1:2)julia> PortfolioOptimisers.nothing_scalar_array_getindex(3.0, 1:2)3.0julia> PortfolioOptimisers.nothing_scalar_array_getindex([1.0, 2.0, 3.0], 2:3)2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.nothing_scalar_array_getindex([[1, 2], [3, 4]], 1)2-element Vector{Int64}: 1 3Related
PortfolioOptimisers.nothing_scalar_array_getindex_odd_order — Function
nothing_scalar_array_getindex_odd_order(::Nothing, i, j)
nothing_scalar_array_getindex_odd_order(x::AbstractMatrix, i, j) -> x[i, j]Utility for safely indexing into possibly nothing or array values with two indices.
- If
xisnothing, returnsnothing. - Otherwise, returns
x[i, j].
Algorithm
xisnothing: returnnothing.xis a matrix: returnx[i, j], which selectsion the row axis andjon the column axis, and copies.
This is the copying twin of nothing_scalar_array_view_odd_order, and it takes different indices on the two axes for the same reason: an odd-order co-moment matrix is $N \times N^{k}$ for an odd order $k$.
Arguments
x: Input value.i,j: Indices to access.
Returns
- The corresponding matrix element or
nothing.
Examples
julia> PortfolioOptimisers.nothing_scalar_array_getindex_odd_order(nothing, 1, 2)julia> PortfolioOptimisers.nothing_scalar_array_getindex_odd_order([1 2; 3 4], 1, 2)2Related
PortfolioOptimisers.fourth_moment_index_generator — Function
fourth_moment_index_generator(
N::Integer,
i
) -> Vector{Int64}
Constructs an index vector for extracting the fourth moment submatrix corresponding to indices i from a covariance matrix of size N × N.
Mathematical definition
\[\begin{align} \mathrm{idx} &= \left( (c - 1) N + r \right)_{c \in \boldsymbol{i},\ r \in \boldsymbol{i}}\,. \end{align}\]
Where:
- $N$: Number of assets.
- $\boldsymbol{i}$: The selected asset indices, of length $n$.
- $r$, $c$: The row and the column of an asset pair in the $N \times N$ grid of pairs.
$(c - 1) N + r$ is the column-major linear index of the pair $(r, c)$ in that grid, which is the axis of the square cokurtosis matrix $\mathbf{K}$, of size $N^{2} \times N^{2}$. So idx selects the $n^{2}$ pairs that the $n$ selected assets make, on either axis of $\mathbf{K}$. c runs on the outside, so the order of idx is the column-major order of the sub-grid too.
Algorithm
- Make
idx, an empty vector of integers, with room forlength(i)^2entries. - For each
cini, take the linear index range of columnc, which is((c - 1) * N + 1):(c * N), and select the entriesiof that range. - Append the selected entries to
idx. - Return
idx.
Arguments
N: Size of the full covariance matrix.i: Indices of the variables of interest.
Returns
idx::VecInt: Indices for extracting the fourth moment submatrix.
Examples
julia> PortfolioOptimisers.fourth_moment_index_generator(3, [1, 2])4-element Vector{Int64}: 1 2 4 5Summary statistics
Some estimators and constraints are based on summary statistics of vectors. These types are used to dispatch the appropriate functions and encapsulate auxiliary data such as weights.
PortfolioOptimisers.Num_VecToScaM — Type
const Num_VecToScaM = Union{<:Number, <:VectorToScalarMeasure, <:Function}Union type representing a numeric value, a VectorToScalarMeasure, or a Function.
This type lets functions and fields accept all three, so a caller can give a fixed number, an object that implements the VectorToScalarMeasure interface, or a plain reduction function. vec_to_real_measure returns a Number unchanged, dispatches a VectorToScalarMeasure to its reduction, and applies a Function to the vector.
Related