Calibration Rules

PortfolioOptimisers.AbstractSignificanceCalibrationAlgorithmType
abstract type AbstractSignificanceCalibrationAlgorithm <: AbstractCalibrationAlgorithm

Computes a significance level, the tail probability that an alpha or a beta slot holds.

All concrete subtypes should subtype AbstractSignificanceCalibrationAlgorithm, and should be callable, because resolve_calibration_slot runs a rule by calling it. A plain Function of the same five arguments is therefore a rule as well, and needs no type at all. Num_SigCal is the bound of every significance slot, and it names this family. So a deformation rule in a significance slot is refused at construction, and the same rule serves the lower tail and the upper tail, because the slot names the end.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractSignificanceCalibrationAlgorithm and implement the following method:

The functor

  • (alg::AbstractSignificanceCalibrationAlgorithm)(key::Symbol, pr::AbstractPriorResult, w, slv, ctx::CalibrationContext) -> Number: Returns the significance level.

Arguments

  • key: Name of the slot that is being resolved.
  • pr: Prior result the rule reads its sample size and moments off.
  • w: Effective observation weights, or nothing when neither the measure nor the prior names any.
  • slv: Effective solver, or nothing when the measure carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. A rule reads the fields it needs off it, and one that reads none writes ::CalibrationContext.

Returns

  • alpha::Number: The significance level.

Related

source
PortfolioOptimisers.AbstractDeformationCalibrationAlgorithmType
abstract type AbstractDeformationCalibrationAlgorithm <: AbstractCalibrationAlgorithm

Computes a deformation parameter, the Kaniadakis $\kappa$ that a kappa slot holds.

All concrete subtypes should subtype AbstractDeformationCalibrationAlgorithm, and should be callable, on the same terms as the significance family. Num_DefCal is the bound of every deformation slot, and it names this family.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractDeformationCalibrationAlgorithm and implement the following method:

The functor

  • (alg::AbstractDeformationCalibrationAlgorithm)(key::Symbol, pr::AbstractPriorResult, w, slv, ctx::CalibrationContext) -> Number: Returns the deformation parameter.

Arguments

  • key: Name of the slot that is being resolved.
  • pr: Prior result the rule reads its sample size and moments off.
  • w: Effective observation weights, or nothing when neither the measure nor the prior names any.
  • slv: Effective solver, or nothing when the measure carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. A rule reads the fields it needs off it, and one that reads none writes ::CalibrationContext.

Returns

  • kappa::Number: The deformation parameter.

The series

A deformation slot sits on a measure of the return distribution and on a drawdown measure alike, and both resolve the key :kappa, so key does not say which quantity the owner prices. A rule whose answer moves with that quantity reads ctx.series, which each slot owner states in the CalibrationContext it hands over. A rule whose answer does not move with it reads no field of the context, and EntropyBudget is that case.

Related

source
PortfolioOptimisers.AbstractAmbiguityRadiusCalibrationAlgorithmType
abstract type AbstractAmbiguityRadiusCalibrationAlgorithm <: AbstractCalibrationAlgorithm

Computes an ambiguity radius from the data a prior result carries, so that the radius refits whenever the sample moves.

An Ambiguity Radius is the radius of the ball of probability measures the model prices, and it is the coefficient of an un-squared norm penalty on the weight vector. It is neither a tail probability nor a deformation parameter, so it takes its own family beside the two of AbstractSignificanceCalibrationAlgorithm and AbstractDeformationCalibrationAlgorithm, under the same root.

All concrete subtypes should subtype AbstractAmbiguityRadiusCalibrationAlgorithm, and should be callable, on the same terms as the two other families. Num_AmbRadCal is the bound of every radius slot, and it names this family.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractAmbiguityRadiusCalibrationAlgorithm and implement the following method:

The functor

  • (alg::AbstractAmbiguityRadiusCalibrationAlgorithm)(key::Symbol, pr::AbstractPriorResult, w, slv, ctx::CalibrationContext) -> Number: Returns the ambiguity radius.

Arguments

  • key: Name of the slot that is being resolved.
  • pr: Prior result the rule reads its sample size and moments off.
  • w: Effective observation weights, or nothing when neither the owner nor the prior names any.
  • slv: Effective solver, or nothing when the owner carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. A rule reads the fields it needs off it, and one that reads none writes ::CalibrationContext.

Returns

  • r::Number: The ambiguity radius.

Related

source
PortfolioOptimisers.AbstractAmbiguityTailWeightCalibrationAlgorithmType
abstract type AbstractAmbiguityTailWeightCalibrationAlgorithm <: AbstractCalibrationAlgorithm

Computes an Esfahani-Kuhn tail weight, the weight that the l slot of a distributionally robust measure holds.

The tail weight scales the tail term of the Esfahani-Kuhn loss, and the mean term is not scaled by it. It is a different quantity from the radius, so it takes a family of its own: a radius rule placed in a tail-weight slot is refused at construction, by Num_AmbTwtCal.

TailTermParity is the rule the family ships. A stated tail weight is dimensionless and is not scale-free in the sample, so one number is a different trade-off at every sampling frequency; the rule reads the sample's own units and leaves the preference in the caller's ratio. Num_AmbTwtCal admits a plain function beside it, so a caller's own rule needs no type.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractAmbiguityTailWeightCalibrationAlgorithm and implement the following method:

The functor

  • (alg::AbstractAmbiguityTailWeightCalibrationAlgorithm)(key::Symbol, pr::AbstractPriorResult, w, slv, ctx::CalibrationContext) -> Number: Returns the tail weight.

Arguments

  • key: Name of the slot that is being resolved.
  • pr: Prior result the rule reads its sample size and moments off.
  • w: Effective observation weights, or nothing when neither the owner nor the prior names any.
  • slv: Effective solver, or nothing when the owner carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. A rule reads the fields it needs off it, and one that reads none writes ::CalibrationContext.

Returns

  • l::Number: The tail weight.

Related

source
PortfolioOptimisers.AbstractNormCeilingCalibrationAlgorithmType
abstract type AbstractNormCeilingCalibrationAlgorithm <: AbstractCalibrationAlgorithm

