WalkForward

PortfolioOptimisers.WalkForwardResultType
struct WalkForwardResult{__T_train_idx, __T_test_idx} <: SequentialCrossValidationResult

Result type produced by WalkForwardEstimator subtypes after splitting time series data.

Stores the train and test index vectors for each fold of the walk-forward cross-validation.

Fields

  • train_idx: Training set indices.
  • test_idx: Test set indices.

Constructors

WalkForwardResult(; train_idx::VecVecInt, test_idx::VecVecInt) -> WalkForwardResult

Keywords correspond to the struct's fields.

Validation

  • !isempty(train_idx) (sufficient data to cover training + testing periods).
  • !isempty(test_idx) (sufficient data to cover training + testing periods).
  • length(train_idx) == length(test_idx).

Related

source
PortfolioOptimisers.OnlineStepType
struct OnlineStep <: AbstractFoldFit

Fit each fold of a walk-forward by the online step: warm up once, then fold the new observations in and read the estimator out.

Under this Fold Fit the loop takes its online arm, online_folds. It resolves every Online wrapper through update_online_estimator, folds the first training window into the estimator with partial_fit!, and then, per fold, folds only the rows the training window has gained since the last fold and reads the estimator out through optimise(opt). The estimator is threaded from fold to fold, so fold i never re-reads the rows fold i - 1 read, and the run reaches the weights of the batch expanding-window walk-forward fold for fold. An online run is expanding by construction — a fold cannot un-fold an observation — so the scheme derives expand_train = true from this selector, and a rolling window computed online is the estimator's to declare, through Online(pe; max_history = train_size - purged_size).

The loop starts cold: an estimator carrying a partial-fit state at entry is refused by name, and a TimeDependent schedule on a field that carries a state — the prior, or the optimiser itself — is refused at warm-up, because a schedule replaces the value a state is threaded through. A schedule on any other field composes with no rule.

Examples

julia> OnlineStep()OnlineStep()

Related

source
PortfolioOptimisers.IndexWalkForwardType
struct IndexWalkForward{__T_train_size, __T_test_size, __T_purged_size, __T_expand_train, __T_reduce_test, __T_wd, __T_pws, __T_fa, __T_store_weight_path, __T_strict, __T_ff} <: WalkForwardEstimator

Implements index-based walk-forward cross-validation for time series, supporting purging and flexible train/test windowing.

purged_size drops the last purged_size rows of each training window. This opens a gap of that many observations before the test window, and removes the training rows whose labels reach into the test period. The test windows do not move, so a purge costs training rows rather than test coverage.

Fields

  • train_size: Training window size.
  • test_size: Test window size.
  • purged_size: Number of observations to purge between train and test sets.
  • expand_train: Whether to expand the training window over time. The constructor keyword takes nothing, which derives the field from the Fold Fit: true under an OnlineStep, because an online run is expanding by construction, and false otherwise. An explicit false beside an OnlineStep is refused.
  • reduce_test: Whether to allow the last test window to be smaller.
  • wd: Weight drift the fold's return series is read under, or nothing to read it at the target weights of the fold.
  • pws: Previous-weights source the fold loop threads into the next fold, or nothing to thread the target weights of the previous fold.
  • fa: Fee amortisation algorithm the fold's realised series charges the two fixed fee terms on, or nothing to inherit the clock the fee itself states. It overrides Fees.fa for that series alone, and it reaches the fit not at all.
  • store_weight_path: If true, the fold stores the weight path it computed; if false, a reader rebuilds it on demand.
  • strict: If true, a Held Gap raises an ArgumentError; if false, it warns and the pair contributes zero. A Held Gap is an (observation, asset) pair at which the fold's weight is non-zero and the asset's return is missing, which is what a delisting inside a test window makes.
  • ff: Fold Fit of the scheme, or nothing to refit every fold from its training window. An OnlineStep makes the fold loop warm up once on the first training window and then fold each fold's new observations into one estimator threaded from fold to fold, reading it out where a refit would have run.

Constructors

IndexWalkForward(    train_size::Integer,    test_size::Integer;    purged_size::Integer = 0,    expand_train::Option{Bool} = nothing,    reduce_test::Bool = false,    wd::Option{<:AbstractWeightDrift} = nothing,    pws::Option{<:AbstractPreviousWeightsSource} = nothing,    fa::Option{<:AbstractFeeAmortisation} = nothing,    store_weight_path::Bool = false,    strict::Bool = false,    ff::Option{<:AbstractFoldFit} = nothing,) -> IndexWalkForward

