(
data: &BacktestData,
run: &BacktestRunConfig,
config: &WalkForwardConfig,
mut evaluator: E,
)
| 182 | } |
| 183 | |
| 184 | pub fn run_walk_forward<E>( |
| 185 | data: &BacktestData, |
| 186 | run: &BacktestRunConfig, |
| 187 | config: &WalkForwardConfig, |
| 188 | mut evaluator: E, |
| 189 | ) -> Result<WalkForwardResult, String> |
| 190 | where |
| 191 | E: FnMut(&SplitDefinition) -> Result<Vec<f64>, String>, |
| 192 | { |
| 193 | data.validate()?; |
| 194 | run.validate(BacktestMode::WalkForward)?; |
| 195 | validate_embargo(config.pct_embargo)?; |
| 196 | if config.min_train_size == 0 { |
| 197 | return Err("min_train_size must be > 0".to_string()); |
| 198 | } |
| 199 | if config.test_size == 0 { |
| 200 | return Err("test_size must be > 0".to_string()); |
| 201 | } |
| 202 | if config.step_size == 0 { |
| 203 | return Err("step_size must be > 0".to_string()); |
| 204 | } |
| 205 | |
| 206 | let n_samples = data.returns.len(); |
| 207 | let mut split_defs = Vec::new(); |
| 208 | let mut start = config.min_train_size; |
| 209 | let mut split_id = 0; |
| 210 | |
| 211 | while start < n_samples { |
| 212 | let stop = (start + config.test_size).min(n_samples); |
| 213 | let test_indices: Vec<usize> = (start..stop).collect(); |
| 214 | if test_indices.is_empty() { |
| 215 | break; |
| 216 | } |
| 217 | let initial_train: Vec<usize> = (0..start).collect(); |
| 218 | let (train_indices, purged_count, embargo_count) = apply_purge_and_embargo( |
| 219 | &initial_train, |
| 220 | &test_indices, |
| 221 | &data.label_spans, |
| 222 | config.pct_embargo, |
| 223 | n_samples, |
| 224 | ); |
| 225 | if train_indices.is_empty() { |
| 226 | return Err("walk-forward produced an empty train split".to_string()); |
| 227 | } |
| 228 | split_defs.push(SplitDefinition { |
| 229 | split_id, |
| 230 | train_indices, |
| 231 | test_indices, |
| 232 | test_groups: vec![split_id], |
| 233 | purged_count, |
| 234 | embargo_count, |
| 235 | }); |
| 236 | split_id += 1; |
| 237 | start += config.step_size; |
| 238 | } |
| 239 | |
| 240 | if split_defs.is_empty() { |
| 241 | return Err("walk-forward produced no splits".to_string()); |
no test coverage detected