1a6eafa056
Dissolve the aura-engine -> aura-std dependency by moving the vol_stop /
risk_executor composite-builders out of aura-engine into a new aura-composites
crate. This restores the engine's domain-free layering, which the
composites-in-engine placement (cycle 0065) had inverted.
Why: the engine routes type-erased Scalar records and never names a concrete
node -- summarize_r reads the PositionManagement record positionally, by column
index, without linking aura-std (which is exactly why aura-std was a dev-only
dependency before 0065). Parking the node-naming composite-builders inside
aura-engine forced the engine's production code to reference the node library
for the first time (aura-engine -> aura-std), an upside-down layering: the
engine is the foundation the node library builds upon, not the reverse.
The clean home is a dedicated crate that depends on BOTH the engine's
GraphBuilder and the std nodes, leaving each lower crate untouched:
aura-composites -> { aura-engine, aura-std } -> aura-core (acyclic)
aura-engine -> aura-core only (domain-free again)
aura-std -> aura-core only (lean node library)
aura-std reverts to a [dev-dependency] of aura-engine (the engine's own E2E
tests -- stage1_r_e2e, random_sweep_e2e, ger40_breakout -- still drive real
std nodes through a bootstrapped Harness; that test-only edge creates no cycle).
Moved with history (git mv):
- crates/aura-engine/src/composites.rs -> crates/aura-composites/src/lib.rs
- crates/aura-engine/tests/risk_executor.rs -> crates/aura-composites/tests/
- crates/aura-engine/tests/vol_stop_composite.rs -> crates/aura-composites/tests/
Consumers rewired: aura-cli imports risk_executor / StopRule from aura_composites;
the moved tests import the builders from the crate under test. The intra-doc link
to RollMode and the module rationale doc are updated for the new home.
Behaviour-preserving: workspace build clean; clippy --all-targets -D warnings
clean; full suite 513 passed / 0 failed, identical to baseline (the two moved
test files run unchanged under aura-composites); cargo doc --no-deps clean (no
broken intra-doc links).
45 lines
2.0 KiB
Rust
45 lines
2.0 KiB
Rust
//! 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()
|
||
);
|
||
}
|