Subset resampling

PortfolioOptimisers.SubsetResamplingResultType
struct SubsetResamplingResult{__T_pr, __T_wb, __T_fees, __T_ress, __T_idx, __T_retcode, __T_w, __T_imsk, __T_fb} <: NonJuMPOptimisationResult

Result type for SubsetResampling.

ress and idx are aligned: ress[m] is the optimisation of the subset whose asset indices are idx[:, m], and w is the average of those results embedded back into the full universe.

idx indexes the universe the subsets were drawn from, which is the reduced universe when imsk is not nothing, as pr, wb and fees are. w alone is on the full asset universe.

Fields

  • pr: Prior result.
  • wb: Weight bounds.
  • fees: Fees estimator or result.
  • ress: Optimisation result of each asset subset, one entry per subset, in the column order of idx.
  • idx: Asset indices of each subset, one column per subset, so size(idx) == (subset_size, n_subsets). They index the reduced universe when imsk is not nothing.
  • retcode: Optimisation return code.
  • w: Aggregated optimal 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

SubsetResamplingResult(;    pr::Option{<:AbstractPriorResult},    wb::Option{<:WeightBounds},    fees::Option{<:Fees},    ress::AbstractVector{<:NonFiniteAllocationOptimisationResult},    idx::MatNum,    retcode::OptRetCode_VecOptRetCode,    w::VecNum_VecVecNum,    imsk::Option{<:BitVector} = nothing,    fb::Option{<:OptE_Opt_FbChain}) -> SubsetResamplingResult

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

  • [144] W. Shen and J. Wang. Portfolio selection via subset resampling. In: Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence (2017); pp. 1517–1523.
source
PortfolioOptimisers.SubsetResamplingType
struct SubsetResampling{__T_pe, __T_wb, __T_fees, __T_sets, __T_opt, __T_wf, __T_ex, __T_subset_size, __T_n_subsets, __T_max_comb, __T_rng, __T_seed, __T_fb, __T_brt, __T_strict, __T_cache} <: BaseSubsetResamplingOptimisationEstimator

Subset Resampling portfolio optimiser.

SubsetResampling applies a resampling strategy by optimising a base optimiser (opt) over randomly drawn subsets of assets, then aggregating the results into a final portfolio weight vector. This improves robustness of portfolio weights to estimation error.

Fields

  • pe: Prior estimator.
  • wb: Weight bounds estimator or weight bounds.
  • fees: Fees estimator.
  • sets: Sets used to map estimator values to assets.
  • opt: Base portfolio optimiser applied to each asset subset.
  • wf: Weight finaliser.
  • ex: Parallel execution strategy.
  • subset_size: Size of each random subset.
  • n_subsets: Number of random subsets.
  • max_comb: Maximum number of unique asset subsets.
  • rng: Random number generator.
  • seed: Seed for the random number generator.
  • 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

SubsetResampling(;    pe::Onl{<:TD{<:PrE_Pr}} = EmpiricalPrior(),    wb::TD_Option{<:WbE_Wb} = nothing,    fees::TD_Option{<:FeesE_Fees} = nothing,    sets::TD_Option{<:UniverseSets} = nothing,    opt::OptE_TD,    wf::TD{<:WeightFinaliser} = IterativeWeightFinaliser(),    ex::FLoops.Transducers.Executor = FLoops.ThreadedEx(),    subset_size::TD{<:SubsetSizeE} = 0.8,    n_subsets::TD{<:NumberSubsetsE} = 2,    max_comb::Integer = 1_000_000_000,    rng::Random.AbstractRNG = Random.default_rng(),    seed::Option{<:Integer} = nothing,    fb::TDO_Option{<:OptE_Opt} = nothing,    brt::Bool = false,    strict::Bool = false,    cache::Option{<:ReturnsBufferState} = nothing) -> SubsetResampling

Keywords correspond to the struct's fields.

Time-dependent fields

pe, wb, fees, sets, opt, wf, subset_size, n_subsets and fb may hold a TimeDependent per-fold schedule. The optimiser-valued positions opt and fb are bind = :outermost only: SubsetResampling's internal loop is over randomly drawn asset subsets, not time folds, so there is no inner fold loop for a :nearest optimiser schedule to bind to and it is rejected at construction. The fold loop that reaches the SubsetResampling resolves its schedules; a fold-less solve resets pe/wf/subset_size/n_subsets to their static defaults, wb/fees/sets/fb to nothing, and requires an opt schedule to carry its own default. max_comb, rng and seed are execution control and stay static.

