Stacking

PortfolioOptimisers.StackingResultType
struct StackingResult{__T_pr, __T_wb, __T_fees, __T_resi, __T_reso, __T_cv, __T_retcode, __T_w, __T_imsk, __T_fb} <: NonJuMPOptimisationResult

Result type for Stacking.

resi holds one entry per inner optimiser, in the order of the estimator's opti. reso is the outer optimisation over the synthetic universe those entries define, so reso.w has one entry per inner optimiser rather than one per asset. w is the combination of the two.

Fields

  • pr: Prior result.
  • wb: Weight bounds.
  • fees: Fees estimator or result.
  • resi: Inner optimisation results.
  • reso: Outer optimisation results.
  • cv: Cross-validation estimator.
  • retcode: Optimisation return code.
  • w: Final aggregated portfolio weights.
  • imsk: The Investable Mask the optimisation reduced on: true at every asset whose prior moments were finite. It is nothing when every asset was investable, and that sentinel is what skips both the reduction and the expansion. investable_mask derives it once from the full-universe prior result, and the result carries it, because the reduced prior can no longer yield it.
  • fb: The fallback chain that answered this result: the (estimator, result) pair of every attempt optimise made before this one, in the order they ran, or nothing when the estimator it was asked of answered (see FbChain).

Constructors

StackingResult(;    pr::Option{<:AbstractPriorResult},    wb::Option{<:WeightBounds},    fees::Option{<:Fees},    resi::AbstractVector{<:NonFiniteAllocationOptimisationResult},    reso::OptimisationResult,    cv::Option{<:OptimisationCrossValidation},    retcode::OptRetCode_VecOptRetCode,    w::VecNum_VecVecNum,    imsk::Option{<:BitVector} = nothing,    fb::Option{<:OptE_Opt_FbChain}) -> StackingResult

Keywords correspond to the struct's fields. The keyword constructor expands w onto the full asset universe through expand_investable_weights, which is the one door _optimise exits through. The positional constructor never expands, so set_retcode and factory rebuild without a second pass.

Related

References

  • [143] D. H. Wolpert. Stacked generalization. Neural Networks 5, 241–259 (1992).
source
PortfolioOptimisers.StackingType
struct Stacking{__T_pe, __T_wb, __T_fees, __T_sets, __T_scale, __T_opti, __T_opto, __T_cv, __T_wf, __T_ex, __T_fb, __T_brt, __T_strict, __T_cache} <: BaseStackingOptimisationEstimator

Stacking portfolio optimiser.

Stacking implements a stacking (model combination) approach to portfolio optimisation. It applies multiple inner optimisers (opti) to the data, then combines their outputs with a single outer optimiser (opto) to produce a final portfolio. Optionally, cross-validation can be used to weight the inner optimisers' contributions.

Fields

  • pe: Prior estimator.
  • wb: Weight bounds estimator or weight bounds.
  • fees: Fees estimator.
  • sets: Sets used to map estimator values to assets.
  • scale: Optional Combination Weight over the inner optimisers, one entry per element of opti: the weight inner optimiser k carries inside the combination that opto's answer defines. Only the ratios between the entries matter, because combination_weights rescales the tilted coefficients back to opto's own total — so a common factor cancels, a uniform weight is neutral, and a lone inner optimiser is inert. nothing leaves opto's answer alone.
  • opti: Inner optimiser.
  • opto: Outer optimiser.
  • cv: Cross-validation estimator.
  • wf: Weight finaliser.
  • ex: Parallel execution strategy.
  • fb: Fallback result or estimator.
  • brt: Whether to use bootstrap returns.
  • strict: Whether to strictly enforce weight bounds.
  • cache: Optional ReturnsBufferState, the fold context of the online step. It is nothing until partial_fit! writes one, and optimise(opt) with no returns reads it. The returns themselves are carried by the prior, which owns the rows once; this holds every other column of the carrier and the context pinned at the first step. factory carries it unchanged and port_opt_view slices it to the selected assets.

Constructors

Stacking(;    pe::Onl{<:TD{<:PrE_Pr}} = EmpiricalPrior(),    wb::TD_Option{<:WbE_Wb} = nothing,    fees::TD_Option{<:FeesE_Fees} = nothing,    sets::TD_Option{<:UniverseSets} = nothing,    scale::TD_Option{<:VecNum} = nothing,    opti::Union{<:AbstractVector, <:TD_VecOptE_Opt},    opto::OptE_TD,    cv::Option{<:OptimisationCrossValidation} = nothing,    wf::TD{<:WeightFinaliser} = IterativeWeightFinaliser(),    ex::FLoops.Transducers.Executor = FLoops.ThreadedEx(),    fb::TDO_Option{<:OptE_Opt} = nothing,    brt::Bool = false,    strict::Bool = false,    cache::Option{<:ReturnsBufferState} = nothing) -> Stacking

Keywords correspond to the struct's fields.

Time-dependent fields

