Base Prior

PortfolioOptimisers.AbstractPriorEstimatorType
abstract type AbstractPriorEstimator <: AbstractEstimator

Abstract supertype for all prior estimators.

AbstractPriorEstimator is the base type for all estimators that compute prior information from asset and/or factor returns. All concrete prior estimators should subtype this type to ensure a consistent interface for prior computation and integration with portfolio optimisation workflows.

Interfaces

In order to implement a new prior estimator which will work seamlessly with the library, subtype the family that names the returns it reads — AbstractLowOrderPriorEstimator_A, AbstractLowOrderPriorEstimator_F, AbstractLowOrderPriorEstimator_AF or AbstractHighOrderPriorEstimator_F — with all necessary parameters as part of the struct, and implement the following method:

  • prior(pe::AbstractPriorEstimator, X::MatNum, F::Option{<:MatNum} = nothing, pnl::Option{<:AssetPanel} = nothing; dims::Int = 1, kwargs...) -> AbstractPriorResult: Estimate the prior from the returns matrices and the Asset Panel.

The family fixes the signature. A member of the _A family declares F as args... and never reads it, a member of the _F family declares it F::MatNum and requires it, and a member of the _AF family declares it F::Option{<:MatNum} = nothing and reads it when it is there.

pnl is the Asset Panel the carrier held, and the ReturnsResult method forwards it to every estimator. Take it and ignore it unless the estimator is fitted on a panel, as CrossSectionalFactorPrior is. An estimator that wraps another over the assets forwards it unchanged, so that the wrapped estimator composes; one that wraps a prior over the factors does not, because no panel describes a factor axis. An estimator that declares args... takes it there and needs no further declaration.

The method returns the carrier of its own order: a low order estimator returns a LowOrderPrior, and a high order estimator returns a HighOrderPrior. An estimator that wraps another rebuilds the wrapped result with forward_prior rather than by a hand-written constructor call, so that every field it does not name survives the hop.

The ReturnsResult method of prior is supplied by this file and needs no implementation.

Arguments

  • pe: Prior estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • F: Factor returns matrix, or nothing.
  • 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.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments passed to the nested estimators.

Returns

  • pr::AbstractPriorResult: Result object containing the estimated prior.

Examples

We can create a dummy prior estimator as follows:

julia> struct MyPriorEstimator <: PortfolioOptimisers.AbstractLowOrderPriorEstimator_A endjulia> function PortfolioOptimisers.prior(pe::MyPriorEstimator, X::PortfolioOptimisers.MatNum,                                          args...; dims::Int = 1, kwargs...)           mu = vec(sum(X; dims = 1)) / size(X, 1)           sigma = Matrix(LinearAlgebra.I * 1.0, size(X, 2), size(X, 2))           return LowOrderPrior(; X = X, mu = mu, sigma = sigma)       endjulia> prior(MyPriorEstimator(), [0.01 0.02; 0.03 0.04])LowOrderPrior      X ┼ 2×2 Matrix{Float64}    o_X ┼ nothing     mu ┼ Vector{Float64}: [0.02, 0.03]  sigma ┼ 2×2 Matrix{Float64}   chol ┼ nothing      w ┼ nothing    ens ┼ nothing    kld ┼ nothing     ow ┼ nothing     rr ┼ nothing    fpr ┴ nothing

Related

source
PortfolioOptimisers.AbstractPriorResultType
abstract type AbstractPriorResult <: AbstractResult

Abstract supertype for all prior result types.

AbstractPriorResult is the base type for all result objects produced by prior estimators, containing computed prior information such as moments, asset returns, and factor returns. All concrete prior result types should subtype this to ensure a consistent interface for integration with portfolio optimisation workflows.

The library ships two carriers: LowOrderPrior holds the returns, the mean and the covariance, and HighOrderPrior holds the co-moments over a LowOrderPrior it wraps.

Interfaces

In order to implement a new prior result carrier which will work seamlessly with the library, subtype AbstractPriorResult with all necessary fields as part of the struct, and implement the following methods:

  • reconstruct_prior(pr::AbstractPriorResult, patch::NamedTuple) -> AbstractPriorResult: Rebuild the carrier through its own constructor with patch applied. This is what makes forward_prior work on the carrier, and it is written per carrier because the constructor is named rather than recovered by reflection.
  • port_opt_view(pr::AbstractPriorResult, i, args...) -> AbstractPriorResult: Restrict the carrier to the assets at index i, for hierarchical and subset optimisation.

The field list is derived by prior_field_values, so a carrier that gains a field needs no further method. An @pprop field may only name a property of the two carriers prior_result_property_pool hard-codes today — LowOrderPrior and HighOrderPrior; a third-party carrier's own property is refused by check_propagatable_contracts rather than recognised.

Arguments

  • pr: Prior result.
  • patch: Named tuple of field overrides.
  • i: Asset indices the view keeps.
  • args...: Additional arguments the view reads.

Returns

  • pr::AbstractPriorResult: A carrier of the same type as the input.

Examples

We can create a dummy prior result carrier as follows:

