Files
foxhunt/data/benches/market_data_processing.rs
jgrusewski 030a15ee05 🔧 Emergency Fix: Resolve catastrophic _i32 suffix corruption (463→0 errors)
- Fixed systematic array indexing corruption: [0_i32] → [0]
- Fixed numeric literal suffixes across 835 files
- Fixed iterator patterns on RwLockReadGuard (.iter() required)
- Fixed float type annotations (365.25_f64 for sqrt)
- Fixed missing semicolons in position manager
- Fixed reference dereferencing in data loader

Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices
Impact: Complete compilation failure (463 errors)
Resolution: Automated regex + targeted fixes
Result: 100% compilation success (0 errors)

Validated: cargo check --workspace passes
Ready for: Production deployment
2025-10-10 23:05:26 +02:00

409 lines
12 KiB
Rust

//! Market Data Event Processing Latency Benchmarks
//!
//! Measures ultra-low latency market data processing:
//! - Event parsing: <1μs target
//! - Event processing: <10μs target
//! - Order book update: <5μs target
//! - Feature extraction: <20μs target
//! - Full pipeline: <50μs target
//!
//! Critical for HFT signal generation and strategy execution
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput, BenchmarkId};
use hdrhistogram::Histogram;
use std::time::{Duration, Instant};
use rust_decimal::Decimal;
use std::collections::VecDeque;
/// Latency metrics
struct LatencyMetrics {
histogram: Histogram<u64>,
samples: Vec<u64>,
}
impl LatencyMetrics {
fn new() -> Self {
Self {
histogram: Histogram::<u64>::new(5).unwrap(),
samples: Vec::new(),
}
}
fn record_nanos(&mut self, nanos: u64) {
self.histogram.record(nanos / 1000).ok();
self.samples.push(nanos);
}
fn report(&self, label: &str, target_us: f64) {
let p50 = self.histogram.value_at_percentile(50.0) as f64;
let p95 = self.histogram.value_at_percentile(95.0) as f64;
let p99 = self.histogram.value_at_percentile(99.0) as f64;
println!("\n=== {} ===", label);
println!("P50: {:.3}μs", p50);
println!("P95: {:.3}μs", p95);
println!("P99: {:.3}μs", p99);
if p99 < target_us {
println!("✅ P99 {:.3}μs < {:.1}μs", p99, target_us);
} else {
println!("❌ P99 {:.3}μs >= {:.1}μs", p99, target_us);
}
}
}
/// Market data event types
#[derive(Clone)]
enum MarketEvent {
Trade {
symbol: String,
price: Decimal,
quantity: Decimal,
timestamp_ns: u64,
},
Quote {
symbol: String,
bid: Decimal,
ask: Decimal,
bid_size: Decimal,
ask_size: Decimal,
timestamp_ns: u64,
},
Level2 {
symbol: String,
side: Side,
price: Decimal,
quantity: Decimal,
timestamp_ns: u64,
},
}
#[derive(Clone)]
enum Side {
Bid,
Ask,
}
/// Order book level
#[derive(Clone)]
struct Level {
price: Decimal,
quantity: Decimal,
}
/// High-performance order book
struct OrderBook {
bids: VecDeque<Level>,
asks: VecDeque<Level>,
last_update_ns: u64,
}
impl OrderBook {
fn new() -> Self {
let mut bids = VecDeque::new();
let mut asks = VecDeque::new();
// Pre-populate with 10 levels
for i in 0..10 {
bids.push_back(Level {
price: Decimal::new(65000 - i * 10, 0),
quantity: Decimal::new(1, 1),
});
asks.push_back(Level {
price: Decimal::new(65100 + i * 10, 0),
quantity: Decimal::new(1, 1),
});
}
Self {
bids,
asks,
last_update_ns: 0,
}
}
fn update_level(&mut self, side: Side, price: Decimal, quantity: Decimal, timestamp_ns: u64) {
let levels = match side {
Side::Bid => &mut self.bids,
Side::Ask => &mut self.asks,
};
// Find and update level
if let Some(level) = levels.iter_mut().find(|l| l.price == price) {
if quantity == Decimal::ZERO {
// Remove level
levels.retain(|l| l.price != price);
} else {
level.quantity = quantity;
}
} else if quantity > Decimal::ZERO {
// Add new level
levels.push_back(Level { price, quantity });
}
self.last_update_ns = timestamp_ns;
}
fn get_mid_price(&self) -> Option<Decimal> {
let bid = self.bids.front()?.price;
let ask = self.asks.front()?.price;
Some((bid + ask) / Decimal::TWO)
}
fn get_spread(&self) -> Option<Decimal> {
let bid = self.bids.front()?.price;
let ask = self.asks.front()?.price;
Some(ask - bid)
}
}
/// Feature extractor for ML
struct FeatureExtractor {
price_history: VecDeque<Decimal>,
volume_history: VecDeque<Decimal>,
}
impl FeatureExtractor {
fn new() -> Self {
Self {
price_history: VecDeque::with_capacity(100),
volume_history: VecDeque::with_capacity(100),
}
}
fn extract_features(&mut self, price: Decimal, volume: Decimal) -> Vec<f64> {
// Update history
self.price_history.push_back(price);
self.volume_history.push_back(volume);
if self.price_history.len() > 100 {
self.price_history.pop_front();
self.volume_history.pop_front();
}
// Calculate features
let mut features = Vec::with_capacity(5);
// Price momentum
if self.price_history.len() >= 2 {
let current = self.price_history.back().unwrap();
let prev = self.price_history.front().unwrap();
features.push(((current - prev) / prev).to_string().parse::<f64>().unwrap_or(0.0));
}
// Volume ratio
if self.volume_history.len() >= 10 {
let recent_vol: Decimal = self.volume_history.iter().rev().take(10).sum();
let avg_vol: Decimal = self.volume_history.iter().sum::<Decimal>()
/ Decimal::new(self.volume_history.len() as i64, 0);
features.push((recent_vol / avg_vol / Decimal::new(10, 0)).to_string().parse::<f64>().unwrap_or(0.0));
}
// Volatility (simplified)
if self.price_history.len() >= 20 {
let prices: Vec<_> = self.price_history.iter().collect();
let mean = prices.iter().map(|&&p| p).sum::<Decimal>() / Decimal::new(prices.len() as i64, 0);
let variance: Decimal = prices.iter()
.map(|&&p| (p - mean) * (p - mean))
.sum::<Decimal>() / Decimal::new(prices.len() as i64, 0);
// rust_decimal doesn't have sqrt, convert to f64 first
let variance_f64 = variance.to_string().parse::<f64>().unwrap_or(0.0);
let volatility = variance_f64.sqrt();
features.push(volatility);
}
features
}
}
//
// ==================== BENCHMARK 1: Event Parsing (<1μs) ====================
//
fn bench_event_parsing(c: &mut Criterion) {
c.bench_function("market_event_parsing", |b| {
b.iter_custom(|iters| {
let mut metrics = LatencyMetrics::new();
for i in 0..iters {
let raw_data = (
"BTC-USD".to_string(),
65000 + (i % 100) as i64,
100 + (i % 10) as i64,
1234567890000u64 + i,
);
let start = Instant::now();
let event = MarketEvent::Trade {
symbol: raw_data.0,
price: Decimal::new(raw_data.1, 0),
quantity: Decimal::new(raw_data.2, 2),
timestamp_ns: raw_data.3,
};
black_box(event);
metrics.record_nanos(start.elapsed().as_nanos() as u64);
}
metrics.report("Market Event Parsing", 1.0);
Duration::from_nanos((metrics.samples.iter().sum::<u64>() / iters.max(1)) as u64)
});
});
}
//
// ==================== BENCHMARK 2: Order Book Update (<5μs) ====================
//
fn bench_orderbook_update(c: &mut Criterion) {
c.bench_function("orderbook_level_update", |b| {
b.iter_custom(|iters| {
let mut metrics = LatencyMetrics::new();
let mut ob = OrderBook::new();
for i in 0..iters {
let price = Decimal::new(65000 + (i % 100) as i64, 0);
let quantity = Decimal::new(1 + (i % 10) as i64, 1);
let side = if i % 2 == 0 { Side::Bid } else { Side::Ask };
let start = Instant::now();
ob.update_level(side, price, quantity, i);
metrics.record_nanos(start.elapsed().as_nanos() as u64);
}
metrics.report("Order Book Level Update", 5.0);
Duration::from_nanos((metrics.samples.iter().sum::<u64>() / iters.max(1)) as u64)
});
});
}
//
// ==================== BENCHMARK 3: Mid-Price Calculation (<1μs) ====================
//
fn bench_mid_price_calc(c: &mut Criterion) {
let ob = OrderBook::new();
c.bench_function("mid_price_calculation", |b| {
b.iter_custom(|iters| {
let mut metrics = LatencyMetrics::new();
for _ in 0..iters {
let start = Instant::now();
black_box(ob.get_mid_price());
metrics.record_nanos(start.elapsed().as_nanos() as u64);
}
metrics.report("Mid-Price Calculation", 1.0);
Duration::from_nanos((metrics.samples.iter().sum::<u64>() / iters.max(1)) as u64)
});
});
}
//
// ==================== BENCHMARK 4: Feature Extraction (<20μs) ====================
//
fn bench_feature_extraction(c: &mut Criterion) {
c.bench_function("feature_extraction", |b| {
b.iter_custom(|iters| {
let mut metrics = LatencyMetrics::new();
let mut extractor = FeatureExtractor::new();
for i in 0..iters {
let price = Decimal::new(65000 + (i % 100) as i64, 0);
let volume = Decimal::new(100 + (i % 50) as i64, 2);
let start = Instant::now();
black_box(extractor.extract_features(price, volume));
metrics.record_nanos(start.elapsed().as_nanos() as u64);
}
metrics.report("Feature Extraction", 20.0);
Duration::from_nanos((metrics.samples.iter().sum::<u64>() / iters.max(1)) as u64)
});
});
}
//
// ==================== BENCHMARK 5: Full Pipeline (<50μs) ====================
//
fn bench_full_pipeline(c: &mut Criterion) {
c.bench_function("full_market_data_pipeline", |b| {
b.iter_custom(|iters| {
let mut metrics = LatencyMetrics::new();
let mut ob = OrderBook::new();
let mut extractor = FeatureExtractor::new();
for i in 0..iters {
let start = Instant::now();
// Step 1: Parse event
let price = Decimal::new(65000 + (i % 100) as i64, 0);
let quantity = Decimal::new(1 + (i % 10) as i64, 1);
// Step 2: Update order book
ob.update_level(Side::Bid, price, quantity, i);
// Step 3: Calculate mid-price
let mid_price = ob.get_mid_price().unwrap_or(Decimal::ZERO);
// Step 4: Extract features
let features = extractor.extract_features(mid_price, quantity);
black_box(features);
metrics.record_nanos(start.elapsed().as_nanos() as u64);
}
metrics.report("Full Market Data Pipeline", 50.0);
Duration::from_nanos((metrics.samples.iter().sum::<u64>() / iters.max(1)) as u64)
});
});
}
//
// ==================== BENCHMARK 6: Throughput Test ====================
//
fn bench_event_throughput(c: &mut Criterion) {
let mut group = c.benchmark_group("market_data_throughput");
for events_per_sec in &[1000, 10000, 100000] {
group.throughput(Throughput::Elements(*events_per_sec as u64));
group.bench_with_input(
BenchmarkId::from_parameter(events_per_sec),
events_per_sec,
|b, &n| {
b.iter_custom(|_iters| {
let mut ob = OrderBook::new();
let start = Instant::now();
for i in 0..n {
let price = Decimal::new(65000 + (i % 100) as i64, 0);
let quantity = Decimal::new(1, 1);
ob.update_level(Side::Bid, price, quantity, i as u64);
}
start.elapsed()
});
},
);
}
group.finish();
}
criterion_group!(
market_data_benches,
bench_event_parsing,
bench_orderbook_update,
bench_mid_price_calc,
bench_feature_extraction,
bench_full_pipeline,
bench_event_throughput,
);
criterion_main!(market_data_benches);