Computes a norm ceiling from the data a prior result carries, so that the ceiling refits whenever the universe moves.

A Norm Ceiling is an upper bound on a norm of the weight vector, the quantity the l2c, lpc and linfc slots of JuMPOptimiser hold. It is not an Ambiguity Radius. A radius is the coefficient of a norm penalty in the objective, and a ceiling bounds that norm in a constraint. The reciprocal of a ceiling is a floor on the effective number of assets, which is a diversification statement rather than a statement about the set of measures the model prices. So the quantity takes its own family beside the others, under the same root.

All concrete subtypes should subtype AbstractNormCeilingCalibrationAlgorithm, and should be callable, on the same terms as the other families. Num_NormCeilCal is the bound of every ceiling slot, and it names this family.

Interfaces

In order to implement a new concrete type that works seamlessly with the library, subtype AbstractNormCeilingCalibrationAlgorithm and implement the following method:

The functor

  • (alg::AbstractNormCeilingCalibrationAlgorithm)(key::Symbol, pr::AbstractPriorResult, w, slv, ctx::CalibrationContext) -> Number: Returns the norm ceiling.

Arguments

  • key: Name of the slot that is being resolved: :l2c, :lpc or :linfc.
  • pr: Prior result the rule reads its asset count and sample size off.
  • w: Effective observation weights, or nothing when neither the owner nor the prior names any.
  • slv: Effective solver, or nothing when the owner carries none.
  • ctx: What the site knows and key does not, as a CalibrationContext. A rule reads the fields it needs off it, and one that reads none writes ::CalibrationContext.

Returns

  • val::Number: The norm ceiling.

The norm order

A ceiling is read against one norm order, and that order belongs to the constraint rather than to the rule. A rule that needs the order reads ctx.p, which each constraint site states in the CalibrationContext it hands over. A rule that needs no order reads no field of the context.

Related

source
PortfolioOptimisers.AbsoluteDrawdownSeriesType
struct AbsoluteDrawdownSeries <: AbstractDrawdownSeries

Names the absolute drawdown series of a column, which absolute_drawdown_vec builds.

The path is the cumulative sum of the column, and the series is the distance of that path below its own running peak. RelativisticDrawdownatRisk prices it, and DrawdownatRisk is the measure it takes its name from.

Related

source
PortfolioOptimisers.RelativeDrawdownSeriesType
struct RelativeDrawdownSeries <: AbstractDrawdownSeries

Names the relative drawdown series of a column, which relative_drawdown_vec builds.

The path is the compounded value of the column, and the series is the fractional distance of that path below its own running peak. RelativeRelativisticDrawdownatRisk prices it, and RelativeDrawdownatRisk is the measure it takes its name from.

Related

source
PortfolioOptimisers.CalibrationContextType
struct CalibrationContext{__T_alpha, __T_series, __T_p} <: AbstractResult

Carries what a calibration site knows and the slot's key does not, from resolve_calibration_slot into the rule it runs.

A rule is run by calling it, as alg(key, pr, w, slv, ctx). key names the slot and not the quantity: :kappa serves both RelativisticValueatRisk and RelativisticDrawdownatRisk, and those two price different series. Three quantities a rule may read are therefore properties of the site rather than of the key, and this type is how the site states them.

FieldThe site that states itThe rules that read it
alphathe slot owner, off a sibling slot it resolved firstEntropyBudget, HillTailDecay, RadialTailDecay, TailTermParity
seriesthe slot owner, through calibration_seriesHillTailDecay, RadialTailDecay, ConcentrationRadius, DimensionalRateRadius, DualNormRadius, TailTermParity
pthe constraint or the penalty the quantity stands inEffectiveAssetFloor, DualNormRadius

No rule holds a field for any of the three. Each belongs to the site, and a rule cannot know which site it reached, so there is no value on the rule for a site to overwrite and no precedence between the two to state. A caller who runs a rule outside a measure builds the context the site would have built, and that context is the only place the three are ever written.

The default is the context a site with nothing to say hands over: no sibling significance level, no norm order, and the ReturnsSeries that calibration_series answers for every owner that names no other. A rule that needs a field the default leaves at nothing refuses, and its message names the field it wanted.

The order between two slots of one owner is what makes alpha reachable. A deformation rule reads the significance level of a sibling slot, so the owner's own resolve_deferred_quantities method resolves alpha first and puts the number in the context of the slot that reads it. No derivation can find that order, which is the reason the resolution is written per type.

Fields

  • alpha: Significance level of a sibling slot the owner resolved first, or nothing when the site names none. The two slots travel together, so the per-type resolution resolves alpha first and puts the number in the context of the slot that reads it.
  • p: Norm order the quantity is read against, or nothing when the site names none. The order belongs to the constraint or to the penalty rather than to the rule, so each site that carries one states it here.

Constructors

CalibrationContext(;    alpha::Option{<:Number} = nothing,    series::AbstractCalibrationSeries = ReturnsSeries(),    p::Option{<:Number} = nothing) -> CalibrationContext

Keywords correspond to the struct's fields. Every field defaults to the state a site that names nothing hands over.

Related

source
PortfolioOptimisers.ScenarioCountType
struct ScenarioCount{__T_n} <: AbstractSignificanceCalibrationAlgorithm

Computes a significance level from a count of observations, so that the tail keeps the same number of scenarios whatever the sample length becomes.

A stated alpha fixes the tail's probability. A fold half as long then leaves half as many observations in the tail, and the measure it feeds grows noisier as the sample shrinks. This rule fixes the count instead and returns n / T, so ceil(alpha * T) == n at every sample length the resolution meets.

T is the effective sample size when observation weights are stated, and the raw row count when they are not. Kish's effective sample size is the number of equally weighted observations that carries the same information as the weighted sample, so a weighted tail holds n observations in that sense rather than n rows. This is the only one of the three rules that reads the weights.

The rule carries no range check of its own. It returns the quantity of the slot it stands in, so the slot owner's constructor is the whole validation, and a count that produces a value outside the slot's range is refused there, at fold time.

Fields

  • n: Number of observations the tail is to hold. It is a count, not a probability, and it is the whole content of the rule.

Constructors

