KFold
PortfolioOptimisers.KFold — Type
struct KFold{__T_n, __T_purged_size, __T_embargo_size, __T_wd, __T_fa, __T_store_weight_path, __T_strict} <: NonSequentialCrossValidationEstimatorImplements non-sequential k-fold cross-validation with optional purging and embargoing of training samples.
The observations are cut into n consecutive folds. Each fold is the test set of one split, and the remaining folds form that split's training set. purged_size drops the last purged_size rows of the training fold that precedes the test fold, which removes the rows whose labels overlap the test period. embargo_size widens the gap on the other side: the first purged_size + embargo_size rows of the training fold that follows the test fold are dropped, which removes the rows whose features are contaminated by the test period. Both default to 0, which gives the plain k-fold scheme.
Fields
n: Number of folds.
purged_size: Number of observations to purge between train and test sets.
embargo_size: Number of observations to embargo after the test set.
wd: Weight drift the fold's return series is read under, ornothingto read it at the target weights of the fold.
fa: Fee amortisation algorithm the fold's realised series charges the two fixed fee terms on, ornothingto inherit the clock the fee itself states. It overridesFees.fafor that series alone, and it reaches the fit not at all.
store_weight_path: Iftrue, the fold stores the weight path it computed; iffalse, a reader rebuilds it on demand.
strict: Iftrue, a Held Gap raises anArgumentError; iffalse, 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.
Constructors
KFold(; n::Integer = 5, purged_size::Integer = 0, embargo_size::Integer = 0, wd::Option{<:AbstractWeightDrift} = nothing, fa::Option{<:AbstractFeeAmortisation} = nothing, store_weight_path::Bool = false, strict::Bool = false,) -> KFoldKeyword arguments correspond to the struct's fields.
Weight drift
wd is the Weight Drift of the scheme, and nothing is the library's original behaviour: a fold's return series is X * w net of fees, read at the target weights of that fold. A SelfFinancingDrift reads the series as the wealth ratio of the drifted holdings instead, and the fold carries a HeldWeightsResult. 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.
A k-fold enumeration is not a timeline, so this scheme carries no Previous-Weights Source. Each of its folds is independent of the others, and no fold has a fold behind it to inherit weights from.
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
nmust be non-empty, greater than zero, and finite.purged_sizeandembargo_sizemust be non-empty and finite.Base.splitadditionally checkspurged_size + embargo_size < div(T, n), because a gap as wide as the smallest fold would empty a training fold.
Examples
julia> KFold(; n = 5, purged_size = 7, embargo_size = 11)KFold n ┼ Int64: 5 purged_size ┼ Int64: 7 embargo_size ┼ Int64: 11 wd ┼ nothing fa ┼ nothing store_weight_path ┼ Bool: false strict ┴ Bool: falseRelated
References
PortfolioOptimisers.KFoldResult — Type
struct KFoldResult{__T_train_idx, __T_test_idx} <: NonSequentialCrossValidationResultResult type produced by KFold after splitting data into training and testing folds.
Stores the train and test index vectors for each fold.
Fields
train_idx: Training set indices.
test_idx: Test set indices.
Constructors
KFoldResult(; train_idx::VecVecInt, test_idx::VecVecInt) -> KFoldResultKeywords correspond to the struct's fields.
Validation
!isempty(train_idx).!isempty(test_idx).length(train_idx) == length(test_idx).
Related
Base.split — Method
Base.split(kf::KFold, rd::Prices_RR) -> KFoldResultSplit the data rd into n non-overlapping folds using k-fold cross-validation with optional purging and embargoing.
Arguments
kf::KFold: K-fold cross-validation estimator.rd: Returns-level or price-level data to split (Prices_RR).
Validation
purged_size + embargo_size < div(T, n), whereTis the number of observations. A gap as wide as the smallest fold would empty a training fold.
Returns
KFoldResult: Result containing train and test indices for each fold.
Details
- The folds are consecutive and cover every observation exactly once.
mod(T, n)extra rows are given one each to the firstmod(T, n)folds, so the fold sizes differ by at most one row. - For the split whose test set is fold
i, the training folds are every fold other thani, concatenated in ascending order. Foldi - 1loses its lastpurged_sizerows and foldi + 1loses its firstpurged_size + embargo_sizerows. - The training indices of every split increase strictly, which is what
assert_unshuffled_foldsneeds.
Related
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).