//! Throughput benchmark for the Wickra Rust core โ€” the zero-FFI baseline. //! //! Reports streaming (`update`) and batch updates-per-second over a synthetic //! OHLCV series, in the same format as every binding's `throughput` benchmark. //! Rust has no FFI boundary โ€” it calls the core directly โ€” so these numbers are //! the ceiling the per-binding benchmarks are measured against, and the value //! their `batch` paths converge towards. See the repository BENCHMARKS.md ยง3. //! //! For per-update latency and the cross-library comparison, use the criterion //! harnesses instead: `cargo bench -p wickra` and `cargo bench -p wickra-bench`. //! //! Run: //! cargo run -p wickra-examples --release --bin throughput # 200k bars //! cargo run -p wickra-examples --release --bin throughput -- 1000000 use std::time::Instant; use wickra::{Atr, Candle, Indicator, MacdIndicator, Sma}; /// Median elapsed-ns over a few repetitions, after one warmup pass. fn time_ns(mut run: impl FnMut()) -> u128 { run(); // warmup let mut samples = [0u128; 3]; for sample in &mut samples { let start = Instant::now(); run(); *sample = start.elapsed().as_nanos(); } samples.sort_unstable(); samples[1] } fn main() { let bars: usize = std::env::args() .nth(1) .and_then(|arg| arg.parse().ok()) .filter(|&n| n >= 1000) .unwrap_or(200_000); // Deterministic synthetic OHLCV (no RNG, so runs are comparable). let mut open = Vec::with_capacity(bars); let mut high = Vec::with_capacity(bars); let mut low = Vec::with_capacity(bars); let mut close = Vec::with_capacity(bars); let mut volume = Vec::with_capacity(bars); for i in 0..bars { let mid = 100.0 + (i as f64 * 0.001).sin() * 20.0 + i as f64 * 1e-4; let c = mid + (i as f64 * 0.05).sin() * 2.0; close.push(c); open.push(mid); high.push(c.max(mid) + 1.5); low.push(c.min(mid) - 1.5); volume.push(1000.0 + (i % 97) as f64 * 13.0); } // ATR streams a Candle per tick; build them once, outside the timed loop. let candles: Vec = (0..bars) .map(|i| { Candle::new( open[i], high[i], low[i], close[i], volume[i], i64::try_from(i).unwrap(), ) .unwrap() }) .collect(); let mups = |ns: u128| bars as f64 / (ns as f64 / 1e9) / 1e6; // SMA (scalar 1-in/1-out), ATR (multi-in/1-out), MACD (1-in/multi-out). let sma_stream = time_ns(|| { let mut ind = Sma::new(20).unwrap(); for &price in &close { ind.update(price); } }); let sma_batch = time_ns(|| { let mut ind = Sma::new(20).unwrap(); ind.batch_nan(&close); }); let atr_stream = time_ns(|| { let mut ind = Atr::new(14).unwrap(); for &candle in &candles { ind.update(candle); } }); let atr_batch = time_ns(|| { let mut ind = Atr::new(14).unwrap(); ind.batch_atr(&high, &low, &close); }); let macd_stream = time_ns(|| { let mut ind = MacdIndicator::new(12, 26, 9).unwrap(); for &price in &close { ind.update(price); } }); println!("Wickra Rust core throughput - {bars} bars (median of 3 runs)\n"); println!( "{:<22}{:>20}{:>18}", "Indicator", "streaming (Mupd/s)", "batch (Mupd/s)" ); println!("{}", "-".repeat(60)); println!( "{:<22}{:>20.1}{:>18.1}", "SMA(20)", mups(sma_stream), mups(sma_batch) ); println!( "{:<22}{:>20.1}{:>18.1}", "ATR(14)", mups(atr_stream), mups(atr_batch) ); println!( "{:<22}{:>20.1}{:>18}", "MACD(12,26,9)", mups(macd_stream), "-" ); println!( "\nMupd/s = million indicator updates per second. This is the Rust core with\n\ no FFI boundary, so it is the ceiling for the per-binding benchmarks and\n\ the value their batch paths converge towards. Numbers are machine-dependent\n\ - use them for relative comparison, not as a speed claim." ); }