Calibration Rules: private API

PortfolioOptimisers.AbstractCalibrationAlgorithmType
abstract type AbstractCalibrationAlgorithm <: AbstractAlgorithm

Computes a calibrated quantity — a tail probability, a deformation parameter, an ambiguity radius, an Esfahani-Kuhn tail weight or a norm ceiling — from the data a prior result carries, so that the quantity refits whenever the sample moves.

All concrete subtypes should subtype one of the families under this root rather than the root itself, and # Related names them. A plain number in place of a rule is the quantity itself, exactly as it is today.

A rule is named for the method it runs, and carries the name of the quantity as a suffix only where the bare method word is already claimed. ScenarioCount, EntropyBudget, HillTailDecay, RadialTailDecay, TailTermParity and EffectiveAssetFloor name a method and stop there. Five names carry the quantity, and each of the five earns it. RateSignificance and RateRadius are one method over two quantities, so neither may hold the bare word Rate, and DimensionalRateRadius carries that same stem under a prefix. ConcentrationRadius and DualNormRadius are each named after a mathematical object, so the bare word would name the object rather than the rule.

A rule states a default for every keyword it can, so a bare call constructs. Two rules state none, because the quantity the keyword takes is the whole content of the rule and no value suits every sample. ScenarioCount and EntropyBudget are those two. The keyword of each stands at nothing, which is not a value of the quantity, so a bare call is refused with a message that names the quantity, the reason there is no default, and a value to start from.

A rule states the method and nothing else. The slot states the quantity: alpha names the lower tail, kappa names the deformation parameter, r names an ambiguity radius and l2c names a norm ceiling. So the caller writes the rule alone, and each slot's Num_ bound names the one family that has a reading in that slot. A rule of another family is refused at construction, by the bound.

A Calibration Rule is not a DeferredQuantity, and the two mechanisms stay parallel end to end. A Deferred Quantity is fitted and the quantity is read off the fit; a rule fits nothing, and reads the sample size and the moments the prior result already carries. A rule also sees the effective observation weights, which resolve_slot does not carry. So a rule resolves through resolve_calibration_slot, is declared through calibration_slots, and is refused at a value-level entry point by assert_calibrated_slots.

Related

source
PortfolioOptimisers.Num_SigCalType
const Num_SigCal = Union{<:AbstractSignificanceCalibrationAlgorithm, <:Function,
                         <:Number}

Field bound for a significance slot: the tail probability itself, a rule of the family, or a plain function of the same five arguments.

The slot names the end of the distribution it addresses, so the occupant does not. alpha names the lower tail and beta names the upper tail, and one rule serves both ends. One bound therefore serves every significance slot, and a Range measure defaults its head slot to whatever its tail slot holds.

A rule is run by calling it, so a function and a callable struct are the same thing to resolve_calibration_slot. The struct earns its keep by carrying parameters and a name that a docstring can describe. The function is the shortest way to state a one-off rule, and a closure over a caller's own data is the case that has no type. A function carries no family, so this bound cannot refuse a deformation rule written as a closure. The name of the slot is what states the quantity there.

Related

source
PortfolioOptimisers.AbstractCalibrationSeriesType
abstract type AbstractCalibrationSeries <: AbstractEstimator

Names the series a calibration rule reads, so that a rule reads the quantity its slot owner prices.

A rule gets no portfolio, which resolve_calibration_slot states, so it cannot form the loss series of a portfolio that does not exist until the solver returns. What it can form is the series of each column of pr.X. This family names which one: the column itself, or the drawdown series that column carries. calibration_series is the trait a slot owner answers, and the CalibrationContext carries the answer into the rule.

The marker states the quantity and not the estimator, so each rule reads it on its own terms. HillTailDecay forms the series and estimates its tail index. RadialTailDecay cannot form it, because a Mahalanobis distance carries no path, so it whitens the drawdown sample instead. The three radius rules read the per-asset dispersion of the series, which calibration_series_dispersion takes, and TailTermParity reads both terms of its ratio over it.

Related

source
PortfolioOptimisers.AbstractDrawdownSeriesType
abstract type AbstractDrawdownSeries <: AbstractCalibrationSeries

