Meucci Entropy Pooling: private API

PortfolioOptimisers.VecMeucciEPType
const VecMeucciEP = AbstractVector{<:MeucciEntropyPoolingPrior}

Groups the vector forms of MeucciEntropyPoolingPrior that a method can dispatch on.

The group exists because a caller that pools opinions holds many estimators rather than one, and a method that takes such a collection needs a single type to sign. The library's own opinion pooling signs the wider VecEP instead, which admits EntropyPoolingPrior beside this estimator, so VecMeucciEP is the narrower name for a method that takes the recursive CVaR route alone.

Related

source
PortfolioOptimisers.ep_cvar_views_setupFunction
ep_cvar_views_setup(cvar_views::Nothing, args...; kwargs...)

No-op pass-through for conditional value at risk view constraints when none are specified.

Arguments

  • cvar_views::Nothing: Indicates that no CVaR view constraints are specified.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

Related

source
ep_cvar_views_setup(cvar_views::CVV_VecCVV, pr::AbstractPriorResult,
                    sets::UniverseSets, w::StatsBase.ProbabilityWeights,
                    ds_opt::Option{<:ConditionalValueatRiskEntropyPooling},
                    dm_opt::Option{<:OptimEntropyPooling}; strict::Bool = false)

Resolve the conditional value at risk views once, into the data one search reads.

ep_cvar_views_setup parses and validates the CVaR view groups, resolves every prior(...) reference against pr under w, and flattens the groups into one loss matrix, one target vector and one level vector. It also chooses the search the targets need. ep_cvar_views_solve! reads that answer and adds nothing to it.

The staged route of ep_prior searches up to three times, once per stage, and the reference each stage reads must be the same number. This verb is therefore called once, before any solve, against the first fit of the wrapped estimator and the prior probabilities that fit was read at. A later stage carries a refit pr whose weights are the previous stage's posterior, so a reference resolved there would state a different target at each stage, and the view the caller wrote would move under it. Resolving once also lifts the whole parse out of the search, which repeated it at every stage. Issue #628 owns that rule.

Algorithm

  1. Wrap cvar_views in a vector when it is a single view, so one loop serves both shapes.
  2. For each view group, read its significance level into alpha, and parse its equations accepting == alone.
  3. Replace every group name by the assets it spans, and every prior(...) reference by the prior conditional value at risk at alpha under w, through replace_prior_views.
  4. Turn the parsed views into the equality block lcs, and check the two preconditions of the section below that read it. Under strict = false every row of the group can drop, and lcs is then nothing: the group states no view, and the loop skips it.
  5. For each row of the block, read the asset it names into cols, its target into B, its level into alphas and its text into eqns. The groups flatten into one search: each level enters only as the divisor of its own view's positive part.
  6. Read the worst realisation of every named asset into min_X, and raise when any target reaches it.
  7. Choose the search d_opt. One view takes ds_opt, or a default ConditionalValueatRiskEntropyPooling. More than one takes dm_opt, or a default OptimEntropyPooling over Optim.Fminbox.
  8. Return nothing when every group stated no view, which sends the stage down the plain solve. Otherwise return the loss columns X, the targets B, the levels alphas and the search d_opt.

Arguments

  • cvar_views: CVaR view constraints.
  • pr: Prior result the prior(...) references are read from. It is the first fit of the wrapped estimator, not a refit of a later stage.
  • sets: Asset set mapping asset names to indices.
  • w: Observation weights pr was read at, which the prior(...) references resolve under.
  • ds_opt: CVaR-specific optimiser (for single view).
  • dm_opt: General optimiser (for multiple views).
  • strict: If true, throws error for missing assets; otherwise, issue warnings.

Validation

  • No view group declares a view formulation. A group whose alg is not nothing raises an ArgumentError: this route writes no constraint formulation, so it has nothing to apply the declaration to.
  • Every view names one asset. A view over more than one asset raises an ArgumentError.
  • Every target is non-negative. A negative target raises a DomainError.
  • Every target stays below the worst realisation of the asset it names. A target that reaches it raises a DomainError naming every offending view beside the largest target its asset admits. The per-view route of ep_assert_reachable_view raises the same type, so one catch reads both.

