//! Ring 2 โ€” property-based fuzz at N โˆˆ {1, 16, 256}. //! //! Random-walk MBP-10 sequences applied to the LOB simulator; assert //! invariants that must hold regardless of input or scheduling: //! //! * bid_px monotonically non-increasing across levels //! * ask_px monotonically non-decreasing across levels //! * ask_px[0] > bid_px[0] (no crossed book) //! * Position lots stays within plausible bounds even under //! random market orders //! * Realised P&L finite (no NaN/Inf leaks) //! //! See docs/superpowers/specs/2026-05-18-real-lob-integration-design.md ยง8 Ring 2. use anyhow::Result; use ml_backtesting::sim::LobSimCuda; use ml_core::device::MlDevice; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; const BOOK_LEVELS: usize = 10; 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] = 5.0 + 5.0 * k as f32; ask_sz[k] = 5.0 + 5.0 * k as f32; } (bid_px, bid_sz, ask_px, ask_sz) } fn step_random_walk( rng: &mut ChaCha8Rng, bid_px: &mut [f32; BOOK_LEVELS], bid_sz: &mut [f32; BOOK_LEVELS], ask_px: &mut [f32; BOOK_LEVELS], ask_sz: &mut [f32; BOOK_LEVELS], ) { // Random mid drift in {-TICK, 0, +TICK} with mild bias. let drift_choices = [-TICK, 0.0, 0.0, 0.0, TICK]; let drift = drift_choices[rng.gen_range(0..drift_choices.len())]; // Shift the whole book by drift, preserving level structure. for k in 0..BOOK_LEVELS { bid_px[k] += drift; ask_px[k] += drift; // Jitter sizes; clamp positive. let dbz: f32 = rng.gen_range(-3.0..3.0); let daz: f32 = rng.gen_range(-3.0..3.0); bid_sz[k] = (bid_sz[k] + dbz).max(1.0); ask_sz[k] = (ask_sz[k] + daz).max(1.0); } } fn assert_book_invariants(b_idx: usize, bid_px: &[f32], bid_sz: &[f32], ask_px: &[f32], ask_sz: &[f32]) { for k in 1..BOOK_LEVELS { assert!( bid_px[k] <= bid_px[k - 1] + 1e-4, "backtest {b_idx}: bid_px non-monotone at level {k}: {} > {}", bid_px[k], bid_px[k - 1] ); assert!( ask_px[k] >= ask_px[k - 1] - 1e-4, "backtest {b_idx}: ask_px non-monotone at level {k}: {} < {}", ask_px[k], ask_px[k - 1] ); } assert!( ask_px[0] > bid_px[0], "backtest {b_idx}: crossed book โ€” ask[0]={}, bid[0]={}", ask_px[0], bid_px[0] ); for k in 0..BOOK_LEVELS { assert!(bid_sz[k] > 0.0, "backtest {b_idx}: bid_sz[{k}] non-positive"); assert!(ask_sz[k] > 0.0, "backtest {b_idx}: ask_sz[{k}] non-positive"); assert!(bid_px[k].is_finite() && ask_px[k].is_finite()); } } fn run_fuzz_n(n_backtests: usize, n_events: usize, with_orders: bool, 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); 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; for event_idx in 0..n_events { step_random_walk(&mut rng, &mut bid_px, &mut bid_sz, &mut ask_px, &mut ask_sz); sim.apply_snapshot(&bid_px, &bid_sz, &ask_px, &ask_sz)?; // 1 in 8 events: each backtest submits a random market order. if with_orders && event_idx % 8 == 0 { for b in 0..n_backtests { let side: u8 = if rng.gen_bool(0.5) { 0 } else { 1 }; let size: u16 = rng.gen_range(1..=3); sim.submit_market(b, side, size, ts_ns)?; } } // Invariants on book state. let books = sim.read_books()?; for (b, book) in books.iter().enumerate() { assert_book_invariants(b, &book.bid_px, &book.bid_sz, &book.ask_px, &book.ask_sz); } if with_orders { // Position + P&L sanity per backtest. for b in 0..n_backtests { let pos = sim.read_pos(b)?; assert!( pos.position_lots.abs() <= 30, "backtest {b}: position_lots out of plausible range: {}", pos.position_lots ); assert!(pos.realized_pnl.is_finite(), "backtest {b}: realized_pnl NaN/Inf"); assert!(pos.vwap_entry.is_finite(), "backtest {b}: vwap_entry NaN/Inf"); } } ts_ns += 1_000_000; } Ok(()) } #[test] #[ignore = "requires CUDA"] fn fuzz_n1_book_only() -> Result<()> { run_fuzz_n(1, 200, /*with_orders=*/ false, 0xCAFE_F00D) } #[test] #[ignore = "requires CUDA"] fn fuzz_n16_with_orders() -> Result<()> { run_fuzz_n(16, 200, /*with_orders=*/ true, 0xDEAD_BEEF) } #[test] #[ignore = "requires CUDA"] fn fuzz_n256_with_orders() -> Result<()> { run_fuzz_n(256, 100, /*with_orders=*/ true, 0x1234_5678) }