ScenarioCount(;    n::Option{<:Number} = nothing) -> ScenarioCount

Keywords correspond to the struct's fields. n has no default a rule could use, because a scenario count that suits every sample does not exist. It stands at nothing, which is not a count, so a bare ScenarioCount() is refused with a message that names the count and says why the rule asks for one. This is the whole reason the keyword admits nothing: the field itself never holds it.

Validation

  • n is stated, else an ArgumentError.
  • n > 0 and finite.

Related

source
PortfolioOptimisers.RateSignificanceType
struct RateSignificance{__T_c} <: AbstractSignificanceCalibrationAlgorithm

Computes a significance level that shrinks with the square root of the sample length.

The tail probability is c / sqrt(T), so the tail's expected count is c * sqrt(T). It grows with the sample, but more slowly than the sample does, which is the rate at which a sample mean's own error falls. A longer sample therefore buys a further tail rather than only a fuller one, and ScenarioCount is the rule that buys neither.

The rule reads the raw row count, and not the effective sample size that ScenarioCount reads. The rate is a statement about the length of the record, whereas a scenario count is a statement about the observations the tail holds. The length of the record is the count the result states in ens when a Scenario Cap carries fewer rows than it fitted over, and the row count otherwise, which is effective_sample_size with no weights.

The rule carries no range check of its own, on the same terms as ScenarioCount.

Fields

  • c: Rate coefficient. The significance level is this coefficient divided by the square root of the number of observations.

Constructors

RateSignificance(;    c::Number = 1) -> RateSignificance

Keywords correspond to the struct's fields. c defaults to 1, which is the plain $1/\sqrt{T}$ rate.

Validation

  • c > 0 and finite.

Related

source
PortfolioOptimisers.EntropyBudgetType
struct EntropyBudget{__T_target} <: AbstractDeformationCalibrationAlgorithm

Computes the Kaniadakis deformation parameter that makes a relativistic measure spend a stated entropy budget.

RRM multiplies its dual variable by kappa_log(inv(alpha * T), kappa), so that coefficient is the price the model pays for the deformation, and target states it directly. The rule returns the $\kappa$ that meets it. A stated kappa fixes the shape of the deformation and lets the price move with the sample; this rule fixes the price and lets the shape move.

The two quantities travel together. The rule reads its sibling alpha off ctx.alpha, so the owner's own resolution method resolves alpha first and puts the number in the context of the slot that reads it. A caller who runs the rule outside a measure states alpha in the context.

The inversion is monotone, and it is solved by bisection over $(0, 1)$. Writing $l = \ln(u)$, the coefficient is $l \sinh(\kappa l) / (\kappa l)$, and $\sinh(x)/x$ rises with $|x|$, so the coefficient walks once from $l$ at $\kappa \to 0$ to $\sinh(l)$ at $\kappa = 1$. There is no elementary inverse of $\sinh(x)/x$, so the solve is a fixed sweep of 64 halvings rather than a formula.

The band the coefficient reaches moves with the sample, and a target outside it is refused at fold time. This is the one check any of the three rules carries, and it is not a range check on the quantity the rule returns — the slot owner's constructor keeps that job, as it does for the two significance rules. It states that the equation has a root at all: a target the band does not reach leaves the sweep at an end of the interval, where $\kappa$ is far too small or too large to be the answer to the question the caller asked and yet still inside the range the slot owner admits.

Fields

  • target: Target value of the Kaniadakis logarithm, the coefficient RRM multiplies its dual variable by. The rule returns the deformation parameter that meets it.

Constructors

EntropyBudget(;    target::Option{<:Number} = nothing) -> EntropyBudget

Keywords correspond to the struct's fields. target has no default a rule could use, because the budget is the whole content of the rule and the band it must lie in moves with the sample. It stands at nothing, which is not a budget, so a bare EntropyBudget() is refused with a message that names the budget and says why the rule asks for one. This is the whole reason the keyword admits nothing: the field itself never holds it.

Validation

  • target is stated, else an ArgumentError.
  • target is finite. The band the target must lie in moves with the sample, so the rule checks the band when it runs, not here.

Related

source
PortfolioOptimisers.HillTailDecayType
struct HillTailDecay{__T_kmin} <: AbstractDeformationCalibrationAlgorithm

Computes the Kaniadakis deformation parameter whose tail decays at the rate the sample's own tail decays at.

The inverse of kappa_log is the $\kappa$-exponential $\exp_{\kappa}(x) = \left(\kappa x + \sqrt{1 + \kappa^{2} x^{2}}\right)^{1/\kappa}$, which goes to $(2 \kappa x)^{1/\kappa}$ for large $x$. A $\kappa$-deformed exponential tail is therefore a power law of index $1/\kappa$, and $\kappa$ is a reciprocal tail index. So the rule estimates the sample's tail index $\hat{a}$ and returns $1/\hat{a}$, which makes the deformation decay at the rate the sample decays at. The band the slot admits, $(0,\, 1)$, reads as $\hat{a} > 1$, and that is the condition for a finite mean. The refusal is built into the reading, on the same terms as the band of EntropyBudget.

The estimator is Hill's, over the worst k order statistics of a pool. Every column of pr.X is centred and divided by its own sample dispersion, the standardised values are signed to the end the slot prices, and the T * N of them are pooled. The rule reads pr.X alone, and never pr.sigma. A column's dispersion comes from that column, which is the line that separates this rule from RadialTailDecay. The two answer two questions: this rule asks how far one end of one column moves, and its sibling asks how far the whole cross-section moves. So this rule keeps the sign of the end and answers per end, and its sibling reads a distance and answers one number for both.

The pool carries two assumptions, and both are stated rather than hidden. The columns share one tail index after standardisation, which is what lets one estimate stand for the whole sample. The columns are also cross-correlated, so the pool holds far fewer than T * N independent points and the estimator's spread is wider than the nominal one of k points. The median of N per-asset estimates drops both assumptions, and pays for it in noise: at alpha = 0.05 and T = 250 a column leaves 12 tail points, and a Hill estimate on 12 points moves from fold to fold for no reason in the data.

