// Milestone fieldtest — axis (a): author a small signal as a reusable NAMED // Composite, build a Blueprint that uses it, compile() + bootstrap() + run(), // and confirm the recorded trace is non-empty. // // Public interface only: Blueprint / Composite / BlueprintNode / Edge / Target / // OutField / SourceSpec from aura-engine; Sma / Sub / Exposure / SimBroker / // Recorder from aura-std; ScalarKind / Scalar / Firing / Timestamp from // aura-core. No crates/*/src was read. // // The downstream task: package an SMA(2)/SMA(4)-cross into a reusable // `sma_cross` composite exposing one output (the cross spread), then wire that // composite into a harness blueprint (source -> composite -> Exposure -> // SimBroker -> Recorder), bootstrap it, and run it over a tiny synthetic price // ramp. This is the milestone's headline authoring story: build a graph // fractally from a Rust builder via a named composite. use std::sync::mpsc; use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, Role, SourceSpec, Target}; use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; fn main() { // --- The reusable composite: a 2/4 SMA cross. ---------------------------- // Interior layout (local indices): // 0: Sma(2) (fast) 1: Sma(4) (slow) 2: Sub (fast - slow) // One input role (the price) fans into BOTH SMAs' slot 0. // The exposed output port is Sub's single output field. // Value-empty recipes (factories); the two SMA lengths are injected at // bootstrap (param vector below), not baked here. let sma_cross = Composite::new( "sma_cross", vec![ Sma::factory().into(), Sma::factory().into(), Sub::factory().into(), ], // interior edges (local indices): 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 }, ], // input role 0 (price) fans into Sma(2).slot0 AND Sma(4).slot0 vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }, ], }], // no param aliases vec![], // single exposed output: Sub's output field 0 vec![OutField { node: 2, field: 0, name: "out".into() }], ); // --- The harness blueprint that USES the composite. ---------------------- // Top-level items: // 0: sma_cross (Composite) 1: Exposure(0.5) 2: SimBroker(1e-4) // 3: Recorder over [F64] (taps the broker equity) let (tx, rx) = mpsc::channel::<(Timestamp, Vec)>(); let blueprint = Blueprint::new( vec![ BlueprintNode::Composite(sma_cross), Exposure::factory().into(), SimBroker::factory(1e-4).into(), Recorder::factory(vec![ScalarKind::F64], Firing::Any, tx).into(), ], // one f64 price source: feeds the composite (item 0, role 0) AND the // broker's price leg (item 2, slot 1). vec![SourceSpec { kind: ScalarKind::F64, targets: vec![ Target { node: 0, slot: 0 }, // composite price role Target { node: 2, slot: 1 }, // broker price leg ], }], // top-level edges: composite-out -> Exposure -> broker.exposure(slot0) // -> Recorder vec![ Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // sma_cross -> Exposure Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // Exposure -> SimBroker.exposure Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // SimBroker -> Recorder ], ); // --- compile() + bootstrap() + run() ------------------------------------- // Param vector (depth-first item order, C12): sma_cross's two SMA lengths // (I64), then Exposure's scale (F64); Sub/SimBroker/Recorder declare none. let mut harness = blueprint .bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]) .expect("composite-authored blueprint should bootstrap"); // a synthetic upward price ramp then a dip, enough to warm SMA(4) and flip let prices: Vec = vec![ 1.00, 1.01, 1.02, 1.03, 1.05, 1.08, 1.06, 1.04, 1.02, 1.01, 1.03, 1.07, ]; let stream: Vec<(Timestamp, Scalar)> = prices .iter() .enumerate() .map(|(i, &p)| (Timestamp(60_000_000_000 * i as i64), Scalar::F64(p))) .collect(); harness.run(vec![stream]); drop(harness); // drop the recorder's tx clone held inside let rows: Vec<(Timestamp, Vec)> = rx.iter().collect(); println!("recorded equity rows: {}", rows.len()); for (ts, row) in &rows { let v = match row[0] { Scalar::F64(x) => x, other => panic!("expected f64 equity, got {other:?}"), }; println!(" ts={:>14} equity_pips={:.4}", ts.0, v); } assert!( !rows.is_empty(), "the composite-authored harness must record a populated equity trace" ); println!("OK: composite authored, compiled, bootstrapped, ran, recorded."); }