//! Example 1 — the north-star "combine one signal with another" move (C10), //! expressed with the SHIPPED `Add` node (no hand-authored combinator). //! //! This is the demonstration that the cycle-0007 fieldtest boilerplate (a //! project-local `Add2`) is retired: aura-std now ships `Add`, so two MA-cross //! spreads can be summed into one exposure from shipped blocks alone. //! //! price ─┬─> Sma(2) ─┐ //! ├─> Sma(4) ─┴─> Sub (fast spread) ─┐ //! ├─> Sma(3) ─┐ ├─> Add ─> Exposure ─> SimBroker ─> Recorder //! ├─> Sma(6) ─┴─> Sub (slow spread) ─┘ ^ //! └────────────────────────────────────────────────────────────┘ (price) mod recorder; use std::cell::RefCell; use std::rc::Rc; use aura_core::{Scalar, Timestamp}; use aura_engine::{Edge, Harness, SourceSpec, Target}; use aura_core::ScalarKind; use aura_std::{Add, Exposure, SimBroker, Sma, Sub}; use recorder::{Recorder, Tape}; fn main() { // Node indices. const SMA2: usize = 0; const SMA4: usize = 1; const SMA3: usize = 2; const SMA6: usize = 3; const SUB_FAST: usize = 4; const SUB_SLOW: usize = 5; const ADD: usize = 6; const EXPOSURE: usize = 7; const BROKER: usize = 8; const REC: usize = 9; let tape: Tape = Rc::new(RefCell::new(Vec::new())); let nodes: Vec> = vec![ Box::new(Sma::new(2)), Box::new(Sma::new(4)), Box::new(Sma::new(3)), Box::new(Sma::new(6)), Box::new(Sub::new()), Box::new(Sub::new()), Box::new(Add::new()), Box::new(Exposure::new(0.5)), Box::new(SimBroker::new(1.0)), Box::new(Recorder::new(tape.clone())), ]; // One price source, fed into all four SMAs (slot 0) and the broker price (slot 1). let sources = vec![SourceSpec { kind: ScalarKind::F64, targets: vec![ Target { node: SMA2, slot: 0 }, Target { node: SMA4, slot: 0 }, Target { node: SMA3, slot: 0 }, Target { node: SMA6, slot: 0 }, Target { node: BROKER, slot: 1 }, ], }]; let edges = vec![ // fast spread = Sma(2) - Sma(4) Edge { from: SMA2, to: SUB_FAST, slot: 0, from_field: 0 }, Edge { from: SMA4, to: SUB_FAST, slot: 1, from_field: 0 }, // slow spread = Sma(3) - Sma(6) Edge { from: SMA3, to: SUB_SLOW, slot: 0, from_field: 0 }, Edge { from: SMA6, to: SUB_SLOW, slot: 1, from_field: 0 }, // combine the two spreads with the SHIPPED Add Edge { from: SUB_FAST, to: ADD, slot: 0, from_field: 0 }, Edge { from: SUB_SLOW, to: ADD, slot: 1, from_field: 0 }, // sum -> exposure -> broker exposure slot (0) Edge { from: ADD, to: EXPOSURE, slot: 0, from_field: 0 }, Edge { from: EXPOSURE, to: BROKER, slot: 0, from_field: 0 }, // broker equity -> recorder Edge { from: BROKER, to: REC, slot: 0, from_field: 0 }, ]; let mut harness = Harness::bootstrap(nodes, sources, edges) .expect("bootstrap should succeed for an acyclic, kind-consistent graph"); // A simple rising-then-falling price path, 1 tick per ns. let prices: &[f64] = &[ 100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0, // rising 106.0, 104.0, 102.0, 100.0, 98.0, 96.0, 94.0, 92.0, // falling ]; let stream: Vec<(Timestamp, Scalar)> = prices .iter() .enumerate() .map(|(i, &p)| (Timestamp(i as i64), Scalar::F64(p))) .collect(); harness.run(vec![stream]); let tape = tape.borrow(); println!("recorded {} pip-equity rows (one per price cycle)", tape.len()); for (ts, eq) in tape.iter() { println!(" t={:>3} equity={:+.4}", ts.0, eq); } // Determinism check (C1): same input -> identical run. let tape2: Tape = Rc::new(RefCell::new(Vec::new())); let nodes2: Vec> = vec![ Box::new(Sma::new(2)), Box::new(Sma::new(4)), Box::new(Sma::new(3)), Box::new(Sma::new(6)), Box::new(Sub::new()), Box::new(Sub::new()), Box::new(Add::new()), Box::new(Exposure::new(0.5)), Box::new(SimBroker::new(1.0)), Box::new(Recorder::new(tape2.clone())), ]; let sources2 = vec![SourceSpec { kind: ScalarKind::F64, targets: vec![ Target { node: SMA2, slot: 0 }, Target { node: SMA4, slot: 0 }, Target { node: SMA3, slot: 0 }, Target { node: SMA6, slot: 0 }, Target { node: BROKER, slot: 1 }, ], }]; let edges2 = vec![ Edge { from: SMA2, to: SUB_FAST, slot: 0, from_field: 0 }, Edge { from: SMA4, to: SUB_FAST, slot: 1, from_field: 0 }, Edge { from: SMA3, to: SUB_SLOW, slot: 0, from_field: 0 }, Edge { from: SMA6, to: SUB_SLOW, slot: 1, from_field: 0 }, Edge { from: SUB_FAST, to: ADD, slot: 0, from_field: 0 }, Edge { from: SUB_SLOW, to: ADD, slot: 1, from_field: 0 }, Edge { from: ADD, to: EXPOSURE, slot: 0, from_field: 0 }, Edge { from: EXPOSURE, to: BROKER, slot: 0, from_field: 0 }, Edge { from: BROKER, to: REC, slot: 0, from_field: 0 }, ]; let stream2: Vec<(Timestamp, Scalar)> = prices .iter() .enumerate() .map(|(i, &p)| (Timestamp(i as i64), Scalar::F64(p))) .collect(); let mut harness2 = Harness::bootstrap(nodes2, sources2, edges2).unwrap(); harness2.run(vec![stream2]); assert_eq!(*tape, *tape2.borrow(), "C1: two runs must be bit-identical"); println!("determinism: two runs bit-identical = OK"); }