alpha fixes the depth of the reading as well as the count. A Hill estimate reads the tail at the depth alpha names, and a sample whose tail approaches its power law slowly is read with a bias that falls as the depth rises: a Student-t sample read at a five per cent depth returns a $\kappa$ above the reciprocal of its degrees of freedom, and the same sample read at one per cent returns most of that distance. The spread of the estimate rises as the count falls, and kmin is the floor under that trade.

key says which end the slot prices, and the answer is not the same for every key. :kappa and :kappa_a read the loss tail, and :kappa_b reads the gain tail. This is the opposite of EntropyBudget, whose budget is a price the model pays and is therefore one number for both ends. A tail index is a statement about a tail, and a skewed sample has two different ones, which is the whole point of the rule on a Range measure.

ctx.series says which quantity the pool holds, and the slot owner states it. A measure of the return distribution pools the columns of pr.X. A drawdown measure pools the per-column drawdown series of pr.X instead, because that is the quantity it prices, and the resolution site names the marker in ctx.series. Nothing else in the reading moves: the same standardisation, the same count and the same estimator run over the drawdown sample. The estimate the pool then carries is the index of the drawdown series rather than of the returns, and the two are different numbers. AbstractDrawdownSeries states which way they part, and it is the record that decides.

The per-column reading stands for the portfolio's own drawdown, under the assumption the pool already states. The rule forms no portfolio, so it reads the drawdown series of each column and pools them, exactly as it pools the columns themselves on a returns series. The columns share one tail index after standardisation, and one map carries a column to its drawdown series, so the reading the pool gives is the reading the portfolio's own drawdown series would give. A portfolio drawdown is shallower than the average asset drawdown, and that is a statement about the scale, which a tail index does not read.

The count is the same count. A drawdown series holds one entry per observation, so the pool holds T N entries under every marker and k = ceil(alpha * T * N) is unchanged. No observation is dropped by the change of series.

alpha reaches the right series as well. A drawdown measure resolves the significance level of its own drawdown series, and the depth the rule reads is that same level under a drawdown marker.

The pool of a drawdown series carries more dependence than the pool of a returns series. A drawdown series is a running functional, so consecutive entries move together and one deep episode fills many of the k order statistics the estimate reads. The T N entries therefore hold far fewer independent tail points than the same count of returns, and the spread of the estimate is wider than the count states. kmin is stated in entries and not in independent points, so the same floor buys less here. Raise it for a drawdown owner.

The rule carries no range check on the parameter it returns. The slot owner's constructor keeps that job, as it does for every calibration rule. Its checks are statements that the estimate exists at all.

Fields

  • kmin: Floor under the count of order statistics the Hill estimate reads. The count is ceil(alpha * T * N) over the pool, and a count below this floor is refused rather than estimated: a Hill estimate over too few order statistics moves from fold to fold for no reason in the data, and the deformation parameter moves with it.

Constructors

HillTailDecay(;    kmin::Integer = 30) -> HillTailDecay

Keywords correspond to the struct's fields. kmin defaults to 30, which is the floor under the count of order statistics the estimate reads. The significance level and the series the estimate is taken over are not fields of the rule: both reach it in the CalibrationContext the site hands over.

Validation

  • kmin > 0.

Related

source
PortfolioOptimisers.RadialTailDecayType
struct RadialTailDecay{__T_kmin} <: AbstractDeformationCalibrationAlgorithm

Computes the Kaniadakis deformation parameter whose tail decays at the rate the sample's radial series decays at.

The inverse of kappa_log is the $\kappa$-exponential $\exp_{\kappa}(x) = \left(\kappa x + \sqrt{1 + \kappa^{2} x^{2}}\right)^{1/\kappa}$, which goes to $(2 \kappa x)^{1/\kappa}$ for large $x$. A $\kappa$-deformed exponential tail is therefore a power law of index $1/\kappa$, and $\kappa$ is a reciprocal tail index. So the rule estimates a tail index $\hat{a}$ and returns $1/\hat{a}$, which makes the deformation decay at the rate the sample decays at. The band the slot admits, $(0,\, 1)$, reads as $\hat{a} > 1$, and that is the condition for a finite mean. The refusal is built into the reading, on the same terms as the band of EntropyBudget.

The series the rule reads is radial. A covariance matrix is a scale and $\kappa$ is a shape, so a rule that turns the one into the other needs a second quantity to fix the units, and the sample is the only one a prior result carries. The covariance matrix is what turns the $T \times N$ sample into one univariate series: whitening_factor gives the factor of $\hat{\mathbf{\Sigma}}$, each row is centred and whitened, and the Euclidean norm of the whitened row is that observation's Mahalanobis distance. Under an elliptical scale mixture that series carries the mixture's tail index, and the whole cross-section stands behind every entry of it. A per-column standardisation drops every off-diagonal term, so HillTailDecay cannot buy that reading and this rule cannot buy the per-column one.

The rule is direction-blind, and that is a statement. A distance has no sign, so one $\kappa$ answers both ends of a Range measure and key is ignored. HillTailDecay answers per end, and the difference is the whole of what the two rules say: a radial reading asks how far the cross-section moves, and a per-column reading asks how far one end of one column moves. A head slot that defaults to its tail slot therefore holds the same rule and reads the same number, which is what makes one rule correct for both ends here.

The series holds T entries where the pool of HillTailDecay holds T N, so this rule reads fewer tail points from the same sample: alpha = 0.05 at T = 250 leaves 12 of them. kmin is the floor under that count, and it is stated in the same units as its sibling's, so the floor binds harder here. A count below it is refused rather than estimated. The points the series does hold are one per observation rather than N per observation, so they carry none of the cross-correlation the pool carries.

series says which sample the rows are read off, and the slot owner states it. A measure of the return distribution whitens the rows of pr.X. A drawdown measure whitens the rows of the drawdown sample: calibration_series_matrix turns each column into the drawdown series it carries, and the rows of that sample are whitened and normed on the same terms. So the radial reading itself is unchanged, and only the sample it reads moves. The distance is then the depth of the whole cross-section in drawdown at one date, where on a returns series it is the distance the whole cross-section moved.

