Base optimisation

All optimisers are defined as their whole names, however this can be unwieldy, so we also provide convenience aliases defined in Public API → Aliases.

PortfolioOptimisers.AbstractOptimisationEstimatorType
abstract type AbstractOptimisationEstimator <: AbstractEstimator

Abstract supertype for all portfolio optimisation estimators.

All optimisers and optimisation components should subtype AbstractOptimisationEstimator to participate in the optimisation dispatch system.

Interfaces

AbstractOptimisationEstimator declares no method of its own. It carries the default port_opt_view, which returns the estimator unchanged, and it splits into two halves. Subtype BaseOptimisationEstimator for a configuration an optimiser holds, and OptimisationEstimator for an estimator optimise runs.

The root stands here rather than beside the optimisers it heads because a field bound earlier in the load order names it. VarianceFraction.w0 admits a NonFiniteAllocationOptimisationEstimator as its reference portfolio, and it is declared in src/11_UncertaintySets/, which loads a hundred includes before src/17_Optimisation/. A bound is the enforcement the library prefers over a runtime check, so the chain the bound names is hoisted rather than the check weakened. CrossValidationEstimator stands here for the same reason.

Related

source
PortfolioOptimisers.OptimisationEstimatorType
abstract type OptimisationEstimator <: AbstractOptimisationEstimator

Abstract supertype for portfolio optimisation estimators that produce portfolio weights.

Subtype OptimisationEstimator to implement concrete portfolio optimisers. All optimisers that can be invoked with optimise should subtype this.

Interfaces

In order to implement a new optimiser that works seamlessly with the library, subtype OptimisationEstimator, give it an fb field, and implement the following method:

_optimise

  • _optimise(opt::MyOptimiser, rd::ReturnsResult, args...; kwargs...) -> OptimisationResult: Solves the problem opt states over the data in rd, and returns the optimiser's own result type.

Arguments

  • opt: The concrete subtype instance.
  • rd: Returns data.
  • args..., kwargs...: Forwarded from optimise.

Returns

  • res::OptimisationResult: The result, whose retcode decides whether optimise walks on to the fallback.

The fb field

optimise reads opt.fb to walk the fallback chain, so every subtype carries one. It holds the next optimiser to try, or nothing to end the chain.

Related

source
PortfolioOptimisers.NonFiniteAllocationOptimisationEstimatorType
abstract type NonFiniteAllocationOptimisationEstimator <: OptimisationEstimator

Abstract supertype for portfolio optimisation estimators that produce continuous (non-integer) portfolio weights.

Interfaces

NonFiniteAllocationOptimisationEstimator adds no method to OptimisationEstimator. It marks the optimisers whose weights are continuous, which is what admits them to the cross-validation and meta-optimisation entry points (see OptE_Opt).

Related

source
PortfolioOptimisers.OptimisationSuccessType
struct OptimisationSuccess{__T_res} <: OptimisationReturnCode

Indicates that a portfolio optimisation completed successfully.

Fields

  • res: Optional result or message from the solver.

Constructors

OptimisationSuccess(; res = nothing) -> OptimisationSuccess

Keywords correspond to the struct's fields.

Examples

julia> OptimisationSuccess()OptimisationSuccess  res ┴ nothing

Related

source
PortfolioOptimisers.OptimisationFailureType
struct OptimisationFailure{__T_res} <: OptimisationReturnCode

Indicates that a portfolio optimisation failed.

Fields

  • res: Optional result or message from the solver.

Constructors

OptimisationFailure(; res = nothing) -> OptimisationFailure

Keywords correspond to the struct's fields.

Examples

julia> OptimisationFailure()OptimisationFailure  res ┴ nothing

Related

source
PortfolioOptimisers.RelativeErrorWeightFinaliserType
struct RelativeErrorWeightFinaliser <: JuMPWeightFinaliserFormulation

Minimises the L1 norm of relative weight deviations when enforcing weight bounds.

Mathematical definition