Positional and keyword arguments correspond to the struct's fields.

Fold Fit

ff is the Fold Fit of the scheme, the switch that says how the fold loop fits each fold. nothing refits every fold from its training window, which is the library's original behaviour. An OnlineStep makes the loop warm up once on the first training window and then fold each fold's new observations into one estimator threaded from fold to fold, reading it out where a refit would have run; the run reaches the weights of the expanding batch scheme fold for fold. expand_train derives from it: nothing, the default, resolves to true under an OnlineStep and to false otherwise, because an online run is expanding by construction. An explicit expand_train = false beside an OnlineStep is refused; a rolling window computed online is the estimator's to declare, through Online(pe; max_history = train_size - purged_size).

Weight drift and previous weights

The two switches are independent, and each one is nothing by default, which is the library's original behaviour.

wd is the Weight Drift of the scheme. nothing reads a fold's return series as X * w net of fees, at the target weights of that fold. A SelfFinancingDrift reads the series as the wealth ratio of the drifted holdings instead. store_weight_path makes the fold store the weight path it computed, which a reader otherwise rebuilds on demand. strict decides what a Held Gap does: an asset that delists inside a test window carries a non-zero weight and a missing return, and the fold zeroes that pair and warns, or refuses with an ArgumentError under strict.

pws is the Previous-Weights Source. nothing threads the target weights of the previous fold into the next one. A DriftedWeights threads the weights held after the last observation of the previous fold instead, so a turnover, a tracking or a fee estimator measures the trades a fund places rather than the change in the decision. A fold enumeration of this scheme is a timeline, so the source has a previous fold to read.

Fee clock

fa is the clock the fold's realised series charges the two fixed fee terms on, and it overrides the fa of the fee itself. nothing inherits that fee's clock, which is the library's original behaviour. A FirstObservationFees charges the two terms on the first observation of the fold, and an AmortisedFees spreads them over the fold. The field reaches the fit not at all, so the optimiser keeps pricing the fee the way its own objective must.

Validation

  • train_size and purged_size must be non-empty, non-negative, and finite.
  • test_size must be non-empty, greater than zero, and finite.
  • purged_size < train_size, because the purge is taken out of the training window.
  • expand_train is true when ff is set, because a Fold Fit cannot un-fold an observation.

The rule train_size < T, where T is the number of observations, belongs to the data rather than to the estimator, so Base.split checks it.

Examples

julia> IndexWalkForward(100, 20; purged_size = 5, expand_train = true, reduce_test = false)IndexWalkForward         train_size ┼ Int64: 100          test_size ┼ Int64: 20        purged_size ┼ Int64: 5       expand_train ┼ Bool: true        reduce_test ┼ Bool: false                 wd ┼ nothing                pws ┼ nothing                 fa ┼ nothing  store_weight_path ┼ Bool: false             strict ┼ Bool: false                 ff ┴ nothing

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 15.1.
  • [118] M. López de Prado. Advances in Financial Machine Learning (John Wiley & Sons, Hoboken, NJ, 2018). Chapter 7.
source
PortfolioOptimisers.DateWalkForwardType
struct DateWalkForward{__T_train_size, __T_test_size, __T_period, __T_period_offset, __T_purged_size, __T_adjuster, __T_previous, __T_expand_train, __T_reduce_test, __T_wd, __T_pws, __T_fa, __T_store_weight_path, __T_strict, __T_ff} <: WalkForwardEstimator

Implements date-based walk-forward cross-validation for time series, supporting flexible windowing, purging, and custom date adjustment.

purged_size drops the last purged_size rows of each training window. This opens a gap of that many observations before the test window, and removes the training rows whose labels reach into the test period. The test window itself is not shortened, so purged_size must be smaller than the training window.