A drawdown sample carries its own moments, and the prior states none of them. pr.mu and pr.sigma are the moments of the returns, and no scaling of them states the moments of a drawdown. So under a drawdown marker the centre is the column means of the drawdown sample and the factor is the Cholesky factor of its covariance matrix, both taken off that sample. radial_series_inputs is where the two readings part, and the precedence of pr.chol over pr.sigma therefore governs the returns reading alone.

A third reading of $\kappa$ exists and this rule does not take it. The excess kurtosis $g$ of a Student-t gives $\nu = 4 + 6/g$, and $\kappa = 1/\nu$ follows. It reads the whole sample rather than the tail, so it is steady where a Hill estimate is noisy, and it pays for that with an assumption about the shape of the whole distribution rather than of its tail.

The rule carries no range check on the parameter it returns. The slot owner's constructor keeps that job, as it does for every calibration rule. Its checks are statements that the estimate exists at all. A near-singular covariance matrix is not among them: the whitening then reads the sample's smallest eigen-direction, the radial series follows it, and that is the covariance matrix speaking rather than a defect.

Fields

  • kmin: Floor under the count of order statistics the Hill estimate reads. The count is ceil(alpha * T) over the radial series, and a count below this floor is refused rather than estimated: a Hill estimate over too few order statistics moves from fold to fold for no reason in the data, and the deformation parameter moves with it. The radial series holds one entry per observation, so the same floor binds harder here than it does over a pool.

Constructors

RadialTailDecay(;    kmin::Integer = 30) -> RadialTailDecay

Keywords correspond to the struct's fields. kmin defaults to 30, which is the floor under the count of order statistics the estimate reads. The significance level and the series the estimate is taken over are not fields of the rule: both reach it in the CalibrationContext the site hands over.

Validation

  • kmin > 0.

Related

source
PortfolioOptimisers.ConcentrationRadiusType
struct ConcentrationRadius{__T_confidence, __T_scale} <: AbstractAmbiguityRadiusCalibrationAlgorithm

Computes an ambiguity radius from the concentration of measure, so that the ball shrinks as the sample grows.

The radius is the Blanchet-Kang-Murthy form, a scale in the units of the series the slot owner prices times the square root of a chi-squared quantile over the sample size. The chi-squared factor is dimensionless and grows with the number of assets, so a wider universe buys a wider ball at a fixed confidence level, and a longer sample shrinks it.

scale states the units. A radius multiplies a norm of the weight vector, so it is in the units of the loss the ball is drawn around, and no caller can intuit that number from the confidence level alone. scale = nothing reads the average per-asset dispersion of the series the slot owner prices instead, which is the first pass the form's source recommends, and it moves with the sample the way the rest of the rule does.

A drawdown owner is read on a drawdown scale. DistributionallyRobustConditionalDrawdownatRisk measures the transport cost of its own programme against the per-asset drawdown sample, so the ball it prices is a ball over drawdown scenarios and the radius carries drawdown units. series names that quantity, calibration_series_dispersion takes the dispersion off the drawdown sample, and pr.sigma reaches nothing there: it is a moment of the returns.

T is the effective sample size when observation weights are stated, and the raw row count when they are not, on the same terms as ScenarioCount. The radius prices estimation error, and the error of a weighted estimate falls with Kish's effective sample size rather than with the number of rows. RateRadius reads the raw row count instead, because a rate speaks of the length of the record.

The rule carries no range check of its own. It returns the quantity of the slot it stands in, so the range is checked where the number lands. resolve_calibration_slot refuses a radius that is not > 0 and finite, and the owner of a slot that rebuilds a term refuses one outside that owner's range at fold time.

Fields

  • confidence: Confidence level of the chi-squared quantile the radius is read off. A higher level buys a larger ball, so the model prices a wider set of measures.
  • scale: Scale of the radius, in the units of the series the slot owner prices, or nothing to read the average per-asset dispersion of that series off the sample. The chi-squared factor is dimensionless, so this field carries the whole of the radius' units.

Constructors

ConcentrationRadius(;    confidence::Number = 0.95,    scale::Option{<:Number} = nothing) -> ConcentrationRadius

Keywords correspond to the struct's fields. confidence defaults to 0.95, and scale defaults to nothing, which reads the average per-asset dispersion off the sample. The series that dispersion is read over is not a field of the rule: it reaches the rule in the CalibrationContext the site hands over.

Validation

  • 0 < confidence < 1.
  • If scale is not nothing: scale > 0 and finite.

Related

source
PortfolioOptimisers.RateRadiusType
struct RateRadius{__T_c} <: AbstractAmbiguityRadiusCalibrationAlgorithm

Computes an ambiguity radius that shrinks with the square root of the sample length.

The radius is c / sqrt(T). The rate is the part of the form to trust, and the coefficient is the part to calibrate: a cross-validation over c is the honest route to a radius, and this is the shape a grid moves over.

The rule reads the raw row count, and not the effective sample size that ConcentrationRadius reads. The rate is a statement about the length of the record, on the same terms as RateSignificance, and the length of the record is read the same way: the count a Scenario Cap states in ens, else the row count, which is effective_sample_size with no weights.

c carries the units of the returns, because the rate itself is dimensionless.

The rule carries no range check of its own, on the same terms as ConcentrationRadius.

Fields

  • c: Rate coefficient. The radius is this coefficient divided by the square root of the number of observations.

Constructors

RateRadius(;    c::Number = 1) -> RateRadius

Keywords correspond to the struct's fields. c defaults to 1, which is the plain $1/\sqrt{T}$ rate.

Validation

  • c > 0 and finite.

Related

source
PortfolioOptimisers.DimensionalRateRadiusType
struct DimensionalRateRadius{__T_confidence, __T_scale} <: AbstractAmbiguityRadiusCalibrationAlgorithm

Computes an ambiguity radius that shrinks at the dimensional rate a Wasserstein ball earns, not at the square-root rate.

The radius is the Esfahani-Kuhn [93] form, a scale in the units of the series the slot owner prices times the sample factor log(1 / (1 - confidence)) / T raised to the power 1 / max(N, 2). A ball of probability measures must hold the true measure, and not merely the true mean, and the measure-concentration result the form is read off charges that exponent for it. At N = 2 the rule returns the square-root rate that ConcentrationRadius and RateRadius both carry, so the family's existing behaviour is the two-dimensional corner of this one.