\[\begin{align} \underset{\boldsymbol{w}}{\min} &\quad \left\lVert \boldsymbol{w} \oslash \boldsymbol{w}_{0} - \boldsymbol{1} \right\rVert_{1}\,, \\ \textrm{s.t.} &\quad \boldsymbol{1}^\intercal \boldsymbol{w} = \boldsymbol{1}^\intercal \boldsymbol{w}_{0}\,, \\ &\quad \boldsymbol{l} \leq \boldsymbol{w} \leq \boldsymbol{u}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{w}_{0}$: Portfolio weights vector $N \times 1$ that the optimisation produced, which the finaliser repairs.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.
  • $\oslash$: Elementwise division. A zero entry of $\boldsymbol{w}_{0}$ is replaced by eps before the division, so the ratio stays finite.

Constructors

RelativeErrorWeightFinaliser() -> RelativeErrorWeightFinaliser

Examples

julia> RelativeErrorWeightFinaliser()RelativeErrorWeightFinaliser()

Related

source
PortfolioOptimisers.SquaredRelativeErrorWeightFinaliserType
struct SquaredRelativeErrorWeightFinaliser <: JuMPWeightFinaliserFormulation

Minimises the L2 norm of relative weight deviations when enforcing weight bounds.

Mathematical definition

\[\begin{align} \underset{\boldsymbol{w}}{\min} &\quad \left\lVert \boldsymbol{w} \oslash \boldsymbol{w}_{0} - \boldsymbol{1} \right\rVert_{2}\,, \\ \textrm{s.t.} &\quad \boldsymbol{1}^\intercal \boldsymbol{w} = \boldsymbol{1}^\intercal \boldsymbol{w}_{0}\,, \\ &\quad \boldsymbol{l} \leq \boldsymbol{w} \leq \boldsymbol{u}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{w}_{0}$: Portfolio weights vector $N \times 1$ that the optimisation produced, which the finaliser repairs.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.
  • $\oslash$: Elementwise division. A zero entry of $\boldsymbol{w}_{0}$ is replaced by eps before the division, so the ratio stays finite.

The second-order cone bounds the norm itself, so the objective value is the L2 norm and not its square. The name records the squared-error criterion, whose minimiser is the same because the square is monotonic on a non-negative norm. RelativeErrorWeightFinaliser differs in the norm, not in the power.

Constructors

SquaredRelativeErrorWeightFinaliser() -> SquaredRelativeErrorWeightFinaliser

Examples

julia> SquaredRelativeErrorWeightFinaliser()SquaredRelativeErrorWeightFinaliser()

Related

source
PortfolioOptimisers.AbsoluteErrorWeightFinaliserType
struct AbsoluteErrorWeightFinaliser <: JuMPWeightFinaliserFormulation

Minimises the L1 norm of absolute weight deviations when enforcing weight bounds.

Mathematical definition

\[\begin{align} \underset{\boldsymbol{w}}{\min} &\quad \left\lVert \boldsymbol{w} - \boldsymbol{w}_{0} \right\rVert_{1}\,, \\ \textrm{s.t.} &\quad \boldsymbol{1}^\intercal \boldsymbol{w} = \boldsymbol{1}^\intercal \boldsymbol{w}_{0}\,, \\ &\quad \boldsymbol{l} \leq \boldsymbol{w} \leq \boldsymbol{u}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{w}_{0}$: Portfolio weights vector $N \times 1$ that the optimisation produced, which the finaliser repairs.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.

Constructors

AbsoluteErrorWeightFinaliser() -> AbsoluteErrorWeightFinaliser

Examples

julia> AbsoluteErrorWeightFinaliser()AbsoluteErrorWeightFinaliser()

Related

source
PortfolioOptimisers.SquaredAbsoluteErrorWeightFinaliserType
struct SquaredAbsoluteErrorWeightFinaliser <: JuMPWeightFinaliserFormulation

Minimises the L2 norm of absolute weight deviations when enforcing weight bounds.

Mathematical definition

\[\begin{align} \underset{\boldsymbol{w}}{\min} &\quad \left\lVert \boldsymbol{w} - \boldsymbol{w}_{0} \right\rVert_{2}\,, \\ \textrm{s.t.} &\quad \boldsymbol{1}^\intercal \boldsymbol{w} = \boldsymbol{1}^\intercal \boldsymbol{w}_{0}\,, \\ &\quad \boldsymbol{l} \leq \boldsymbol{w} \leq \boldsymbol{u}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{w}_{0}$: Portfolio weights vector $N \times 1$ that the optimisation produced, which the finaliser repairs.
  • $\boldsymbol{l}$, $\boldsymbol{u}$: Lower and upper weight bounds. An absent bound is dropped from the programme rather than set to an infinity.

The second-order cone bounds the norm itself, so the objective value is the L2 norm and not its square. The name records the squared-error criterion, whose minimiser is the same because the square is monotonic on a non-negative norm. AbsoluteErrorWeightFinaliser differs in the norm, not in the power.

Constructors

SquaredAbsoluteErrorWeightFinaliser() -> SquaredAbsoluteErrorWeightFinaliser

Examples

julia> SquaredAbsoluteErrorWeightFinaliser()SquaredAbsoluteErrorWeightFinaliser()

Related

source
PortfolioOptimisers.IterativeWeightFinaliserType
struct IterativeWeightFinaliser{__T_iter} <: WeightFinaliser

Iteratively projects weights into the feasible region defined by weight bounds.

Each pass clips the weights to the bounds, then redistributes the clipped mass over the entries that lie strictly inside the bounds, in proportion to their own weights. The pass ends by rescaling the vector to the budget it started with, so the sum is preserved. Passes run until the bounds hold or until iter passes are done. An absent bound is read as typemin or typemax of the weight element type.

The bounds are not guaranteed on exit. A bound set that no rescaled vector can satisfy exhausts the passes and returns the last vector: four assets summing to 1 under lb = 0.3 return [0.25, 0.25, 0.25, 0.25]. finalise_weight_bounds tests finiteness alone, so such a vector is reported as an OptimisationSuccess.

Fields

  • iter: Maximum number of iterations.

Constructors

IterativeWeightFinaliser(;    iter::Integer = 100) -> IterativeWeightFinaliser

Keywords correspond to the struct's fields.

Validation

  • iter > 0.

Examples

julia> IterativeWeightFinaliser()IterativeWeightFinaliser  iter ┴ Int64: 100

Related

source
PortfolioOptimisers.JuMPWeightFinaliserType
struct JuMPWeightFinaliser{__T_slv, __T_sc, __T_so, __T_alg} <: WeightFinaliser

Uses a JuMP optimisation model to enforce weight bounds.

The programme keeps the budget of the input weights and holds every weight between the bounds, and alg states which deviation it minimises over that set. An absent bound adds no constraint. A failed solve raises a warning and falls back to a default IterativeWeightFinaliser.

Fields

  • slv: Solver or vector of solvers.
  • sc: Constraint scale factor.
  • so: Objective scale factor.
  • alg: Weight finaliser error formulation algorithm.

Constructors

JuMPWeightFinaliser(;    slv::Slv_VecSlv,    sc::Number = 1.0,    so::Number = 1.0,    alg::JuMPWeightFinaliserFormulation = RelativeErrorWeightFinaliser()) -> JuMPWeightFinaliser

Keywords correspond to the struct's fields.

Validation

  • If slv is a VecSlv: !isempty(slv).
  • sc > 0, so > 0.

Examples

julia> JuMPWeightFinaliser(; slv = Solver(; solver = nothing))JuMPWeightFinaliser  slv ┼ Solver      │          name ┼ String: ""      │        solver ┼ nothing      │      settings ┼ nothing      │     check_sol ┼ @NamedTuple{}: NamedTuple()      │   add_bridges ┴ Bool: true   sc ┼ Float64: 1.0   so ┼ Float64: 1.0  alg ┴ RelativeErrorWeightFinaliser()

Related

source
PortfolioOptimisers.TimeDependentType
struct TimeDependent{T1, T2} <: AbstractEstimator

Varies one optimiser input across the folds of a cross-validation scheme.

A TimeDependent is stored directly in the optimiser field it varies — e.g. JuMPOptimiser(; lt = TimeDependent([...])) — so the field's position names the target and a field holds either a static value or a per-fold schedule, never both. It is recognised at top-level optimiser fields only, never nested inside another input (e.g. inside a Fees or a risk measure).

val is either a vector of per-fold values — entry i is the complete field value for fold i of the consuming scheme's split enumeration — or a callable evaluated per fold: a bare function f(ctx::TimeDependentContext) (optionally wrapped in PreviousWeightsFunction) or a TimeDependentCallable functor struct.

For a field that itself accepts a vector of constraints statically, a per-fold entry is that whole vector, so a schedule of per-fold constraint vectors is a vector of vectors — TimeDependent([[c₁ᵃ, c₁ᵇ], [c₂ᵃ, c₂ᵇ], …]), entry i being fold i's complete constraint vector. There is no separate "vector of TimeDependent" facility and none is needed: TimeDependent is recognised only at a top-level field, so to vary individual constraints within a vector, build the fold's vector in a callable — TimeDependent(ctx -> [dynamic(ctx), static]) — which keeps the shared static parts in one place.

The machinery imposes no ordering of its own: fold i is whatever split(cv, rd) enumerates i-th, which is chronological for walk-forward and (unshuffled) KFold schemes. For schemes whose enumeration is not a timeline (combinatorial splits, randomised paths) it is the user's responsibility to key entries off the fold's indices — a callable sees its own fold's windows via ctx.train_idx[ctx.i]/ctx.test_idx[ctx.i] and may derive any ordering from them.

A time-dependent constraint participates only where folds exist and is inert everywhere else — a fold-less optimise replaces it with the field's fold-less value (see reset_time_dependent_estimator). Vector entries must have length equal to the number of folds of the consuming cross-validation scheme, validated at split time. Entries may be nothing, giving the field nothing for that fold.

The fold-less value is the field's static default, unless default overrides it. A field with no static default — the required, optimiser-valued fields — has nothing to reset to, so a schedule there must supply default; a fold-less solve of one that does not throws a TimeDependentDefaultError.

A vector whose entries are all optimisers or precomputed results (OptE_Opt) is stored as a Vector{OptE_Opt}, so a mixed schedule — fold i optimising or predicting depending on what entry i is — is admissible in an optimiser-valued field on its element type alone (see TD_OptE_Opt) rather than falling out to a Vector{Any} the field cannot accept.

A schedule and an Online do not wrap each other, and the reason is when each resolves: a wrapper resolves once, at warm-up, because the sample buffer it seeds is threaded from step to step, while a schedule resolves per fold, because its value is that fold's. So neither val, nor a vector entry of val, nor default may be an Online — a wrapper reached through one of them would be resolved at no fold at all, or re-seeded at every fold, throwing the buffer away. They do compose the other way round: an estimator an Online wraps may hold schedules of its own, which resolve per fold after the seeding, and one host may hold a wrapper in one field and a schedule in another.

Schedules do not nest: neither val, nor a vector entry of val, nor default may be a TimeDependent. Entry i is fold i's complete field value, and the fold-less value is by definition outside every fold loop, so nesting has no meaning. An estimator swapped in by a schedule may itself carry schedules — those resolve against the same fold context after the swap — but they live in its fields, not inside this wrapper.

Recovering which entry a fold ran needs no stored provenance, because a vector schedule is keyed by the fold index and nothing else. Entry i runs at fold i of the consuming scheme's split enumeration (time_dependent_value indexes val[ctx.i]), so val[i] is fold i's value — the same index you keyed the schedule by. Under the time-ordered schemes (walk-forward, unshuffled KFold, Pipeline) fold i is also the i-th entry of the returned MultiPeriodPredictionResult; under schemes that regroup for reporting (MultipleRandomised sorts by test index, combinatorial recombines each split's test groups into paths) the prediction order no longer tracks the fold order, so re-run split(cv, rd) and read the fold→path map off its path_ids — it is keyed by the very enumeration index the schedule was, so entry k still governs enumeration fold k. A callable schedule computes its value rather than selecting an entry, so there is no index to recover: what it returned is knowable only by re-running it on the fold's TimeDependentContext, or by having it record its own choice. Recording is a logging concern the caller owns, and the TimeDependentCallable struct interface is its natural home — a functor can stash the regime it picked per fold in a field of its own.

Fields

  • bind: Which fold loop consumes the schedule: :outermost (default) binds it to the outermost fold loop processing the estimator tree; :nearest binds it to the nearest enclosing fold loop — inside a meta-optimiser's inner estimators that is the meta's own cross-validation leg, which then consumes the schedule even when the meta is backtested under an outer fold loop.
  • default: Value the field takes outside every fold loop, overriding the host's static default (see time_dependent_field_defaults). NoDefault (the default) defers to the host's static default; a field that has none requires this to be set.

Constructors

TimeDependent(val, bind::Symbol = :outermost; default = NoDefault())TimeDependent(; val::Union{<:AbstractVector, <:Base.Callable,                           <:PreviousWeightsFunction, <:TimeDependentCallable,                           <:TimeDependent}, bind::Symbol = :outermost,              default = NoDefault())

Validation

  • If val is a vector: !isempty(val), and no entry is a TimeDependent or an Online.
  • val is not a TimeDependent or an Online.
  • default is not a TimeDependent or an Online.
  • bind in (:outermost, :nearest).

Examples

julia> TimeDependent([Fees(; l = 0.001), Fees(; l = 0.002)])TimeDependent      val ┼ 2-element Vector{Fees}          │ Fees ⋯          │ Fees ⋯     bind ┼ Symbol: :outermost  default ┴ NoDefault()

Related

source
PortfolioOptimisers.TimeDependentContextType
struct TimeDependentContext{T1, T2, T3, T4, T5, T6, T7} <: AbstractResult

Describes one fold to the time-dependent constraints that resolve against it.

Carries the fold's position in the consuming scheme's split enumeration and the data needed for a callable entry to compute its value. i indexes train_idx/test_idx, so ctx.train_idx[ctx.i]/ctx.test_idx[ctx.i] are always the fold's own windows; no ordering beyond the scheme's enumeration is implied. rd is the fold loop's (possibly asset-viewed) input data, so callables see the current universe and timestamps: the returns-level data at the optimiser fold loops, or the raw, pre-preprocessing price- or returns-level input at the Pipeline fold loop — a pipeline-level callable sees the fold's data before any pipeline step has transformed it. w_prev is populated only when the fold loop runs sequentially and a previous fold exists; path_id only under multi-path schemes.

Fields

  • i: Index of the fold within the scheme's split enumeration (1-based); indexes train_idx/test_idx.
  • n: Number of folds within the path.
  • rd: The fold loop's (possibly asset-viewed) returns data.
  • train_idx: Per-path training index vectors.
  • test_idx: Per-path test index vectors.
  • w_prev: Previous fold's portfolio weights, when threaded; nothing otherwise.
  • path_id: Path identifier under multi-path schemes; nothing otherwise.

Constructors

TimeDependentContext(;    i::Integer, n::Integer, rd::Prices_RR, train_idx, test_idx,    w_prev::Option{<:VecNum} = nothing, path_id::Option{<:Integer} = nothing) -> TimeDependentContext

Keywords correspond to the struct's fields.

Validation

  • 1 <= i <= n.

Related

source
PortfolioOptimisers.PreviousWeightsFunctionType
struct PreviousWeightsFunction{T} <: AbstractAlgorithm

Declares that a callable time-dependent entry requires the previous optimisation's weights.

A bare callable inside a TimeDependent cannot be inspected for previous-weight requirements, so it contributes false to needs_previous_weights and its context's w_prev is only populated when something else makes the fold loop sequential. Wrapping the callable in PreviousWeightsFunction declares the requirement as data: it contributes true to needs_previous_weights, forcing sequential fold execution and a populated w_prev in the TimeDependentContext.

Fields

  • f: Callable evaluated per fold as f(ctx::TimeDependentContext), returning the fold's field value.

Constructors

PreviousWeightsFunction(; f) -> PreviousWeightsFunction

Keywords correspond to the struct's fields.

Examples

julia> PreviousWeightsFunction(; f = identity)PreviousWeightsFunction  f ┴ typeof(identity): identity

Related

source
PortfolioOptimisers.NoDefaultType
struct NoDefault <: AbstractAlgorithm

States that no fold-less value exists. It stands in the two places such a value may be missing.

Constructors

NoDefault() -> NoDefault

Examples

julia> NoDefault()NoDefault()

Related

source
PortfolioOptimisers.TimeDependentDefaultErrorType
struct TimeDependentDefaultError{__T_msg} <: PortfolioOptimisersError

Exception thrown when a fold-less solve reaches a TimeDependent schedule that has no value to fall back to: the field has no static default and the schedule supplies no default.

A schedule is defined only over the folds of a cross-validation scheme. Fields with a static default reset to it silently; a required field (the optimiser-valued ones) has nothing to reset to, so the schedule must state the value a fold-less solve should use, via TimeDependent(val; default = x).

Fields

  • msg: Error message describing the condition that triggered the exception.

Constructors

TimeDependentDefaultError(msg)

Related

source
PortfolioOptimisers.optimiseMethod
optimise(opt::OptimisationEstimator, args...; kwargs...) -> OptimisationResult
optimise(opt::OptimisationResult, args...; kwargs...) -> OptimisationResult

Run portfolio optimisation using the given estimator opt and return an OptimisationResult.

If opt returns an OptimisationFailure, the fallback estimator is tried automatically until either a successful result is obtained or all fallbacks are exhausted.

Passing an OptimisationResult directly returns it unchanged (pass-through method).

Arguments

  • opt: Optimisation estimator (e.g. a JuMPOptimisationEstimator subtype).
  • args: Additional positional arguments (ignored).
  • kwargs: Additional keyword arguments (ignored).

Returns

Related

source
PortfolioOptimisers.optimiseMethod
optimise(
    opt::OptimisationEstimator,
    args...;
    kwargs...
) -> RiskBudgetingResult{__T_jr, __T_r, __T_prb, Nothing} where {__T_jr, __T_r, __T_prb}

High level optimisation function that wraps around estimator-specific optimisation functions. This takes care of fallback methods if the primary optimisation fails. It returns the first successful optimisation result, or the last failure when every fallback fails, and stores the (estimator, result) pair of every failed attempt in the fb field of that result, in the order they ran (see FbChain). When no fallback was needed, fb is nothing.

This is a fold-less entry point, so time-dependent schedules are inert here: the estimator is reset to its fold-less values (see reset_time_dependent_estimator) before the solve — in particular a scheduled fallback resets to its default, or to nothing (no fallback) when it has none, before the fallback chain is walked. Inside a fold loop this reset is a no-op, because the loop resolves every schedule before optimising.

It is a batch fit, so an Online anywhere in the estimator's tree is refused by name through assert_batch_entry before any solve: the wrapper resolves only at the warm-up of the fold loop's online arm, and a plain optimise runs none. The read-out of a stepped estimator, optimise(opt), never meets this refusal, because the warm-up that seeded its buffer replaced the wrapper.

Arguments

  • opt::OptimisationEstimator: The optimisation estimator to use.
  • args: Additional positional arguments passed to the optimisation function.
  • kwargs: Additional keyword arguments passed to the optimisation function.

Validation

  • No field in the tree of opt holds an Online. An ArgumentError naming the field is thrown otherwise.
source
PortfolioOptimisers._optimiseFunction
_optimise(opt, rd, args...; dims, str_names, save, kwargs...)

Internal dispatch function for portfolio optimisation.

Called by optimise to perform the actual optimisation. Each optimisation estimator type implements its own overload. Returns the estimator-specific result type.

Arguments

  • opt: Optimisation estimator (e.g. MeanRisk, RiskBudgeting, etc.).
  • rd::ReturnsResult: Returns data.
  • dims::Int: Observation dimension.
  • str_names::Bool: Whether to use string names in the JuMP model.
  • save::Bool: Whether to save the JuMP model in the result.
  • kwargs...: Additional keyword arguments.

Returns

  • Estimator-specific optimisation result.

Related

source
PortfolioOptimisers.calc_net_returnsFunction
calc_net_returns(res::OptimisationResult, X::MatNum, fees = nothing, wd = nothing, obs = nothing)
calc_net_returns(res::OptimisationResult, pr::Pr_RR, fees = nothing, wd = nothing, obs = nothing)

Compute net returns for a OptimisationResult.

fees takes precedence over res.fees if both are provided. Delegates to calc_net_returns(w, X, fees, wd, obs).

When pr::Pr_RR is passed, the carrier is paired whole and its X is read after.

The weights, the matrix and the fee meet on the investable universe of res, through result_investable_view: res.w is on the caller's universe and res.fees on the one the fit solved, so the weights and a caller's X are viewed at the result's Investable Mask, and a caller's fees takes the door a fee takes at the fit.

wd is the Weight Drift the window is read under. nothing reads the window at the constant weights res.w, which is the library's original behaviour. A SelfFinancingDrift reads it as the wealth ratio of the drifted holdings, and obs then names the observations of the message a non-positive wealth raises.

Related

source
PortfolioOptimisers.factoryMethod
factory(td::TimeDependent, args...) -> TimeDependent

Apply factory through a TimeDependent schedule: to each vector entry and to the default, rebuilding the schedule.

A schedule can survive a fold loop's resolution pass (a bind = :nearest element left for a meta's inner cross-validation), so the factory pass that follows resolution must see through it. Callable forms pass through unchanged — their per-fold values do not exist yet, and a callable receives the fold's context (including w_prev) when it runs.

Related

source
PortfolioOptimisers.optimiseMethod
optimise(
    td::Union{TimeDependent{<:AbstractVector{<:Union{var"#s7100", var"#s7099"} where {var"#s7100"<:NonFiniteAllocationOptimisationEstimator, var"#s7099"<:NonFiniteAllocationOptimisationResult}}}, TimeDependent{<:TimeDependentOptimiserCallable}, TimeDependent{<:PreviousWeightsFunction}, TimeDependent{<:Union{Function, Type}}},
    args...;
    kwargs...
) -> Any

Optimise with a TimeDependent schedule standing in for the optimiser, outside any fold loop.

There are no folds to index, so the schedule resolves to its default and that optimiser runs (see reset_time_dependent_estimator); a schedule with no default throws a TimeDependentDefaultError. Inside a fold loop this method is never reached — the loop resolves entry i first.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(opt, i, args...)

Return a view or subset of an optimisation estimator for a given cluster index i.

Default fallback returns the estimator unchanged. Overridden for composite estimators (e.g. JuMPOptimiser, HierarchicalRiskParity) to slice all sub-estimators for the i-th cluster.

Arguments

  • opt: Optimisation estimator or result.
  • i: Cluster or asset index.
  • args...: Additional arguments (e.g. asset returns matrix).

Returns

  • Sliced or unchanged optimisation estimator.

Related

source
PortfolioOptimisers.port_opt_viewMethod
port_opt_view(
    res::NonFiniteAllocationOptimisationResult,
    _::Colon,
    args...
) -> NonFiniteAllocationOptimisationResult

A precomputed optimisation result cannot be restricted to an asset subset.

Its weights were solved over the full universe and a sub-portfolio of them has no defined meaning, so an asset-subset view of a result throws. In particular, a TimeDependent schedule holding result entries is incompatible with asset-subsampling cross-validation (MultipleRandomised), whose fold loops view the optimiser to each fold's asset subset before the swap. The trivial all-assets view (Colon) passes the result through unchanged.

Related

source
PortfolioOptimisers.factoryMethod
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
PortfolioOptimisers.BaseOptimisationEstimatorType
abstract type BaseOptimisationEstimator <: AbstractOptimisationEstimator

Abstract supertype for base portfolio optimisation estimators.

BaseOptimisationEstimator is the parent for all internal optimiser components that configure the optimisation problem but are not directly invokable as top-level optimisers.

Interfaces

A subtype gains the time-dependent host methods from this supertype: is_time_dependent, update_time_dependent_estimator, reset_time_dependent_estimator and assert_time_dependent_fold_count all scan its fields generically, through time_dependent_fields. One method is worth implementing:

time_dependent_field_defaults

  • time_dependent_field_defaults(opt::MyConfiguration) -> NamedTuple: The static default of each field that may hold a TimeDependent, for those whose default is not nothing. A required field is listed with NoDefault, which declares that a schedule there must carry its own default.

Arguments

  • opt: The concrete subtype instance.

Returns

  • defaults::NamedTuple: The fold-less value of each listed field. The fallback method returns an empty tuple, which gives every scheduled field the fold-less value nothing.

Related

source
PortfolioOptimisers.OptimisationAlgorithmType
abstract type OptimisationAlgorithm <: AbstractAlgorithm

Abstract supertype for optimisation algorithms used by portfolio optimisers.

Interfaces

A subtype is a tag that an optimiser dispatches on, so it declares no method of its own. To add a behaviour, subtype OptimisationAlgorithm and add the methods of the consuming optimiser that are specialised on it.

Related

source
PortfolioOptimisers.OptimisationResultType
abstract type OptimisationResult <: AbstractResult

Abstract supertype for portfolio optimisation result types.

All concrete optimisation result types should subtype OptimisationResult.

Interfaces

A subtype declares no method, but optimise and factory read three properties of it. A subtype exposes them either as its own fields or by forwarding from an embedded core, as the JuMP and hierarchical leaves do:

  • w: The portfolio weights.
  • retcode: An OptimisationReturnCode. optimise walks the fallback chain until it reads an OptimisationSuccess.
  • fb: The record of the fallbacks that ran. It must be the last field of the struct, because factory rebuilds the result by replacing its trailing field.

Related

source
PortfolioOptimisers.NonFiniteAllocationOptimisationResultType
abstract type NonFiniteAllocationOptimisationResult <: OptimisationResult

Abstract supertype for continuous (non-integer allocation) optimisation results.

Interfaces

The family adds no method to OptimisationResult, but it is the bound of the generic factory(res, fb) that rebuilds a result with a new fallback record, which is why the trailing fb field is required here rather than one level up.

Related

source
PortfolioOptimisers.OptimisationReturnCodeType
abstract type OptimisationReturnCode <: AbstractResult

Abstract supertype for optimisation return codes.

Concrete subtypes indicate whether an optimisation succeeded or failed.

Interfaces

A subtype declares no method. It carries one field, res, which holds the diagnostic text of a failure or nothing. optimise tests the code by type alone: only an OptimisationSuccess ends the fallback chain, so any other subtype is read as a failure.

Related

source
PortfolioOptimisers.OptimisationModelResultType
abstract type OptimisationModelResult <: AbstractResult

Abstract supertype for intermediate optimisation model results.

Sits off the optimisation-result tree, like BaseHierarchicalOptimisationResult does: an intermediate record is not a thing optimise returns. Its one subtype is JuMPOptimisationSolution, the record of what a solver returned.

Interfaces

A subtype is a record of one solver attempt, and it declares no method. It is held by a result rather than returned by an optimiser.

Related

source
PortfolioOptimisers.JuMPWeightFinaliserFormulationType
abstract type JuMPWeightFinaliserFormulation <: AbstractAlgorithm

Abstract supertype for JuMP-based weight finaliser formulations.

Defines the interface for norm types used when adjusting portfolio weights to satisfy bounds via a JuMP model.

Interfaces

In order to implement a new formulation that works seamlessly with the library, subtype JuMPWeightFinaliserFormulation and implement the following method:

set_clustering_weight_finaliser_alg!

  • set_clustering_weight_finaliser_alg!(model::JuMP.Model, alg::MyFormulation, wi::VecNum) -> Nothing: Adds the deviation objective to a model that already carries the decision vector w, the budget equality and the weight bounds.

Arguments

  • model: The JuMP model, built by opt_weight_bounds.
  • alg: The concrete subtype instance.
  • wi: The weights the optimisation produced, which the model repairs.

Returns

  • nothing. The method works by adding variables, constraints and the objective to model.

Related

source
PortfolioOptimisers.WeightFinaliserType
abstract type WeightFinaliser <: AbstractAlgorithm

Abstract supertype for weight finaliser strategies.

A WeightFinaliser enforces weight bounds after the optimisation has produced unconstrained weights.

Interfaces

In order to implement a new strategy that works seamlessly with the library, subtype WeightFinaliser and implement the following method:

opt_weight_bounds

  • opt_weight_bounds(wf::MyFinaliser, wb::WeightBounds, w::VecNum) -> VecNum: Moves w into the bounds wb, keeping the budget it already carries.

Arguments

  • wf: The concrete subtype instance.
  • wb: The weight bounds. Either bound may be nothing.
  • w: The weights the optimisation produced.

Returns

Related

source
PortfolioOptimisers.TimeDependentCallableType
abstract type TimeDependentCallable <: AbstractEstimator

Abstract supertype for the callable structs used as time-dependent values.

A subtype is a data-carrying alternative to a bare function inside a TimeDependent: it must implement a functor (x::MySubtype)(ctx::TimeDependentContext) returning the fold's field value. Because it is a struct, it participates in a trait a bare function cannot: define needs_previous_weights(::MySubtype) = true to declare a previous-weights requirement directly (the default is false), instead of wrapping in PreviousWeightsFunction.

Being a struct also makes it the natural home for recording what a callable schedule chose: a bare ctx -> … selects nothing by index, so its per-fold decision is not otherwise recoverable (see the provenance note on TimeDependent). A functor can carry a mutable field — e.g. a vector it writes at ctx.i — and log the fold's resolved value as a side effect of computing it.

The family classifies by what the functor returns, and a subtype declares that kind in its type. Subtype TimeDependentConstraintCallable when the per-fold value is a constraint value, and TimeDependentOptimiserCallable when it is an optimiser. Only the second is statically admissible in an optimiser-valued field (see TD_OptE_Opt), so the classification is what that admissibility is read off. Do not subtype this root directly.

Interfaces

Subtype one of the two children, not this root, and implement the following:

The functor

  • (x::MySubtype)(ctx::TimeDependentContext): Returns the field value for the fold that ctx describes.

Arguments

  • x: The concrete subtype instance.
  • ctx: The fold's context, which carries the fold index, the fold loop's data and, when the loop runs sequentially, the previous fold's weights.

Returns

  • The complete field value for that fold. Its kind is the one the subtype's supertype declares.

needs_previous_weights

  • needs_previous_weights(::MySubtype) -> Bool: Declares whether the functor reads ctx.w_prev. The default is false. Define it as true to force sequential fold execution, which is what PreviousWeightsFunction does for a bare function.

Related

source
PortfolioOptimisers.TimeDependentConstraintCallableType
abstract type TimeDependentConstraintCallable <: TimeDependentCallable

Abstract supertype for callable structs whose per-fold value is a constraint value.

A subtype implements a functor (x::MySubtype)(ctx::TimeDependentContext) returning the fold's value for a constraint-position field — a budget, a set of weight bounds, a fee structure, a turnover limit, anything a TimeDependent may carry other than an optimiser. The value is checked when the fold loop swaps it into the field, by the host's own keyword constructor.

This is the kind to subtype for a functor whose output is not an optimiser. A functor returning an optimiser declares TimeDependentOptimiserCallable instead, which is what makes an optimiser-position schedule statically admissible (see TD_OptE_Opt).

Interfaces

The methods are those of TimeDependentCallable: the functor (x::MySubtype)(ctx::TimeDependentContext), and the optional needs_previous_weights. This child adds no method. It states what the functor returns — a constraint value — and the host's own keyword constructor checks that value when the fold loop swaps it in.

Related

source
PortfolioOptimisers.TimeDependentOptimiserCallableType
abstract type TimeDependentOptimiserCallable <: TimeDependentCallable

Abstract supertype for callable structs whose per-fold value is an optimiser.

A subtype implements a functor (x::MySubtype)(ctx::TimeDependentContext) returning the fold's optimiser (an OptE_Opt), so a TimeDependent holding it is admissible wherever an optimiser-valued field accepts a schedule (see TD_OptE_Opt). Declaring the functor's output kind in the type is what makes the schedule statically admissible: a bare ctx -> optimiser is admitted as a Base.Callable and checked only when the fold loop swaps its value in.

Interfaces

The methods are those of TimeDependentCallable: the functor (x::MySubtype)(ctx::TimeDependentContext), and the optional needs_previous_weights. This child adds no method. It states that the functor returns an OptE_Opt, and assert_time_dependent_optimiser checks that promise when the fold loop swaps the value in.

Related

source
PortfolioOptimisers.needs_previous_weightsMethod
needs_previous_weights(opt)

Return true if the optimiser requires the previous period's weights.

The default returns false. Overridden for optimisers that contain turnover constraints, tracking error constraints, or other time-dependent components that require the previous optimisation's weights.

Arguments

  • opt: Optimisation estimator, result, risk measure, fee structure, or vector thereof.

Returns

  • Bool: true if previous weights are needed.

Related

source
PortfolioOptimisers.needs_previous_weightsMethod
needs_previous_weights(
    opt::AbstractVector{<:Union{var"#s7100", var"#s7099"} where {var"#s7100"<:(Union{var"#s7100", var"#s7099"} where {var"#s7100"<:NonFiniteAllocationOptimisationEstimator, var"#s7099"<:NonFiniteAllocationOptimisationResult}), var"#s7099"<:Union{TimeDependent{<:AbstractVector{<:Union{var"#s7100", var"#s7099"} where {var"#s7100"<:NonFiniteAllocationOptimisationEstimator, var"#s7099"<:NonFiniteAllocationOptimisationResult}}}, TimeDependent{<:TimeDependentOptimiserCallable}, TimeDependent{<:PreviousWeightsFunction}, TimeDependent{<:Union{Function, Type}}}}}
) -> Any

Return true if any element of the vector of optimisation estimators or results requires previous portfolio weights.

Related

source
PortfolioOptimisers.needs_previous_weightsMethod
needs_previous_weights(td::TimeDependent) -> Bool

Return true if a time-dependent constraint requires the previous optimisation's weights.

true for a PreviousWeightsFunction value; for vector values, delegates to needs_previous_weights on entries that support it (turnover, fees, tracking), descending into per-fold vector entries. Bare callables contribute false — their output cannot be inspected.

Related

source
PortfolioOptimisers.needs_previous_weightsMethod
needs_previous_weights(
    opt::AbstractVector{<:Union{var"#s7100", var"#s7099"} where {var"#s7100"<:(Union{var"#s7100", var"#s7099"} where {var"#s7100"<:NonFiniteAllocationOptimisationEstimator, var"#s7099"<:NonFiniteAllocationOptimisationResult}), var"#s7099"<:Union{TimeDependent{<:AbstractVector{<:Union{var"#s7100", var"#s7099"} where {var"#s7100"<:NonFiniteAllocationOptimisationEstimator, var"#s7099"<:NonFiniteAllocationOptimisationResult}}}, TimeDependent{<:TimeDependentOptimiserCallable}, TimeDependent{<:PreviousWeightsFunction}, TimeDependent{<:Union{Function, Type}}}}}
) -> Any

Return true if any element of the vector of optimisation estimators or results requires previous portfolio weights.

Related

source
PortfolioOptimisers.time_dependent_field_defaultsFunction
time_dependent_field_defaults(opt)

Return a NamedTuple of the static defaults of the optimiser fields that may hold a TimeDependent, for those whose default is not nothing.

Used by reset_time_dependent_estimator to replace per-fold schedules with their static defaults on fold-less solves; fields absent from the tuple default to nothing. A required field — one with no static default at all, i.e. the optimiser-valued fields — is listed with NoDefault, which is not a value it can take but a declaration that a schedule there must carry its own default. The fallback method returns an empty tuple.

Related

source
time_dependent_field_defaults(
    _::HierarchicalOptimiser
) -> @NamedTuple{pe::EmpiricalPrior{PortfolioOptimisersCovariance{Covariance{SimpleExpectedReturns{Nothing, Nothing, Nothing}, GeneralCovariance{SimpleCovariance, Nothing, Nothing}, FullMoment, Nothing, Nothing, Nothing}, MatrixProcessing{Posdef{UnionAll, @NamedTuple{}}, Nothing, Nothing, Nothing, NTuple{4, Symbol}}, Nothing}, SimpleExpectedReturns{Nothing, Nothing, Nothing}, Nothing, Nothing, Nothing, Nothing}, cle::ClustersEstimator{PortfolioOptimisersCovariance{Covariance{SimpleExpectedReturns{Nothing, Nothing, Nothing}, GeneralCovariance{SimpleCovariance, Nothing, Nothing}, FullMoment, Nothing, Nothing, Nothing}, MatrixProcessing{Posdef{UnionAll, @NamedTuple{}}, Nothing, Nothing, Nothing, NTuple{4, Symbol}}, Nothing}, Distance{Nothing, CanonicalDistance}, HClustAlgorithm{Symbol}, OptimalNumberClusters{Nothing, SecondOrderDifference{StandardisedValue{MeanValue{Nothing}, StdValue{Nothing, Bool}}}}}, wb::WeightBounds{Float64, Float64}, wf::IterativeWeightFinaliser{Int64}}

Return the static defaults of the HierarchicalOptimiser fields that may hold a TimeDependent.

Related

source
PortfolioOptimisers.set_clustering_weight_finaliser_alg!Function
set_clustering_weight_finaliser_alg!(model::JuMP.Model,
                                     alg::JuMPWeightFinaliserFormulation,
                                     wi::VecNum)

Add the deviation objective of alg to the weight finalisation model.

opt_weight_bounds has already added the decision vector w, the budget equality and the weight bounds. This method adds the epigraph variable t, the cone that bounds the deviation of w from wi, and the objective Min so * t. The cone is a NormOneCone for the two L1 formulations and a SecondOrderCone for the two L2 formulations.

Arguments

  • model: JuMP model, which must already carry w and the two scale expressions.
  • alg: The deviation formulation, one of the four JuMPWeightFinaliserFormulation subtypes.
  • wi: The weights the optimisation produced, which the model repairs.

Returns

  • nothing.

Details

  • The two relative formulations divide by wi, so they first replace each zero entry of wi in place with eps(eltype(wi)). The caller's vector carries that substitution afterwards.

Related

source
PortfolioOptimisers.opt_weight_boundsFunction
opt_weight_bounds(wf::JuMPWeightFinaliser, wb::WeightBounds, wi::VecNum) -> VecNum
opt_weight_bounds(wf::IterativeWeightFinaliser, wb::WeightBounds, w::VecNum) -> VecNum

Move a weight vector into the bounds wb, keeping the budget it already carries.

The bounds themselves are not changed. Weights that already satisfy the bounds are returned unchanged, without a solve.

The JuMPWeightFinaliser method builds the programme of its alg (see set_clustering_weight_finaliser_alg!) and solves it. A failed solve warns and falls back to a default IterativeWeightFinaliser. The IterativeWeightFinaliser method clips and redistributes instead, and may exhaust its passes with the bounds still violated.

Arguments

  • wf: Weight finaliser algorithm.
  • wb: Weight bounds.
  • wi, w: The weights the optimisation produced.

Returns

  • w::VecNum: The repaired weight vector.

Related

source