julia> struct MyPriorResult <: PortfolioOptimisers.AbstractPriorResult           X::Matrix{Float64}           mu::Vector{Float64}       endjulia> function PortfolioOptimisers.reconstruct_prior(pr::MyPriorResult, patch::NamedTuple)           vals = merge(PortfolioOptimisers.prior_field_values(pr), patch)           return MyPriorResult(vals.X, vals.mu)       endjulia> function PortfolioOptimisers.port_opt_view(pr::MyPriorResult, i, args...)           return MyPriorResult(pr.X[:, i], pr.mu[i])       endjulia> pr = MyPriorResult([0.01 0.02; 0.03 0.04], [0.02, 0.03]);julia> PortfolioOptimisers.forward_prior(pr; mu = [0.05, 0.06]).mu2-element Vector{Float64}: 0.05 0.06julia> PortfolioOptimisers.port_opt_view(pr, [1]).mu1-element Vector{Float64}: 0.02

Related

source
PortfolioOptimisers.AbstractLowOrderPriorEstimator_AType
abstract type AbstractLowOrderPriorEstimator_A <: AbstractLowOrderPriorEstimator

Low order prior estimator using only asset returns.

AbstractLowOrderPriorEstimator_A is the base type for estimators that compute low order moments (mean and covariance) using only asset returns data. All concrete asset-only prior estimators should subtype this type.

This is the first of the three source shapes. A member admits asset returns only: its prior method declares the factor argument as args... and never reads it, so factor returns handed to it are ignored rather than refused.

Interfaces

  • prior(pe::AbstractLowOrderPriorEstimator_A, X::MatNum, args...; dims::Int = 1, kwargs...) -> LowOrderPrior: Estimate the prior from asset returns alone; args... absorbs and ignores any factor returns handed in.

Related

source
PortfolioOptimisers.AbstractLowOrderPriorEstimator_FType
abstract type AbstractLowOrderPriorEstimator_F <: AbstractLowOrderPriorEstimator

Low order prior estimator using factor returns.

AbstractLowOrderPriorEstimator_F is the base type for estimators that compute low order moments (mean and covariance) requiring the use of both asset and factor returns data. All concrete factor-adjusted prior estimators should subtype this type.

This is the second of the three source shapes. A member admits asset returns and requires factor returns: its prior method declares the factor argument as F::MatNum with no default, so a call that omits factor returns is a MethodError. prior raises earlier and more clearly when a ReturnsResult with F === nothing reaches such an estimator.

Interfaces

  • prior(pe::AbstractLowOrderPriorEstimator_F, X::MatNum, F::MatNum, args...; dims::Int = 1, kwargs...) -> LowOrderPrior: Estimate the prior from asset and factor returns; a call that omits F is a MethodError.

Related

source
PortfolioOptimisers.AbstractLowOrderPriorEstimator_AFType
abstract type AbstractLowOrderPriorEstimator_AF <: AbstractLowOrderPriorEstimator

Low order prior estimator using both asset and factor returns.

AbstractLowOrderPriorEstimator_AF is the base type for estimators that compute low order moments (mean and covariance) using both asset and optionally factor returns data. All concrete prior estimators which may optionally use factor returns should subtype this type.

This is the third of the three source shapes. A member admits asset returns and admits factor returns optionally: its prior method declares the factor argument as F::Option{<:MatNum} = nothing and reads it when it is supplied. The shape therefore says nothing about whether the result carries a regression: use assert_prior_regression to establish that.

Nor does the shape say whether the fit reads factor returns, and needs_factor_returns answers that three ways. A member that requires them answers true, a member that never reads them answers false, and this shape answers nothing by default: the type does not say; take what the fold is given, which is what its batch verb does. Every member of this shape the library ships embeds another prior and hands F to it, so each defines the recursion and answers the leaf's value. A caller's own subtype that embeds a prior should define the same recursion; one that reads F itself may leave the default.

Interfaces

  • prior(pe::AbstractLowOrderPriorEstimator_AF, X::MatNum, F::Option{<:MatNum} = nothing, args...; dims::Int = 1, kwargs...) -> LowOrderPrior: Estimate the prior from asset returns, reading F when it is supplied.

Related

source
PortfolioOptimisers.AbstractHighOrderPriorEstimator_FType
abstract type AbstractHighOrderPriorEstimator_F <: AbstractHighOrderPriorEstimator

High order prior estimator using factor returns.

AbstractHighOrderPriorEstimator_F is the base type for estimators that compute high order moments (such as coskewness and cokurtosis) requiring both asset and factor returns data. All concrete factor-based high order prior estimators should subtype this type.

A member admits asset returns and requires factor returns, on the same terms as AbstractLowOrderPriorEstimator_F one order down: its prior method declares the factor argument as F::MatNum with no default. The two are the members of AbstractHiLoOrderPriorEstimator_F, which is how prior recognises a factor prior without naming an order.

Interfaces

  • prior(pe::AbstractHighOrderPriorEstimator_F, X::MatNum, F::MatNum, args...; dims::Int = 1, kwargs...) -> HighOrderPrior: Estimate the prior from asset and factor returns; a call that omits F is a MethodError.

Related

source
PortfolioOptimisers.LowOrderPriorType
struct LowOrderPrior{__T_X, __T_o_X, __T_mu, __T_sigma, __T_chol, __T_w, __T_ens, __T_kld, __T_ow, __T_rr, __T_fpr} <: AbstractPriorResult

Carries the returns, mean and covariance a low order prior estimator produced.