Returns

  • nothing: Every group stated no view, because strict is false and every row of every group was dropped.
  • cvv::NamedTuple: The search data, carrying the loss columns X, the targets B, the levels alphas and the search d_opt.

Related

References

  • [80] A. Meucci, D. Ardia and S. Keel. Fully flexible extreme views. The Journal of Risk 14, 39–49 (2011).
source
PortfolioOptimisers.ep_cvar_views_solve!Function
ep_cvar_views_solve!(cvv::Nothing, epc::AbstractDict,
                     w::StatsBase.ProbabilityWeights,
                     opt::AbstractEntropyPoolingOptimiser)

Solve the entropy pooling problem when no CVaR views are specified.

ep_cvar_views_solve! is an internal API compatibility method that solves the entropy pooling problem when ep_cvar_views_setup prepared no search, which happens when no conditional value at risk view constraint is present. It delegates to the main entropy pooling solver using the provided prior weights, constraint dictionary, and optimiser.

Algorithm

  1. Call entropy_pooling with the prior weights w, the constraint dictionary epc and the optimiser opt, giving the posterior probability weights pw.
  2. Return pw. With no conditional value at risk view there is no value at risk to search over, so the whole staged solve collapses to the one call of step 1.

Arguments

  • cvv: Indicates that ep_cvar_views_setup prepared no search.
  • epc: Dictionary of entropy pooling constraints, mapping keys to (lhs, rhs) pairs.
  • w: Prior probability weights.
  • opt: Entropy pooling optimiser.

Returns

  • pw::StatsBase.ProbabilityWeights: Posterior probability weights satisfying the constraints.

Related

source
ep_cvar_views_solve!(cvv::NamedTuple, epc::AbstractDict,
                     w::StatsBase.ProbabilityWeights,
                     opt::AbstractEntropyPoolingOptimiser)

Solve the entropy pooling problem with Conditional Value-at-Risk (CVaR) view constraints.

ep_cvar_views_solve! reads the targets, the levels and the search that ep_cvar_views_setup resolved, and solves for the posterior probability weights by either root-finding (for a single CVaR view) or optimisation (for more than one). It parses nothing and resolves nothing: the staged route calls it once per stage, and every stage must read the same targets.

The search runs over the value at risk levels etas, one per view, each bounded by [0, B]. This is a continuous relaxation of the recursive algorithm, which searches over discrete tail sizes instead; it reaches the same target and it takes more than one view.

The single-view bracket stops a hair inside B, at B * (1 - sqrt(eps)). At B itself the constraint demands a posterior tail contribution of exactly zero, and no interior posterior carries one, so the problem there is degenerate and its dual is unbounded. Roots evaluates both ends of a bracket before it searches, so every single-view solve would run that problem once. The root sits near half of B, so the shrunk end holds it.

Mathematical definition

The conditional value at risk of asset $i$ is the value of the Rockafellar-Uryasev programme, whose minimiser is the value at risk. A view that pins it to $\bar{c}$ is therefore a pair of conditions on the posterior, one linear in $\boldsymbol{p}$ at a fixed $\eta$ and one that fixes $\eta$:

\[\begin{align} \mathrm{CVaR}_{\alpha}(X) &= \underset{\eta}{\min}\; \left\{ \eta + \frac{1}{\alpha} \sum_{t=1}^{T} p_{t} \left(-x_{t,\,i} - \eta\right)^{+} \right\}\,, \\ \frac{1}{\alpha} \sum_{t=1}^{T} p_{t} \left(-x_{t,\,i} - \eta\right)^{+} &= \bar{c} - \eta\,, \\ \sum_{t=1}^{T} p_{t} \mathbb{1}\left\{-x_{t,\,i} > \eta\right\} &= \alpha\,. \end{align}\]

