cross_validation
Concept Overview
Section titled “Concept Overview”Standard k-fold leaks in finance because labels overlap: an observation’s label is realised over a span of bars, and a training observation whose span touches a test observation’s span has effectively seen the answer. PurgedKFold takes those spans as samples_info_sets, drops the overlapping training observations (purging), then drops a further pct_embargo fraction of observations immediately after each test fold to catch the serial correlation the spans do not literally share.
When to Use
Section titled “When to Use”Use it in place of plain k-fold for every model whose labels are event-based — which is every model built on labeling. ml_cross_val_score wraps it for scoring and ml_get_train_times exposes the purged training index if you are driving your own loop. Report fold-to-fold variance, not only the mean: a high mean with high variance across purged folds usually means the leakage moved rather than disappeared.
Mathematical Foundations
Section titled “Mathematical Foundations”Purged Train Set
Section titled “Purged Train Set”
where is observation ‘s label span — the samples_info_sets entry PurgedKFold::new requires. Purging drops any training observation whose label lifetime overlaps a test label’s; is the embargo set below. Overlap, not adjacency, is what leaks: two observations sampled a month apart still share information if their labels resolve on the same bar.
Embargo
Section titled “Embargo”
where is the total number of observations and the pct_embargo fraction (0.01 = 1%), so is an observation count. The embargo drops the observations immediately after each test fold, which catches serial correlation that purging alone misses because the label spans do not literally overlap.
Usage Examples
Section titled “Usage Examples”Configure PurgedKFold
Section titled “Configure PurgedKFold”use chrono::{Duration, NaiveDateTime};use openquant::cross_validation::PurgedKFold;
let t0 = NaiveDateTime::parse_from_str("2024-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")?;
// samples_info_sets is one (label_start, label_end) span per observation. It is// mandatory: without label lifetimes there is nothing to purge against.let samples_info_sets: Vec<(NaiveDateTime, NaiveDateTime)> = (0..100) .map(|i| (t0 + Duration::days(i), t0 + Duration::days(i + 3))) .collect();
// n_splits = 5 folds; pct_embargo = 0.01 drops a further 1% of the sample// immediately after each test fold. new() validates and returns a Result.let cv = PurgedKFold::new(5, samples_info_sets, 0.01)?;
let splits = cv.split(100)?;println!("{} folds; fold 0 keeps {} training rows", splits.len(), splits[0].0.len());API Reference
Section titled “API Reference”Rust API
Section titled “Rust API”ml_cross_val_scoreml_get_train_timesPurgedKFoldScoring
Risk Notes and Caveats
Section titled “Risk Notes and Caveats”- Always align event end-times when purging.
- Report variance across folds, not only mean score.