LowOrderPrior stores the output of low order prior estimation routines, including asset returns, mean vector, covariance matrix, Cholesky factor, weights, entropy, Kullback-Leibler divergence, outlier weights, regression results, and optional factor moments. It is used throughout the package to represent validated prior information for portfolio optimisation and analytics.

Fields

  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • o_X: The returns matrix the caller supplied, kept only when the carrier's own X is not it, and nothing otherwise. The three estimators that lift a factor-axis prior onto the asset axis overwrite X with the reconstruction F * transpose(M) .+ transpose(b); o_X is the asset returns they were handed, over the same observations and the same assets. Read it as original_X, which is always a matrix, rather than as this field.
  • mu: Expected returns vector assets × 1.
  • sigma: Covariance matrix assets × assets.
  • chol: Cholesky factorisation of the covariance matrix.
  • w: Observation weights the prior was computed under observations × 1 (see ObsWeights), or nothing if it was computed unweighted. Binds ens, kld and ow, which are diagnostics of it (see forward_prior).
  • ens: Effective sample size behind the moments, or nothing, which every reader takes as size(X, 1). Two producers write it: an entropy-pooling prior writes the effective count of its posterior weights w, to which it is bound (see forward_prior), and EmpiricalPrior writes the number of observations its moments were fitted over when a Scenario Cap max_scenarios cuts the rows X carries below it, so a consumer that prices a sample size reads the count behind the moments and not the rows carried.
  • kld: Kullback-Leibler divergence of w from the weights it was derived from: a scalar against the prior observation weights for a single reweighting, or one entry per opinion when w came from pooling several.
  • ow: Opinion pooling weights.
  • rr: Regression result.
  • fpr: Prior result over the factor axis, or nothing. Its X is the factor returns matrix, so its mu, sigma and w describe factors rather than assets, over the same observations as the asset block.

Constructors

LowOrderPrior(;    X::MatNum,    o_X::Option{<:MatNum} = nothing,    mu::VecNum,    sigma::MatNum,    chol::Option{<:MatNum} = nothing,    w::Option{<:ObsWeights} = nothing,    ens::Option{<:Number} = nothing,    kld::Option{<:Num_VecNum} = nothing,    ow::Option{<:VecNum} = nothing,    rr::Option{<:AbstractLoadingsRegressionResult} = nothing,    fpr::Option{<:LowOrderPrior} = nothing) -> LowOrderPrior

Keywords correspond to the struct's fields.

The factor block

A prior fit through a factor model carries two distributions: one over the assets, in the carrier's own fields, and one over the factors. The factor one is a nested LowOrderPrior in fpr rather than a set of f_-prefixed flat fields, so it gains every field the carrier has — w, ens, kld and ow as well as mu and sigma — and gains any field added in future without a second edit. Its X is the factor returns matrix, over the same observations as the asset X.

fpr travels with rr: the two are the factor block, and the constructor requires them together or not at all. rr is what projects the block onto the assets (mu ≈ rr.M * fpr.mu + rr.b), so a factor distribution with no loadings could not be read against this asset axis.

rr is bound to AbstractLoadingsRegressionResult, the root that states a member carries the loadings matrix M, so a Regression and a CrossSectionalFactorModel both sit in the slot. The bound is the loadings criterion and not a fitting geometry: every invariant the constructor checks here reads rr.M alone, fpr sits on the axis M's columns name, and every consumer of the slot reads M, or reads L and gets M back when L is unset.

One property of the block does not follow from the slot, and a consumer that needs it must ask. A member fitted in a re-based Factor Family states so through has_family_rebasis, and its fpr.sigma is then singular by construction, because the raw factor axis is a linear image of the re-based one. Projecting through M is unaffected — that is what HighOrderFactorPriorEstimator does — but inverting or factorising fpr.sigma has no answer. The inversion does not say so: it raises nothing and returns a result whose scale looks ordinary, so BayesianBlackLittermanPrior refuses such a carrier rather than reporting one.

The flat names are virtual reads of the nested block, so code written against the old shape is unaffected: pr.f_mu, pr.f_sigma and pr.f_w return fpr.mu, fpr.sigma and fpr.w, or nothing when there is no factor block, and pr.f_ens, pr.f_kld and pr.f_ow come with them. They are properties, not fields — forward_prior and prior_field_values see only fpr.

Which read is idiomatic

pr.fpr.mu is the public read; the flat f_-prefixed names are a compatibility surface, kept so that code written against the pre-nesting shape keeps working, and useful where a value-or-nothing read without branching is wanted.

The reason is not taste. The flat surface is partial and frozen: there are six flat names over eleven fields, so fpr.X — the factor returns matrix — and fpr.chol and fpr.rr have no flat spelling at all and never will. A surface that cannot express the whole block cannot be the way to read it. The set is fixed at the six here and the seven on HighOrderPrior; a field added to a carrier in future is reachable as pr.fpr.<name> and gains no f_ counterpart, so nothing has to be added in two places to stay complete.

The two reads also differ where the block is absent, which is the one case worth checking before choosing: pr.f_mu returns nothing, while pr.fpr.mu throws, because fpr is nothing. Guard with assert_prior_regressionrr and fpr are supplied together or not at all, so checking rr establishes the whole block — and then read through fpr.

