//! Runnable World-family demo: **`compare`** the session-breakout `Composite` //! blueprint across two structural axes (C11/C20 experiment matrix), //! on REAL data, with NO re-authoring: //! //! 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). //! 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 //! construction, locking Resample and Session equal — it is NOT a sweep param). //! //! A "comparison" here is a plain Rust loop over the matrix (C20): one blueprint //! per matrix cell, each bootstrapped + run + summarized, the reports tabulated. //! //! Run: //! ```text //! cargo run -p aura-ingest --example ger40_breakout_compare //! ``` //! Prints both comparison tables. Skips a row cleanly where a symbol is absent. use std::sync::Arc; use aura_core::{Cell, Scalar, Timestamp}; use aura_engine::{summarize, RunMetrics, Source}; use chrono::TimeZone; use chrono_tz::Europe::Berlin; use data_server::{DataServer, DEFAULT_DATA_PATH}; use aura_ingest::{M1Field, M1FieldSource}; #[path = "shared/breakout_real.rs"] mod breakout_real; use breakout_real::*; // The baseline tuning point both axes hold fixed (so only the structural axis // varies): entry bar 3, exit bar 5 — the shipped baseline. 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"), ]; /// Bootstrap the blueprint (at `bar_period`) at `{entry=3, exit=5}`, run it over /// `[from_ms, to_ms]` 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). fn run_cell( server: &Arc, symbol: &str, bar_period: i64, from_ms: i64, to_ms: i64, ) -> Option<(RunMetrics, u32)> { let (bp, taps) = ger40_breakout_blueprint(bar_period, SESSION_HOUR, SESSION_MINUTE, Berlin); let point: Vec = bp .param_space() .iter() .map(|ps| match ps.name.as_str() { "entry_bar.target" => Scalar::i64(ENTRY_BAR).cell(), "exit_bar.target" => Scalar::i64(EXIT_BAR).cell(), other => panic!("unexpected param slot {other}"), }) .collect(); let mut h = bp.bootstrap_with_cells(&point).expect("point kind-checked"); let fields = [M1Field::Open, M1Field::High, M1Field::Low, M1Field::Close]; let mut srcs: Vec> = Vec::with_capacity(4); for &f in &fields { let s = M1FieldSource::open(server, symbol, Some(from_ms), Some(to_ms), f)?; srcs.push(Box::new(s)); } h.run(srcs); drop(h); let equity: Vec<(Timestamp, f64)> = taps .equity .try_iter() .map(|(t, r)| (t, as_f64(&r[0]))) .collect(); let held: Vec<(Timestamp, f64)> = taps .held .try_iter() .map(|(t, r)| (t, as_f64(&r[0]))) .collect(); // Count entries (0 -> 1 transitions of held). let mut entries = 0u32; let mut prev = 0.0_f64; for (_, v) in &held { if *v == 1.0 && prev == 0.0 { entries += 1; } prev = *v; } Some((summarize(&equity, &held), entries)) } fn main() { let server = Arc::new(DataServer::new(DEFAULT_DATA_PATH)); if !server.has_symbol(SYMBOL) { println!("skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent)."); return; } // Full year 2024 for body. let from_ms = chrono::Utc .with_ymd_and_hms(2024, 1, 1, 0, 0, 0) .unwrap() .timestamp_millis(); let to_ms = chrono::Utc .with_ymd_and_hms(2024, 12, 31, 23, 59, 59) .unwrap() .timestamp_millis(); println!("=== GER40 session-breakout — World `compare` (structural matrix) ==="); println!("window=2024 (UTC, full year), fixed tuning point entry={ENTRY_BAR}, exit={EXIT_BAR}\n"); // --- Axis 1: instrument (GER40 vs FRA40), 15m blueprint. ----------------- println!("--- axis 1: instrument (same 15m blueprint, GER40 vs FRA40) ---"); println!( "{:<8} {:<14} {:>9} {:>11} {:>10} {:>7}", "symbol", "pip-meaning", "sessions", "total_pips", "max_dd", "flips" ); println!("{}", "-".repeat(62)); for &(symbol, _pip, note) in INSTRUMENTS { if !server.has_symbol(symbol) { println!("{symbol:<8} (no local data — skip)"); continue; } match run_cell(&server, symbol, BAR_MINUTES, from_ms, to_ms) { Some((m, entries)) => println!( "{symbol:<8} {note:<14} {entries:>9} {:>11.1} {:>10.1} {:>7}", m.total_pips, m.max_drawdown, m.exposure_sign_flips ), None => println!("{symbol:<8} (no overlapping OHLC files — skip)"), } } println!(); // --- Axis 2: bar period (15m vs 30m blueprint), GER40. ------------------- // The STRUCTURAL axis: ger40_breakout_blueprint instantiated at 15 AND at 30 // — two *different strategies* (the period binds Resample AND Session equal at // construction; it is not a sweep param). C34 / C11. println!("--- axis 2: bar period (GER40, 15m blueprint vs 30m blueprint) ---"); println!( "{:<10} {:>9} {:>11} {:>10} {:>7}", "bar-period", "sessions", "total_pips", "max_dd", "flips" ); println!("{}", "-".repeat(52)); for &period in &[15_i64, 30] { match run_cell(&server, SYMBOL, period, from_ms, to_ms) { Some((m, entries)) => println!( "{:<10} {entries:>9} {:>11.1} {:>10.1} {:>7}", format!("{period}m"), m.total_pips, m.max_drawdown, m.exposure_sign_flips, ), None => println!("{period}m (no data — skip)"), } } println!("\nOK: one blueprint, two structural axes (instrument + bar period) compared"); println!(" with no re-authoring — the bar period is a construction arg (15m and 30m"); println!(" are different strategies, C34), the instrument a plain matrix loop (C20)."); } fn as_f64(s: &Scalar) -> f64 { match s { Scalar::F64(v) => *v, other => panic!("expected f64, got {other:?}"), } }