pe, wb, fees, sets, scale, wf, opto and fb may hold a TimeDependent per-fold schedule — no inner fold loop of Stacking consumes them, so the fold loop that reaches the Stacking resolves them; the optimiser positions opto and fb are bind = :outermost only. opti admits schedules at two levels:

  • Element (opti = [static, TimeDependent(…)]): the element is an optimiser position of the inner cross-validation (entered per candidate), so bind = :nearest is legal there — with a mandatory explicit default and cv !== nothing, because the full-sample wi fit always resolves the element fold-lessly to its default (see assert_nearest_optimiser_schedule).
  • Field (opti = TimeDependent([[…], […]]), a per-fold vector of candidate vectors, see TD_VecOptE_Opt): bind = :outermost only. A :nearest field-level schedule is rejected — the inner cross-validation is handed the elements, never the field, and a per-fold candidate vector would change the number and identity of the returns-proxy columns opto sees.

Validation

  • If opti is a vector: !isempty(opti), every element is an OptE_Opt_TD, and any bind = :nearest element schedule has an explicit default and cv !== nothing.
  • If opti is a TimeDependent: bind !== :nearest.
  • If scale is provided and static: all elements are finite, and length(scale) == length(opti) when opti is a vector.
  • opto and fb schedules: bind !== :nearest.

Mathematical definition

Let $K$ inner optimisers produce weight vectors $\boldsymbol{w}_1, \ldots, \boldsymbol{w}_K$. Each one defines a synthetic asset whose return series is its portfolio's, and the outer optimiser allocates across that synthetic universe. The Combination Weight then re-weights the outer answer:

\[\begin{align} \boldsymbol{R}_{\cdot k} &= \boldsymbol{X} \boldsymbol{w}_k\,,\\ \boldsymbol{v} &= \mathrm{opto}(\boldsymbol{R})\,,\\ c_k &= \frac{s_k v_k}{\sum_{j=1}^{K} s_j v_j} \sum_{j=1}^{K} v_j\,,\\ \boldsymbol{w}^* &= \sum_{k=1}^{K} c_k \boldsymbol{w}_k\,. \end{align}\]

Where:

  • $\boldsymbol{w}^*$: Final stacked portfolio weights.
  • $K$: Number of inner optimisers.
  • $\boldsymbol{X}$: Asset returns matrix.
  • $\boldsymbol{R}$: Returns proxy matrix, one column per synthetic asset.
  • $\boldsymbol{v}$: Outer optimiser weights over the synthetic universe.
  • $s_k$: Combination Weight of inner optimiser $k$. Absent, or uniform, $\boldsymbol{c} = \boldsymbol{v}$.
  • $c_k$: Coefficient inner optimiser $k$ carries in the combination.
  • $\mathrm{opto}$: Outer optimiser applied to the synthetic universe.

The outer problem is built from $\boldsymbol{w}_k$, never from $s_k \boldsymbol{w}_k$: the weight acts at the combination alone, so a cross-validated run and a fold-less one agree on it (see combination_weights).

$\boldsymbol{w}^*$ then passes through wf and wb (see finalise_weight_bounds), which is what the result's w and retcode carry.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • fees: Recursively updated via factory.
  • opti: Recursively updated via factory.
  • opto: Recursively updated via factory.
  • fb: Recursively updated via factory.

View parameters

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

  • The method reads the returns matrix X as its third argument. When pe already holds a prior result, the method replaces X with pe.X, so the children are viewed against the prior's own observations rather than the caller's matrix.
  • pe, wb, fees and sets recurse through port_opt_view with the index alone.
  • opti and opto recurse with that matrix.
  • scale is carried through unchanged, because it holds one entry per inner optimiser rather than one per asset.
  • The remaining fields are carried through unchanged.

Related

References

  • [143] D. H. Wolpert. Stacked generalization. Neural Networks 5, 241–259 (1992).
source
PortfolioOptimisers.factoryMethod
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a
factory(a::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                                  <:AbstractResult}}, args...; kwargs...) -> Vector

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

The vector method is the one forwarding contract for every vector-valued propagation field: it applies factory to each element and forwards args... and kwargs... unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract), defines its own more specific method.

Algorithm

The scalar method:

  1. Return a unchanged, and drop args... and kwargs.... This method is the leaf of the recursion, and it is what makes an untagged type safe to call the verb on.

The vector method:

  1. For each element ai of a, call factory on ai, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of a, and return it.

A @propagatable struct with at least one @fprop- or @wprop-tagged field carries a generated method that dominates the scalar method. That method rebuilds the struct with its keyword constructor, sending each @fprop field through factory_child and each @wprop field through _wprop.

Arguments

  • a: Indicates no object should be constructed, or a vector whose elements are rebuilt one by one.
  • args...: Arbitrary positional arguments (ignored by the scalar method, forwarded by the vector method).
  • kwargs...: Arbitrary keyword arguments (ignored by the scalar method, forwarded by the vector method).

Returns

  • a: The input unchanged.
  • v::Vector: The element-wise rebuilds, for the vector method.

Examples