The ball hardly shrinks over a wide universe, and that is the model speaking rather than a defect. At N = 20 and T = 250 the factor T^(-1/20) is 0.76, so a record ten times longer buys almost nothing. This is the curse of dimensionality of the Wasserstein ball. A caller who reads a square-root rate and expects an estimation error reads a statement about a mean, and this rule makes one about a measure.

The radius is conservative in practice, and its source recommends a cross-validation over the radius in its place. A portfolio priced against a ball this wide can hold nothing but cash. RateRadius is the shape such a grid moves over, so calibrate there and read this rule as the statement of what the rate is.

The source result carries a second branch for a short record, whose exponent is 1 / a for a tail-decay exponent a rather than 1 / max(N, 2). That branch binds only below a threshold that depends on constants the source leaves to the caller, so this rule drops it and ships the one branch. A reader who holds the paper beside this form finds the difference stated here, rather than by deriving it.

The branch stays dropped, and HillTailDecay does not supply it. That rule estimates the tail index of a power law, which is the heavy-tailed regime. The a of the second branch is the exponent of the light-tail moment condition the concentration result assumes, and the same condition carries the two constants that place the threshold. So the two exponents share a letter and nothing else, and a rule that read one for the other would state a rate the result does not give. The threshold is also unreachable in practice: it puts the short branch below a record of a few dozen rows, which no fold of a portfolio problem uses.

N is the asset count, size(pr.X, 2), because the ball is over the assets. A factor prior carries a smaller effective dimension, and the ball is still over the assets, so a factor prior does not move N.

scale states the units. A radius multiplies a norm of the weight vector, so it is in the units of the loss the ball is drawn around, and no caller can intuit that number from the confidence level alone. scale = nothing reads the average per-asset dispersion of the series the slot owner prices instead, on the same terms as ConcentrationRadius, and a drawdown owner is read on a drawdown scale there for the reason that rule states.

DistributionallyRobustConditionalDrawdownatRisk prices a ball around the drawdown scenarios. The scenario dimension there is still N, so the rate carries, and the scale moves with the series: the site states the owner's marker in ctx.series, and calibration_series_dispersion then reads the per-asset dispersion off the drawdown sample rather than off pr.sigma. A drawdown column is a running functional of its returns, so its dispersion is the wider of the two, and a scale of nothing therefore gives a wider ball on that owner than on a returns owner of the same sample. A stated scale still wins, and it is the way to price a ball whose units are neither.

T is the effective sample size when observation weights are stated, and the raw row count when they are not, on the same terms as ConcentrationRadius and ScenarioCount. The rate is a concentration statement, so the record it prices is the one Kish's count measures. RateRadius reads the raw row count instead, because its rate speaks of the length of the record.

The rule carries no range check of its own, on the same terms as ConcentrationRadius. It returns the quantity of the slot it stands in, so the range is checked where the number lands, by resolve_calibration_slot and by the owner of a slot that rebuilds a term.

Fields

  • confidence: Confidence level the measure-concentration bound is read at. It enters the radius as log(1 / (1 - confidence)), so a higher level buys a larger ball, and the exponent of the sample size flattens the buying.
  • scale: Scale of the radius, in the units of the series the slot owner prices, or nothing to read the average per-asset dispersion of that series off the sample. The rate factor is dimensionless, so this field carries the whole of the radius' units.

Constructors

DimensionalRateRadius(;    confidence::Number = 0.95,    scale::Option{<:Number} = nothing) -> DimensionalRateRadius

Keywords correspond to the struct's fields. confidence defaults to 0.95, and scale defaults to nothing, which reads the average per-asset dispersion off the sample. The series that dispersion is read over is not a field of the rule: it reaches the rule in the CalibrationContext the site hands over.

Validation

  • 0 < confidence < 1.
  • If scale is not nothing: scale > 0 and finite.

Related

References

  • [93] P. Mohajerin Esfahani and D. Kuhn. Data-driven distributionally robust optimization using the Wasserstein metric: performance guarantees and tractable reformulations. Mathematical Programming 171, 115–166 (2018).
source
PortfolioOptimisers.DualNormRadiusType
struct DualNormRadius{__T_confidence} <: AbstractAmbiguityRadiusCalibrationAlgorithm

Computes an ambiguity radius in the ground metric that the slot it stands in names, so that two slots of two different norms get two different numbers.

The eight radius slots of the library do not measure distance in one norm. A radius multiplies a norm of the weight vector, and the ground metric of the ball is the dual of that norm, so the l1 coefficient of JuMPOptimiser is a distance in the ∞-norm while its linf coefficient is a distance in the 1-norm. This rule reads key, picks the ground metric of that slot, and returns the sampling error of the empirical measure in it. ConcentrationRadius and RateRadius return one number for every key, which is right inside one measure and wrong across the eight slots.

The sampling error of the mean vector is the part a linear loss sees, and its per-asset scale is the dispersion of the series the slot owner prices over the square root of the effective sample size. The radius is a norm of that error vector, at the confidence level confidence states.

No term of the covariance error belongs in that vector. A radius multiplies a norm of the weight vector, so it prices the part of the loss that is linear in the weights, and the loss of a distributionally robust measure is linear in the sample point. The error of the second moment reaches the objective through the tail term rather than through the ball, and the tail term carries its own coefficient, which TailTermParity sets. A radius that carried both would price one error twice.

confidence is a per-coordinate level, and it is not corrected for the number of assets. The ∞-norm case is a maximum over N coordinates, so a per-coordinate level understates it, and a caller who wants a level over the whole vector states the corrected number themselves, as 1 - (1 - c) / N.

The correction stays with the caller, and it is not a rounding. A Bonferroni step from 0.95 to 1 - 0.05 / N moves the quantile by about a third at four assets and by more at twenty. It is also one choice of several: a level over the vector read off N correlated coordinates and one read off N independent ones disagree by an amount only the correlation states. The source result states a per-coordinate level and no correction, so the rule ships what the result states and names the corrected number in prose. A rule that corrected silently would give a caller a level they did not ask for and cannot see.