Composition: what a wrapping estimator forwards

Most prior estimators wrap another and return a carrier built from the one they were handed. Which fields survive that hop is governed by a single rule, enforced by forward_prior:

Forward when forwarding is correct; drop only where forwarding would state something false; document every drop in the estimator's docstring.

Consistency of the returned result is the criterion, and destroying a value the caller explicitly computed is not an acceptable way to buy it — so forwarding is the default, and each estimator's docstring lists the fields it drops and why. Two fields are bound to another and therefore never forwarded alone: chol is bound to sigma (it takes precedence over sigma at every consumer, so a stale factor is silently used in place of the updated covariance), and ens, kld and ow are bound to w (they are diagnostics of those weights). forward_prior refuses a forward that would break either binding.

The original returns matrix

Those same three estimators are the reason o_X exists. They overwrite X, so on their carriers X is a posterior matrix — the asset distribution this prior asserts — and not the returns the caller supplied. o_X holds the returns the caller supplied. It is nothing everywhere else, where X already is them.

The two matrices are not interchangeable. The reconstruction spans only the factors: it has rank size(F, 2), and the residual is absent. A consumer that refits a moment on the sample must therefore read the original, or it gets a singular matrix whenever there are more assets than factors.

Read it as original_X, never as o_X. The property is always a matrix — the field where there is one, X where there is not — so a consumer needs no fallback and cannot forget one. The field is storage, and it answers a different question: isnothing(pr.o_X) is how to ask whether this carrier reconstructed X. The field carries the state rather than the property carrying it, because forward_prior rebuilds through the keyword constructor with every field named, and a nothing is inert there where an always-populated matrix would go stale past a change to X.

o_X requires rr. Every estimator that overwrites X today does so by projecting a factor prior through regression loadings, so a carrier claiming a reconstruction it cannot explain is a bug. This is a present-tense constraint rather than a law of the domain, and a future estimator that transforms X without a regression must relax it deliberately.

Validation

  • X, mu, and sigma must be non-empty.
  • size(sigma, 1) == size(sigma, 2).
  • size(X, 2) == length(mu) == size(sigma, 1).
  • If w is not nothing, !isempty(w) and length(w) == size(X, 1).
  • If kld is an AbstractVector, !isempty(kld).
  • If ow is not nothing, !isempty(ow).
  • rr and fpr must be provided together or not at all.
  • If the factor block is present, size(rr.M, 2) == length(fpr.mu) == size(fpr.sigma, 1), size(rr.M, 1) == length(mu), and size(fpr.X, 1) == size(X, 1) — the two blocks describe the same observations. Everything internal to the factor block, including its own w against its own X, is validated by its own constructor.
  • If o_X is not nothing, o_X !== X, size(o_X) == size(X), and rr is not nothing. o_X !== X is an identity test and not an equality test, so o_X = copy(X) is admitted where o_X = X raises. The two calls read identically at a call site, and only the first carries a matrix a later change to X cannot follow. What the guard rejects is the carrier that has no original distinct from the one it asserts, not a matrix whose values happen to agree.
  • If chol is not nothing, !isempty(chol) and length(mu) == size(chol, 2).

View parameters

LowOrderPrior defines its own port_opt_view method rather than deriving one from field tags.

  • It reads no argument beyond i. Further positional arguments are accepted and ignored.
  • rr recurses through port_opt_view with i, which cuts the loadings down on their asset axis.
  • X, o_X, mu, sigma and chol are sliced to i on the asset axis. o_X takes the same cut as X, so a subproblem's original returns stay the caller's returns for that subproblem's assets.
  • w, ens, kld and ow pass through unchanged. They live on the observation axis, and i indexes assets.
  • fpr passes through unchanged, because it is a distribution over factors rather than over assets. It is why the view keeps rr and fpr together, and so keeps the carrier's own factor-block rule satisfied.

Examples

julia> LowOrderPrior(; X = [0.01 0.02; 0.03 0.04], mu = [0.02, 0.03],                     sigma = [0.0001 0.0002; 0.0002 0.0003])LowOrderPrior      X ┼ 2×2 Matrix{Float64}    o_X ┼ nothing     mu ┼ Vector{Float64}: [0.02, 0.03]  sigma ┼ 2×2 Matrix{Float64}   chol ┼ nothing      w ┼ nothing    ens ┼ nothing    kld ┼ nothing     ow ┼ nothing     rr ┼ nothing    fpr ┴ nothing

Related

source
PortfolioOptimisers.HighOrderPriorType
struct HighOrderPrior{__T_pr, __T_kt, __T_D2, __T_L2, __T_S2, __T_sk, __T_V, __T_skmp, __T_fpr} <: AbstractPriorResult

Carries the coskewness and cokurtosis a high order prior estimator produced, over the low order prior it wraps.

HighOrderPrior stores the output of high order prior estimation routines, including low order prior results, cokurtosis tensor, elimination and summation matrices, coskewness tensor, quadratic skewness matrix, and matrix processing estimator. It is used throughout the package to represent validated prior information for portfolio optimisation and analytics involving higher moments.

