(
data: &BacktestData,
run: &BacktestRunConfig,
config: &CpcvConfig,
mut evaluator: E,
)
| 295 | } |
| 296 | |
| 297 | pub fn run_cpcv<E>( |
| 298 | data: &BacktestData, |
| 299 | run: &BacktestRunConfig, |
| 300 | config: &CpcvConfig, |
| 301 | mut evaluator: E, |
| 302 | ) -> Result<CpcvResult, String> |
| 303 | where |
| 304 | E: FnMut(&SplitDefinition) -> Result<Vec<f64>, String>, |
| 305 | { |
| 306 | data.validate()?; |
| 307 | run.validate(BacktestMode::CombinatorialPurgedCrossValidation)?; |
| 308 | validate_cpcv_params(config.n_groups, config.test_groups)?; |
| 309 | validate_embargo(config.pct_embargo)?; |
| 310 | |
| 311 | let group_index_map = contiguous_folds(data.returns.len(), config.n_groups)?; |
| 312 | let group_combinations = combinations(config.n_groups, config.test_groups); |
| 313 | let mut split_defs = Vec::with_capacity(group_combinations.len()); |
| 314 | |
| 315 | for (split_id, groups) in group_combinations.iter().enumerate() { |
| 316 | let mut test_indices = Vec::new(); |
| 317 | for g in groups { |
| 318 | test_indices.extend(group_index_map[*g].iter().copied()); |
| 319 | } |
| 320 | test_indices.sort_unstable(); |
| 321 | |
| 322 | let initial_train: Vec<usize> = |
| 323 | (0..data.returns.len()).filter(|idx| !test_indices.contains(idx)).collect(); |
| 324 | let (train_indices, purged_count, embargo_count) = apply_purge_and_embargo( |
| 325 | &initial_train, |
| 326 | &test_indices, |
| 327 | &data.label_spans, |
| 328 | config.pct_embargo, |
| 329 | data.returns.len(), |
| 330 | ); |
| 331 | |
| 332 | if train_indices.is_empty() { |
| 333 | return Err("CPCV produced an empty train split".to_string()); |
| 334 | } |
| 335 | |
| 336 | split_defs.push(SplitDefinition { |
| 337 | split_id, |
| 338 | train_indices, |
| 339 | test_indices, |
| 340 | test_groups: groups.clone(), |
| 341 | purged_count, |
| 342 | embargo_count, |
| 343 | }); |
| 344 | } |
| 345 | |
| 346 | let (folds, split_returns) = evaluate_splits_with_returns(&split_defs, &mut evaluator)?; |
| 347 | let path_count = cpcv_path_count(config.n_groups, config.test_groups)?; |
| 348 | let path_assignments = build_cpcv_path_assignments(config.n_groups, &split_defs, path_count)?; |
| 349 | let path_distribution = build_path_distribution( |
| 350 | config.n_groups, |
| 351 | &group_index_map, |
| 352 | &path_assignments, |
| 353 | &split_defs, |
| 354 | &split_returns, |
no test coverage detected