Names a drawdown series, the quantity a drawdown measure prices.

A drawdown is a path functional of one series, so it is formed per column and never per row. The two members differ in how the path compounds, and they are the two series absolute_drawdown_vec and relative_drawdown_vec build.

A drawdown series is non-positive, so it carries one end. A rule that answers per end therefore refuses a head key under this family, and the refusal is a statement about the quantity rather than about the sample.

What a drawdown reading says depends on the record. A drawdown is a running functional, so its law over a finite record is not the law of one step, and a rule that reads the shape of a series reads a different shape here. A sample whose drift is strong enough for the drawdown process to settle reads heavier than its own returns, which is the reading a stationary drawdown carries. A sample whose drift is weak reads the range of the path over the record instead, and that range is thinner in the tail than the step law that made it. Both are readings of the series the measure prices over the record the measure prices it on, so a rule states the sample rather than a claim about drawdowns in general.

Related

source
PortfolioOptimisers.Num_AmbRadCalType
const Num_AmbRadCal = Union{<:AbstractAmbiguityRadiusCalibrationAlgorithm, <:Function,
                            <:Number}

Field bound for an ambiguity-radius slot: the radius itself, a rule of the family, or a plain function of the same five arguments.

The union names one rule family and no other, so a tail-weight rule placed in a radius slot fails the constructor's signature and is refused at construction. That is the whole of the family validation, and no guard method is written for it. A function stands in the union on the terms Num_SigCal sets out: it carries no family, so the name of the slot is what states the quantity.

Related

source
PortfolioOptimisers.Num_NormCeilCalType
const Num_NormCeilCal = Union{<:AbstractNormCeilingCalibrationAlgorithm, <:Function,
                              <:Number}

Field bound for a norm-ceiling slot: the ceiling itself, a rule of the family, or a plain function of the same five arguments.

The union names one rule family and no other, so a radius rule placed in a ceiling slot is refused at construction, and a ceiling rule placed in a radius slot is refused the same way. AbstractNormCeilingCalibrationAlgorithm states the difference between the two quantities.

Related

source
PortfolioOptimisers.Num_AmbRadNormCeilCalType
const Num_AmbRadNormCeilCal = Union{<:AbstractAmbiguityRadiusCalibrationAlgorithm,
                                    <:AbstractNormCeilingCalibrationAlgorithm, <:Number}

Field bound for the one slot the library reads as two quantities, the val field of LpRegularisation.

That estimator is a penalty in the lp field of JuMPOptimiser and a norm constraint in its lpc field, so val is an ambiguity radius on one route and a norm ceiling on the other. One field cannot carry two bounds, so this bound admits both rule families and each route refuses the family that has no reading on it. It is the only slot in the library whose quantity is settled after construction rather than by its bound.

A plain function is not admitted here, and this is the one slot that refuses one. Every other slot names one quantity, so a function placed in it needs no family of its own. This slot names two, and the two guards read the family to tell them apart, so a function would reach a route that has no reading for it and no guard could say so.

Related

source
PortfolioOptimisers.resolve_calibration_slotFunction
resolve_calibration_slot(slot, key::Symbol, pr::AbstractPriorResult, w, slv = nothing,
                         ctx::CalibrationContext = CalibrationContext())

Resolve one calibration slot against prior result pr, the effective observation weights w, the effective solver slv and the site's context ctx, and return a plain number.

A slot that holds a rule is called as alg(key, pr, w, slv, ctx). So a callable rule and a plain function are the same thing here. Anything else, a stated number above all, is returned unchanged.

A rule gets no portfolio. A prior result carries no portfolio weight vector, so no rule can measure a portfolio's own loss series. What it can measure is the series of each column of the sample, and ctx.series tells it which series the slot owner prices. It does get the solver, on both of the routes that resolve a measure, so a rule may call ERM or RRM. On the factory route @propagatable runs the @cprop selection before the resolution, so the solver is on the struct. On the JuMP route no selection runs, so set_risk_constraints! threads it into resolve_deferred_quantities and the owner settles it as sel(x.slv, slv).