The second line is linear in $\boldsymbol{p}$, so one entropy pooling solve answers it at any candidate $\eta$. The third line holds exactly when that $\eta$ is the value at risk of the posterior it produced, so the two lines share one fixed point and the view is met only there.

Where:

  • $\mathrm{CVaR}_{\alpha}(X)$: Conditional value at risk of the loss series $\boldsymbol{x}$ at level $\alpha$.
  • $\bar{c}$: Target conditional value at risk of the view.
  • $\boldsymbol{p}$: $T \times 1$ posterior probabilities of the observations, summing to one. They are the unknown of the entropy pooling problem.
  • $x_{t,\,i}$: Return of asset $i$ at observation $t$.
  • $\alpha$: Significance level (left tail probability), $\alpha \in (0, 1)$.
  • $T$: Number of observations.
  • $\eta$: Value at risk of asset $i$ at level $\alpha$, the minimiser of the first line and the variable the outer search carries.
  • $(\cdot)^{+}$: Positive part, $\max(\cdot,\, 0)$.

Algorithm

  1. Read the loss columns X, the targets B, the levels alphas and the search d_opt off cvv.
  2. Define func(etas). It writes the second line above into epc under the key :cvar_eq at the candidate etas, solves the whole constraint set with entropy_pooling into wi, and returns wi beside the residual of the third line. One view residual is the posterior tail mass minus alpha. More than one is the norm_error of the posterior conditional value at risk minus the target, over the views.
  3. Search for the value at risk res. One view root-finds the residual of func over [0, B[1] * (1 - sqrt(eps))] with Roots.find_zero. More than one minimises it over the box [0, B] from the start 0.5 * B with Optim.optimize.
  4. Call func(res) once more, and return the posterior probability weights it produces.

Arguments

  • cvv: Search data, answered by ep_cvar_views_setup.
  • epc: Dictionary of entropy pooling constraints, mapping keys to (lhs, rhs) pairs.
  • w: Prior probability weights the stage projects.
  • opt: Main entropy pooling optimiser.

Validation

  • Every candidate value at risk stays in [0, B]. A candidate outside the box raises a DomainError.
  • The search must succeed. A Roots.find_zero that raises is rethrown as an ErrorException, and an Optim.optimize that Optim.converged reports as failed raises an ErrorException.
  • An infeasible view set is not caught. The residual this search minimises is the posterior tail mass minus alpha, and a posterior that sits on one observation carries a small residual while it misses the view by any margin. The summary paragraph of MeucciEntropyPoolingPrior states how to recognise that answer.
  • A missed feasible view is not caught either. The search reads its own residual, and never how closely the inner entropy_pooling solve met the constraint that carries the view. With more than one view Optim.converged accepts a solve that stopped on the step in x rather than on stationarity. The warning of MeucciEntropyPoolingPrior states the mechanism, the measured sizes and how to read the answer. See issue #573.

Returns

  • pw::StatsBase.ProbabilityWeights: Posterior probability weights satisfying CVaR view constraints.

Related

References

  • [80] A. Meucci, D. Ardia and S. Keel. Fully flexible extreme views. The Journal of Risk 14, 39–49 (2011).
source
PortfolioOptimisers.ep_priorFunction
ep_prior(alg::StagedEP, pe::MeucciEntropyPoolingPrior, X::MatNum,
         F::Option{<:MatNum}, pnl::Option{<:AssetPanel} = nothing;
         strict::Bool = false, kwargs...)

Compute entropy pooling prior moments for asset returns with iterative constraint enforcement.

ep_prior estimates the mean and covariance of asset returns using the entropy pooling framework, supporting iterative constraint enforcement via the H1_EntropyPooling and H2_EntropyPooling algorithms. It integrates moment and view constraints (mean, variance, CVaR, skewness, kurtosis, correlation), flexible confidence specification, and composable optimisation algorithms. The method iteratively applies constraints, updating prior weights and moments at each step, and ensures that higher moment views do not inadvertently alter lower moments.