Fields

  • train_size: Training window size.
  • test_size: Test window size.
  • period: Time period for date-based walk-forward cross-validation.
  • period_offset: Offset applied to the walk-forward period.
  • purged_size: Number of observations to purge between train and test sets.
  • adjuster: Function for adjusting walk-forward dates.
  • previous: Whether to include the previous period in the training window.
  • expand_train: Whether to expand the training window over time. The constructor keyword takes nothing, which derives the field from the Fold Fit: true under an OnlineStep, because an online run is expanding by construction, and false otherwise. An explicit false beside an OnlineStep is refused.
  • reduce_test: Whether to allow the last test window to be smaller.
  • wd: Weight drift the fold's return series is read under, or nothing to read it at the target weights of the fold.
  • pws: Previous-weights source the fold loop threads into the next fold, or nothing to thread the target weights of the previous fold.
  • fa: Fee amortisation algorithm the fold's realised series charges the two fixed fee terms on, or nothing to inherit the clock the fee itself states. It overrides Fees.fa for that series alone, and it reaches the fit not at all.
  • store_weight_path: If true, the fold stores the weight path it computed; if false, a reader rebuilds it on demand.
  • strict: If true, a Held Gap raises an ArgumentError; if false, it warns and the pair contributes zero. A Held Gap is an (observation, asset) pair at which the fold's weight is non-zero and the asset's return is missing, which is what a delisting inside a test window makes.
  • ff: Fold Fit of the scheme, or nothing to refit every fold from its training window. An OnlineStep makes the fold loop warm up once on the first training window and then fold each fold's new observations into one estimator threaded from fold to fold, reading it out where a refit would have run.

Constructors

DateWalkForward(    train_size::IntPeriodDateRange,    test_size::Integer;    period::DatesUnionPeriod = Dates.Day(1),    period_offset::Option{<:DatesUnionPeriod} = nothing,    purged_size::Integer = 0,    adjuster::DateAdjType = identity,    previous::Bool = false,    expand_train::Option{Bool} = nothing,    reduce_test::Bool = false,    wd::Option{<:AbstractWeightDrift} = nothing,    pws::Option{<:AbstractPreviousWeightsSource} = nothing,    fa::Option{<:AbstractFeeAmortisation} = nothing,    store_weight_path::Bool = false,    strict::Bool = false,    ff::Option{<:AbstractFoldFit} = nothing,) -> DateWalkForward

Positional and keyword arguments correspond to the struct's fields.

Fold Fit

ff is the Fold Fit of the scheme, the switch that says how the fold loop fits each fold. nothing refits every fold from its training window, which is the library's original behaviour. An OnlineStep makes the loop warm up once on the first training window and then fold each fold's new observations into one estimator threaded from fold to fold, reading it out where a refit would have run; the run reaches the weights of the expanding batch scheme fold for fold. expand_train derives from it: nothing, the default, resolves to true under an OnlineStep and to false otherwise, because an online run is expanding by construction. An explicit expand_train = false beside an OnlineStep is refused; a rolling window computed online is the estimator's to declare, through Online(pe; max_history = …) with the cap the window's rows count to.

Weight drift and previous weights

The two switches are independent, and each one is nothing by default, which is the library's original behaviour.

wd is the Weight Drift of the scheme. nothing reads a fold's return series as X * w net of fees, at the target weights of that fold. A SelfFinancingDrift reads the series as the wealth ratio of the drifted holdings instead. store_weight_path makes the fold store the weight path it computed, which a reader otherwise rebuilds on demand. strict decides what a Held Gap does: an asset that delists inside a test window carries a non-zero weight and a missing return, and the fold zeroes that pair and warns, or refuses with an ArgumentError under strict.

pws is the Previous-Weights Source. nothing threads the target weights of the previous fold into the next one. A DriftedWeights threads the weights held after the last observation of the previous fold instead, so a turnover, a tracking or a fee estimator measures the trades a fund places rather than the change in the decision. A fold enumeration of this scheme is a timeline, so the source has a previous fold to read.

Fee clock

fa is the clock the fold's realised series charges the two fixed fee terms on, and it overrides the fa of the fee itself. nothing inherits that fee's clock, which is the library's original behaviour. A FirstObservationFees charges the two terms on the first observation of the fold, and an AmortisedFees spreads them over the fold. The field reaches the fit not at all, so the optimiser keeps pricing the fee the way its own objective must.

Validation

  • test_size must be non-empty, greater than zero, and finite.
  • purged_size must be non-empty, non-negative, and finite.
  • If train_size is an integer, it must be non-empty, non-negative, and finite.
  • expand_train is true when ff is set, because a Fold Fit cannot un-fold an observation.

Examples

julia> DateWalkForward(252, 21; period = Dates.Day(1), purged_size = 5, expand_train = true)DateWalkForward         train_size ┼ Int64: 252          test_size ┼ Int64: 21             period ┼ Dates.Day: Dates.Day(1)      period_offset ┼ nothing        purged_size ┼ Int64: 5           adjuster ┼ typeof(identity): identity           previous ┼ Bool: false       expand_train ┼ Bool: true        reduce_test ┼ Bool: false                 wd ┼ nothing                pws ┼ nothing                 fa ┼ nothing  store_weight_path ┼ Bool: false             strict ┼ Bool: false                 ff ┴ nothing