The context is the whole of what the site says, and nothing is rebuilt here. key names the slot rather than the quantity, so a rule that reads the shape of a series, the significance level of a sibling slot, or the norm order of the constraint it stands in reads it off ctx. No rule carries a field for any of the three, so no occupant is rebuilt on the way in and nothing a caller stated is overwritten. CalibrationContext states which rule reads which field.

A ceiling rule and a radius rule are checked here, and the three other families are not. AbstractNormCeilingCalibrationAlgorithm and AbstractAmbiguityRadiusCalibrationAlgorithm each take their own method, which runs assert_nonempty_gt0_finite_val on the number the rule returned, under the slot's own key. Both quantities reach a JuMP model from a bare field of JuMPOptimiserl2c and linfc for the ceiling, l1 and linf for the radius — where no term is rebuilt, so nothing downstream would state the range. The three other families stand only in slots their owner rebuilds, and the rebuild runs the owner's constructor on the calibrated number, so a second check here would restate a rule that file already owns. The cost is paid on the calibrated path alone: a stated number takes the first method and meets no check here at all.

A rule written as a plain function is checked by the site, not here. A function carries no family, so no method here can read the quantity it computes. assemble_jump_model! is the one site that reads a calibrated number without rebuilding a term, and it states the range of each of its four norm slots itself.

This is the parallel of resolve_slot, and it is a second verb rather than a widening of the first for two reasons. resolve_slot's body is deferred_quantity(fit_deferred_quantity(dq, pr), key), a fit followed by an extraction, and a rule fits nothing. resolve_slot also carries neither w nor slv, which a rule needs. So the rule families stay out of the DeferredQuantity union.

The caller computes w itself, as sel(r.w, pr.w), and threads it with the measure's own slv. A parent that carries no observation weights of its own passes pr.w, and one that carries no solver leaves slv at its default.

A TimeDependent reaches the host that holds the slot, and no further. A schedule varies a field of an estimator, and it is consumed by update_time_dependent_fields before any prior is fitted. A rule is never standalone: it stands in a slot of a host, so the host is what a schedule swaps. Where the host is a JuMPOptimiser the four norm fields are themselves schedulable, and a schedule over them selects a rule per fold. Where the host is a risk measure the slot's own bound admits no schedule, and the caller varies the whole measure instead, through the schedulable risk-measure field of the optimiser. Both routes land in the same place, because the selection runs first and the rule then resolves against the prior of the period that was selected. A schedule inside a rule is therefore not a gap: it would name a fold the rule cannot see, and it would duplicate the channel the host already carries.

Algorithm

  1. Return slot unchanged when is_calibration_rule answers false for it. A stated number takes that arm.
  2. Call the rule as slot(key, pr, w, slv, ctx), and return the number it gives. A callable struct and a plain function are the same thing here.
  3. A norm-ceiling rule or an ambiguity-radius rule takes a more specific method, which checks that number before it returns.

Arguments

  • slot: The slot's occupant: a number, or a rule.
  • key: Name of the slot that is being resolved.
  • pr: Prior result the rule reads.
  • w: Effective observation weights, or nothing.
  • slv: Effective solver, or nothing when the measure carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. The default names no sibling significance level, no norm order, and the returns series.

Validation

  • A norm-ceiling rule or an ambiguity-radius rule returns a number that is > 0 and finite, checked by assert_nonempty_gt0_finite_val under key. Every other occupant is returned unchecked, and the owner of its slot states the range when it rebuilds.

Returns

  • val::Number: The calibrated quantity, or the stated value unchanged.

Related

source
PortfolioOptimisers.calibration_slotsFunction
calibration_slots(x)

Declare the slots of x that may hold a Calibration Rule, as a NamedTuple mapping each slot's name to its current value. The default is empty: a type with no calibration slot needs no method.

This is the parallel of deferred_slots. Three consumers read it: assert_calibrated_slots refuses a rule that reached a value-level entry point, assert_declared_calibration_resolver refuses a rule the library itself left unresolved, and resolve_calibration_slots resolves the slots.

