From 2ee0d74a8c45d1b5ce612cfa5f7d8538f4fab95e Mon Sep 17 00:00:00 2001 From: Brummel Date: Wed, 24 Jun 2026 12:30:08 +0200 Subject: [PATCH] fix(stage1-r): scale-robust tolerance for the R-record redundancy guard The PositionManagement redundancy `debug_assert` (position_management.rs:229) re-derives realized_r from the dense-record columns as dir*(exit-entry)/|entry-stop| and compared it to the stored value with an ABSOLUTE < 1e-9 tolerance. At tiny-pip price scale (EURUSD/GBPUSD entry ~1.1, vol-stop distance ~1e-6) the denominator reconstruction `entry - stop` is a catastrophic-cancellation subtraction that loses ~6 significant digits; with a deep gap-through-stop realising a large |R|, the recomputed R drifts from the (correct) stored realized_r by more than 1e-9 - so a debug build PANICS (exit 101) while a release build (assert compiled out) returns the correct R. GER40 (large pip) never trips it. Found by the Stage-1 R milestone fieldtest: `aura run --harness stage1-r --real EURUSD` crashed the headline "score real FX in R" use case on the default (debug) build, and the debug/release disagreement made the R numbers' validity unknowable from the public surface. Root cause (via debug): the stored realized_r uses the cancellation-free FROZEN latched_dist and is exact - only the guard's tolerance was wrong for small price scales. Fix: a scale-robust relative tolerance `< 1e-9 * realized.abs().max(1.0)` (floored at the historical 1e-9 for |R| <= 1, so no change for normal-scale runs). RED-first: the hermetic synthetic-tiny-pip test `tiny_pip_gap_stop_does_not_trip_the_redundancy_guard` reproduces the panic and is pinned here. Verified end-to-end: `aura run --harness stage1-r --real EURUSD` now returns the R block (exit 0) on a debug build. cargo test --workspace green; clippy --workspace --all-targets -D warnings clean. refs #117 --- crates/aura-std/src/position_management.rs | 49 +++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/aura-std/src/position_management.rs b/crates/aura-std/src/position_management.rs index ca334c3..597dab2 100644 --- a/crates/aura-std/src/position_management.rs +++ b/crates/aura-std/src/position_management.rs @@ -226,10 +226,16 @@ impl Node for PositionManagement { Cell::from_f64(unrealized), Cell::from_f64(self.cum_realized_r), ]; + // Scale-robust tolerance: the column re-derivation reconstructs the R-denominator + // via `d_entry - d_stop`, a catastrophic-cancellation subtraction at tiny-pip price + // scale (entry ~1.1, dist ~1e-6) that drifts from the cancellation-free frozen + // `latched_dist` `realized` was computed with. A relative tolerance (floored at the + // historical absolute 1e-9 for |R| <= 1) keeps the guard meaningful while letting the + // two build profiles agree on tiny-pip instruments — `realized` itself is exact. debug_assert!( !closed || (realized - d_dir as f64 * (d_exit - d_entry) / (d_entry - d_stop).abs()).abs() - < 1e-9 + < 1e-9 * realized.abs().max(1.0) ); Some(&self.out) } @@ -347,6 +353,47 @@ mod tests { assert!(row[11].bool()); // reopened: open assert_eq!(row[4].i64(), -1); // new direction short } + // Property: the realized_r <-> dense-record-columns redundancy guard holds for a + // tiny-pip instrument (EURUSD-scale entry, a sub-pip stop distance). `eval` stores + // `realized` from the cancellation-free frozen `latched_dist`; the in-`eval` + // redundancy guard RE-derives R from the record columns as + // `direction * (exit - entry) / |entry - stop|`, reconstructing the R-denominator + // by the subtraction `entry - stop`. At a price scale ~1.1 with a stop distance + // ~1e-6, that subtraction loses ~6 significant digits relative to the magnitude of + // `entry` (~2e-16 ULP / 1e-6 dist), so the recomputed denominator drifts from the + // frozen `latched_dist`; with a deep gap-through-stop realising a large |R|, the + // recomputed R diverges from the (correct) stored `realized` by more than the + // guard's ABSOLUTE 1e-9 tolerance, panicking a debug build while a release build + // (assert compiled out) returns the correct R. The stored `realized` is correct; + // the guard must use a scale-robust (relative) tolerance so the two build profiles + // agree. A no-op for large-pip instruments (GER40), where the cancellation is + // negligible. + #[test] + fn tiny_pip_gap_stop_does_not_trip_the_redundancy_guard() { + let entry = 1.10000_f64; // EURUSD-scale price + let dist = 0.000001_f64; // sub-pip vol-stop distance (a quiet M1 bar) + let mut n = PositionManagement::new(); + let mut c = cols(); + let _ = step(&mut n, &mut c, 1.0, entry, dist); // open long @1.10000, stop @1.099999 + // A fast gap straight through the stop: price 1.09990, ~100e-6 below entry -> + // a ~-100R loss (stop-outs are NOT capped at -1R; the honest tail). In a debug + // build the current absolute-tolerance guard PANICS here. + let row = step(&mut n, &mut c, 1.0, 1.09990_f64, dist); + assert!(row[0].bool(), "closed_this_cycle"); + assert!(row[3].bool(), "was_stopped"); + // The stored realized_r and the record-column re-derivation must agree within a + // tolerance robust to the tiny price scale (relative, not absolute 1e-9). + let realized = row[1].f64(); + let (dir, d_entry, d_stop, d_exit) = + (row[4].i64() as f64, row[6].f64(), row[7].f64(), row[8].f64()); + let recomputed = dir * (d_exit - d_entry) / (d_entry - d_stop).abs(); + assert!( + (realized - recomputed).abs() <= 1e-9 * realized.abs().max(1.0), + "realized_r ({realized}) must match the column re-derivation ({recomputed}) \ + within a scale-robust tolerance", + ); + } + // (4) Open at window end is carried on the last row: open=true, the unrealized R the // post-run fold will force-close, and the direction for window-end synthesis. #[test]