Each stage refits the wrapped estimator before it parses its own views, so a prior(...) reference in a later stage resolves against the previous stage's posterior rather than against the estimator's own prior. A sigma_views target written as prior(A)*1.3 under a mu_views or cvar_views view therefore asks for 1.3 times the variance stage one left, which is not 1.3 times the empirical variance. Write the target as a number where the reference must be the empirical one.

Mathematical definition

The staged posterior is a chain of Kullback-Leibler projections rather than one. Stage $k$ carries the constraint set $\mathcal{C}_{k}$ of every stage up to and including itself, and it projects a reference $\boldsymbol{r}^{(k)}$ that the algorithm tag fixes:

\[\begin{align} \boldsymbol{p}^{(k)} &= \underset{\boldsymbol{p} \in \mathcal{C}_{k}}{\arg\min} \sum_{t=1}^{T} p_{t} \ln\!\frac{p_{t}}{r_{t}^{(k)}}\,, \\ \mathcal{C}_{k} &= \left\{ \boldsymbol{p} : \mathbf{A}_{k} \boldsymbol{p} = \boldsymbol{B}_{k},\; \boldsymbol{p} \geq \boldsymbol{0},\; \boldsymbol{1}^\intercal \boldsymbol{p} = 1 \right\}\,, \\ \mathcal{C}_{1} &\supseteq \mathcal{C}_{2} \supseteq \mathcal{C}_{3}\,, \\ \boldsymbol{r}^{(k)} &= \begin{cases} \boldsymbol{q} & \text{under } \texttt{H1\_EntropyPooling} \\ \boldsymbol{p}^{(k-1)} & \text{under } \texttt{H2\_EntropyPooling} \end{cases}\,, \\ \boldsymbol{p}^{*} &= \boldsymbol{p}^{(K)}\,. \end{align}\]

The three stages hold the mean and value at risk views, the variance and covariance views, and the correlation, skewness and kurtosis views. The sets nest, so the last stage's posterior meets every view, and the two references differ only in what the chain is measured from: $\texttt{H1\_EntropyPooling}$ gives the projection of the prior onto the whole view set, and $\texttt{H2\_EntropyPooling}$ gives the projection of each stage onto the next. A conditional value at risk view is not a row of $\mathbf{A}_{k}$: it enters every stage as the fixed point of ep_cvar_views_solve!, whose own section states it.

Where:

  • $\boldsymbol{p}$: $T \times 1$ posterior probabilities of the observations, summing to one. They are the unknown of the entropy pooling problem.
  • $\boldsymbol{q}$: $T \times 1$ prior probabilities of the observations, summing to one.
  • $T$: Number of observations.
  • $\boldsymbol{p}^{(k)}$: Posterior probabilities of stage $k$, and $\boldsymbol{p}^{*}$ those of the last stage $K$.
  • $\boldsymbol{r}^{(k)}$: Reference probabilities stage $k$ projects.
  • $\mathcal{C}_{k}$: Constraint set of stage $k$, carrying the rows of every stage up to it.
  • $\mathbf{A}_{k}$, $\boldsymbol{B}_{k}$: Rows and right-hand side that state $\mathcal{C}_{k}$.

Posterior moments are then read as probability-weighted sample statistics under $\boldsymbol{p}^{*}$.