Fields

  • pr: Prior result.
  • kt: Cokurtosis matrix assets^2 × assets^2.
  • D2: Duplication matrix.
  • L2: Elimination matrix.
  • S2: Summation matrix.
  • sk: Coskewness matrix assets × assets^2.
  • V: Sum of the negative spectral slices of the coskewness matrix assets × assets.
  • skmp: Coskewness matrix processing estimator.
  • fpr: Prior result over the factor axis, or nothing. Its X is the factor returns matrix, so its mu, sigma and w describe factors rather than assets, over the same observations as the asset block.

Constructors

HighOrderPrior(;    pr::AbstractPriorResult,    kt::Option{<:MatNum} = nothing,    D2::Option{<:MatNum} = nothing,    L2::Option{<:MatNum} = nothing,    S2::Option{<:MatNum} = nothing,    sk::Option{<:MatNum} = nothing,    V::Option{<:MatNum} = nothing,    skmp::Option{<:AbstractMatrixProcessingEstimator} = nothing,    fpr::Option{<:HighOrderPrior} = nothing) -> HighOrderPrior

Keywords correspond to the struct's fields.

The factor block

A high order prior fit through a factor model carries factor co-moments alongside the asset ones. They are a nested HighOrderPrior in fpr rather than the f_-prefixed flat fields f_kt, f_sk and f_V, so the factor block gains every field the carrier has — D2, L2, S2 and skmp as well as kt, sk and V — and gains any field added in future without a second edit. The flat names remain readable as virtual reads of it: pr.f_kt, pr.f_sk and pr.f_V return fpr.kt, fpr.sk and fpr.V, or nothing when there is no factor block, and pr.f_D2, pr.f_L2, pr.f_S2 and pr.f_skmp come with them.

fpr.pr is the factor block one order down: the LowOrderPrior over the factors. The same distribution is also reachable as pr.pr.fpr, the low order carrier's own factor block, and the constructor enforces that the two are the same object — see the validation below.

fpr is this carrier's own field, so it resolves ahead of the forward(pr) block and names the high order factor block, where before nesting it resolved through to the low order one. Reads through it are unaffected by that shift: the nested carrier forwards to its own pr, which the invariant pins to pr.fpr, so hop.fpr.mu is the factor mean either way and hop.fpr is simply "the factor prior at this order".

Which read is idiomatic

pr.fpr.kt is the public read, on the same terms as on LowOrderPrior — see the fuller reasoning there. The seven flat names here are a frozen compatibility surface: f_kt, f_sk, f_V, f_D2, f_L2, f_S2 and f_skmp, and no more will be added. A field added to this carrier in future is reachable as pr.fpr.<name> and gains no f_ counterpart.

As there, the two reads differ where the block is absent — pr.f_kt returns nothing, pr.fpr.kt throws — so guard first and then read through fpr.

Validation

Defining N = length(pr.mu).

  • If any of kt, L2, or S2 are provided, all must be provided, non-empty, and size(kt) == (N^2, N^2), size(L2) == size(S2) == (div(N * (N + 1), 2), N^2).
  • If sk or V are provided, both must be provided, non-empty, and size(sk) == (N, N^2), size(V) == (N, N).
  • If that first triple is provided and sk is too, D2 must be provided, non-empty, and size(D2) == size(transpose(L2)). D2 carries no other rule: it is the one moment field the constructor accepts on its own, and a carrier holding it alone is legal.
  • If fpr is provided, pr.fpr must be provided and fpr.pr === pr.fpr — the factor distribution the factor co-moments were computed against is the low order carrier's own factor block, not a second copy of it. The converse does not hold: a low order factor block with no factor co-moments is ordinary, so fpr === nothing is always allowed. Everything internal to the factor block, including its own shapes against its own N, is validated by its own constructor.

View parameters

HighOrderPrior defines its own port_opt_view method rather than deriving one from field tags.

  • It reads no argument beyond i. Further positional arguments are accepted and ignored.
  • pr recurses through port_opt_view with i, which is where every low order field is cut.
  • kt is indexed by a fourth-moment index derived from i, not by i itself. It is $N^2 \times N^2$ over ordered pairs of assets, so the asset index does not address it.
  • sk is cut by i on its asset axis and by that same fourth-moment index on its pair axis.
  • V is recomputed from the cut sk rather than cut. It is a spectral quantity of the coskewness matrix, so the submatrix of V is not the V of the submatrix.
  • D2, L2 and S2 are rebuilt at the subproblem's asset count rather than cut. They are combinatorial matrices of that count alone, and carry no asset-specific content to preserve.
  • skmp passes through unchanged. It is the matrix-processing estimator, which the recomputation of V reads.
  • fpr passes through unchanged, because it holds co-moments over factors rather than over assets. Forwarding it by identity is also what keeps fpr.pr === pr.fpr true of the view, since the low order view forwards its own factor block the same way.

Examples