Related

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 15.1.
  • [118] M. López de Prado. Advances in Financial Machine Learning (John Wiley & Sons, Hoboken, NJ, 2018). Chapter 7.
source
Base.splitMethod
Base.split(iwf::IndexWalkForward, rd::Prices_RR) -> WalkForwardResult

Split the returns data rd into sequential walk-forward folds using integer observation indices. Each fold advances the test window by test_size observations.

Arguments

  • iwf::IndexWalkForward: Index-based walk-forward cross-validation estimator.
  • rd: Returns-level or price-level data to split (Prices_RR).

Validation

  • train_size < T, where T is the number of observations in rd.

Returns

  • WalkForwardResult: Result containing train and test index ranges for each fold.

Related

source
Base.splitMethod
Base.split(dwf::DateWalkForward{<:Integer}, rd::Prices_RR) -> WalkForwardResult

Split the returns data rd into sequential walk-forward folds using date-aligned indices, where train_size is specified as an integer number of date-range steps.

The timestamp vector (cv_timestamps) must not be nothing. Training and test windows are aligned to the period date range and advanced by test_size steps at a time.

Arguments

  • dwf::DateWalkForward{<:Integer}: Date-based walk-forward estimator with an integer train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • WalkForwardResult: Result containing train and test index ranges for each fold.

Related

source
Base.splitMethod
Base.split(dwf::DateWalkForward{<:Any}, rd::Prices_RR) -> WalkForwardResult

Split the returns data rd into sequential walk-forward folds using date-aligned indices, where train_size is specified as a date Period (e.g., Dates.Month(6)).

The timestamp vector (cv_timestamps) must not be nothing. Training windows are defined by subtracting train_size from the split date, allowing calendar-based window lengths.

Arguments

  • dwf::DateWalkForward{<:Any}: Date-based walk-forward estimator with a Period train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • WalkForwardResult: Result containing train and test index ranges for each fold.

Related

source
PortfolioOptimisers.n_splitsFunction
n_splits(cv, rd::Prices_RR)
n_splits(cv)

Return the number of cross-validation splits (folds) that would be produced by cv for the given returns data rd.

Arguments

Returns

  • Integer: The number of folds.

Related

source
n_splits(dwf::DateWalkForward{<:Integer}, rd::Prices_RR) -> Integer

Return the number of walk-forward folds that would be produced by dwf for the given returns data rd when the training window size is specified as an integer number of date-range steps.

Arguments

  • dwf::DateWalkForward{<:Integer}: Date-based walk-forward estimator with an integer train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • Integer: The number of folds.

Related

source
n_splits(dwf::DateWalkForward{<:Any}, rd::Prices_RR) -> Integer

Return the number of walk-forward folds that would be produced by dwf for the given returns data rd when the training window size is specified as a date Period.

Arguments

  • dwf::DateWalkForward{<:Any}: Date-based walk-forward estimator with a Period train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • Integer: The number of folds.

Related

source
PortfolioOptimisers.n_splitsMethod
n_splits(dwf::DateWalkForward{<:Integer}, rd::Prices_RR) -> Integer

Return the number of walk-forward folds that would be produced by dwf for the given returns data rd when the training window size is specified as an integer number of date-range steps.

Arguments

  • dwf::DateWalkForward{<:Integer}: Date-based walk-forward estimator with an integer train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • Integer: The number of folds.

Related

source
n_splits(dwf::DateWalkForward{<:Any}, rd::Prices_RR) -> Integer

Return the number of walk-forward folds that would be produced by dwf for the given returns data rd when the training window size is specified as a date Period.

Arguments

  • dwf::DateWalkForward{<:Any}: Date-based walk-forward estimator with a Period train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • Integer: The number of folds.

Related

source
PortfolioOptimisers.n_splitsMethod
n_splits(dwf::DateWalkForward{<:Any}, rd::Prices_RR) -> Integer

Return the number of walk-forward folds that would be produced by dwf for the given returns data rd when the training window size is specified as a date Period.

Arguments

  • dwf::DateWalkForward{<:Any}: Date-based walk-forward estimator with a Period train_size.
  • rd: Returns-level or price-level data with timestamps (Prices_RR).

Returns

  • Integer: The number of folds.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[118]
M. López de Prado. Advances in Financial Machine Learning (John Wiley & Sons, Hoboken, NJ, 2018).