7e4b91ab5f
The data-server now ships per-symbol geometry sidecars, so aura no longer hard-codes instrument geometry. A consumption grep showed production reads exactly one `InstrumentSpec` field — `pip_size` — at the real-data path; the other eight (incl. `instrument_id`) were constructed but never read. - aura-cli resolves the real-path pip from the recorded sidecar (`instrument_geometry` -> `InstrumentGeometry.pip_size`), refusing (exit 2) on absent geometry. The authored table no longer gates: any symbol with a sidecar + bars now runs (new test: USDJPY, never vetted, resolves its JPY pip 0.01 from the sidecar). - Removed the redundant authored floor: `InstrumentSpec`, `instrument_spec`, `VETTED_SYMBOLS`, and the table-only tests. The `InstrumentGeometry` seam (the surviving pip source) and its nameability guard are kept. - `instrument_id` (the "find out"): no production consumer. The position-event table's `instrument_id` is a decoupled caller parameter; the engine never imports an instrument spec. Dropped with the struct. - The example `pip_size_of` is re-sourced to the sidecar; the pure-structural blueprint tests use a literal pip to stay archive-independent. Speculative deploy-grade fields and the #124 override/floor tiers are deferred to the Stage-2 broker that will consume them, read from the sidecar geometry then. Ledger (C8/C15/#22/#106) updated; #124 collapses to "recorded sidecar geometry -> refuse" in the meantime. Verified: cargo test --workspace (all green), clippy -D warnings clean, cargo doc clean, with the local archive present so the sidecar paths run. refs #124
171 lines
6.6 KiB
Rust
171 lines
6.6 KiB
Rust
//! 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
|
|
//! pip is sourced per cell from the recorded geometry sidecar — #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
|
|
//! 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};
|
|
use chrono_tz::Europe::Berlin;
|
|
|
|
use aura_ingest::{default_data_server, open_ohlc, DataServer, DEFAULT_DATA_PATH};
|
|
|
|
#[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. The pip is sourced
|
|
// per cell from the recorded geometry sidecar (#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` is sourced per cell
|
|
/// from the recorded geometry sidecar (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<DataServer>,
|
|
symbol: &str,
|
|
bar_period: i64,
|
|
from: Timestamp,
|
|
to: Timestamp,
|
|
) -> Option<(RunMetrics, u32)> {
|
|
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<Cell> = 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 srcs = open_ohlc(server, symbol, from, to)?;
|
|
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 = default_data_server();
|
|
if !server.has_symbol(SYMBOL) {
|
|
println!("skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent).");
|
|
return;
|
|
}
|
|
|
|
// Full year 2024 (the same UTC span, via the shared Timestamp helper).
|
|
let (from, _) = utc_month_window(2024, 1);
|
|
let (_, to) = utc_month_window(2024, 12);
|
|
|
|
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, note) in INSTRUMENTS {
|
|
if !server.has_symbol(symbol) {
|
|
println!("{symbol:<8} (no local data — skip)");
|
|
continue;
|
|
}
|
|
match run_cell(&server, symbol, BAR_MINUTES, from, to) {
|
|
Some((m, entries)) => println!(
|
|
"{symbol:<8} {note:<14} {entries:>9} {:>11.1} {:>10.1} {:>7}",
|
|
m.total_pips, m.max_drawdown, m.bias_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, to) {
|
|
Some((m, entries)) => println!(
|
|
"{:<10} {entries:>9} {:>11.1} {:>10.1} {:>7}",
|
|
format!("{period}m"),
|
|
m.total_pips,
|
|
m.max_drawdown,
|
|
m.bias_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:?}"),
|
|
}
|
|
}
|