julia> HighOrderPrior(;                      pr = LowOrderPrior(; X = [0.01 0.02; 0.03 0.04], mu = [0.02, 0.03],                                         sigma = [0.0001 0.0002; 0.0002 0.0003]), kt = rand(4, 4),                      D2 = PortfolioOptimisers.duplication_matrix(2),                      L2 = PortfolioOptimisers.elimination_matrix(2),                      S2 = PortfolioOptimisers.summation_matrix(2), sk = rand(2, 4),                      V = rand(2, 2))HighOrderPrior    pr ┼ LowOrderPrior       │       X ┼ 2×2 Matrix{Float64}       │     o_X ┼ nothing       │      mu ┼ Vector{Float64}: [0.02, 0.03]       │   sigma ┼ 2×2 Matrix{Float64}       │    chol ┼ nothing       │       w ┼ nothing       │     ens ┼ nothing       │     kld ┼ nothing       │      ow ┼ nothing       │      rr ┼ nothing       │     fpr ┴ nothing    kt ┼ 4×4 Matrix{Float64}    D2 ┼ 4×3 SparseArrays.SparseMatrixCSC{Int64, Int64}    L2 ┼ 3×4 SparseArrays.SparseMatrixCSC{Int64, Int64}    S2 ┼ 3×4 SparseArrays.SparseMatrixCSC{Int64, Int64}    sk ┼ 2×4 Matrix{Float64}     V ┼ 2×2 Matrix{Float64}  skmp ┼ nothing   fpr ┴ nothing

Related

source
PortfolioOptimisers.priorMethod
prior(pe::AbstractPriorEstimator, rd::ReturnsResult; kwargs...)

Compute prior information from asset and/or factor returns using a prior estimator.

prior applies the specified prior estimator to a ReturnsResult object, extracting asset and factor returns and passing them, along with any additional information, to the estimator. Returns a prior result containing computed moments and other prior information for use in portfolio optimisation workflows.

This method is the entry point every caller uses, and it is written once here. What each estimator implements is the returns-matrix method that this one delegates to; AbstractPriorEstimator states that contract.

Algorithm

  1. Check that rd carries asset returns, so that the estimator is not handed a nothing for X.
  2. When pe requires factor returns — when needs_factor_returns answers true, which walks the tree to a factor leaf under an optional-argument host — check that rd carries them. The check is made here so that the caller reads a named error against rd.F rather than a MethodError against the leaf's own signature one call later.
  3. Call the estimator's returns-matrix method with rd.X, rd.F and rd.pnl, forwarding rd.iv and rd.ivpa as keyword arguments alongside kwargs, and return the prior result it produces.

The Asset Panel travels as the third positional argument for the same reason rd.F travels as the second: a wrapping prior holds no carrier, so it can compose an estimator that is fitted on a panel only if the panel reaches its own returns-matrix method. Every returns-matrix method takes the argument, every wrapping prior forwards it unchanged to the estimator it nests over the assets, and an estimator that reads no panel ignores it.

Arguments

  • pe: Prior estimator.
  • rd: Asset and/or factor returns result.
  • kwargs...: Additional keyword arguments passed to the estimator.

Validation

  • !isnothing(rd.X).
  • !isnothing(rd.F), when needs_factor_returns(pe) === true.

Returns

  • pr::AbstractPriorResult: Result object containing computed prior information.

Related

source
PortfolioOptimisers.priorMethod
prior(pr::AbstractPriorResult, args...; kwargs...)

Propagate or pass through prior result objects.

prior returns the input prior result object unchanged. This method is used to propagate already constructed prior results or enable uniform interface handling in workflows that accept either estimators or results.

It is the second half of PrE_Pr: a slot bounded by that union calls prior once, and this method is why a slot holding an already-fitted result needs no branch of its own. Every further argument is accepted and ignored, so the call site does not change either.

Arguments

  • pr: Prior result.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • pr::AbstractPriorResult: The input prior result object, unchanged.

Related

source
PortfolioOptimisers.forward_priorFunction
forward_prior(
    pr::AbstractPriorResult;
    overrides...
) -> AbstractPriorResult

Forward a wrapped prior result, spelling out only what the wrapping estimator changes or drops.

This is the mechanical half of the composition rule:

Forward when forwarding is correct; drop only where forwarding would state something false; document every drop in the estimator's docstring.

Forwarding is the default and costs nothing to write, so a wrapper cannot accidentally return a narrower result than the one it wraps. Every deviation is spelled at the call site — a new value as field = value, a drop as field = nothing — which makes the set of drops greppable and reviewable instead of implicit in a hand-written constructor call listing all thirteen fields.

Reconstruction goes through the carrier's ordinary keyword constructor (see reconstruct_prior), so every @argcheck runs: a forward that leaves the carrier internally inconsistent throws exactly as a hand-written constructor call would. Only the carrier's own fields may be named — a forwarded or computed property is a view of a nested value, so setting it could only ever mean setting the field that value came from.

The three enforced bindings

Three fields are bound to another field's value rather than being independent, so forwarding them past a change to the field they describe is what the rule calls stating something false. Because the binding is mechanical, the helper enforces it rather than leaving it to reviewer memory — naming the field on the left obliges the caller to name the fields on the right, either with a rebuilt value or with nothing:

  • sigma binds chol. chol takes precedence over sigma at every consumer, so a stale chol makes the optimisation silently ignore the posterior covariance.
  • w binds ens, kld and ow. Those are diagnostics of w; weights carrying another weighting's provenance cannot be interrogated.
  • rr binds o_X. o_X says X is a reconstruction, and rr is what records the projection that produced it, so the carrier refuses one without the other. Dropping the factor block therefore drops the original with it.

A binding is inert when the bound field is already nothing (there is nothing stale to carry) or absent from the carrier.

Everything else the constructor already covers: rr and fpr must be supplied together or not at all, and w, chol and Z are re-checked against the shape of X and mu.