The 1-norm case sums the per-asset errors, which prices them as if they moved together. That is the worst case over the correlations, and it is therefore the conservative reading for a radius. A correlation-aware form would give a smaller ball, and this rule does not compute one.

The worst case is the reading a radius wants, so the correlation-aware form stays out. A radius that understates the ball gives a model that is robust to less than the caller asked for, and the failure is silent: the optimisation solves and the weights are not robust. A radius that overstates it is visible, because the portfolio moves towards cash. The two errors are not symmetric, so the rule takes the one a caller can see.

ctx.p serves the :lpreg_val slot alone. The ground metric of LpRegularisation is the type-$q$ metric with $1/p + 1/q = 1$, and key names the slot rather than the norm order. The order belongs to the penalty, so that site states it in the CalibrationContext before it resolves the slot. The rule holds no order of its own, so a caller who runs it outside that site states the order in the context. Every other key ignores it.

The drawdown owner is served on a drawdown scale, and series is what says so. DistributionallyRobustConditionalDrawdownatRisk measures the transport cost of its own programme against the per-asset drawdown sample, so the ball it prices is a ball over drawdown scenarios and the ground metric is a distance between two such vectors. calibration_series_dispersion reads the error scale off that sample under a drawdown marker, and pr.sigma reaches nothing there: it is a moment of the returns, and no scaling of it states a moment of a drawdown. The ground metric does not move with the series, only the vector it is taken of.

The error scale of a drawdown series is a floor, and the record is the reason. A drawdown is a running functional, so its entries are strongly dependent down a column, and $\hat{s}_{i} / \sqrt{T_{e}}$ prices a record of $T_{e}$ independent draws that a drawdown sample does not hold. The rule states no correction for it, on the same terms it states none for the number of assets: a correction needs a model of the dependence, and the sample states none. So the reading is the honest scale of the quantity, taken as if the record were independent.

T_e is Kish's effective sample size when observation weights are stated, and the raw row count when they are not, on the same terms as ConcentrationRadius.

The rule carries no range check of its own, on the same terms as ConcentrationRadius.

A sample of no dispersion gives a radius of zero, and the slot admits it. Every diagonal of the dispersion is non-negative, so the radius is non-negative and finite for every admissible input. A sample whose columns never move gives an error vector of zeros, and a ball of no width is the right answer for it: the empirical measure is the only measure such a sample supports, and the robust measure reduces to the plain one it is drawn around. In floating point that sample gives a number many orders below the smallest one a caller would state rather than an exact zero, so the slot owner's > 0 check passes and the model prices a ball of no width. Both outcomes read the same way, and neither is a defect.

Fields

  • confidence: confidence: Per-coordinate confidence level of the normal quantile the error scale is multiplied by. It is not corrected for the number of assets, so the ∞-norm case reads a level over one coordinate and not over the vector.

Constructors

DualNormRadius(;    confidence::Number = 0.95) -> DualNormRadius

Keywords correspond to the struct's fields. confidence defaults to 0.95. The norm order and the series are not fields of the rule: both reach it in the CalibrationContext the site hands over.

Validation

  • 0 < confidence < 1.
  • The norm order is not a field, so the constructor cannot check it. dual_norm_radius_scale checks ctx.p when the :lpreg_val slot is resolved: it is stated, finite and above one, on the same terms as LpRegularisation.

Related

source
PortfolioOptimisers.TailTermParityType
struct TailTermParity{__T_ratio} <: AbstractAmbiguityTailWeightCalibrationAlgorithm

Computes the Esfahani-Kuhn tail weight that prices the tail term of the loss at a stated multiple of its mean term.

At $r \to 0$ the loss of DistributionallyRobustConditionalValueatRisk reduces to $-\mathbb{E}[\boldsymbol{w}^{\intercal} \boldsymbol{\xi}] + l \, \mathrm{CVaR}_{\alpha}(\boldsymbol{w}^{\intercal} \boldsymbol{\xi})$, so l is the exchange rate between one unit of expected return and one unit of tail loss. Both terms carry the units of the returns, so l is dimensionless. A dimensionless number is not a scale-free one, and that is the trap this rule exists for. The mean of a daily return column sits two orders of magnitude below its five per cent CVaR, and on a monthly column the gap is far smaller, so a stated l = 1.0 prices the tail term about forty times the mean term on a daily sample and under ten times on a monthly one. One stated number is a different trade-off at every sampling frequency, and at every re-fit that changes the record.

The rule carries the sample's own units and nothing else, so the preference stays the caller's. ratio states how many mean terms one tail term is worth, and the rule returns the l that prices it so on the sample the prior result carries. ratio = 1 is parity, and ratio = 2 prices the tail term at twice the mean term on every sample.

A rule reads no portfolio, so it cannot form $\boldsymbol{w}^{\intercal} \boldsymbol{\xi}$. The series it reads is the cross-section of the per-asset losses of the quantity the slot owner prices: the mean-term scale $m$ is the mean of that pool, and the tail-term scale $c$ is the mean of the per-column $\mathrm{CVaR}_{\alpha}$. The mean of the per-column values is not the pooled $\mathrm{CVaR}_{\alpha}$, and the difference is deliberate. A pooled tail is drawn from the worst columns, so it is dominated by the most volatile assets and gives a smaller weight. The measure prices a portfolio rather than a single asset, so the per-column mean is the reading that answers it.

A caller who holds a reference portfolio still cannot state one, and that is the design. The pooled cross-section is the equal-weight reading, and a stated reference portfolio would give a sharper one. It would also make the rule a second place where a portfolio enters the problem, and the calibration channel carries no portfolio by construction: resolve_calibration_slot hands a rule the Prior and nothing else, so a reference vector would have to travel a channel of its own, be checked against the universe of the fold, and be re-stated whenever a subset view or a cluster changed that universe. The exchange rate the rule prices is a property of the sample, and the equal-weight cross-section is the reading of it that needs no second input. A caller who wants the rate at their own portfolio computes l themselves and states the number.

$m$ is negative for a sample of positive expected return, and the rule takes $\lvert m \rvert$. No field states the sign: a negative weight is not admissible in the slot, and a sample of negative expected return does not turn the trade-off around.

