diff --git a/crates/aura-engine/src/walkforward.rs b/crates/aura-engine/src/walkforward.rs index 492e672..aa9343d 100644 --- a/crates/aura-engine/src/walkforward.rs +++ b/crates/aura-engine/src/walkforward.rs @@ -13,7 +13,7 @@ use crate::sweep::run_indexed; use crate::{MetricStats, ParamSpec, RunReport, ScalarKind, Timestamp}; -use aura_core::Cell; +use aura_core::{zip_params, Cell, Scalar}; /// One rolling split's time bounds: an in-sample window and the out-of-sample /// window that follows it. Carries ONLY bounds — never tick data (#71). C2 @@ -165,6 +165,15 @@ pub struct WalkForwardResult { pub stitched_oos_equity: Vec<(Timestamp, f64)>, } +impl WalkForwardResult { + /// The chosen params of the `window`-th outcome paired with their names — a + /// derived view over the carried param-space (reuses [`zip_params`]); the + /// walk-forward mirror of [`SweepFamily::named_params`](crate::SweepFamily). + pub fn named_params(&self, window: usize) -> Vec<(String, Scalar)> { + zip_params(&self.space, &self.windows[window].run.chosen_params) + } +} + /// Roll the windows, run `run_window` on each disjointly in parallel (C1, via the /// shared [`run_indexed`](crate::sweep) core), then stitch the OOS equity into one /// continuous curve. The varying dimension is the *data window* (C12 axis 3). @@ -437,6 +446,48 @@ mod tests { assert_eq!(stab[0].mean, 0.75); } + #[test] + fn named_params_pairs_each_name_with_the_chosen_window_value() { + // #76: WalkForwardResult::named_params(w) is the typed/named view — it + // pairs each param-space NAME with window `w`'s chosen VALUE, in slot + // order, reading the right window. Mirror of SweepFamily::named_params. + // Expected vectors are spelled out explicitly (not via zip_params) so the + // assertion pins the observable pairing, not the implementation. + let mk = |fast: i64, scale: f64| WindowOutcome { + bounds: WindowBounds { + is: (Timestamp(0), Timestamp(0)), + oos: (Timestamp(0), Timestamp(0)), + }, + run: WindowRun { + chosen_params: vec![Cell::from_i64(fast), Cell::from_f64(scale)], + oos_equity: vec![], + oos_report: dummy_report(), + }, + }; + let result = WalkForwardResult { + space: vec![ + ParamSpec { name: "sma.fast".to_string(), kind: ScalarKind::I64 }, + ParamSpec { name: "exposure.scale".to_string(), kind: ScalarKind::F64 }, + ], + windows: vec![mk(8, 0.25), mk(13, 0.75)], + stitched_oos_equity: vec![], + }; + assert_eq!( + result.named_params(0), + vec![ + ("sma.fast".to_string(), Scalar::i64(8)), + ("exposure.scale".to_string(), Scalar::f64(0.25)), + ], + ); + assert_eq!( + result.named_params(1), + vec![ + ("sma.fast".to_string(), Scalar::i64(13)), + ("exposure.scale".to_string(), Scalar::f64(0.75)), + ], + ); + } + #[test] fn roller_rolling_emits_consecutive_oos_windows() { // spec §Testing 1: rolling, origin=0, is_len=10, oos_len=5, step=5, end=24.