What does not fit

The estimators that lift a factor-axis prior into an asset-axis result (FactorPrior, FactorBlackLittermanPrior) and the one that merges two priors (AugmentedBlackLittermanPrior) are not forwarding a single wrapped result along its own axis, so they construct their carrier directly and should not be forced through this helper. forward_prior still applies to the factor block they build, which is an ordinary forward of the factor prior.

Algorithm

  1. Collect the keyword overrides into the named tuple patch. When patch is empty, return pr itself: a forward that changes nothing rebuilds nothing.
  2. Compare the names of patch against the fields of typeof(pr), giving extra, the names that are not fields. A non-empty extra raises an ArgumentError naming the carrier's fields.
  3. Enforce the binding of chol to sigma. When patch names sigma, does not name chol, and bound_field_is_stale says pr holds a chol, raise a ConflictingArgumentError.
  4. Enforce the binding of o_X to rr, on the same three tests, giving the second ConflictingArgumentError.
  5. Enforce the binding of ens, kld and ow to w. When patch names w, collect into stale each of the three that patch does not name and that pr holds, and raise when stale is non-empty.
  6. Rebuild the carrier through reconstruct_prior, which merges patch over prior_field_values and calls the ordinary keyword constructor, so every @argcheck of the carrier runs on the result.

Arguments

  • pr: Prior result produced by the wrapped estimator.
  • overrides...: Field overrides; a value to replace, or nothing to drop.

Validation

  • Naming sigma requires naming chol, unless pr.chol is already nothing.
  • Naming w requires naming each of ens, kld and ow that is not already nothing.
  • Naming rr requires naming o_X, unless pr.o_X is already nothing.
  • Every name in overrides is a field of typeof(pr).
  • Every @argcheck of the constructor of typeof(pr).

Returns

  • pr::AbstractPriorResult: The wrapped result with overrides applied, or pr itself when there are none.

Examples

julia> pr = LowOrderPrior(; X = [0.01 0.02; 0.03 0.04], mu = [0.02, 0.03],                          sigma = [0.0004 0.0002; 0.0002 0.0003], chol = [0.02 0.01; 0.0 0.01415]);julia> PortfolioOptimisers.forward_prior(pr) === prtruejulia> pr2 = PortfolioOptimisers.forward_prior(pr; mu = [0.05, 0.06], chol = nothing);julia> (pr2.mu, pr2.chol, pr2.sigma === pr.sigma)([0.05, 0.06], nothing, true)julia> PortfolioOptimisers.forward_prior(pr; sigma = [0.0009 0.0001; 0.0001 0.0004])ERROR: ConflictingArgumentError: forwarding `chol` past a change to `sigma` would state something false: `chol` takes precedence over `sigma` at every consumer, so a stale factor makes the optimisation silently ignore the updated covariance. Pass `chol = nothing` to drop it, or a factor rebuilt from the new `sigma`.[...]

Related