Algorithm

  1. Fit the wrapped prior estimator, giving pr. The fit states the observation axis: T is size(pr.X, 1), which a nested prior that drops rows makes smaller than size(X, 1).
  2. Read the prior probabilities w0 on that axis with ep_prior_probabilities. They are pe.w where the caller set one, pr.w where the fit answered one, and the uniform 1/T otherwise. A caller's pe.w reaches the wrapped estimator through factory, and pr is refitted under it.
  3. Build the empty constraint dictionary epc and the fixing ledger fixed. Resolve the cvar_views once against that fit, through ep_cvar_views_setup into cvv. Every stage searches the targets it holds, so a prior(...) reference states one number for the whole chain.
  4. Stage one, the mean and the value at risk. Write the mu_views and var_views rows into epc. When epc holds a row or cvv states a search, solve through ep_cvar_views_solve! into w1, and refit pr under it.
  5. Stage two, the variance and the covariance. Write the sigma_views and cov_views rows into epc, and pin the mean of every asset those rows read with fix_mu!, so the stage cannot move a moment an earlier stage set. Under the same emptiness test, solve into w1, and refit pr under it.
  6. Stage three, the correlation, the skewness and the kurtosis. Write the sk_views, kt_views and rho_views rows into epc, and pin the mean and the variance of every asset those rows read with fix_mu! and fix_sigma!. Under the same emptiness test, solve into w1, and refit pr under it.
  7. Read the reference each of steps 5 and 6 solves from: w0 under H1_EntropyPooling, and the previous stage's w1 under H2_EntropyPooling.
  8. Read the effective number of scenarios ens as the exponential of the entropy of w1, and the divergence kld as the Kullback-Leibler divergence of w1 from w0.
  9. Return a LowOrderPrior carrying the last refit's moments, w1, ens and kld. The feature matrix Z and the factor block fpr are forwarded from that refit unchanged.

Arguments

  • alg: Staged entropy pooling algorithm, taken from pe.alg by prior.
  • pe: Entropy pooling prior estimator.
  • X: Asset returns matrix (observations × assets), oriented by prior.
  • F: Optional factor matrix, oriented by prior.
  • pnl: Optional AssetPanel, the panel the carrier held. A wrapping prior forwards it unchanged, so that it can compose an estimator that is fitted on a panel. An estimator that reads no panel ignores it.
  • strict: If true, throws error for missing assets; otherwise, issues warnings.
  • kwargs...: Additional keyword arguments passed to underlying estimators and solvers.

Validation

  • If any view constraint is not nothing, !isnothing(sets).
  • If prior weights pe.w are provided, length(pe.w) == size(pr.X, 1), the observations the wrapped estimator answered. A length that does not match raises a DimensionMismatch naming that count, the count the estimator was handed, and the rule.
  • Every view equation carries a comparison operator its own family accepts. An unsupported operator raises a Meta.ParseError naming the operators that family accepts.

Returns

  • pr::LowOrderPrior: Result object containing asset returns, posterior mean vector, posterior covariance matrix, weights, effective number of scenarios, Kullback-Leibler divergence, and optional factor moments.

Related

source
PortfolioOptimisers.ep_priorFunction
ep_prior(alg::H0_EntropyPooling, pe::MeucciEntropyPoolingPrior, X::MatNum,
         F::Option{<:MatNum}, pnl::Option{<:AssetPanel} = nothing;
         strict::Bool = false, kwargs...)

Compute entropy pooling prior moments for asset returns with single-shot constraint enforcement.

ep_prior estimates the mean and covariance of asset returns using the entropy pooling framework, enforcing all moment and view constraints in a single optimisation step via the H0_EntropyPooling algorithm. This approach is fast but may distort lower moments when higher moment views are present, as all constraints are applied simultaneously.

Mathematical definition

The single-shot posterior is one Kullback-Leibler projection of the prior onto the intersection of every view's constraint set. It is the staged chain of ep_prior collapsed to one stage:

\[\begin{align} \boldsymbol{p}^{*} &= \underset{\boldsymbol{p} \in \mathcal{C}}{\arg\min} \sum_{t=1}^{T} p_{t} \ln\!\frac{p_{t}}{q_{t}}\,, \\ \mathcal{C} &= \mathcal{C}_{1} \cap \mathcal{C}_{2} \cap \mathcal{C}_{3}\,. \end{align}\]

One reference and one feasible set carry every view, so no moment can be pinned between stages: a higher moment view is free to move a lower moment of the same asset. That is the whole difference from the staged chain, whose $\mathcal{C}_{k}$ this $\mathcal{C}$ intersects. A conditional value at risk view is not a row of $\mathcal{C}$: it enters as the fixed point of ep_cvar_views_solve!, whose own section states it.