For most types the declaration is the whole statement, and the resolution is derived from it. A type whose slots carry an order between them writes its own resolve_deferred_quantities method instead, and a CalibrationContext built from a sibling's resolved number is what such an order looks like: the slot reads that number off the context, and no derivation can know which sibling. A slot that names a quantity under a key of its own also writes its own resolution, which is what the three regularisation keys do.

A slot that holds a child measure is declared here too, so a container names its children and each child names its own slots. Such a slot is declared in deferred_slots as well, and the deferred recursion is what resolves the child. So a container's declaration is read by the two assert_ walks alone, and it names a child only where that child can carry a slot: an occupant that carries none is not named here, because a plain function standing in a calibration slot is a rule. OWA_CalOccupant is the one bound in the library that states which occupants of a weight slot qualify.

Related

source
PortfolioOptimisers.is_calibration_ruleFunction
is_calibration_rule(x) -> Bool

Answer whether the occupant x of a calibration slot is a Calibration Rule.

A rule is run by calling it, so the two things a slot may hold and call are an AbstractCalibrationAlgorithm and a plain function of the same five arguments. Both answer true, and everything else answers false: a stated number, nothing, and a child measure a container declares.

A callable is not a rule by being callable. calibration_slots also declares the children of a container, and one such child is an ordered-weights builder wrapped in a reversal, which is a Function and computes no quantity. So the marker is this trait rather than the Function type, and a type that is callable for a reason of its own withdraws itself with one method here. OWA_CalOccupant is the one that does.

The three readers are resolve_calibration_slot, which calls a rule and returns anything else unchanged, assert_calibrated_slots, which refuses a rule that reached a value-level entry point, and assert_declared_calibration_resolver, which refuses a rule the library itself left unresolved.

Arguments

  • x: The slot's occupant.

Returns

  • y::Bool: Whether x is a rule.

Related

source
PortfolioOptimisers.assert_calibrated_slotsFunction
assert_calibrated_slots(x)

Refuse a Calibration Rule that reached a value-level entry point, which has no prior result to resolve it against.

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: a rule reads the sample size and the moments the prior carries, and it reads the effective observation weights, none of which a bare returns matrix supplies. So it refuses instead, and names the slot, the rule standing in it and the way out.

This is the shape assert_resolved_slots already uses on the Deferred-Quantity side, and the message names both types with nameof for the same reason: a printed type carries a module prefix wherever the name is not visible from Main, and the message must read the same in every process. A rule written as a plain function carries no name of its own, so the message names the slot alone there.

The slots come from calibration_slots and the check recurses into whatever they hold, so a container is covered by its children's declarations.

Algorithm

  1. Walk the pairs that calibration_slots declares for x, giving each slot's name key and its occupant slot.
  2. Refuse an occupant that is_calibration_rule answers true for.
  3. Recurse into the 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.
  4. Return nothing once the walk is spent.

Validation

  • Throws an ArgumentError when a slot of x, or of any child the walk reaches, holds a Calibration Rule. The message names the slot, the rule standing in it and the two ways out.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_declared_calibration_resolverFunction
assert_declared_calibration_resolver(x, slots::NamedTuple)
assert_declared_calibration_resolver(x)

Refuse a type that declares a calibration slot and no way to resolve it.

slots is what the resolution produced. A Calibration Rule that survives it names a type that declared the slot in calibration_slots and then wrote no resolution for it, so the rule would reach the model builders and be multiplied as though it were a number. This is where the declaration and the resolver are paired.

The two channels check alike. assert_declared_slot_resolver is the Deferred-Quantity half, and the two-argument method here carries its shape: the resolver holds the slots it produced and hands them over. The one-argument method reads them off x instead, and is what a funnel takes, which holds a resolved value and not the slots that made it.

The walk is the one assert_calibrated_slots makes, and the message is the other half of the pair. That one names a caller who reached a value-level entry point, and this one names a slot the library itself left unresolved. So one message never has to serve two failures.

The calibration channel derives no recursion of its own, which is why the pairing is a check and not a derivation: a rule that reads a sibling slot must be resolved after that sibling, and no derivation can know the order. So each type writes its resolution beside its declaration, and this refuses the pair that does not meet.

Algorithm

  1. Walk the pairs of slots, giving each slot's name key and its occupant slot.
  2. Refuse an occupant that is_calibration_rule answers true for.
  3. Return nothing once the walk is spent.

