//! The volatility stop as a COMPOSITION of primitives (the post-correction design):
//! `stop_distance = k · Sqrt(Ema(Mul(Δ,Δ), length))`, `Δ = Sub(price, Delay(price,1))`
//! — a rolling EWMA standard deviation. This proves the decomposition wires through
//! `GraphBuilder`, bootstraps, and computes the right number end-to-end — the principle
//! that a node expressible as a DAG of primitives is a composition, not a fused node.
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
use aura_composites::vol_stop;
use aura_engine::{GraphBuilder, VecSource};
use aura_std::Recorder;
use std::sync::mpsc::channel;

/// An alternating ±1 price path makes `Δ² ≡ 1`, so the EWMA variance is `1`, `σ = 1`,
/// and `stop_distance = k·σ = k`. With `k = 2.0` the warmed-up output is exactly `2.0`.
#[test]
fn vol_stop_emits_k_times_rolling_stddev() {
    let (tx, rx) = channel();
    let mut g = GraphBuilder::new("vol_stop_harness");
    let price = g.source_role("price", ScalarKind::F64);
    let vs = g.add(vol_stop(/*length*/ 3, /*k*/ 2.0));
    let rec = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx));
    g.feed(price, [vs.input("price")]);
    g.connect(vs.output("stop_distance"), rec.input("col[0]"));
    let mut h = g
        .build()
        .expect("harness wires")
        .bootstrap_with_params(vec![])
        .expect("bootstraps");

    let prices = [100.0, 101.0, 100.0, 101.0, 100.0, 101.0, 100.0, 101.0, 100.0, 101.0];
    let stream: Vec<(Timestamp, Scalar)> = prices
        .iter()
        .enumerate()
        .map(|(i, &p)| (Timestamp(i as i64), Scalar::f64(p)))
        .collect();
    h.run(vec![Box::new(VecSource::new(stream))]);

    let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
    let last = rows.last().expect("vol_stop produced output after warm-up");
    assert!(
        (last.1[0].as_f64() - 2.0).abs() < 1e-9,
        "expected stop_distance = k·σ = 2.0, got {}",
        last.1[0].as_f64()
    );
}
