combinatorial_purged_cv

Defined in fynance.data

combinatorial_purged_cv(T, n_groups=6, n_test_groups=2, purge=0, embargo=0)[source]

Generate combinatorial purged cross-validation (CPCV) folds.

A single walk-forward path (or a plain purged K-fold) gives exactly one out-of-sample (OOS) performance estimate, so its variance from the particular split chosen is never measured – a lucky or unlucky path looks identical to a robust one. CPCV instead splits [0, T) into n_groups contiguous groups and, for every combination of n_test_groups groups held out as test, purges/embargoes the remainder and yields it as one train/test fold. That produces n_splits = math.comb(n_groups, n_test_groups) folds – versus the single path of walk_forward – whose paths can be reassembled into many distinct OOS return series, turning one backtest into a distribution of OOS Sharpe ratios (and letting one estimate the probability of backtest overfitting, PBO).

Parameters:
Tint

Number of observations.

n_groupsint, optional

Number of contiguous groups to split [0, T) into (sizes as equal as possible; any remainder bars go to the first groups, i.e. the same convention as numpy.array_split). Default 6.

n_test_groupsint, optional

Number of groups held out as test in each fold. Default 2.

purgeint, optional

Number of bars removed from train on both sides of every test group’s boundary (immediately before its start and immediately after its end), so that no train observation straddles a test boundary. Default 0.

embargoint, optional

Number of bars additionally removed from train immediately after each test group’s end (beyond purge), to absorb serial correlation that would otherwise leak test information forward into a subsequent train block. Default 0.

Yields:
(train_idx, test_idx)tuple of numpy.ndarray

Sorted, unique int64 index arrays, same convention as walk_forward. test_idx is the union of the chosen test groups; train_idx is every other index minus the purge/embargo windows.

Raises:
ValueError

If n_test_groups is not strictly between 0 and n_groups, or if n_groups exceeds T (a group would then be empty).

See also

train_test_split, walk_forward

Notes

Combinations are generated in the deterministic order of itertools.combinations applied to range(n_groups). By a standard counting argument, each of the n_groups groups appears in the test set of exactly math.comb(n_groups - 1, n_test_groups - 1) of the math.comb(n_groups, n_test_groups) folds.

References

[1]

Lopez de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. Chapter 7, “Cross-Validation in Finance”.

Examples

>>> import math
>>> folds = list(combinatorial_purged_cv(12, n_groups=4, n_test_groups=1, purge=1))
>>> len(folds) == math.comb(4, 1)
True
>>> train_idx, test_idx = folds[0]
>>> test_idx
array([0, 1, 2])
>>> train_idx  # bar 3 purged (1 bar after the test group's end)
array([ 4,  5,  6,  7,  8,  9, 10, 11])