// Task 2 — Walk-forward the SHIPPED breakout across real GER40 history. // // The researcher's task: "the engine advertises a World walk-forward family. // Roll my GER40 breakout across 2018..2024 with in-sample/out-of-sample windows // and stitch the OOS pip curve." The breakout I want to roll is the SHIPPED one // (crates/aura-ingest/examples/shared/breakout_real.rs) — a hand-wired FlatGraph. // // This file records the discovery journey verbatim. The public walk_forward is: // walk_forward(roller: WindowRoller, space: Vec, // run_window: Fn(WindowBounds) -> WindowRun) -> WalkForwardResult // with WindowRun { chosen_params: Vec, oos_equity, oos_report }. // // SEAM 1 (recorded, not worked around): the breakout has NO swept params in this // scenario (a fixed-param OOS roll), yet walk_forward DEMANDS a space: // Vec and each WindowRun a chosen_params: Vec. With a raw // FlatGraph there is no param_space() to hand it — the FlatGraph is built by // Harness::bootstrap, which takes no params. So I pass an EMPTY space (vec![]) and // EMPTY chosen_params (vec![]). Whether that is the intended idiom for a // param-free strategy is a spec question — recorded. // // SEAM 2 (recorded): the breakout source is FOUR M1FieldSources (OHLC) opened by // Unix-ms, but WindowBounds.{is,oos} are epoch-ns. aura-ingest exports // unix_ms_to_epoch_ns but NOT its inverse, so I hand-divide ns->ms (the milestone // fixture's mw_3 hit the same seam). // // PUBLIC SURFACE ONLY: walk_forward signature from cargo doc; the breakout wiring // reused verbatim from the shipped example's shared module. use std::sync::Arc; use chrono::TimeZone; use data_server::{DataServer, DEFAULT_DATA_PATH}; use aura_core::Timestamp; use aura_engine::{ walk_forward, RollMode, RunReport, WindowBounds, WindowRoller, WindowRun, }; #[path = "../../crates/aura-ingest/examples/shared/breakout_real.rs"] mod breakout_real; use breakout_real::*; // SEAM 2: ns -> ms (aura-ingest exports only the forward direction). fn ns_to_ms(ts: Timestamp) -> i64 { ts.0 / 1_000_000 } // Run the SHIPPED breakout over one [from, to] epoch-ns window of real GER40 M1 // OHLC, returning the OOS pip-equity stream + the folded RunReport. fn run_breakout_window( server: &Arc, from: Timestamp, to: Timestamp, ) -> (Vec<(Timestamp, f64)>, RunReport) { let from_ms = ns_to_ms(from); let to_ms = ns_to_ms(to); // Open the four OHLC field sources directly (the shipped open_ohlc_sources // helper hard-codes Some(from)/Some(to) and returns Option — reuse it). let sources = open_ohlc_sources(server, from_ms, to_ms).expect("window overlaps GER40 data"); let (mut h, taps) = build_harness(); h.run(sources); let trace = drain_trace(&taps); let report = report_from_trace(&trace, from_ms, to_ms); let oos_equity: Vec<(Timestamp, f64)> = trace.iter().map(|b| (b.ts, b.equity)).collect(); (oos_equity, report) } fn main() { let server = Arc::new(DataServer::new(DEFAULT_DATA_PATH)); if !server.has_symbol(SYMBOL) { println!("skip: no local {SYMBOL} data"); return; } println!("=== Task 2: walk-forward the shipped GER40 breakout (2018..2024) ===\n"); // The roll span: 2018-01 .. 2024-12 in epoch-ns. is=12mo, oos=3mo, step=3mo. let day_ns: i64 = 86_400_000 * 1_000_000; let month_ns: i64 = 30 * day_ns; let origin = aura_ingest::unix_ms_to_epoch_ns( chrono::Utc.with_ymd_and_hms(2018, 1, 1, 0, 0, 0).unwrap().timestamp_millis(), ); let end = aura_ingest::unix_ms_to_epoch_ns( chrono::Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap().timestamp_millis(), ); let roller = WindowRoller::new( (origin, end), 12 * month_ns, // in-sample length 3 * month_ns, // out-of-sample length 3 * month_ns, // step RollMode::Rolling, ) .expect("span fits >= 1 window"); let server_for_closure = Arc::clone(&server); // SEAM 1: the breakout is param-free here, so space = vec![] and each // WindowRun carries chosen_params = vec![]. There is no in-sample optimize to // do (no params to tune with a raw FlatGraph), so each window just runs the // fixed strategy on its OOS slice. (Tuning the breakout's structural params is // Task 3 — and the seam there is that they are NOT a FlatGraph param_space.) let result = walk_forward(roller, vec![], move |w: WindowBounds| -> WindowRun { let (oos_equity, oos_report) = run_breakout_window(&server_for_closure, w.oos.0, w.oos.1); WindowRun { chosen_params: vec![], oos_equity, oos_report } }); println!("walk-forward produced {} windows\n", result.windows.len()); for (k, w) in result.windows.iter().enumerate() { let b = w.bounds; println!( " window {k}: is=[{}..{}] oos=[{}..{}] oos_pips={:>8.1} oos_dd={:>7.1}", ns_to_ms(b.is.0), ns_to_ms(b.is.1), ns_to_ms(b.oos.0), ns_to_ms(b.oos.1), w.run.oos_report.metrics.total_pips, w.run.oos_report.metrics.max_drawdown, ); } let stitched = &result.stitched_oos_equity; println!( "\nstitched OOS curve: {} points, final cumulative = {:.1} pips", stitched.len(), stitched.last().map(|(_, v)| *v).unwrap_or(0.0), ); // The stitched OOS curve is the breakout's out-of-sample pip total over the // concatenated OOS quarters (no in-sample optimization happened — same fixed // FlatGraph strategy everywhere, since a raw FlatGraph has no param_space()). println!("\nOK: fixed-param OOS walk-forward over the shipped breakout FlatGraph."); }