The one-argument method reads slots from calibration_slots, then recurses into each occupant, so a child measure's own declarations are paired as well. A slot that holds a vector of children is walked element by element.

Arguments

  • x: The slot owner, whose type the message names.
  • slots: The slots the resolution produced, or the declaration itself for the one-argument method.

Validation

  • Throws an ArgumentError when an entry of slots, or of any child the walk reaches, still holds a Calibration Rule. The message names the type, the slot and the resolution to write beside the declaration.

Returns

  • nothing.

Related

source
PortfolioOptimisers.calibration_seriesFunction
calibration_series(x)

Declare the series that x prices, so that a rule in one of its calibration slots reads that quantity.

The default is ReturnsSeries, so a type that prices the return distribution needs no method. The two relativistic drawdown measures write one, because the series they price is not the sample they carry.

This is a trait on the slot owner and not a field on the rule, for the reason the CalibrationContext carries a norm order rather than a rule holding one: the quantity belongs to the measure, and a rule cannot know which measure it reached. No rule holds a marker of its own, so there is nothing for the owner's answer to overwrite, and a caller who runs a rule by hand states the marker in the context.

Arguments

  • x: The slot owner, a risk measure above all.

Returns

  • series::AbstractCalibrationSeries: The series the owner prices.

Related

source
PortfolioOptimisers.calibration_series_vecFunction
calibration_series_vec(series::AbstractCalibrationSeries, x::VecNum)

Return the univariate series that series names, built from one column of the sample.

A drawdown marker builds a fresh vector, and ReturnsSeries returns the column itself. So the returns reading allocates nothing and reads the sample it was always given.

The verb takes one column rather than the whole matrix because a drawdown is a path functional: it accumulates down a column and says nothing across a row. A caller that needs the pool walks the columns and calls this once per column.

Arguments

  • series: The series marker.
  • x: One column of the sample.

Returns

  • s::VecNum: The series the marker names.

Related

source
PortfolioOptimisers.calibration_series_matrixFunction
calibration_series_matrix(series::AbstractCalibrationSeries, X::AbstractMatrix)

Return the sample that series names, one series per column of X.

A drawdown series reaches the array builder of its own convention along dims = 1, which is the observations dimension: absolute_drawdown_arr for AbsoluteDrawdownSeries, and relative_drawdown_arr for RelativeDrawdownSeries. Those builders hold the running-peak convention, so the matrix reading and the vector reading state one definition of a drawdown and cannot drift apart. The result holds the same shape as X: a drawdown series carries one entry per observation, so no observation is lost and the count of a tail is the count the caller formed on X.

ReturnsSeries returns X itself, so the returns reading allocates nothing. A rule that walks the columns one at a time should call calibration_series_vec instead, and allocate one column rather than a matrix.

Arguments

  • series: The series marker.
  • X: Returns matrix, T × N.

Returns

  • Y::AbstractMatrix: The sample the marker names, T × N.

Related

source
PortfolioOptimisers.calibration_series_dispersionFunction
calibration_series_dispersion(series::AbstractCalibrationSeries, pr::AbstractPriorResult)

Return the per-asset dispersion of the series that series names, one entry per column of the sample.

A ReturnsSeries reads it off pr.sigma, as the square root of that matrix's diagonal. That is the reading the radius family has always carried, so a prior that states a shrunk or a robust covariance matrix is read through it, and this verb leaves that untouched.

A drawdown series reads it off the drawdown sample instead, because a prior result states no drawdown moment. calibration_series_matrix builds the sample, and the dispersion is the sample dispersion of each of its columns. This is the reading radial_series_inputs takes a centre and a whitening factor by, and it parts from the returns reading for the same reason: pr.sigma is a moment of the returns, and no scaling of it states a moment of a drawdown.

A radius is a distance in the space of the scenarios the model prices, so this vector carries its units. ConcentrationRadius and DimensionalRateRadius average it into one scale, and DualNormRadius divides it by the square root of the effective sample size and takes a norm of the result.

Arguments

  • series: The series the slot owner prices.
  • pr: Prior result the covariance matrix is read off on a returns series, and the sample on a drawdown series.

