Move 17 library crates into crates/, CLI binary into bin/fxt, consolidate 10 test crates into testing/, split config crate from deployment config files. Root directory reduced from 38+ to ~17 directories. All Cargo.toml paths and build.rs proto refs updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
944 lines
28 KiB
Rust
944 lines
28 KiB
Rust
//! Comprehensive HFT Performance Benchmarks
|
|
//!
|
|
//! This module provides exhaustive performance benchmarking for all latency-critical
|
|
//! paths in the Foxhunt HFT system. Benchmarks target sub-microsecond operations
|
|
//! and validate HFT performance requirements.
|
|
//!
|
|
//! Performance Targets:
|
|
//! - Order validation: < 1μs
|
|
//! - Risk calculation: < 5μs
|
|
//! - Market data processing: < 100ns
|
|
//! - PnL calculation: < 50ns
|
|
//! - Position updates: < 2μs
|
|
//! - Message serialization: < 500ns
|
|
//! - Event publishing: < 1μs
|
|
//! - Database writes: < 10μs
|
|
|
|
use criterion::{
|
|
black_box, criterion_group, criterion_main, BenchmarkId, Criterion,
|
|
Throughput, measurement::WallTime, BatchSize
|
|
};
|
|
use std::collections::{HashMap, BTreeMap};
|
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
use std::sync::{Arc, Mutex, atomic::{AtomicU64, Ordering}};
|
|
use parking_lot::RwLock;
|
|
use crossbeam::queue::SegQueue;
|
|
use serde::{Deserialize, Serialize};
|
|
// CANONICAL TYPE IMPORTS - Use types::prelude::Decimal
|
|
use uuid::Uuid;
|
|
use chrono::{DateTime, Utc};
|
|
|
|
// ===== PERFORMANCE-CRITICAL DATA STRUCTURES =====
|
|
|
|
/// High-performance order structure optimized for HFT
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HFTOrder {
|
|
pub id: u64,
|
|
pub symbol: [u8; 8], // Fixed-size symbol for better cache performance
|
|
pub side: OrderSide,
|
|
pub quantity: u64, // Using integers for exact arithmetic
|
|
pub price: u64, // Price in ticks (e.g., cents)
|
|
pub timestamp_ns: u64, // Nanosecond timestamp
|
|
pub strategy_id: u16,
|
|
}
|
|
|
|
// OrderSide now imported from canonical source
|
|
use common::OrderSide;
|
|
|
|
/// High-performance market data tick
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C)] // Ensure memory layout for SIMD operations
|
|
pub struct MarketTick {
|
|
pub symbol_id: u32,
|
|
pub bid: u64,
|
|
pub ask: u64,
|
|
pub bid_size: u32,
|
|
pub ask_size: u32,
|
|
pub last: u64,
|
|
pub volume: u32,
|
|
pub timestamp_ns: u64,
|
|
}
|
|
|
|
/// High-performance position tracking
|
|
#[derive(Debug, Clone)]
|
|
pub struct PositionManager {
|
|
positions: Arc<RwLock<HashMap<u32, i64>>>, // symbol_id -> quantity
|
|
pnl: Arc<AtomicU64>, // Atomic for lock-free updates
|
|
update_count: Arc<AtomicU64>,
|
|
}
|
|
|
|
impl PositionManager {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
positions: Arc::new(RwLock::new(HashMap::new())),
|
|
pnl: Arc::new(AtomicU64::new(0)),
|
|
update_count: Arc::new(AtomicU64::new(0)),
|
|
}
|
|
}
|
|
|
|
pub fn update_position(&self, symbol_id: u32, quantity_delta: i64) {
|
|
let mut positions = self.positions.write();
|
|
*positions.entry(symbol_id).or_insert(0) += quantity_delta;
|
|
self.update_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
pub fn get_position(&self, symbol_id: u32) -> i64 {
|
|
self.positions.read().get(&symbol_id).copied().unwrap_or(0)
|
|
}
|
|
|
|
pub fn calculate_pnl(&self, symbol_id: u32, current_price: u64, entry_price: u64) -> i64 {
|
|
let position = self.get_position(symbol_id);
|
|
(current_price as i64 - entry_price as i64) * position
|
|
}
|
|
}
|
|
|
|
/// High-performance risk calculator
|
|
#[derive(Debug)]
|
|
pub struct RiskCalculator {
|
|
limits: RiskLimits,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskLimits {
|
|
pub max_position: i64,
|
|
pub max_order_size: u64,
|
|
pub max_notional: u64,
|
|
pub max_leverage: f32,
|
|
}
|
|
|
|
impl RiskCalculator {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
limits: RiskLimits {
|
|
max_position: 10000,
|
|
max_order_size: 1000,
|
|
max_notional: 1000000,
|
|
max_leverage: 3.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn validate_order(&self, order: &HFTOrder, current_position: i64) -> bool {
|
|
// Fast validation checks
|
|
if order.quantity > self.limits.max_order_size {
|
|
return false;
|
|
}
|
|
|
|
let new_position = match order.side {
|
|
OrderSide::Buy => current_position + order.quantity as i64,
|
|
OrderSide::Sell => current_position - order.quantity as i64,
|
|
};
|
|
|
|
new_position.abs() <= self.limits.max_position
|
|
}
|
|
|
|
pub fn calculate_var(&self, positions: &[(u32, i64, u64)], confidence: f32) -> u64 {
|
|
// Simplified VaR calculation for benchmarking
|
|
let mut total_risk = 0u64;
|
|
for (_, quantity, price) in positions {
|
|
let notional = quantity.abs() as u64 * price;
|
|
total_risk += (notional as f32 * confidence) as u64;
|
|
}
|
|
total_risk
|
|
}
|
|
}
|
|
|
|
/// High-performance order book
|
|
#[derive(Debug)]
|
|
pub struct OrderBook {
|
|
bids: BTreeMap<u64, u64>, // price -> quantity
|
|
asks: BTreeMap<u64, u64>,
|
|
last_update_ns: AtomicU64,
|
|
}
|
|
|
|
impl OrderBook {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
bids: BTreeMap::new(),
|
|
asks: BTreeMap::new(),
|
|
last_update_ns: AtomicU64::new(0),
|
|
}
|
|
}
|
|
|
|
pub fn update_bid(&mut self, price: u64, quantity: u64) {
|
|
if quantity == 0 {
|
|
self.bids.remove(&price);
|
|
} else {
|
|
self.bids.insert(price, quantity);
|
|
}
|
|
self.last_update_ns.store(
|
|
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
Ordering::Relaxed
|
|
);
|
|
}
|
|
|
|
pub fn get_best_bid(&self) -> Option<(u64, u64)> {
|
|
self.bids.iter().next_back().map(|(p, q)| (*p, *q))
|
|
}
|
|
|
|
pub fn get_best_ask(&self) -> Option<(u64, u64)> {
|
|
self.asks.iter().next().map(|(p, q)| (*p, *q))
|
|
}
|
|
|
|
pub fn get_mid_price(&self) -> Option<u64> {
|
|
match (self.get_best_bid(), self.get_best_ask()) {
|
|
(Some((bid, _)), Some((ask, _))) => Some((bid + ask) / 2),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// High-performance message queue for order flow
|
|
#[derive(Debug)]
|
|
pub struct HFTMessageQueue {
|
|
queue: SegQueue<HFTOrder>,
|
|
message_count: AtomicU64,
|
|
}
|
|
|
|
impl HFTMessageQueue {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
queue: SegQueue::new(),
|
|
message_count: AtomicU64::new(0),
|
|
}
|
|
}
|
|
|
|
pub fn push(&self, order: HFTOrder) {
|
|
self.queue.push(order);
|
|
self.message_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
pub fn pop(&self) -> Option<HFTOrder> {
|
|
self.queue.pop()
|
|
}
|
|
|
|
pub fn len(&self) -> u64 {
|
|
self.message_count.load(Ordering::Relaxed)
|
|
}
|
|
}
|
|
|
|
// ===== BENCHMARK IMPLEMENTATIONS =====
|
|
|
|
/// Benchmark order validation performance
|
|
fn bench_order_validation(c: &mut Criterion) {
|
|
let risk_calculator = RiskCalculator::new();
|
|
let position_manager = PositionManager::new();
|
|
|
|
// Pre-populate some positions
|
|
position_manager.update_position(1, 500);
|
|
position_manager.update_position(2, -300);
|
|
|
|
let orders: Vec<HFTOrder> = (0..1000).map(|i| HFTOrder {
|
|
id: i,
|
|
symbol: *b"AAPL ",
|
|
side: if i % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell },
|
|
quantity: 100 + (i % 900) as u64,
|
|
price: 15000 + (i % 1000) as u64, // $150.00 + variation
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: (i % 10) as u16,
|
|
}).collect();
|
|
|
|
let mut group = c.benchmark_group("order_validation");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("validate_single_order", |b| {
|
|
b.iter_batched(
|
|
|| orders[black_box(0)].clone(),
|
|
|order| {
|
|
let current_position = position_manager.get_position(1);
|
|
black_box(risk_calculator.validate_order(&order, current_position))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("validate_order_batch", |b| {
|
|
b.iter_batched(
|
|
|| orders[0..100].to_vec(),
|
|
|order_batch| {
|
|
for order in order_batch {
|
|
let current_position = position_manager.get_position(1);
|
|
black_box(risk_calculator.validate_order(&order, current_position));
|
|
}
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark market data processing performance
|
|
fn bench_market_data_processing(c: &mut Criterion) {
|
|
let ticks: Vec<MarketTick> = (0..10000).map(|i| MarketTick {
|
|
symbol_id: (i % 100) as u32,
|
|
bid: 15000 + (i % 100) as u64,
|
|
ask: 15001 + (i % 100) as u64,
|
|
bid_size: 1000 + (i % 9000) as u32,
|
|
ask_size: 1000 + (i % 9000) as u32,
|
|
last: 15000 + (i % 100) as u64,
|
|
volume: (i % 10000) as u32,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
}).collect();
|
|
|
|
let mut group = c.benchmark_group("market_data_processing");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("process_single_tick", |b| {
|
|
b.iter_batched(
|
|
|| ticks[0],
|
|
|tick| {
|
|
// Simulate market data processing
|
|
let mid_price = (tick.bid + tick.ask) / 2;
|
|
let spread = tick.ask - tick.bid;
|
|
black_box((mid_price, spread))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("process_tick_batch", |b| {
|
|
b.iter_batched(
|
|
|| &ticks[0..1000],
|
|
|tick_batch| {
|
|
for tick in tick_batch {
|
|
let mid_price = (tick.bid + tick.ask) / 2;
|
|
let spread = tick.ask - tick.bid;
|
|
black_box((mid_price, spread));
|
|
}
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("calculate_vwap", |b| {
|
|
b.iter_batched(
|
|
|| &ticks[0..100],
|
|
|tick_batch| {
|
|
let mut total_notional = 0u64;
|
|
let mut total_volume = 0u64;
|
|
|
|
for tick in tick_batch {
|
|
total_notional += tick.last * tick.volume as u64;
|
|
total_volume += tick.volume as u64;
|
|
}
|
|
|
|
let vwap = if total_volume > 0 { total_notional / total_volume } else { 0 };
|
|
black_box(vwap)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark position management performance
|
|
fn bench_position_management(c: &mut Criterion) {
|
|
let position_manager = PositionManager::new();
|
|
|
|
let mut group = c.benchmark_group("position_management");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("update_position", |b| {
|
|
b.iter_batched(
|
|
|| (black_box(1u32), black_box(100i64)),
|
|
|(symbol_id, quantity_delta)| {
|
|
position_manager.update_position(symbol_id, quantity_delta)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("get_position", |b| {
|
|
b.iter_batched(
|
|
|| black_box(1u32),
|
|
|symbol_id| {
|
|
black_box(position_manager.get_position(symbol_id))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("calculate_pnl", |b| {
|
|
// Pre-populate position
|
|
position_manager.update_position(1, 1000);
|
|
|
|
b.iter_batched(
|
|
|| (black_box(1u32), black_box(15050u64), black_box(15000u64)),
|
|
|(symbol_id, current_price, entry_price)| {
|
|
black_box(position_manager.calculate_pnl(symbol_id, current_price, entry_price))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark risk calculation performance
|
|
fn bench_risk_calculations(c: &mut Criterion) {
|
|
let risk_calculator = RiskCalculator::new();
|
|
|
|
// Generate test portfolio
|
|
let positions: Vec<(u32, i64, u64)> = (0..100).map(|i| {
|
|
(i as u32, 100 + (i * 10), 15000 + (i * 10) as u64)
|
|
}).collect();
|
|
|
|
let mut group = c.benchmark_group("risk_calculations");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("calculate_var", |b| {
|
|
b.iter_batched(
|
|
|| (positions.as_slice(), 0.95f32),
|
|
|(positions, confidence)| {
|
|
black_box(risk_calculator.calculate_var(positions, confidence))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("portfolio_exposure", |b| {
|
|
b.iter_batched(
|
|
|| positions.as_slice(),
|
|
|positions| {
|
|
let mut total_long = 0u64;
|
|
let mut total_short = 0u64;
|
|
|
|
for (_, quantity, price) in positions {
|
|
let notional = quantity.abs() as u64 * price;
|
|
if *quantity > 0 {
|
|
total_long += notional;
|
|
} else {
|
|
total_short += notional;
|
|
}
|
|
}
|
|
|
|
black_box((total_long, total_short))
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark order book operations
|
|
fn bench_order_book_operations(c: &mut Criterion) {
|
|
let mut order_book = OrderBook::new();
|
|
|
|
// Pre-populate order book
|
|
for i in 0..1000 {
|
|
order_book.update_bid(15000 - i, 100);
|
|
}
|
|
|
|
let mut group = c.benchmark_group("order_book");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("update_bid", |b| {
|
|
let mut ob = order_book;
|
|
b.iter_batched(
|
|
|| (black_box(14500u64), black_box(200u64)),
|
|
|(price, quantity)| {
|
|
ob.update_bid(price, quantity)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("get_best_bid", |b| {
|
|
b.iter(|| {
|
|
black_box(order_book.get_best_bid())
|
|
})
|
|
});
|
|
|
|
group.bench_function("get_mid_price", |b| {
|
|
b.iter(|| {
|
|
black_box(order_book.get_mid_price())
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark message queue performance
|
|
fn bench_message_queue(c: &mut Criterion) {
|
|
let queue = HFTMessageQueue::new();
|
|
|
|
let orders: Vec<HFTOrder> = (0..10000).map(|i| HFTOrder {
|
|
id: i,
|
|
symbol: *b"AAPL ",
|
|
side: if i % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell },
|
|
quantity: 100,
|
|
price: 15000,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
}).collect();
|
|
|
|
let mut group = c.benchmark_group("message_queue");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("push_order", |b| {
|
|
b.iter_batched(
|
|
|| orders[0].clone(),
|
|
|order| {
|
|
queue.push(order)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
// Pre-populate queue for pop benchmark
|
|
for order in &orders[0..1000] {
|
|
queue.push(order.clone());
|
|
}
|
|
|
|
group.bench_function("pop_order", |b| {
|
|
b.iter(|| {
|
|
black_box(queue.pop())
|
|
})
|
|
});
|
|
|
|
group.bench_function("queue_throughput", |b| {
|
|
b.iter_batched(
|
|
|| orders[0..100].to_vec(),
|
|
|order_batch| {
|
|
for order in order_batch {
|
|
queue.push(order);
|
|
}
|
|
for _ in 0..100 {
|
|
queue.pop();
|
|
}
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark serialization performance
|
|
fn bench_serialization(c: &mut Criterion) {
|
|
let order = HFTOrder {
|
|
id: 12345,
|
|
symbol: *b"AAPL ",
|
|
side: OrderSide::Buy,
|
|
quantity: 1000,
|
|
price: 15050,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
};
|
|
|
|
let mut group = c.benchmark_group("serialization");
|
|
group.throughput(Throughput::Bytes(std::mem::size_of::<HFTOrder>() as u64));
|
|
|
|
group.bench_function("bincode_serialize", |b| {
|
|
b.iter_batched(
|
|
|| order.clone(),
|
|
|order| {
|
|
black_box(bincode::serialize(&order).unwrap())
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
let serialized = bincode::serialize(&order).unwrap();
|
|
group.bench_function("bincode_deserialize", |b| {
|
|
b.iter_batched(
|
|
|| serialized.clone(),
|
|
|data| {
|
|
black_box(bincode::deserialize::<HFTOrder>(&data).unwrap())
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("json_serialize", |b| {
|
|
b.iter_batched(
|
|
|| order.clone(),
|
|
|order| {
|
|
black_box(serde_json::to_string(&order).unwrap())
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
let json_data = serde_json::to_string(&order).unwrap();
|
|
group.bench_function("json_deserialize", |b| {
|
|
b.iter_batched(
|
|
|| json_data.clone(),
|
|
|data| {
|
|
black_box(serde_json::from_str::<HFTOrder>(&data).unwrap())
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark memory allocation patterns
|
|
fn bench_memory_patterns(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("memory_patterns");
|
|
|
|
group.bench_function("vec_allocation", |b| {
|
|
b.iter(|| {
|
|
let mut vec = Vec::with_capacity(1000);
|
|
for i in 0..1000 {
|
|
vec.push(black_box(i));
|
|
}
|
|
black_box(vec)
|
|
})
|
|
});
|
|
|
|
group.bench_function("hashmap_insertion", |b| {
|
|
b.iter(|| {
|
|
let mut map = HashMap::with_capacity(1000);
|
|
for i in 0..1000 {
|
|
map.insert(black_box(i), black_box(i * 2));
|
|
}
|
|
black_box(map)
|
|
})
|
|
});
|
|
|
|
group.bench_function("btreemap_insertion", |b| {
|
|
b.iter(|| {
|
|
let mut map = BTreeMap::new();
|
|
for i in 0..1000 {
|
|
map.insert(black_box(i), black_box(i * 2));
|
|
}
|
|
black_box(map)
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark financial calculations
|
|
fn bench_financial_calculations(c: &mut Criterion) {
|
|
let prices = vec![150.0, 151.5, 149.8, 152.1, 150.9];
|
|
let returns: Vec<f64> = prices.windows(2).map(|w| (w[1] - w[0]) / w[0]).collect();
|
|
|
|
let mut group = c.benchmark_group("financial_calculations");
|
|
|
|
group.bench_function("simple_return", |b| {
|
|
b.iter_batched(
|
|
|| (black_box(150.0), black_box(151.5)),
|
|
|(start_price, end_price)| {
|
|
black_box((end_price - start_price) / start_price)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("volatility_calculation", |b| {
|
|
b.iter_batched(
|
|
|| returns.clone(),
|
|
|returns| {
|
|
let mean = returns.iter().sum::<f64>() / returns.len() as f64;
|
|
let variance = returns.iter()
|
|
.map(|r| (r - mean).powi(2))
|
|
.sum::<f64>() / returns.len() as f64;
|
|
black_box(variance.sqrt())
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.bench_function("sharpe_ratio", |b| {
|
|
b.iter_batched(
|
|
|| (returns.clone(), 0.02f64), // 2% risk-free rate
|
|
|(returns, risk_free_rate)| {
|
|
let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;
|
|
let std_dev = {
|
|
let variance = returns.iter()
|
|
.map(|r| (r - mean_return).powi(2))
|
|
.sum::<f64>() / returns.len() as f64;
|
|
variance.sqrt()
|
|
};
|
|
black_box((mean_return - risk_free_rate) / std_dev)
|
|
},
|
|
BatchSize::SmallInput
|
|
)
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark timestamp operations
|
|
fn bench_timestamp_operations(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("timestamp_operations");
|
|
|
|
group.bench_function("system_time_now", |b| {
|
|
b.iter(|| {
|
|
black_box(SystemTime::now())
|
|
})
|
|
});
|
|
|
|
group.bench_function("unix_timestamp_ns", |b| {
|
|
b.iter(|| {
|
|
black_box(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos())
|
|
})
|
|
});
|
|
|
|
group.bench_function("chrono_utc_now", |b| {
|
|
b.iter(|| {
|
|
black_box(Utc::now())
|
|
})
|
|
});
|
|
|
|
group.bench_function("timestamp_comparison", |b| {
|
|
let ts1 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
|
|
let ts2 = ts1 + 1000; // 1μs later
|
|
|
|
b.iter(|| {
|
|
black_box(ts2 > ts1)
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ===== CRITERION CONFIGURATION =====
|
|
|
|
criterion_group! {
|
|
name = hft_benchmarks;
|
|
config = Criterion::default()
|
|
.measurement_time(Duration::from_secs(10))
|
|
.sample_size(1000)
|
|
.warm_up_time(Duration::from_secs(3));
|
|
targets =
|
|
bench_order_validation,
|
|
bench_market_data_processing,
|
|
bench_position_management,
|
|
bench_risk_calculations,
|
|
bench_order_book_operations,
|
|
bench_message_queue,
|
|
bench_serialization,
|
|
bench_memory_patterns,
|
|
bench_financial_calculations,
|
|
bench_timestamp_operations
|
|
}
|
|
|
|
criterion_main!(hft_benchmarks);
|
|
|
|
// ===== PERFORMANCE VALIDATION TESTS =====
|
|
|
|
#[cfg(test)]
|
|
mod performance_validation_tests {
|
|
use super::*;
|
|
use std::time::Instant;
|
|
|
|
#[test]
|
|
fn test_order_validation_performance() {
|
|
let risk_calculator = RiskCalculator::new();
|
|
let order = HFTOrder {
|
|
id: 1,
|
|
symbol: *b"AAPL ",
|
|
side: OrderSide::Buy,
|
|
quantity: 100,
|
|
price: 15000,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
};
|
|
|
|
let iterations = 10000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
black_box(risk_calculator.validate_order(&order, 500));
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / iterations;
|
|
|
|
// Should validate orders in less than 1μs (1000ns)
|
|
assert!(avg_duration_ns < 1000,
|
|
"Order validation too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_market_data_processing_performance() {
|
|
let tick = MarketTick {
|
|
symbol_id: 1,
|
|
bid: 15000,
|
|
ask: 15001,
|
|
bid_size: 1000,
|
|
ask_size: 1000,
|
|
last: 15000,
|
|
volume: 5000,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
};
|
|
|
|
let iterations = 100000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
let mid_price = (tick.bid + tick.ask) / 2;
|
|
let spread = tick.ask - tick.bid;
|
|
black_box((mid_price, spread));
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / iterations;
|
|
|
|
// Should process market data in less than 100ns
|
|
assert!(avg_duration_ns < 100,
|
|
"Market data processing too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_position_update_performance() {
|
|
let position_manager = PositionManager::new();
|
|
|
|
let iterations = 10000;
|
|
let start = Instant::now();
|
|
|
|
for i in 0..iterations {
|
|
position_manager.update_position(1, if i % 2 == 0 { 100 } else { -100 });
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / iterations;
|
|
|
|
// Should update positions in less than 2μs (2000ns)
|
|
assert!(avg_duration_ns < 2000,
|
|
"Position update too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pnl_calculation_performance() {
|
|
let position_manager = PositionManager::new();
|
|
position_manager.update_position(1, 1000);
|
|
|
|
let iterations = 100000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
black_box(position_manager.calculate_pnl(1, 15050, 15000));
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / iterations;
|
|
|
|
// Should calculate PnL in less than 50ns
|
|
assert!(avg_duration_ns < 50,
|
|
"PnL calculation too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_message_queue_performance() {
|
|
let queue = HFTMessageQueue::new();
|
|
let order = HFTOrder {
|
|
id: 1,
|
|
symbol: *b"AAPL ",
|
|
side: OrderSide::Buy,
|
|
quantity: 100,
|
|
price: 15000,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
};
|
|
|
|
let iterations = 10000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
queue.push(order.clone());
|
|
}
|
|
|
|
for _ in 0..iterations {
|
|
queue.pop();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / (iterations * 2); // push + pop
|
|
|
|
// Should handle queue operations in less than 1μs (1000ns)
|
|
assert!(avg_duration_ns < 1000,
|
|
"Message queue operations too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_serialization_performance() {
|
|
let order = HFTOrder {
|
|
id: 12345,
|
|
symbol: *b"AAPL ",
|
|
side: OrderSide::Buy,
|
|
quantity: 1000,
|
|
price: 15050,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
};
|
|
|
|
let iterations = 10000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
let serialized = bincode::serialize(&order).unwrap();
|
|
let _deserialized: HFTOrder = bincode::deserialize(&serialized).unwrap();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_ns = duration.as_nanos() / iterations;
|
|
|
|
// Should serialize/deserialize in less than 500ns
|
|
assert!(avg_duration_ns < 500,
|
|
"Serialization too slow: {}ns average", avg_duration_ns);
|
|
}
|
|
|
|
#[test]
|
|
fn test_overall_system_latency() {
|
|
// Simulate complete order processing workflow
|
|
let risk_calculator = RiskCalculator::new();
|
|
let position_manager = PositionManager::new();
|
|
let queue = HFTMessageQueue::new();
|
|
|
|
let order = HFTOrder {
|
|
id: 1,
|
|
symbol: *b"AAPL ",
|
|
side: OrderSide::Buy,
|
|
quantity: 100,
|
|
price: 15000,
|
|
timestamp_ns: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64,
|
|
strategy_id: 1,
|
|
};
|
|
|
|
let iterations = 1000;
|
|
let start = Instant::now();
|
|
|
|
for _ in 0..iterations {
|
|
// Step 1: Queue order
|
|
queue.push(order.clone());
|
|
|
|
// Step 2: Get order from queue
|
|
let order = queue.pop().unwrap();
|
|
|
|
// Step 3: Validate order
|
|
let current_position = position_manager.get_position(1);
|
|
let is_valid = risk_calculator.validate_order(&order, current_position);
|
|
|
|
if is_valid {
|
|
// Step 4: Update position
|
|
let position_delta = match order.side {
|
|
OrderSide::Buy => order.quantity as i64,
|
|
OrderSide::Sell => -(order.quantity as i64),
|
|
};
|
|
position_manager.update_position(1, position_delta);
|
|
|
|
// Step 5: Calculate PnL
|
|
position_manager.calculate_pnl(1, 15050, 15000);
|
|
}
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let avg_duration_us = duration.as_micros() / iterations;
|
|
|
|
// Complete workflow should be under 10μs
|
|
assert!(avg_duration_us < 10,
|
|
"Overall system latency too high: {}μs average", avg_duration_us);
|
|
|
|
println!("Performance Summary:");
|
|
println!(" Complete workflow latency: {}μs average", avg_duration_us);
|
|
println!(" Theoretical throughput: {} orders/sec", 1_000_000 / avg_duration_us);
|
|
}
|
|
} |