b39fd63396
Phase 4 of the Stratification milestone. aura-std held four C28 ladder layers in one roster; this cuts them into layer-aligned, aura-core-only node crates so the import direction is enforced by the crate graph: - aura-std — engine nodes only (arithmetic/logic/rolling + sinks) - aura-market — session, resample - aura-strategy — bias, stops, sizer, cost-model machinery - aura-backtest — sim_broker, position_management - aura-vocabulary — the relocated closed std_vocabulary roster Node modules move verbatim (byte-identical renames); consumers are rewired by import path only. A new structural test (aura-vocabulary/tests/c28_layering.rs) asserts each node crate's [dependencies] stay within its C28-permitted inner set, catching the acyclic-but-outward violation the compiler misses. Behaviour byte-identical: full workspace suite green (1448 tests), no golden edited, clippy -D warnings clean. C28 Status block updated. closes #288
117 lines
4.4 KiB
Rust
117 lines
4.4 KiB
Rust
//! Shared `#[cfg(test)]` fixtures for the SMA-cross signal-quality harness,
|
|
//! consumed by the `blueprint` and `sweep` unit-test modules. Kept in one place
|
|
//! so the harness topology cannot silently diverge between them (see #53).
|
|
//! Gated test-only at the declaration site in `lib.rs`
|
|
//! (`#[cfg(test)] mod test_fixtures;`), matching the `reexport_tests` precedent.
|
|
|
|
use crate::{BlueprintNode, Composite, Edge, OutField, Role, Target};
|
|
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
|
use aura_backtest::SimBroker;
|
|
use aura_std::{Recorder, Sma, Sub};
|
|
use aura_strategy::Bias;
|
|
use std::sync::mpsc;
|
|
|
|
/// Seven synthetic F64 ticks driving the harness; deterministic input fixture.
|
|
pub(crate) fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
|
|
[
|
|
(1_i64, 1.0000_f64),
|
|
(2, 1.0010),
|
|
(3, 1.0030),
|
|
(4, 1.0060),
|
|
(5, 1.0040),
|
|
(6, 1.0010),
|
|
(7, 0.9990),
|
|
]
|
|
.iter()
|
|
.map(|&(t, p)| (Timestamp(t), Scalar::f64(p)))
|
|
.collect()
|
|
}
|
|
|
|
/// The SMA-cross signal as a reusable value-empty composite: one input role
|
|
/// (price), one output (fast-minus-slow spread). Lengths injected at compile.
|
|
pub(crate) fn sma_cross() -> Composite {
|
|
Composite::new(
|
|
"sma_cross",
|
|
vec![
|
|
Sma::builder().named("fast").into(),
|
|
Sma::builder().named("slow").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,
|
|
}],
|
|
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
|
)
|
|
}
|
|
|
|
/// The signal-quality harness as a value-empty composite blueprint with two
|
|
/// recording sinks (equity, exposure).
|
|
#[allow(clippy::type_complexity)]
|
|
pub(crate) fn composite_sma_cross_harness() -> (
|
|
Composite,
|
|
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
|
|
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
|
|
) {
|
|
let (tx_eq, rx_eq) = mpsc::channel();
|
|
let (tx_ex, rx_ex) = mpsc::channel();
|
|
let bp = Composite::new(
|
|
"root",
|
|
vec![
|
|
BlueprintNode::Composite(sma_cross()),
|
|
Bias::builder().named("bias").into(),
|
|
SimBroker::builder(0.0001).into(),
|
|
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
|
|
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
|
|
],
|
|
vec![
|
|
Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // composite out -> Bias
|
|
Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // exposure -> broker slot 0
|
|
Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // equity -> sink
|
|
Edge { from: 1, to: 4, slot: 0, from_field: 0 }, // exposure -> sink
|
|
],
|
|
vec![Role {
|
|
name: "src".into(),
|
|
targets: vec![
|
|
Target { node: 0, slot: 0 }, // price -> sma_cross role 0
|
|
Target { node: 2, slot: 1 }, // price -> SimBroker price slot
|
|
],
|
|
source: Some(ScalarKind::F64),
|
|
}],
|
|
vec![], // output: the root ends in sinks
|
|
);
|
|
(bp, rx_eq, rx_ex)
|
|
}
|
|
|
|
/// The SMA-cross **signal** graph as a sink-free, param-generic composite: fast
|
|
/// SMA (length bound to 2) and slow SMA over one `price` role, their spread, a
|
|
/// `Bias`. Output is the `bias` field. No recorder/broker — observation is
|
|
/// attached by the caller. This is the canonical round-trip fixture (cycle 0087).
|
|
pub(crate) fn sink_free_sma_cross_signal() -> Composite {
|
|
Composite::new(
|
|
"sma_cross",
|
|
vec![
|
|
Sma::builder().named("fast").bind("length", Scalar::i64(2)).into(),
|
|
Sma::builder().named("slow").into(),
|
|
Sub::builder().into(),
|
|
Bias::builder().into(),
|
|
],
|
|
vec![
|
|
Edge { from: 0, to: 2, slot: 0, from_field: 0 }, // fast -> Sub slot 0
|
|
Edge { from: 1, to: 2, slot: 1, from_field: 0 }, // slow -> Sub slot 1
|
|
Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // spread -> Bias
|
|
],
|
|
vec![Role {
|
|
name: "price".into(),
|
|
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
|
source: None,
|
|
}],
|
|
vec![OutField { node: 3, field: 0, name: "bias".into() }],
|
|
)
|
|
}
|