Returns

  • s::AbstractVector: The per-asset dispersion of that series, N × 1.

Related

source
PortfolioOptimisers.effective_sample_sizeFunction
effective_sample_size(pr::AbstractPriorResult, w::Option{<:ObsWeights})

Return the number of observations behind the moments of pr, weighted by w.

This is the count a calibration rule divides by, so the rules that read a count read one definition of it. Three sources are read in order, and the first that answers wins. With weights it is Kish's effective sample size, which is the number of equally weighted observations that carries the information the weighted sample carries. Without weights it is the count the result states in ens, when it states one and carries no weighting of its own: a Scenario Cap states the number of observations its moments were fitted over there, because the rows the result carries are then fewer than the observations behind its moments, and a count read off the shape would price every rule by t / w. Otherwise it is the row count of pr.X, which is then the sample.

An ens beside a w is not read by this arm. ens is bound to w as a diagnostic of it — an entropy-pooling prior writes exp(entropy(w)) there — so a reader that reads no weights reads no diagnostic of them either, and a rule that reads the raw row count under such a prior still does. The only ens a result states with no w is the one a cap writes.

The weights are read through get_observation_weights, so the whole of the ObsWeights union that a rule's own w field admits is served here. A DynamicAbstractWeights that states no method for a column of the sample raises ObservationWeightsError, which names the two signatures to write, rather than a bare MethodError off sum.

Mathematical definition

\[T_{e} = \begin{cases} \dfrac{\left(\sum\limits_{t=1}^{T} w_{t}\right)^{2}}{\sum\limits_{t=1}^{T} w_{t}^{2}} & \textrm{if } w \textrm{ is given}\\ T_{\mathrm{ens}} & \textrm{if } w \textrm{ is } \texttt{nothing} \textrm{ and the result states } T_{\mathrm{ens}} \textrm{ beside no weighting}\\ T & \textrm{otherwise} \end{cases}\,.\]

Where:

  • $T$: Number of observations.
  • $T_{e}$: Effective sample size, which is Kish's when the observation weights are stated.
  • $w_{t}$: Observation weight of observation $t$.
  • $T_{\mathrm{ens}}$: The effective sample size the result states in ens.

Algorithm

  1. Resolve w against the first column of pr.X through get_observation_weights, giving ws.
  2. When ws is not nothing, return Kish's count of it.
  3. Otherwise return stated_sample_size of the result's own w, ens and X: pr.ens when it is stated beside no pr.w, else size(pr.X, 1).

Arguments

  • pr: Prior result the sample length is read off.
  • w: Effective observation weights, or nothing.

Validation

Returns

  • T_e::Number: The effective sample size.

Related

source
PortfolioOptimisers.stated_sample_sizeFunction
stated_sample_size(::Nothing, ens::Number, X::MatNum)
stated_sample_size(w, ens, X::MatNum)

Return the count a Prior Result states in ens beside no weighting, and the row count of X otherwise.

The unweighted arm of effective_sample_size, by dispatch on the result's own w and ens. A result that carries no w and states an ens is one a Scenario Cap wrote, and the count is the observations its moments were fitted over. An ens beside a w is a diagnostic of that weighting, which an unweighted read does not take, and a result that states no ens carries its sample in X.

Arguments

  • w: The result's own observation weights, or nothing.
  • ens: The count the result states, or nothing.
  • X: The returns matrix the result carries, observations × assets.

Returns

  • T::Number: ens when it is stated beside no w, size(X, 1) otherwise.

Related

source
PortfolioOptimisers.hill_tail_indexFunction
hill_tail_index(
    series::AbstractCalibrationSeries,
    X::AbstractArray{E<:Number, 2},
    s::Integer,
    k::Integer
) -> Any

Estimate the tail index of the pool of standardised values of X, over the worst k order statistics of the pool.

series names the series each column carries, and calibration_series_vec builds it. Every such series is centred and divided by its own sample dispersion, and s names the end. Hill's estimator reads the k of the T N standardised values that lie furthest into that end. HillTailDecay states the reading and the assumptions the pool carries. This verb is the estimate alone.

