Time-dependent estimators
PortfolioOptimisers.TimeDependent — Type
struct TimeDependent{T1, T2} <: AbstractEstimatorVaries 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
val: Vector of per-fold values (in the consuming scheme'ssplitenumeration order), or a callable of the fold'sTimeDependentContext: a bare function (optionally wrapped inPreviousWeightsFunction) or aTimeDependentCallablefunctor struct.
bind: Which fold loop consumes the schedule::outermost(default) binds it to the outermost fold loop processing the estimator tree;:nearestbinds 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 (seetime_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
valis a vector:!isempty(val), and no entry is aTimeDependentor anOnline. valis not aTimeDependentor anOnline.defaultis not aTimeDependentor anOnline.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
PortfolioOptimisers.TimeDependentContext — Type
struct TimeDependentContext{T1, T2, T3, T4, T5, T6, T7} <: AbstractResultDescribes 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'ssplitenumeration (1-based); indexestrain_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;nothingotherwise.
path_id: Path identifier under multi-path schemes;nothingotherwise.
Constructors
TimeDependentContext(; i::Integer, n::Integer, rd::Prices_RR, train_idx, test_idx, w_prev::Option{<:VecNum} = nothing, path_id::Option{<:Integer} = nothing) -> TimeDependentContextKeywords correspond to the struct's fields.
Validation
1 <= i <= n.
Related
PortfolioOptimisers.PreviousWeightsFunction — Type
struct PreviousWeightsFunction{T} <: AbstractAlgorithmDeclares 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 asf(ctx::TimeDependentContext), returning the fold's field value.
Constructors
PreviousWeightsFunction(; f) -> PreviousWeightsFunctionKeywords correspond to the struct's fields.
Examples
julia> PreviousWeightsFunction(; f = identity)PreviousWeightsFunction f ┴ typeof(identity): identityRelated
PortfolioOptimisers.NoDefault — Type
struct NoDefault <: AbstractAlgorithmStates that no fold-less value exists. It stands in the two places such a value may be missing.
- As a
TimeDependent'sdefault: the schedule states no fold-less value of its own, so a fold-less solve falls back to the field's static default (seetime_dependent_field_defaults). - As an entry of a host's
time_dependent_field_defaults: the field is required and has no static default (the optimiser-valued fields), so a schedule there must carry its owndefault. A fold-less solve of a host whose required field holds a defaultless schedule throws aTimeDependentDefaultError.
Constructors
NoDefault() -> NoDefaultExamples
julia> NoDefault()NoDefault()Related
PortfolioOptimisers.TimeDependentDefaultError — Type
struct TimeDependentDefaultError{__T_msg} <: PortfolioOptimisersErrorException 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
PortfolioOptimisers.factory — Method
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
PortfolioOptimisers.TimeDependentCallable — Type
abstract type TimeDependentCallable <: AbstractEstimatorAbstract 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 thatctxdescribes.
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 readsctx.w_prev. The default isfalse. Define it astrueto force sequential fold execution, which is whatPreviousWeightsFunctiondoes for a bare function.
Related
PortfolioOptimisers.TimeDependentConstraintCallable — Type
abstract type TimeDependentConstraintCallable <: TimeDependentCallableAbstract 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
PortfolioOptimisers.TimeDependentOptimiserCallable — Type
abstract type TimeDependentOptimiserCallable <: TimeDependentCallableAbstract 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
PortfolioOptimisers.needs_previous_weights — Method
needs_previous_weights(
opt::AbstractVector{<:Union{var"#s7444", var"#s7443"} where {var"#s7444"<:(Union{var"#s7444", var"#s7443"} where {var"#s7444"<:NonFiniteAllocationOptimisationEstimator, var"#s7443"<:NonFiniteAllocationOptimisationResult}), var"#s7443"<:Union{TimeDependent{<:AbstractVector{<:Union{var"#s7444", var"#s7443"} where {var"#s7444"<:NonFiniteAllocationOptimisationEstimator, var"#s7443"<: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
PortfolioOptimisers.needs_previous_weights — Method
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
PortfolioOptimisers.needs_previous_weights — Method
needs_previous_weights(
opt::AbstractVector{<:Union{var"#s7444", var"#s7443"} where {var"#s7444"<:(Union{var"#s7444", var"#s7443"} where {var"#s7444"<:NonFiniteAllocationOptimisationEstimator, var"#s7443"<:NonFiniteAllocationOptimisationResult}), var"#s7443"<:Union{TimeDependent{<:AbstractVector{<:Union{var"#s7444", var"#s7443"} where {var"#s7444"<:NonFiniteAllocationOptimisationEstimator, var"#s7443"<: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
PortfolioOptimisers.time_dependent_field_defaults — Function
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
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 value that each schedulable field of a HierarchicalOptimiser takes when no fold resolves it.
It returns hierarchical_optimiser_td_defaults, which the constructor also reads.
Returns
defaults::NamedTuple: The static default ofpe,cle,wbandwf.
Related