From e1ad0979deb8a4041d8facb812f24b1c535ed115 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 21 Jun 2026 16:41:37 +0200 Subject: [PATCH] refactor(ger40-examples): source pip from instrument_spec, drop the baked literal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runnable GER40/FRA40 examples baked `const PIP_SIZE = 1.0` and a dead pip column in the compare INSTRUMENTS table — a second source of pip truth parallel to the vetted `aura_ingest::instrument_spec` channel the CLI `--real` path already uses (#22). Route them all through one `pip_size_of(symbol)` helper: build_harness sources GER40 internally, ger40_breakout_blueprint gains a `pip_size` first arg fed from the lookup, and compare's run_cell sources each instrument's pip per cell. Value-identical (GER40/FRA40 both resolve to 1.0) and behaviour-preserving: the gated determinism / blueprint param-space / world tests stay green unchanged, and the frozen `sim-optimal(pip_size=1)` broker-label pins are untouched. closes #98 --- .../examples/ger40_breakout_compare.rs | 26 ++++++------ .../examples/ger40_breakout_real.rs | 6 ++- .../examples/ger40_breakout_sweep.rs | 14 +++++-- .../examples/ger40_breakout_walkforward.rs | 20 +++++++-- .../examples/shared/breakout_real.rs | 23 +++++++---- .../tests/ger40_breakout_blueprint.rs | 14 +++++-- .../aura-ingest/tests/ger40_breakout_world.rs | 41 +++++++++++++++---- 7 files changed, 103 insertions(+), 41 deletions(-) diff --git a/crates/aura-ingest/examples/ger40_breakout_compare.rs b/crates/aura-ingest/examples/ger40_breakout_compare.rs index ff0067d..8032e42 100644 --- a/crates/aura-ingest/examples/ger40_breakout_compare.rs +++ b/crates/aura-ingest/examples/ger40_breakout_compare.rs @@ -5,7 +5,8 @@ //! 1. **Instrument** — GER40 vs FRA40 (both `pip_size = 1.0`, index points), the //! SAME blueprint bootstrapped at the same `{entry=3, exit=5}` point over each //! symbol. The pip honesty is by construction (both quote in index points; the -//! engine has no pip registry yet — #22, filed). +//! pip is sourced per cell from the central `instrument_spec` channel — #22 +//! shipped). //! 2. **Bar period** — a 15m blueprint vs a 30m blueprint. The bar period is a //! STRUCTURAL axis (C34): `ger40_breakout_blueprint` is instantiated TWICE, //! at 15 and at 30, each a *different strategy* (the period is bound at @@ -38,18 +39,17 @@ const ENTRY_BAR: i64 = 3; const EXIT_BAR: i64 = 5; // GER40 and FRA40 both quote in index points, so pip_size = 1.0 for both — the -// only reason a cross-instrument pip comparison is honest here (no engine -// registry, #22). (symbol, pip_size, human note) -const INSTRUMENTS: &[(&str, f64, &str)] = &[ - ("GER40", 1.0, "index point"), - ("FRA40", 1.0, "index point"), -]; +// only reason a cross-instrument pip comparison is honest here. The pip is sourced +// per cell from the central vetted `instrument_spec` channel (#22 shipped), not a +// literal carried here. (symbol, human note) +const INSTRUMENTS: &[(&str, &str)] = &[("GER40", "index point"), ("FRA40", "index point")]; /// Bootstrap the blueprint (at `bar_period`) at `{entry=3, exit=5}`, run it over /// `[from, to]` (epoch-ns) of real `symbol` OHLC, and summarize — or `None` if the -/// symbol has no file overlapping the window. The `pip_size` rides on the -/// blueprint's baked SimBroker (always 1.0 here; both indices). Returns the -/// report plus the entry count (0→1 transitions of held). +/// symbol has no file overlapping the window. The `pip_size` is sourced per cell +/// from the central vetted `instrument_spec` channel (always 1.0 here; both +/// indices) and rides on the blueprint's baked SimBroker. Returns the report plus +/// the entry count (0→1 transitions of held). fn run_cell( server: &Arc, symbol: &str, @@ -57,7 +57,9 @@ fn run_cell( from: Timestamp, to: Timestamp, ) -> Option<(RunMetrics, u32)> { - let (bp, taps) = ger40_breakout_blueprint(bar_period, SESSION_HOUR, SESSION_MINUTE, Berlin); + let pip_size = pip_size_of(symbol); + let (bp, taps) = + ger40_breakout_blueprint(pip_size, bar_period, SESSION_HOUR, SESSION_MINUTE, Berlin); let point: Vec = bp .param_space() .iter() @@ -117,7 +119,7 @@ fn main() { "symbol", "pip-meaning", "sessions", "total_pips", "max_dd", "flips" ); println!("{}", "-".repeat(62)); - for &(symbol, _pip, note) in INSTRUMENTS { + for &(symbol, note) in INSTRUMENTS { if !server.has_symbol(symbol) { println!("{symbol:<8} (no local data — skip)"); continue; diff --git a/crates/aura-ingest/examples/ger40_breakout_real.rs b/crates/aura-ingest/examples/ger40_breakout_real.rs index 0d2ab04..3d6aee3 100644 --- a/crates/aura-ingest/examples/ger40_breakout_real.rs +++ b/crates/aura-ingest/examples/ger40_breakout_real.rs @@ -58,7 +58,9 @@ fn main() { // the two genuine tuning knobs (entry bar 3, exit bar 5). The bar-period (15m), // session open (09:00 Berlin) and delay.lag are structural — bound at // construction, not exposed as params. - let (bp, taps) = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let pip_size = pip_size_of(SYMBOL); + let (bp, taps) = + ger40_breakout_blueprint(pip_size, BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); let mut h = bp .with("entry_bar.target", aura_core::Scalar::i64(3)) .with("exit_bar.target", aura_core::Scalar::i64(5)) @@ -76,7 +78,7 @@ fn main() { println!("=== GER40 15m session-breakout — REAL bars ==="); println!( "symbol={SYMBOL} window={YEAR}-{MONTH:02} (UTC, inclusive) \ - bars={} pip_size={PIP_SIZE}", + bars={} pip_size={pip_size}", trace.len() ); println!("session: {SESSION_HOUR:02}:{SESSION_MINUTE:02} Europe/Berlin, {BAR_MINUTES}m bars; entry bar 3, exit bar 5"); diff --git a/crates/aura-ingest/examples/ger40_breakout_sweep.rs b/crates/aura-ingest/examples/ger40_breakout_sweep.rs index 5d6ee0c..f7c4fd5 100644 --- a/crates/aura-ingest/examples/ger40_breakout_sweep.rs +++ b/crates/aura-ingest/examples/ger40_breakout_sweep.rs @@ -35,7 +35,13 @@ const EXIT_BARS: &[i64] = &[4, 5, 6]; /// A fresh blueprint per call (fresh nodes + channels) keeps the disjoint sweep /// points independent (C1). fn run_point(server: &Arc, point: &[Cell], from: Timestamp, to: Timestamp) -> RunReport { - let (bp, taps) = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let (bp, taps) = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ); let mut h = bp .bootstrap_with_cells(point) .expect("sweep point kind-checked against param_space"); @@ -76,9 +82,11 @@ fn main() { let (from, _) = utc_month_window(2024, 1); let (_, to) = utc_month_window(2024, 12); + let pip_size = pip_size_of(SYMBOL); + // The grid is built from the blueprint's param_space() directly — the two // EqConst targets are the swept axes (no re-authoring). - let space = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin) + let space = ger40_breakout_blueprint(pip_size, BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin) .0 .param_space(); let grid = GridSpace::new( @@ -96,7 +104,7 @@ fn main() { println!("=== GER40 session-breakout — World `sweep` over entry×exit ==="); println!( - "symbol={SYMBOL} window=2024 (UTC, full year) bars=15m pip_size={PIP_SIZE} \ + "symbol={SYMBOL} window=2024 (UTC, full year) bars=15m pip_size={pip_size} \ grid={}×{} = {} points", ENTRY_BARS.len(), EXIT_BARS.len(), diff --git a/crates/aura-ingest/examples/ger40_breakout_walkforward.rs b/crates/aura-ingest/examples/ger40_breakout_walkforward.rs index 6dfae38..2c266f5 100644 --- a/crates/aura-ingest/examples/ger40_breakout_walkforward.rs +++ b/crates/aura-ingest/examples/ger40_breakout_walkforward.rs @@ -45,7 +45,13 @@ fn run_point( from: Timestamp, to: Timestamp, ) -> (RunReport, Vec<(Timestamp, f64)>) { - let (bp, taps) = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let (bp, taps) = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ); let mut h = bp .bootstrap_with_cells(point) .expect("point kind-checked against param_space"); @@ -105,9 +111,15 @@ fn main() { // The non-empty space the per-window in-sample sweep optimizes over: the two // genuine tuning knobs (the #97 resolution — a real space to optimize). - let space = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin) - .0 - .param_space(); + let space = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ) + .0 + .param_space(); println!( "optimizing param_space() per window: {:?} (in-sample grid {}×{})", space.iter().map(|p| p.name.as_str()).collect::>(), diff --git a/crates/aura-ingest/examples/shared/breakout_real.rs b/crates/aura-ingest/examples/shared/breakout_real.rs index 4e4466a..62b4eb2 100644 --- a/crates/aura-ingest/examples/shared/breakout_real.rs +++ b/crates/aura-ingest/examples/shared/breakout_real.rs @@ -65,12 +65,19 @@ pub const REC_HELD: usize = 10; // tap B — Latch exposure (f64, 0.0/1.0) pub const REC_BARS: usize = 11; // tap C — Session bars_since_open (i64) pub const REC_BREAKOUT: usize = 12; // tap D — Gt breakout flag (bool) -/// The instrument. GER40 is an index, so pips are index points: `pip_size = 1.0`. +/// The instrument. GER40 is an index, so pips are index points (`pip_size = 1.0`). pub const SYMBOL: &str = "GER40"; -/// SimBroker pip size for GER40. An index quotes in points, so one pip is one -/// point — `1.0`, exactly as the synthetic capstone test uses. -pub const PIP_SIZE: f64 = 1.0; +/// The SimBroker pip divisor for `symbol`, sourced from the central vetted +/// instrument-metadata channel ([`aura_ingest::instrument_spec`], C7/C15) instead of a +/// baked literal — so these examples consume the same pip table the CLI `--real` path +/// does, and a cross-instrument comparison is honest by construction (#98). Panics on +/// an un-vetted symbol (the examples only ever run known instruments). +pub fn pip_size_of(symbol: &str) -> f64 { + aura_ingest::instrument_spec(symbol) + .unwrap_or_else(|| panic!("no vetted instrument spec for {symbol}")) + .pip_size +} /// Session config: Frankfurt cash open 09:00 Europe/Berlin, 15m bars. The same /// `Session::new(9, 0, Europe::Berlin, 15)` the synthetic test pins. @@ -94,6 +101,7 @@ pub struct Taps { pub fn build_harness() -> (Harness, Taps) { use aura_core::Firing; + let pip_size = pip_size_of(SYMBOL); let (tx_equity, rx_equity) = mpsc::channel(); let (tx_held, rx_held) = mpsc::channel(); let (tx_bars, rx_bars) = mpsc::channel(); @@ -111,7 +119,7 @@ pub fn build_harness() -> (Harness, Taps) { EqConst::builder().schema().clone(), // 5 And::builder().schema().clone(), // 6 Latch::builder().schema().clone(), // 7 - SimBroker::builder(PIP_SIZE).schema().clone(), // 8 + SimBroker::builder(pip_size).schema().clone(), // 8 Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_equity.clone()) .schema() .clone(), // 9 @@ -135,7 +143,7 @@ pub fn build_harness() -> (Harness, Taps) { Box::new(EqConst::new(5)), // 5 Box::new(And::new()), // 6 Box::new(Latch::new()), // 7 - Box::new(SimBroker::new(PIP_SIZE)), // 8 + Box::new(SimBroker::new(pip_size)), // 8 Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx_equity)), // 9 Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx_held)), // 10 Box::new(Recorder::new(&[ScalarKind::I64], Firing::Any, tx_bars)), // 11 @@ -220,6 +228,7 @@ pub fn build_harness() -> (Harness, Taps) { /// (fresh nodes + channels) keeps two bootstraps disjoint for the C1 determinism /// assertion, exactly as `build_harness` does. pub fn ger40_breakout_blueprint( + pip_size: f64, bar_period_minutes: i64, open_hour: u32, open_minute: u32, @@ -249,7 +258,7 @@ pub fn ger40_breakout_blueprint( let exit_bar = g.add(EqConst::builder().named("exit_bar")); let and = g.add(And::builder()); let latch = g.add(Latch::builder()); - let broker = g.add(SimBroker::builder(PIP_SIZE)); + let broker = g.add(SimBroker::builder(pip_size)); // Recording sinks: equity (A), held (B), bars_since_open (C), breakout (D). let rec_equity = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_equity)); diff --git a/crates/aura-ingest/tests/ger40_breakout_blueprint.rs b/crates/aura-ingest/tests/ger40_breakout_blueprint.rs index 6c6f96c..43b2cac 100644 --- a/crates/aura-ingest/tests/ger40_breakout_blueprint.rs +++ b/crates/aura-ingest/tests/ger40_breakout_blueprint.rs @@ -37,7 +37,7 @@ const MONTH: u32 = 9; /// be swept into a desync. No data needed — runs everywhere. #[test] fn param_space_is_exactly_entry_and_exit_bar_targets() { - let (bp, _taps) = ger40_breakout_blueprint(15, 9, 0, Berlin); + let (bp, _taps) = ger40_breakout_blueprint(pip_size_of(SYMBOL), 15, 9, 0, Berlin); let space = bp.param_space(); let names: Vec<&str> = space.iter().map(|p| p.name.as_str()).collect(); @@ -64,8 +64,8 @@ fn param_space_is_exactly_entry_and_exit_bar_targets() { /// `composite_matches_flatgraph_bit_identical`.) #[test] fn blueprint_param_space_is_construction_deterministic() { - let a = ger40_breakout_blueprint(15, 9, 0, Berlin).0.param_space(); - let b = ger40_breakout_blueprint(15, 9, 0, Berlin).0.param_space(); + let a = ger40_breakout_blueprint(pip_size_of(SYMBOL), 15, 9, 0, Berlin).0.param_space(); + let b = ger40_breakout_blueprint(pip_size_of(SYMBOL), 15, 9, 0, Berlin).0.param_space(); assert_eq!(a, b, "blueprint param_space() is a deterministic function of its args"); } @@ -74,7 +74,13 @@ fn blueprint_param_space_is_construction_deterministic() { /// build per call (fresh nodes + channels) keeps two runs disjoint for the C1 /// determinism assertion. fn run_blueprint(server: &Arc, from: Timestamp, to: Timestamp) -> Vec<(f64, f64)> { - let (bp, taps) = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let (bp, taps) = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ); let mut h = bp .with("entry_bar.target", Scalar::i64(3)) .with("exit_bar.target", Scalar::i64(5)) diff --git a/crates/aura-ingest/tests/ger40_breakout_world.rs b/crates/aura-ingest/tests/ger40_breakout_world.rs index 9325c2c..30b29e9 100644 --- a/crates/aura-ingest/tests/ger40_breakout_world.rs +++ b/crates/aura-ingest/tests/ger40_breakout_world.rs @@ -40,7 +40,13 @@ use breakout_real::*; /// `run_point` do). A fresh blueprint per call (fresh nodes + channels) keeps the /// disjoint sweep points independent (C1). fn run_point(server: &Arc, point: &[Cell], from: Timestamp, to: Timestamp) -> RunReport { - let (bp, taps) = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let (bp, taps) = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ); let mut h = bp .bootstrap_with_cells(point) .expect("sweep point kind-checked against param_space"); @@ -88,9 +94,15 @@ fn sweep_consumes_blueprint_over_named_params() { } let (from, to) = utc_month_window(2024, 9); - let space = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin) - .0 - .param_space(); + let space = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ) + .0 + .param_space(); // The grid is exactly over `param_space()`: entry ∈ {2,3,4}, exit ∈ {4,5,6}. let grid = GridSpace::new( &space, @@ -170,9 +182,15 @@ fn walk_forward_consumes_blueprint_non_degenerate() { // The non-empty space the in-sample sweep optimizes over per window: the two // genuine tuning knobs. This is the #97 resolution — a real space, so the // chosen params are populated, never an empty degenerate vec. - let space = ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin) - .0 - .param_space(); + let space = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ) + .0 + .param_space(); let server_for_closure = Arc::clone(&server); let result = walk_forward(roller, space.clone(), move |w: WindowBounds| -> WindowRun { @@ -210,8 +228,13 @@ fn walk_forward_consumes_blueprint_non_degenerate() { // Apply the chosen params on the OOS window. let oos_report = run_point(&server_for_closure, &chosen, w.oos.0, w.oos.1); // Re-run once on OOS to capture the recorded equity segment for stitching. - let (bp, taps) = - ger40_breakout_blueprint(BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin); + let (bp, taps) = ger40_breakout_blueprint( + pip_size_of(SYMBOL), + BAR_MINUTES, + SESSION_HOUR, + SESSION_MINUTE, + Berlin, + ); let mut h = bp.bootstrap_with_cells(&chosen).expect("chosen point kind-checked"); let sources = open_ohlc(&server_for_closure, SYMBOL, w.oos.0, w.oos.1) .expect("OOS window overlaps data");