The pool holds T values per column under every marker, because a drawdown series holds one entry per observation. So the count k the caller formed is the count this verb reads, whatever the series is.

The element type is bound by the signature, so the pool and the sum it feeds are concrete. A rule reads pr.X off an AbstractPriorResult, whose field types no signature states, and this is the boundary that type crosses at.

Algorithm

  1. Read the shape of X into T and N, and allocate the pool pool of T * N values.
  2. Walk the columns of X. Build each column's series with calibration_series_vec, take its sample mean mu and its sample dispersion sd, and write -s * (col[t] - mu) / sd into pool.
  3. Partially sort pool so that its k + 1 smallest values sit in order at the front, and read the last of them into vkp1. partialsort! places one index alone, and it makes no promise about the rest of the vector, so the range is what puts the k entries the estimate reads before vkp1. The sign of step 2 puts the end the caller prices in the lower tail of the pool.
  4. Return k over the sum of log(pool[i] / vkp1) across those k entries. Both terms of each ratio are negative, so the ratio is one of magnitudes and the sum is Hill's with no further sign.

Arguments

  • series: The series each column carries.
  • X: Returns matrix, T × N.
  • s: Sign of the end the estimate reads: 1 reads the gain tail and -1 reads the loss tail.
  • k: Number of order statistics the estimate reads. The caller states it, and the caller keeps the floor under it.

Validation

  • The k + 1-th largest pooled value must be positive.

Returns

  • a::Number: The Hill estimate of the tail index of the pool.

Related

source
PortfolioOptimisers.series_end_signFunction
series_end_sign(series::AbstractCalibrationSeries, key::Symbol)

Return the sign of the end that key prices on series, and refuse an end the series does not have.

:kappa_b is the only head key, so on a returns series every other key prices the loss end. A drawdown series is non-positive and carries one end alone, so the head key names nothing on it and is refused. No drawdown Range measure ships, so the refusal is reached by a caller who runs a rule by hand.

Arguments

  • series: The series the estimate is taken over.
  • key: Name of the slot that is being resolved.

Validation

Returns

  • s::Int: 1 for the gain end and -1 for the loss end.

Related

source
PortfolioOptimisers.whitening_factorFunction
whitening_factor(pr::AbstractPriorResult) -> Any

Return the square factor of the covariance matrix that pr states, so that the factor's transpose whitens a centred observation.

The factor $\mathbf{U}$ satisfies $\mathbf{U}^{\top} \mathbf{U} = \hat{\mathbf{\Sigma}}$, which is the convention a prior result's chol carries and the one the variance constraint layer reads. chol takes precedence over sigma, which is the rule the chol field states, so a prior that carries a factor is whitened by that factor and never by a factorisation of sigma. chol is optional, so the common case is a sigma this verb factorises itself.

chol is checked against the length of mu alone, so it can be $K \times N$ rather than square. A rectangular factor has no inverse. A tall factor states a covariance matrix all the same, because $\mathbf{U}^{\top} \mathbf{U}$ is $N \times N$ at any $K$, and the $\mathbf{R}$ factor of its QR factorisation is the square factor of that same matrix. A wide factor states a singular covariance matrix, and no whitening exists for it.

RadialTailDecay is the caller. The verb is separate because the precedence rule and its two refusals are a statement about a prior result rather than about a tail index.

Algorithm

  1. Read pr.chol into F.
  2. When F is nothing, factorise pr.sigma with a Cholesky factorisation that reports rather than raises, and return its upper factor.
  3. Otherwise refuse a wide F, and take F itself when it is square and the R factor of its QR factorisation when it is tall.
  4. Refuse a zero on the diagonal of that factor, which is the rank of a triangular factor, and return the factor.

Arguments

  • pr: Prior result the covariance matrix is read off.

Validation

  • pr.sigma must be positive definite, when pr.chol is nothing.
  • pr.chol must have at least as many rows as columns.
  • The diagonal of the square factor must carry no zero.

Returns

  • U::AbstractMatrix: Square factor of the covariance matrix, upper triangular whenever the prior's own factor is.

Related