The rule reads its sibling alpha, because $c$ is a $\mathrm{CVaR}$ at the measure's own significance level. alpha and l are a travelling pair, on the same terms as alpha and kappa: the rule reads ctx.alpha, and the owner's own resolution method resolves alpha first for that reason. A caller who runs the rule outside a measure states alpha in the context.

Both scales read the observation weights. They are sample statistics rather than counts, so a weighted sample is read weighted. RateRadius and RateSignificance ignore w because a rate speaks of the length of the record, and that reading does not carry to a moment.

ctx.series says which quantity both terms are read over, and the slot owner states it. DistributionallyRobustConditionalDrawdownatRisk carries the key :l as well, and its tail term is a $\mathrm{CDaR}$ of the portfolio drawdown series. A rule is given no portfolio, but it can form the drawdown series of each column, and the resolution site names the marker in ctx.series. Both terms then move together: the mean term is the mean drawdown of the pool, and the tail term is the mean of the per-column $\mathrm{CDaR}_{\alpha}$. The same ConditionalValueatRisk reading forms it, because the tail mean of a non-positive drawdown column is the $\mathrm{CDaR}$ of that column, so the rule and the measure it calibrates still cannot drift apart.

A second reading of l exists, and this rule does not take it. l can be read as a risk-aversion coefficient and mapped from a mean-variance one, but a variance penalty is quadratic in the weight vector and a $\mathrm{CVaR}$ term is positively homogeneous, so the two objectives are not comparable term by term. The map holds at one reference portfolio and nowhere else, and a rule gets no portfolio.

The rule carries no range check on the weight it returns, on the same terms as RateRadius. The slot owner's constructor keeps that job. Its two refusals state that the ratio exists at all, which is the shape of the one check EntropyBudget carries. A sample whose mean loss is near zero returns a very large weight, and that is the sample speaking rather than a defect: no threshold separates a small mean loss from a smaller one.

Fields

  • ratio: Number of mean terms that one tail term is worth. The rule returns the tail weight that prices the tail term at this multiple of the mean term, on the sample the prior result carries, so 1 is parity and 2 prices the tail term at twice the mean term.

Constructors

TailTermParity(;    ratio::Number = 1) -> TailTermParity

Keywords correspond to the struct's fields. ratio defaults to 1, which is parity between the two terms. The significance level and the series both terms are read over are not fields of the rule: both reach it in the CalibrationContext the site hands over.

Validation

  • ratio > 0 and finite.

Related

References

  • [93] P. Mohajerin Esfahani and D. Kuhn. Data-driven distributionally robust optimization using the Wasserstein metric: performance guarantees and tractable reformulations. Mathematical Programming 171, 115–166 (2018).
source
PortfolioOptimisers.EffectiveAssetFloorType
struct EffectiveAssetFloor{__T_fraction} <: AbstractNormCeilingCalibrationAlgorithm

Computes a norm ceiling that holds a stated fraction of the universe effective, so that the floor refits whenever the universe changes.

A norm ceiling and the effective number of assets are reciprocally related, so a bound on the norm is a floor on that count. This rule states the floor as a fraction of the universe rather than as a count. The asset count comes off the prior result, so a subset view, a cluster and a cross-validation fold each get the floor their own universe earns, and no number is pinned to the universe it was written for.

ctx.p is the norm order the ceiling is read against, and it belongs to the constraint. Each of the three constraint sites states it in the CalibrationContext before it resolves the slot. The rule holds no order of its own, so a caller who runs it outside those sites states the order in the context.

The rule carries no range check on its answer, on the same terms as ConcentrationRadius. It returns the quantity of the slot it stands in, so the slot owner is the whole validation.

Mathematical definition

\[\begin{align} m &= f N\,,\\ \mathrm{val} &= \begin{cases} m^{-1} & \textrm{if } p \textrm{ is infinite}\\ m^{1/p - 1} & \textrm{otherwise} \end{cases}\,. \end{align}\]

Where:

  • $\mathrm{val}$: Norm ceiling.
  • $m$: Effective number of assets the ceiling holds as a floor.
  • $f$: Fraction of the universe, the fraction field.
  • $N$: Number of assets.
  • $p$: Norm order of the constraint the ceiling stands in.

The order-$p$ effective number of assets is $N_{\mathrm{eff},\, p}(\boldsymbol{w}) = \left(\sum_{i} \lvert w_{i} \rvert^{p}\right)^{1/(1 - p)}$, so $N_{\mathrm{eff},\, p} \geq m$ is $\lVert \boldsymbol{w} \rVert_{p} \leq m^{1/p - 1}$. It is the reading number_effective_assets states, taken to an arbitrary order: at $p = 2$ the two are the same number, and at every order an equal-weight portfolio over $m$ assets reports exactly $m$. The exponent is also $-1/q$ for the conjugate order $q$, because $1/p - 1 = -1/q$.

The infinite arm is the limit of the finite one and not a second reading. $m^{1/p - 1} \to m^{-1}$ as $p$ grows, and a cap of $1/m$ on the largest weight of a fully invested portfolio spreads it over at least $m$ assets. So the two arms meet, and a caller who raises $p$ towards the infinite order sees the ceiling move towards $1/m$ rather than away from it.

Fields

  • fraction: Fraction of the universe that must stay effective. The rule reads the asset count off the prior result and multiplies it by this fraction, so the floor moves with the universe rather than with a count the caller pins.

Constructors

EffectiveAssetFloor(;    fraction::Number = 0.5) -> EffectiveAssetFloor

Keywords correspond to the struct's fields. fraction defaults to 0.5, which holds half of the universe effective. The norm order the ceiling is read against is not a field of the rule: it reaches the rule as ctx.p in the CalibrationContext the constraint site hands over.

Validation

  • 0 < fraction <= 1.
  • The norm order is not a field, so the constructor cannot check it. The rule checks ctx.p when it runs: it is stated, and it is one or more.

Related

source

References

[93]
P. Mohajerin Esfahani and D. Kuhn. Data-driven distributionally robust optimization using the Wasserstein metric: performance guarantees and tractable reformulations. Mathematical Programming 171, 115–166 (2018).