skfolio.model_selection.WalkForward#
- class skfolio.model_selection.WalkForward(test_size, train_size, freq=None, freq_offset=None, previous=False, expand_train=False, reduce_test=False, purged_size=0)[source]#
Walk Forward Cross-Validator.
Provides train/test indices to split time series data samples using a walk-forward logic.
In each split, test indices must be higher than the previous ones. Therefore, shuffling in cross-validator is inappropriate.
Compared to
sklearn.model_selection.TimeSeriesSplit, you control the train/test folds by specifying the number of training and test samples instead of the number of splits, making it more suitable for portfolio cross-validation.If your data is a DataFrame indexed with a DatetimeIndex, you can split the data using specific datetime frequencies and offsets.
- Parameters:
- test_sizeint
Length of each test set. If
freqisNone(default), it represents the number of observations. Otherwise, it represents the number of periods defined byfreq.- train_sizeint | pandas.offsets.DateOffset | datetime.timedelta
Length of each training set. If
freqisNone(default), it represents the number of observations. Otherwise, for integers, it represents the number of periods defined byfreq. For a pandas DateOffset or datetime timedelta, that offset is subtracted from each test period’s start date to determine when training begins.- freqstr | pandas.offsets.DateOffset, optional
If provided, it must be a frequency string or a pandas DateOffset, and the returns
Xmust be a DataFrame with an index of typeDatetimeIndex. For a list of pandas frequencies and offsets, see here. The default (None) meanstest_sizeandtrain_sizerepresent the number of observations. Calendar-based splits with no test observations are omitted.Below are some common examples:
Rebalancing : Monthly on the first day
Test Duration : 1 month
Train Duration : 6 months
>>> cv = WalkForward(test_size=1, train_size=6, freq="MS")
Rebalancing : Quarterly on the first day
Test Duration : 1 quarter
Train Duration : 2 months
>>> cv = WalkForward(test_size=1, train_size=pd.DateOffset(months=2), freq="QS")
Rebalancing : Monthly on the third Friday
Test Duration : 1 month
Train Duration : 6 weeks
>>> cv = WalkForward(test_size=1, train_size=pd.offsets.Week(6), freq= "WOM-3FRI")
Rebalancing : Semi-annually on the last day
Test Duration : 6 months
Train Duration : 1 year
>>> cv = WalkForward(test_size=1, train_size=2, freq=pd.offsets.MonthEnd(6))
Rebalancing : Every 2 months on the second day
Test Duration : 2 months
Train Duration : 6 months
>>> cv = WalkForward(test_size=2, train_size=6, freq="MS", freq_offset=dt.timedelta(days=1))
- freq_offsetpandas DateOffset | datetime timedelta, optional
Shifts the dates generated by
freqby this amount. Only used whenfreqis provided.- previousbool, default=False
Only used if
freqis provided. If set toTrue, and if the period start or period end is not in theDatetimeIndex, the previous observation is used. Otherwise, the next observation is used (default).- expand_trainbool, default=False
If set to
True, each subsequent training set after the first one will use all past observations. The default isFalse.- reduce_testbool, default=False
If
True, the final split is included even when there is not enough data for a full test window. Otherwise, that split is omitted. The default isFalse.- purged_sizeint, default=0
The number of observations to exclude from the end of each training set before the test set. The default value is
0.Warning
Execution timing and look-ahead control
- With
purged_size=0: Training ends at the current period and testing begins immediately.
Assumes you can observe, compute, and execute within the same period.
If observation/computation-to-execution latency is non-negligible (submission cutoffs, illiquidity, end-of-period finalization, or markets with no intraday quotation), results may be too optimistic.
- With
purged_size=1: One observation is dropped between training and test.
Decisions made on the current period start affecting performance from the next period.
- Rules of thumb:
Use
purged_size=0only when you truly can execute at the same period with minimal latency.Use
purged_size >= 1when execution is delayed (daily-priced assets, illiquid markets, end-of-day data that settles after the close).
- With
Methods
Get metadata routing of this object.
get_n_splits([X, y, groups])Return the number of train/test splits without generating their indices.
split(X[, y, groups])Generate positional indices for each training and test set.
Examples
- Tutorials using
WalkForward:
>>> import numpy as np >>> from skfolio.datasets import load_sp500_dataset, load_factors_dataset >>> from skfolio.model_selection import WalkForward >>> from skfolio.preprocessing import prices_to_returns >>> >>> X = np.random.randn(6, 2) # 6 observations >>> cv = WalkForward(test_size=1, train_size=2) >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: index={train_index}") ... print(f" Test: index={test_index}") Fold 0: Train: index=[0 1] Test: index=[2] Fold 1: Train: index=[1 2] Test: index=[3] Fold 2: Train: index=[2 3] Test: index=[4] Fold 3: Train: index=[3 4] Test: index=[5] >>> cv = WalkForward(test_size=1, train_size=2, purged_size=1) >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: index={train_index}") ... print(f" Test: index={test_index}") Fold 0: Train: index=[0 1] Test: index=[3] Fold 1: Train: index=[1 2] Test: index=[4] Fold 2: Train: index=[2 3] Test: index=[5] >>> cv = WalkForward(test_size=2, train_size=3) >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: index={train_index}") ... print(f" Test: index={test_index}") Fold 0: Train: index=[0 1 2] Test: index=[3 4] >>> cv = WalkForward(test_size=2, train_size=3, reduce_test=True) >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: index={train_index}") ... print(f" Test: index={test_index}") Fold 0: Train: index=[0 1 2] Test: index=[3 4] Fold 1: Train: index=[2 3 4] Test: index=[5] >>> cv = WalkForward(test_size=2, train_size=3, expand_train=True, reduce_test=True) >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: index={train_index}") ... print(f" Test: index={test_index}") Fold 0: Train: index=[0 1 2] Test: index=[3 4] Fold 1: Train: index=[0 1 2 3 4] Test: index=[5] >>> >>> # Time-based (calendar) rebalancing >>> prices = load_sp500_dataset() >>> X = prices_to_returns(prices) >>> X = X["2021":"2022"] >>> # Rebalance every 3 months on the third Friday, and train on the last 12 months. >>> cv = WalkForward(test_size=3, train_size=12, freq="WOM-3FRI") >>> >>> for i, (train_index, test_index) in enumerate(cv.split(X)): ... print(f"Fold {i}:") ... print(f" Train: size={len(train_index)}") ... print(f" Test: size={len(test_index)}") Fold 0: Train: size=256 Test: size=59 Fold 1: Train: size=253 Test: size=61 Fold 2: Train: size=251 Test: size=69
- get_metadata_routing()#
Get metadata routing of this object.
Please check User Guide on how the routing mechanism works.
- Returns:
- routingMetadataRequest
A
MetadataRequestencapsulating routing information.
- get_n_splits(X=None, y=None, groups=None)[source]#
Return the number of train/test splits without generating their indices.
- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- yNone
Always ignored, exists for compatibility.
- groupsNone
Always ignored, exists for compatibility.
- Returns:
- n_foldsint
Number of train/test splits.
- Raises:
- ValueError
If
XisNone, a window size has an invalid type, an integer window size is not positive, orpurged_sizeis not a non-negative integer. Whenfreq=None, also raised if training and purging leave no observations for testing. Whenfreqis set, also raised if any split with test observations has no training observations after applyingexpand_trainandpurged_size.
- split(X, y=None, groups=None)[source]#
Generate positional indices for each training and test set.
- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- yNone
Always ignored, exists for compatibility.
- groupsNone
Always ignored, exists for compatibility.
- Yields:
- trainndarray
Positional indices of the training observations.
- testndarray
Positional indices of the test observations.
- Raises:
- ValueError
If a window size has an invalid type, an integer window size is not positive, or
purged_sizeis not a non-negative integer. Whenfreq=None, also raised if training and purging leave no observations for testing. Whenfreqis set, also raised if any split with test observations has no training observations after applyingexpand_trainandpurged_size. This is checked before the first split is yielded.