Audit script (audit-rust-consts.sh) scans Rust src/examples for numeric
literals mirroring structural kernel-side consts (N_ACTIONS, Q_N_ATOMS,
HIDDEN_DIM, MAX_UNITS, BOOK_LEVELS). Closes the layer-3 gap noted in
feedback_use_consts_not_literals_for_structural_dims:
Layer 1: kernel `#define` allowlist → audit-isv
Layer 2: Rust `pub const` canonical → exists (e.g. N_ACTIONS in rl/common.rs)
Layer 3: Rust literals mirroring (2) → audit-rust-consts (this commit)
Honors `// audit-ignore: <SYMBOL>` per-line markers and skips `[u8; N]`
byte-buffer patterns (high false-positive class — almost always I/O
scratch, not structural dims).
Cleanup driven by first run (19 real flags, no grandfathering):
* New canonical: `BOOK_LEVELS` in `ml-alpha/src/cfc/snap_features.rs`
(10 book levels = same place as `Mbp10RawInput` struct)
* `ml-backtesting/src/lob/mod.rs`: redefine as `pub use` re-export from
ml-alpha (single source of truth; ml-backtesting depends on ml-alpha
via `Mbp10RawInput` already)
* 19 sites switched literal `10` → `BOOK_LEVELS`:
- snap_features.rs:44-47 (struct fields)
- data/loader.rs:872-876, 960 (Mbp10Snapshot → Mbp10RawInput convert)
- data/aggregation.rs:161 (level-wise aggregation loop)
- trainer/perception.rs:2750-2756, 6272-6278, 6686-6690, 7247-7253
(snapshot → batch staging loops)
- tests/lob_sim_fuzz.rs:21, lob_sim_integrated_fuzz.rs:22 (duplicate
const → use ml_backtesting::lob::BOOK_LEVELS)
* 5 sites marked `// audit-ignore: BOOK_LEVELS — <reason>`:
- harness.rs:572,574,594 (conviction-bucket histograms, 10 ≠ depth)
- multi_horizon_labels.rs:489,557,564 (10-element test price vecs)
Re-run after fixes: 0 suspect literals flagged. PASS.
195 lines
7.7 KiB
Rust
195 lines
7.7 KiB
Rust
//! Extended Ring 2 fuzz that exercises the FULL integrated pipeline
|
|
//! (book_update + resting_orders + decision_policy + pnl_track +
|
|
//! isv_kelly_update + step_resting_orders) under random adversarial
|
|
//! conditions. Existing lob_sim_fuzz only covers book + market orders.
|
|
//!
|
|
//! Goals:
|
|
//! * No NaN / Inf leaks in Pos.realized_pnl, IsvKellyState fields,
|
|
//! or per-event book state after thousands of random alpha decisions.
|
|
//! * submission_overflow counter stays plausible (i.e. doesn't run
|
|
//! away even when alpha churns orders every event).
|
|
//! * Trade-log + audit-ring never overflow their caps.
|
|
//! * Book invariants (monotonicity, no-crossed) hold under sustained
|
|
//! resting-order pressure + trade-flow simulation.
|
|
|
|
use anyhow::Result;
|
|
use ml_backtesting::lob::BOOK_LEVELS;
|
|
use ml_backtesting::policy::{IsvKellyStateHost, N_HORIZONS};
|
|
use ml_backtesting::sim::LobSimCuda;
|
|
use ml_core::device::MlDevice;
|
|
use rand::{Rng, SeedableRng};
|
|
use rand_chacha::ChaCha8Rng;
|
|
|
|
const TICK: f32 = 0.25;
|
|
|
|
fn make_initial_book() -> ([f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS], [f32; BOOK_LEVELS]) {
|
|
let mut bid_px = [0.0; BOOK_LEVELS];
|
|
let mut bid_sz = [0.0; BOOK_LEVELS];
|
|
let mut ask_px = [0.0; BOOK_LEVELS];
|
|
let mut ask_sz = [0.0; BOOK_LEVELS];
|
|
let mid = 5500.00_f32;
|
|
for k in 0..BOOK_LEVELS {
|
|
bid_px[k] = mid - TICK * (k as f32 + 1.0);
|
|
ask_px[k] = mid + TICK * (k as f32 + 1.0);
|
|
bid_sz[k] = 20.0 + 10.0 * k as f32;
|
|
ask_sz[k] = 20.0 + 10.0 * k as f32;
|
|
}
|
|
(bid_px, bid_sz, ask_px, ask_sz)
|
|
}
|
|
|
|
fn random_warm_start_isv(rng: &mut ChaCha8Rng) -> [IsvKellyStateHost; N_HORIZONS] {
|
|
let mut out: [IsvKellyStateHost; N_HORIZONS] = Default::default();
|
|
for h in 0..N_HORIZONS {
|
|
// Make at least one horizon credibly profitable so the decision
|
|
// kernel actually opens positions; others get random warm-starts.
|
|
let positive_h = h == N_HORIZONS - 1;
|
|
let win_rate: f32 = if positive_h { 0.7 } else { rng.gen_range(0.3..0.6) };
|
|
let pnl_win: f32 = if positive_h { 2.0 } else { rng.gen_range(0.5..1.5) };
|
|
let pnl_loss: f32 = rng.gen_range(0.3..1.0);
|
|
let var: f32 = rng.gen_range(0.1..0.5);
|
|
let recent_sharpe: f32 = if positive_h { 1.0 } else { rng.gen_range(-0.5..0.5) };
|
|
out[h] = IsvKellyStateHost {
|
|
pnl_ema_win: pnl_win,
|
|
pnl_ema_loss: pnl_loss,
|
|
win_rate_ema: win_rate,
|
|
n_trades_seen: rng.gen_range(10..100),
|
|
realised_return_var: var,
|
|
recent_sharpe,
|
|
};
|
|
}
|
|
out
|
|
}
|
|
|
|
fn assert_finite_pos(b_idx: usize, pos: &ml_backtesting::lob::PosFlat) {
|
|
assert!(pos.realized_pnl.is_finite(), "backtest {b_idx}: realized_pnl NaN/Inf");
|
|
assert!(pos.vwap_entry.is_finite(), "backtest {b_idx}: vwap_entry NaN/Inf");
|
|
assert!(
|
|
pos.position_lots.abs() <= 100,
|
|
"backtest {b_idx}: position_lots out of plausible range: {}",
|
|
pos.position_lots
|
|
);
|
|
}
|
|
|
|
fn assert_finite_isv(b_idx: usize, h: usize, s: &IsvKellyStateHost) {
|
|
assert!(s.pnl_ema_win.is_finite(), "backtest {b_idx} h{h}: pnl_ema_win NaN/Inf");
|
|
assert!(s.pnl_ema_loss.is_finite(), "backtest {b_idx} h{h}: pnl_ema_loss NaN/Inf");
|
|
assert!(s.win_rate_ema.is_finite(), "backtest {b_idx} h{h}: win_rate_ema NaN/Inf");
|
|
assert!(s.realised_return_var.is_finite(), "backtest {b_idx} h{h}: realised_return_var NaN/Inf");
|
|
assert!(s.recent_sharpe.is_finite(), "backtest {b_idx} h{h}: recent_sharpe NaN/Inf");
|
|
}
|
|
|
|
fn run_integrated_fuzz(n_backtests: usize, n_events: usize, seed: u64) -> Result<()> {
|
|
let dev = MlDevice::cuda(0).map_err(|e| anyhow::anyhow!("cuda dev: {e}"))?;
|
|
let mut sim = LobSimCuda::new(n_backtests, &dev)?;
|
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
|
|
|
// Warm-start every backtest with random-but-plausible Kelly state.
|
|
for b in 0..n_backtests {
|
|
let states = random_warm_start_isv(&mut rng);
|
|
sim.write_isv_kelly(b, &states)?;
|
|
}
|
|
|
|
let (mut bid_px, mut bid_sz, mut ask_px, mut ask_sz) = make_initial_book();
|
|
let mut ts_ns: u64 = 1_000_000_000;
|
|
let mut decision_idx: u32 = 0;
|
|
|
|
for event_idx in 0..n_events {
|
|
// Random walk the book.
|
|
let drift_choices = [-TICK, 0.0, 0.0, 0.0, TICK];
|
|
let drift = drift_choices[rng.gen_range(0..drift_choices.len())];
|
|
for k in 0..BOOK_LEVELS {
|
|
bid_px[k] += drift;
|
|
ask_px[k] += drift;
|
|
let dbz: f32 = rng.gen_range(-5.0..5.0);
|
|
let daz: f32 = rng.gen_range(-5.0..5.0);
|
|
bid_sz[k] = (bid_sz[k] + dbz).max(1.0);
|
|
ask_sz[k] = (ask_sz[k] + daz).max(1.0);
|
|
}
|
|
sim.apply_snapshot(&bid_px, &bid_sz, &ask_px, &ask_sz)?;
|
|
|
|
// Inferred-like trade flow (random sign, small magnitude).
|
|
let signed_vol: f32 = rng.gen_range(-3.0..3.0);
|
|
sim.step_resting_orders(ts_ns, signed_vol)?;
|
|
|
|
// Decide every 4th event.
|
|
if event_idx % 4 == 0 {
|
|
let mut probs = [0.5_f32; N_HORIZONS];
|
|
for h in 0..N_HORIZONS {
|
|
probs[h] = rng.gen_range(0.1..0.9);
|
|
}
|
|
sim.broadcast_alpha(&probs)?;
|
|
// Mix latency vs immediate paths to cover both.
|
|
let latency = if decision_idx % 2 == 0 { 0 } else { 50_000_000 };
|
|
let sim_cfg = ml_backtesting::sim::BatchedSimConfig::from_uniform(
|
|
sim.n_backtests(),
|
|
&ml_backtesting::sim::UniformSimParams {
|
|
target_annual_vol_units: rng.gen_range(20.0..60.0),
|
|
annualisation_factor: rng.gen_range(500.0..1200.0),
|
|
max_lots: rng.gen_range(2..6),
|
|
latency_ns: latency,
|
|
kelly_frac_floor: 0.10,
|
|
sharpe_weight_floor: 0.10,
|
|
threshold: 0.0,
|
|
cost_per_lot_per_side: 0.0,
|
|
max_hold_ns: 0,
|
|
delta_floor: 0.0,
|
|
min_reasonable_px: 0.0,
|
|
max_reasonable_px: f32::INFINITY,
|
|
},
|
|
);
|
|
sim.step_decision_with_latency(ts_ns, &sim_cfg)?;
|
|
decision_idx += 1;
|
|
}
|
|
|
|
// Spot-check invariants every 50 events to bound test time.
|
|
if event_idx % 50 == 0 {
|
|
let books = sim.read_books()?;
|
|
for (b, book) in books.iter().enumerate() {
|
|
for k in 1..BOOK_LEVELS {
|
|
assert!(
|
|
book.bid_px[k] <= book.bid_px[k - 1] + 1e-4,
|
|
"event {event_idx} backtest {b}: bid monotonicity violated"
|
|
);
|
|
assert!(
|
|
book.ask_px[k] >= book.ask_px[k - 1] - 1e-4,
|
|
"event {event_idx} backtest {b}: ask monotonicity violated"
|
|
);
|
|
}
|
|
assert!(
|
|
book.ask_px[0] > book.bid_px[0],
|
|
"event {event_idx} backtest {b}: crossed book"
|
|
);
|
|
}
|
|
for b in 0..n_backtests {
|
|
let pos = sim.read_pos(b)?;
|
|
assert_finite_pos(b, &pos);
|
|
let isv = sim.read_isv_kelly(b)?;
|
|
for h in 0..5 {
|
|
assert_finite_isv(b, h, &isv[h]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ts_ns += 1_000_000;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires CUDA"]
|
|
fn integrated_fuzz_n1_short() -> Result<()> {
|
|
run_integrated_fuzz(1, 200, 0xCAFE_F00D)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires CUDA"]
|
|
fn integrated_fuzz_n8_medium() -> Result<()> {
|
|
run_integrated_fuzz(8, 500, 0xDEAD_BEEF)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires CUDA"]
|
|
fn integrated_fuzz_n64_long() -> Result<()> {
|
|
run_integrated_fuzz(64, 1000, 0x1234_5678)
|
|
}
|