source
PortfolioOptimisers.radial_tail_indexFunction
radial_tail_index(
    X::AbstractArray{E<:Number, 2},
    mu::AbstractVector,
    U::AbstractMatrix,
    k::Integer
) -> Any

Estimate the tail index of the radial series of X, over the largest k order statistics of that series.

Every row is centred on mu and whitened by U, and the norm of the whitened row is one entry of the radial series. Hill's estimator then reads the k entries that lie furthest out. RadialTailDecay states the reading the series carries. This verb is the estimate alone.

The element type is bound by the signature, so the series and the sum it feeds are concrete. A rule reads pr.X off an AbstractPriorResult, whose field types no signature states, and this is the boundary that type crosses at.

Algorithm

  1. Solve transpose(U) \ transpose(X .- transpose(mu)), giving the whitened observations as the columns of Z.
  2. Walk the columns of Z, and write the negated Euclidean norm of each into the series d.
  3. Partially sort d so that its k + 1 smallest values sit in order at the front, and read the last of them into vkp1. partialsort! places one index alone, and it makes no promise about the rest of the vector, so the range is what puts the k entries the estimate reads before vkp1. The negation of step 2 puts the largest distances at the front.
  4. Return k over the sum of log(d[i] / vkp1) across those k entries. Both terms of each ratio are negative, so the ratio is one of magnitudes and the sum is Hill's with no further sign.

Arguments

  • X: Returns matrix, T × N.
  • mu: Expected returns vector, N × 1. The rows are centred on it.
  • U: Square factor of the covariance matrix, which whitening_factor returns.
  • k: Number of order statistics the estimate reads. The caller states it, and the caller keeps the floor under it.

Validation

  • The k + 1-th largest radial distance must be positive.

Returns

  • a::Number: The Hill estimate of the tail index of the radial series.

Related

source
PortfolioOptimisers.radial_series_inputsFunction
radial_series_inputs(series::AbstractCalibrationSeries, pr::AbstractPriorResult)

Return the sample, the centre and the whitening factor that the radial series of series is built from.

A ReturnsSeries reads the three off the prior result: pr.X, pr.mu and the factor whitening_factor takes off pr.chol or pr.sigma. That is the reading RadialTailDecay has always carried, and this verb leaves it untouched.

A drawdown series reads the three off the drawdown sample instead, because a prior result states no drawdown moment. calibration_series_matrix builds the sample, the centre is the column means of that sample, and the factor is the Cholesky factor of its covariance matrix. So the whole reading moves to the quantity the measure prices, and pr.mu and pr.sigma reach nothing: they are the moments of the returns, and no scaling of them states the moments of a drawdown.

Arguments

  • series: The series the slot owner prices.
  • pr: Prior result the sample and, on a returns series, the moments are read off.

Validation

  • The covariance matrix of the drawdown sample must be positive definite. A column with no movement, and two columns with one path between them, are what reach this.

Returns

  • Y::AbstractMatrix: The sample the marker names, T × N.
  • mu::AbstractVector: The centre of that sample, N × 1.
  • U::AbstractMatrix: Square factor of its covariance matrix.

Related

source
PortfolioOptimisers.dual_norm_radius_scaleFunction
dual_norm_radius_scale(key::Symbol, p::Option{<:Number}, e::AbstractVector)

Reduce the per-asset error vector e to one number, in the ground metric that key names.

This is the whole of the key's meaning, held apart from the functor. Six of the seven keys name a fixed norm order, so they are a literal table and not a chain of branches. The seventh, :lpreg_val, derives its order from the p the rule carries, because no key can name it. An eighth key is a refusal, because a caller who writes their own measure reaches it first and the message must name the keys the rule serves.

Arguments

  • key: Name of the slot that is being resolved.
  • p: Norm order the site states in its CalibrationContext, read on the :lpreg_val arm alone.
  • e: Per-asset scale of the sampling error of the mean vector.

Validation

  • key is one of the seven DualNormRadius serves, else an ArgumentError.
  • ctx.p is stated when key is :lpreg_val, else an ArgumentError. It is then finite and above one, so that the conjugate order q = p / (p - 1) exists.

Returns

  • s::Number: The norm of e in the ground metric of the slot.

Related

source