Validation

  • opt must pass assert_internal_optimiser (a schedule delegates to its entries and default).
  • If wb is a WeightBoundsEstimator: !isnothing(sets).
  • If fees is a FeesEstimator: !isnothing(sets).
  • If subset_size is an Integer: subset_size >= 1.
  • If subset_size is a Float: 0 < subset_size < 1.
  • If n_subsets is an Integer: n_subsets >= 2.
  • max_comb > 0 and finite.
  • opt and fb schedules: bind !== :nearest; vector-schedule entries of every time-dependent field are validated per entry through the constructor.

Mathematical definition

Draw $M$ distinct random subsets $S_1, \ldots, S_M$ of the $N$ assets, each of size $k$. Optimise on each, embed the answer back into the full universe, and average:

\[\begin{align} k &= \begin{cases} \mathrm{subset\_size} & \text{if } \mathrm{subset\_size} \in \mathbb{Z}\,, \\ \max\!\left(\mathrm{round}(s N),\, 1\right) & \text{if } s = \mathrm{subset\_size} \in (0, 1)\,, \end{cases} \\ \boldsymbol{w}^* &= \frac{1}{M} \sum_{m=1}^{M} \boldsymbol{e}_{S_m}(\boldsymbol{w}_{S_m})\,. \end{align}\]

Where:

  • $\boldsymbol{w}^*$: Averaged portfolio weights, before the weight finalisation below.
  • $M$: Number of random subsets, n_subsets.
  • $k$: Size of each subset, subset_size resolved against the universe.
  • $S_m$: $m$-th randomly drawn asset subset.
  • $\boldsymbol{w}_{S_m}$: Optimal weights from opt applied to subset $S_m$.
  • $\boldsymbol{e}_{S_m}(\cdot)$: Embedding operator that places subset weights into full $N$-asset space (zero-filling excluded assets).
  • $N$: Total number of assets.
  • $s$: Fractional subset_size.

A fractional subset_size rounds; it does not truncate. At the default $s = 0.8$ on $N = 12$ assets the subsets hold $10$ assets, not $9$.

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

The subsets are drawn without repetition, so n_subsets may not exceed $\binom{N}{k}$; the constructor's max_comb caps the enumeration this needs.

Propagated parameters

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

  • fees: Recursively updated via factory.
  • opt: Recursively updated via factory.
  • fb: Recursively updated via factory.

View parameters

SubsetResampling 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.
  • opt recurses with that matrix.
  • The remaining fields are carried through unchanged.

Related

References

  • [144] W. Shen and J. Wang. Portfolio selection via subset resampling. In: Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence (2017); pp. 1517–1523.
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(
    sr::SubsetResampling,
    i,
    X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
    args...
) -> SubsetResampling{_A, _B, _C, _D, _E, _F, var"#s185", _G, _H, var"#s1851", var"#s1852", _I, _J, Bool, Bool} where {_A, _B, _C, _D, _E, _F, var"#s185"<:Transducers.Executor, _G, _H, var"#s1851"<:Integer, var"#s1852"<:AbstractRNG, _I, _J}

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

source
PortfolioOptimisers.optimiseMethod
optimise(sr::SubsetResampling{<:Any, <:Any, <: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...) -> SubsetResamplingResult

Run the Subset Resampling portfolio optimisation.

Arguments

  • sr: The subset resampling optimiser to use.
  • rd: The returns result to use.
  • dims: The dimension along which observations advance in time.
  • branchorder: Passed to the internal optimiser. The branch order to use for the clusterisation.
  • str_names: Passed to the internal optimiser. Whether to use string names for the assets in the optimisation.
  • save: Passed to the internal optimiser. 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 sr 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::SubsetResamplingResult: The averaged portfolio. retcode is an OptimisationFailure when any subset failed, or when the weight finalisation did.

Related

source

References

[144]
W. Shen and J. Wang. Portfolio selection via subset resampling. In: Proceedings of the Thirty-First AAAI Conference on Artificial Intelligence (2017); pp. 1517–1523.