source
PortfolioOptimisers.reconstruct_priorFunction
reconstruct_prior(
    pr::LowOrderPrior,
    patch::NamedTuple
) -> LowOrderPrior{var"#s185", _A, var"#s1851", <:AbstractMatrix{var"#s137"}} where {var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s185"<:AbstractMatrix{var"#s137"}, _A, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}

Rebuild a prior result through its ordinary keyword constructor, patching the fields named in patch.

One method per carrier, because the carrier's constructor is named here rather than recovered by reflection. Recovering it generically would mean either Base.typename(T).wrapper or a dependency on ConstructionBase, and neither buys anything: the field list is already derived, via prior_field_values, so a carrier that gains a field needs no edit here. Only a new carrier type needs a method — and until it has one it gets a MethodError naming this function, rather than being reconstructed by machinery that has never seen it.

Reconstruction runs the carrier's full validation, which is the point of routing through the constructor at all: a patch that leaves the carrier internally inconsistent throws exactly as a hand-written constructor call would. Keyword arguments are order-independent, so patch may name fields in any order.

These methods are defined here, after both carriers, because they dispatch on the concrete types.

Algorithm

Both methods run the same three steps, and differ only in the constructor step 3 names.

  1. Read the carrier's own fields into a named tuple with prior_field_values, keyed in declaration order.
  2. Merge patch over that tuple. A field patch names takes the patch's value, and every field it does not name keeps the carrier's.
  3. Splat the merged tuple into the carrier's keyword constructor — LowOrderPrior in the first method, HighOrderPrior in the second — and return the carrier it builds. Every @argcheck of that constructor runs on the merged values.

Arguments

  • pr: Prior result to rebuild.
  • patch: Named tuple of field overrides. Every name must be a field of typeof(pr)forward_prior checks that before calling, so a bad name is reported against the rule rather than as an unsupported keyword.

Returns

  • pr::AbstractPriorResult: Reconstructed result of the same carrier type.

Related

source
PortfolioOptimisers.clusteriseMethod
clusterise(cle::AbstractClustersEstimator, pr::AbstractPriorResult; kwargs...)

Clusterise asset or factor returns from a prior result using a clustering estimator.

clusterise applies the specified clustering estimator to the asset returns matrix contained in the prior result object, producing a clustering result for use in phylogeny analysis, constraint generation, or portfolio construction.

Algorithm

  1. Pick the asset returns matrix X from the carrier that x_src names, with returns_matrix_picker.
  2. Call the asset-returns method of clusterise with X, passing both carriers on as pr and rd, and return the clustering result it produces.

Arguments

  • cle: Clustering estimator.
  • pr: Prior result or returns result. Both carry the asset returns matrix X and the feature matrix Z, so either can supply them.
  • rd: The returns result to use. Read for X only when x_src is :data, and passed on to the estimator tree.
  • x_src: Which returns matrix the clustering, phylogeny and centrality estimators read: :prior takes the prior result's X, :data takes the raw returns result's X. Ignored when no returns result is available, in which case the prior result's X is used.
  • kwargs...: Additional keyword arguments passed to the clustering estimator.

Returns

  • clr::AbstractClusteringResult: Result object containing clustering information.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(pr::Option{<:AbstractPriorEstimator}, ::Any, args...; kwargs...)
port_opt_view(pr::AbstractVector{<:Union{<:AbstractPriorResult, <:AbstractPriorEstimator}},
              ::Any, args...; kwargs...)

Pass a prior estimator, or a vector of priors, through a view unchanged.

Both methods are the not-sliceable branch of port_opt_view. An estimator carries a recipe rather than data on an asset axis, so there is nothing in it to cut down: the subproblem refits it on its own universe instead. A vector arrives already resolved per subproblem — one entry per cluster or per subset — so the entry has been chosen by the time the view is taken, and slicing the vector by an asset index would cut the wrong axis.

The carriers that do hold data on the asset axis take their own methods: see port_opt_view on LowOrderPrior and on HighOrderPrior.

Arguments

  • pr: Prior estimator or result.
  • The second positional argument is the asset index. It is unnamed, because neither method reads it.
  • args...: Additional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • pr: The input, unchanged.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    pr::LowOrderPrior,
    i,
    args...
) -> LowOrderPrior{var"#s185", _A, var"#s1851", <:AbstractMatrix{var"#s137"}} where {var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s185"<:AbstractMatrix{var"#s137"}, _A, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractVector{var"#s137"}, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}

Return a view of a LowOrderPrior restricted to assets at index i.

The feature matrix is subselected on its asset axis only. Its feature axis is never sliced: a prior-side Z is derived, and every producer that builds a square one refits on the subproblem's own universe, so there is no full-universe square matrix here to cut down. Observations are taken whole (Colon): folds slice observations before the prior is fit, so a derived Z is already fold-local by the time it reaches here.

The factor block is forwarded unsliced: i indexes assets, and fpr is a distribution over factors. Only rr is cut down, on its asset axis.

Algorithm

  1. Cut the Cholesky factor to i on its column axis, giving chol. A carrier that holds none keeps nothing.
  2. Cut the original returns matrix to i on its asset axis, giving o_X. A carrier that holds none keeps nothing. It takes the same cut X takes in the next step, because the two are assets-major over the same observations.
  3. Rebuild the carrier through its ordinary keyword constructor, naming every field: X and mu cut to i, sigma cut to i on both axes, chol and o_X from the two steps above, rr recursed through port_opt_view with i, and w, ens, kld, ow and fpr forwarded unchanged. Every @argcheck of the constructor therefore runs on the view.

Arguments

  • pr: Prior result.
  • i: Asset indices the view keeps.
  • args...: Additional arguments (ignored).

Returns

  • pr::LowOrderPrior: The carrier restricted to the assets at i, holding views rather than copies.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    pr::HighOrderPrior,
    i,
    args...
) -> HighOrderPrior{<:AbstractPriorResult}

Return a view of a HighOrderPrior restricted to assets at index i, slicing all relevant moment tensors accordingly.

The factor block is forwarded unsliced, as it is on LowOrderPrior: i indexes assets, and fpr holds co-moments over factors. Forwarding it by identity is also what keeps fpr.pr === pr.fpr true of the view, since the low order view forwards its own factor block the same way.

Algorithm

  1. Make idx, the fourth-moment index that addresses the co-moment tensors of the assets at i, with fourth_moment_index_generator against the carrier's full asset count.
  2. Cut the coskewness matrix to i on its asset axis and to idx on its pair axis, with nothing_scalar_array_view_odd_order, giving sk. A carrier that holds none keeps nothing.
  3. Recompute V from the sk of step 2 and the cut returns matrix, with negative_spectral_coskewness and the carrier's skmp. V is a spectral quantity of the coskewness matrix, so it is rebuilt rather than cut. When step 2 gave nothing, V is nothing.
  4. Rebuild D2, L2 and S2 at the subproblem's asset count with dup_elim_sum_view, rather than cutting them. Take all three when the carrier holds D2, take L2 and S2 alone and leave D2 as nothing when it holds S2 but no D2, and take none when it holds neither.
  5. Rebuild the carrier through its ordinary keyword constructor: pr recursed through port_opt_view with i, kt indexed by idx, the values of steps 2 to 4, and skmp and fpr forwarded unchanged. Every @argcheck of the constructor therefore runs on the view.

Arguments

  • pr: Prior result.
  • i: Asset indices the view keeps.
  • args...: Additional arguments (ignored).

Returns

  • pr::HighOrderPrior: The carrier restricted to the assets at i.

Related

source