Where:

  • $\boldsymbol{p}$: $T \times 1$ posterior probabilities of the observations, summing to one. They are the unknown of the entropy pooling problem.
  • $\boldsymbol{q}$: $T \times 1$ prior probabilities of the observations, summing to one.
  • $T$: Number of observations.
  • $\boldsymbol{p}^{*}$: Posterior probabilities of the one solve.
  • $\mathcal{C}$, $\mathcal{C}_{k}$: Feasible set of the one solve, and the stage sets it intersects.

Posterior moments are then read as probability-weighted sample statistics under $\boldsymbol{p}^{*}$.

Algorithm

  1. Fit the wrapped prior estimator, giving pr. The fit states the observation axis: T is size(pr.X, 1), which a nested prior that drops rows makes smaller than size(X, 1).
  2. Read the prior probabilities w0 on that axis with ep_prior_probabilities. They are pe.w where the caller set one, pr.w where the fit answered one, and the uniform 1/T otherwise. A caller's pe.w reaches the wrapped estimator through factory, and pr is refitted under it.
  3. Build the empty constraint dictionary epc. Resolve the cvar_views once against that fit, through ep_cvar_views_setup into cvv.
  4. Write the mu_views and var_views rows into epc.
  5. Write the sigma_views and cov_views rows into epc. No moment is pinned, so fix_mu! is never called on this route.
  6. Write the sk_views, kt_views and rho_views rows into epc.
  7. When epc holds a row or cvv states a search, solve the whole accumulated set once through ep_cvar_views_solve! into w1, and refit pr under it. Every row of every family can drop under strict = false, and the view set then states nothing: w1 is w0, kld is zero, and no refit runs.
  8. Read the effective number of scenarios ens as the exponential of the entropy of w1, and the divergence kld as the Kullback-Leibler divergence of w1 from w0.
  9. Return a LowOrderPrior carrying the refit's moments, w1, ens and kld. The feature matrix Z and the factor block fpr are forwarded from that refit unchanged.

Arguments

  • alg: Single-shot entropy pooling algorithm, taken from pe.alg by prior.
  • pe: Entropy pooling prior estimator.
  • X: Asset returns matrix (observations × assets), oriented by prior.
  • F: Optional factor matrix, oriented by prior.
  • pnl: Optional AssetPanel, the panel the carrier held. A wrapping prior forwards it unchanged, so that it can compose an estimator that is fitted on a panel. An estimator that reads no panel ignores it.
  • strict: If true, throws error for missing assets; otherwise, issues warnings.
  • kwargs...: Additional keyword arguments passed to underlying estimators and solvers.

Validation

  • If any view constraint is not nothing, !isnothing(pe.sets).
  • If prior weights pe.w are provided, length(pe.w) == size(pr.X, 1), the observations the wrapped estimator answered. A length that does not match raises a DimensionMismatch naming that count, the count the estimator was handed, and the rule.
  • Every view equation carries a comparison operator its own family accepts. An unsupported operator raises a Meta.ParseError naming the operators that family accepts.

Returns

  • pr::LowOrderPrior: Result object containing asset returns, posterior mean vector, posterior covariance matrix, weights, effective number of scenarios, Kullback-Leibler divergence, and optional factor moments.

Related

source
PortfolioOptimisers.show_fieldsMethod
show_fields(
    _::MeucciEntropyPoolingPrior
) -> NTuple{15, Symbol}

Renders every field of a MeucciEntropyPoolingPrior except cache.

The state a cache holds is the running detail of an incremental fit, not the configuration a reader looks the type up for, and it prints under the estimator at every site that renders one. Set set_show_nothing_fields!(:MeucciEntropyPoolingPrior, true) to render it.

Arguments

  • ::MeucciEntropyPoolingPrior: Prior estimator, read for its type alone.

Returns

  • fields::Tuple: The field names to render, which is (:pe, :mu_views, :var_views, :cvar_views, :sigma_views, :sk_views, :kt_views, :cov_views, :rho_views, :sets, :ds_opt, :dm_opt, :opt, :w, :alg).

Related

source

References

[80]
A. Meucci, D. Ardia and S. Keel. Fully flexible extreme views. The Journal of Risk 14, 39–49 (2011).