julia> factory(nothing, 1, 2; x = 3)julia> factory(MeanValue())MeanValue  w ┴ nothing

Related

source
factory(res::NonFiniteAllocationOptimisationResult, fb::Option{<:OptE_Opt_FbChain})

Rebuild a continuous optimisation result with an updated fallback record fb.

Every optimisation result carries fb as its last field, so the generic rebuild copies all fields unchanged except the trailing fb. Concrete result types may override this method when rebuilding requires more than swapping fb. optimise is the one caller, and it hands in the FbChain it walked.

Related

source
factory(
    opt::Union{NonFiniteAllocationOptimisationEstimator, NonFiniteAllocationOptimisationResult},
    _
) -> RandomWeighted{_A, var"#s185", _B, _C, _D, _E, _F, Bool} where {_A, var"#s185"<:AbstractRNG, _B, _C, _D, _E, _F}

Return opt unchanged.

Default pass-through factory for optimisation estimators and results. Overridden for estimators that carry parameters requiring update at each optimisation step.

Related

source
PortfolioOptimisers.factoryMethod
factory(a::Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                 <:AbstractResult}, args...; kwargs...) -> a
factory(a::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm,
                                  <:AbstractResult}}, args...; kwargs...) -> Vector

No-op factory function for constructing objects with a uniform interface.

Defining methods which dispatch on the first argument allows for a consistent factory interface across different types.

factory and port_opt_view are the two propagation mechanisms in this library. They are duals: factory threads runtime values (prior moments, observation weights, previous portfolio weights) down through a composed struct tree; port_opt_view threads an index selection (a subset of assets or observations) down through the same tree.

The vector method is the one forwarding contract for every vector-valued propagation field: it applies factory to each element and forwards args... and kwargs... unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract), defines its own more specific method.

Algorithm

The scalar method:

  1. Return a unchanged, and drop args... and kwargs.... This method is the leaf of the recursion, and it is what makes an untagged type safe to call the verb on.

The vector method:

  1. For each element ai of a, call factory on ai, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of a, and return it.

A @propagatable struct with at least one @fprop- or @wprop-tagged field carries a generated method that dominates the scalar method. That method rebuilds the struct with its keyword constructor, sending each @fprop field through factory_child and each @wprop field through _wprop.

Arguments

  • a: Indicates no object should be constructed, or a vector whose elements are rebuilt one by one.
  • args...: Arbitrary positional arguments (ignored by the scalar method, forwarded by the vector method).
  • kwargs...: Arbitrary keyword arguments (ignored by the scalar method, forwarded by the vector method).

Returns

  • a: The input unchanged.
  • v::Vector: The element-wise rebuilds, for the vector method.

Examples

julia> factory(nothing, 1, 2; x = 3)julia> factory(MeanValue())MeanValue  w ┴ nothing

Related

source
factory(
    opt::Union{NonFiniteAllocationOptimisationEstimator, NonFiniteAllocationOptimisationResult},
    _
) -> RandomWeighted{_A, var"#s185", _B, _C, _D, _E, _F, Bool} where {_A, var"#s185"<:AbstractRNG, _B, _C, _D, _E, _F}

Return opt unchanged.

Default pass-through factory for optimisation estimators and results. Overridden for estimators that carry parameters requiring update at each optimisation step.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    st::Stacking,
    i,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    args...
) -> Stacking{_A, _B, _C, _D, _E, _F, _G, _H, _I, var"#s185", _J, Bool, Bool} where {_A, _B, _C, _D, _E, _F, _G, _H, _I, var"#s185"<:Transducers.Executor, _J}

Return a cluster-sliced copy of Stacking for asset index set i and returns matrix X.

source
PortfolioOptimisers.optimiseMethod
optimise(st::Stacking{<:Any, <:Any, <:Any, <:Any, <:Any, <:Any, <:Any, <:Any,
                 <:Any, <:Any, Nothing
             }, rd::ReturnsResult;
         dims::Int = 1, branchorder::Symbol = :optimal, str_names::Bool = false,
         save::Bool = true, kwargs...) -> StackingResult

Run the Stacking portfolio optimisation.

Arguments

  • st: The stacking optimiser to use.
  • rd: The returns result to use.
  • dims: The dimension along which observations advance in time.
  • branchorder: Passed to the inner and outer optimisers. The branch order to use for the clusterisation.
  • str_names: Passed to the inner and outer optimisers. Whether to use string names for the assets in the optimisation.
  • save: Passed to the inner and outer optimisers. Whether to save the JuMP model in the optimisation result.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Validation

  • No field in the tree of st holds an Online. An ArgumentError naming the field is thrown otherwise, through assert_batch_entry: a plain optimise is a batch fit, and a wrapper resolves only at the warm-up of the fold loop's online arm.

Returns

  • res::StackingResult: The combined portfolio. retcode is an OptimisationFailure when any inner optimisation, the outer optimisation, or the weight finalisation failed.

Related

source

References

[143]
D. H. Wolpert. Stacked generalization. Neural Networks 5, 241–259 (1992).