// Milestone fieldtest — scenario 1: a single concrete run, bound BY NAME. // // Promise probed: an author declares a strategy's tunable knobs in its schema // (typed), inspects them via `param_space()`, and binds one concrete runnable // instance by NAME — `.with("sma_cross.fast", 2).with("scale", 0.5).bootstrap()`. // // Public interface only: discovered from the ledger + specs 0015/0030 + rustdoc. // No crates/*/src was read. // // The harness: source -> sma_cross composite { Sma, Sma, Sub } -> Exposure // -> SimBroker -> Recorder. The two SMA lengths live INSIDE the // `sma_cross` composite (so param_space() path-qualifies them as // `sma_cross.fast` / `sma_cross.slow`), `scale` is a root-level Exposure knob // (bare). The 0024 root model: the root graph IS a Composite all of whose roles // are source-bound. use std::sync::mpsc; use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; use aura_engine::{ BindError, BlueprintNode, Composite, Edge, OutField, ParamAlias, Role, Target, }; use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; /// Build the reusable inner 2/4-SMA-cross composite (value-empty recipes; the /// two lengths are open knobs bound at bootstrap). fn sma_cross() -> Composite { Composite::new( "sma_cross", vec![ Sma::builder().into(), Sma::builder().into(), Sub::builder().into(), ], // interior edges: fast -> Sub.slot0, slow -> Sub.slot1 vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 1, from_field: 0 }, ], // one OPEN input role (price) fanning into both SMAs' slot 0. // source: None => open interior port, wired by the enclosing graph. vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }, ], // source: None => an OPEN interior port, wired by the enclosing graph // (0024 root model: Role.source distinguishes open vs source-bound). source: None, }], vec![], // no param aliases vec![OutField { node: 2, field: 0, name: "out".into() }], ) } /// The SAME composite, but with C23 ParamAlias overlays so the two interior SMA /// knobs surface as the DISTINCT names `fast` / `slow` the milestone promise /// example assumes. This is the work the author must do by hand to make /// `sma_cross.fast` real. fn sma_cross_aliased() -> Composite { Composite::new( "sma_cross", vec![ Sma::builder().into(), Sma::builder().into(), Sub::builder().into(), ], vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 1, from_field: 0 }, ], vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }, ], source: None, }], // ALIASES: rename Sma#0's length slot -> "fast", Sma#1's -> "slow". vec![ ParamAlias { name: "fast".into(), node: 0, slot: 0 }, ParamAlias { name: "slow".into(), node: 1, slot: 0 }, ], vec![OutField { node: 2, field: 0, name: "out".into() }], ) } /// Harness over an arbitrary inner cross composite (plain or aliased). fn harness_with( inner: Composite, tx: mpsc::Sender<(Timestamp, Vec)>, ) -> Composite { Composite::new( "harness", vec![ BlueprintNode::Composite(inner), Exposure::builder().into(), SimBroker::builder(1e-4).into(), Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx).into(), ], vec![ Edge { from: 0, to: 1, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 0, from_field: 0 }, Edge { from: 2, to: 3, slot: 0, from_field: 0 }, ], vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }, ], source: Some(ScalarKind::F64), }], vec![], vec![], ) } /// The root harness over the PLAIN (un-aliased) cross — its two SMA knobs both /// surface as `sma_cross.length`. fn harness(tx: mpsc::Sender<(Timestamp, Vec)>) -> Composite { harness_with(sma_cross(), tx) } /// The root harness over the ALIASED cross — knobs surface as /// `sma_cross.fast` / `sma_cross.slow`. fn harness_aliased(tx: mpsc::Sender<(Timestamp, Vec)>) -> Composite { harness_with(sma_cross_aliased(), tx) } fn synthetic_prices() -> Vec<(Timestamp, Scalar)> { let prices = [ 1.00, 1.01, 1.02, 1.03, 1.05, 1.08, 1.06, 1.04, 1.02, 1.01, 1.03, 1.07, ]; prices .iter() .enumerate() .map(|(i, &p)| (Timestamp(60_000_000_000 * i as i64), Scalar::F64(p))) .collect() } fn main() { // --- 1. Inspect the param-space: does it tell me the knob names? ---------- let (tx0, _rx0) = mpsc::channel(); let space = harness(tx0).param_space(); println!("param_space() knobs (name : kind):"); for p in &space { println!(" {} : {:?}", p.name, p.kind); } // --- 2a. FIRST ATTEMPT: bind by the milestone-promise names. -------------- // The carrier / spec 0030 worked example says these are `sma_cross.fast` / // `sma_cross.slow`. Try it verbatim and record the diagnostic. let (txp, _rxp) = mpsc::channel(); let promise_form: Result<_, BindError> = harness(txp) .with("sma_cross.fast", 2) .with("sma_cross.slow", 4) .with("scale", 0.5) .bootstrap(); println!("\n[2a] promise-form (sma_cross.fast/.slow) result: {:?}", promise_form.err()); // --- 2b. SECOND ATTEMPT: bind by the names param_space() ACTUALLY emits. --- // Both interior SMA knobs surface as the IDENTICAL `sma_cross.length`. let (txq, _rxq) = mpsc::channel(); let real_name_form: Result<_, BindError> = harness(txq) .with("sma_cross.length", 2) .with("scale", 0.5) .bootstrap(); println!("[2b] real-emitted-name (sma_cross.length) result: {:?}", real_name_form.err()); // --- 2c. The FIX: add C23 ParamAlias overlays so fast/slow become real. --- // With the aliases, param_space() emits the DISTINCT names the promise wants. let (txal0, _rxal0) = mpsc::channel(); let aliased_space = harness_aliased(txal0).param_space(); println!("\n[2c] param_space() AFTER aliasing:"); for p in &aliased_space { println!(" {} : {:?}", p.name, p.kind); } // Now the milestone-promise named bind works AND runs. let (tx, rx) = mpsc::channel::<(Timestamp, Vec)>(); let mut h = harness_aliased(tx) .with("sma_cross.fast", 2) .with("sma_cross.slow", 4) .with("scale", 0.5) .bootstrap() .expect("aliased harness binds by the promised names"); h.run(vec![synthetic_prices()]); drop(h); let rows: Vec<(Timestamp, Vec)> = rx.iter().collect(); println!("\n[2c] aliased named-bound run — recorded equity rows: {}", rows.len()); for (ts, row) in &rows { if let Scalar::F64(x) = row[0] { println!(" ts={:>14} equity_pips={:.4}", ts.0, x); } } assert!(!rows.is_empty(), "named-bound harness must record a populated trace"); // --- 3. Probe diagnostics: a misspelled knob name (against aliased names). - let (txa, _rxa) = mpsc::channel(); let wrong_name: Result<_, BindError> = harness_aliased(txa) .with("sma_cross.fast", 2) .with("sma_cross.slo", 4) // typo: "slo" not "slow" .with("scale", 0.5) .bootstrap(); println!("\n[3] wrong-name (sma_cross.slo) result: {:?}", wrong_name.err()); // --- 4. Probe diagnostics: the SHORT bare name (spec says UnknownKnob). ---- let (txb, _rxb) = mpsc::channel(); let bare: Result<_, BindError> = harness_aliased(txb) .with("fast", 2) // unqualified — spec: UnknownKnob .with("sma_cross.slow", 4) .with("scale", 0.5) .bootstrap(); println!("[4] bare-name (\"fast\") result: {:?}", bare.err()); // --- 5. Probe diagnostics: a wrong-KIND value. ---------------------------- let (txc, _rxc) = mpsc::channel(); let wrong_kind: Result<_, BindError> = harness_aliased(txc) .with("sma_cross.fast", 2) .with("sma_cross.slow", 4) .with("scale", 1) // I64 literal into an F64 slot -> KindMismatch .bootstrap(); println!("[5] wrong-kind (scale=1 into F64) result: {:?}", wrong_kind.err()); // --- 6. Probe: binding the AMBIGUOUS un-aliased name -> AmbiguousKnob. ----- let (txd, _rxd) = mpsc::channel(); let ambiguous: Result<_, BindError> = harness(txd) // PLAIN harness, both = sma_cross.length .with("sma_cross.length", 2) .with("scale", 0.5) .bootstrap(); println!("[6] ambiguous (plain sma_cross.length) result: {:?}", ambiguous.err()); println!("\nOK: param-space inspected, named bind ran (aliased), diagnostics probed."); }