refactor(aura-ingest): fuse drain_trace taps via join_on_ts; doc the cadence rule
drain_trace now joins its four recording-sink taps through aura-engine's join_on_ts instead of a hand-rolled per-tap HashMap, resolving the #93 zip-by-index panic at its source: the four taps fire at different cardinalities (breakout one bar short via cold Delay(1); bars_since_open filtered by Session), so a positional zip misaligns or panics. The BarTrace mapping stays a thin layer with its caller-side defaults unchanged (held->0.0, bars_since_open->-1, breakout->false) — the engine reports presence, the consumer interprets absence. Behaviour is byte-preserved: the gated GER40 tests (ger40_breakout_real, ger40_breakout_blueprint, open_ohlc_seam) stay green against the real archive. The Recorder doc gains a prose note on the multi-tap cadence rule (join on ts, never by index), pointing at join_on_ts by name rather than rustdoc link since aura-std does not depend on aura-engine. Verified: cargo build/test/clippy --workspace --all-targets clean; the _by_ts hand-rolled join is gone (acceptance grep empty). closes #93
This commit is contained in:
@@ -40,8 +40,8 @@ use std::sync::mpsc;
|
||||
|
||||
use aura_core::{NodeSchema, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{
|
||||
summarize, Composite, Edge, FlatGraph, GraphBuilder, Harness, RunManifest, RunReport,
|
||||
SourceSpec, Target,
|
||||
join_on_ts, summarize, Composite, Edge, FlatGraph, GraphBuilder, Harness, RunManifest,
|
||||
RunReport, SourceSpec, Target,
|
||||
};
|
||||
use aura_std::{And, Delay, EqConst, Gt, Latch, Recorder, Resample, Session, SimBroker};
|
||||
use chrono::TimeZone;
|
||||
@@ -352,30 +352,19 @@ pub struct BarTrace {
|
||||
/// (the sinks fire on different cardinalities — see [`BarTrace`]). Reads only
|
||||
/// recorded sink output (C8/C18) — never engine internals.
|
||||
pub fn drain_trace(taps: &Taps) -> Vec<BarTrace> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let equity: Vec<(Timestamp, Vec<Scalar>)> = taps.equity.try_iter().collect();
|
||||
let held: Vec<(Timestamp, Vec<Scalar>)> = taps.held.try_iter().collect();
|
||||
let bars: Vec<(Timestamp, Vec<Scalar>)> = taps.bars.try_iter().collect();
|
||||
let breakout: Vec<(Timestamp, Vec<Scalar>)> = taps.breakout.try_iter().collect();
|
||||
|
||||
// held shares the equity spine (same node-firing cadence); align by index is
|
||||
// safe between those two, but we still fuse the side taps by ts.
|
||||
let held_by_ts: HashMap<i64, f64> =
|
||||
held.iter().map(|(t, r)| (t.0, as_f64(&r[0]))).collect();
|
||||
let bars_by_ts: HashMap<i64, i64> =
|
||||
bars.iter().map(|(t, r)| (t.0, as_i64(&r[0]))).collect();
|
||||
let breakout_by_ts: HashMap<i64, bool> =
|
||||
breakout.iter().map(|(t, r)| (t.0, as_bool(&r[0]))).collect();
|
||||
|
||||
equity
|
||||
.iter()
|
||||
.map(|(ts, row)| BarTrace {
|
||||
ts: *ts,
|
||||
equity: as_f64(&row[0]),
|
||||
held: held_by_ts.get(&ts.0).copied().unwrap_or(0.0),
|
||||
bars_since_open: bars_by_ts.get(&ts.0).copied().unwrap_or(-1),
|
||||
breakout: breakout_by_ts.get(&ts.0).copied().unwrap_or(false),
|
||||
join_on_ts(&equity, &[&held, &bars, &breakout])
|
||||
.into_iter()
|
||||
.map(|j| BarTrace {
|
||||
ts: j.ts,
|
||||
equity: as_f64(&j.spine[0]),
|
||||
held: j.sides[0].as_ref().map_or(0.0, |r| as_f64(&r[0])),
|
||||
bars_since_open: j.sides[1].as_ref().map_or(-1, |r| as_i64(&r[0])),
|
||||
breakout: j.sides[2].as_ref().is_some_and(|r| as_bool(&r[0])),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user