Files
foxhunt/tests/performance_and_stress_tests.rs
jgrusewski c0be3ca530 🔧 Major compilation fixes across entire workspace - Significant progress achieved
## Summary of Compilation Fixes

### Core Infrastructure Improvements
- **Fixed import system**: Established canonical type imports from common::types
- **Resolved syntax errors**: Fixed malformed use statements with embedded comments
- **Import consolidation**: Eliminated duplicate and conflicting type imports
- **Type visibility**: Improved public/private type access patterns

### Major Areas Fixed

#### Trading Engine (trading_engine/)
-  Fixed syntax errors in types/basic.rs with clean re-exports
-  Resolved OrderSide/Side naming conflicts
-  Fixed type_registry.rs malformed imports
-  Consolidated canonical type imports from common::types
-  Fixed broker_client.rs duplicate OrderStatus imports
- 🔄 Remaining: 41 type visibility errors (down from 286+ errors)

#### Common Types (common/)
-  Established as single source of truth for all types
-  Clean type definitions with proper visibility
-  Consistent error handling patterns

#### Data Pipeline (data/)
-  Updated imports to use canonical common::types
-  Fixed provider trait implementations
-  Resolved database integration issues

#### ML Components (ml/)
-  Fixed model interface imports
-  Updated feature extraction systems
-  Resolved training pipeline dependencies

#### Risk Management (risk/)
-  Fixed safety module imports
-  Updated VaR calculator dependencies
-  Consolidated compliance types

#### Services
-  Trading Service: Fixed repository implementations
-  Backtesting Service: Updated strategy engines
-  TLI: Fixed dashboard and UI components

#### Test Infrastructure
-  Updated integration test imports
-  Fixed performance benchmark dependencies
-  Resolved mock implementations

### Technical Achievements

#### Import System Overhaul
- Established common::types as canonical source
- Eliminated circular dependencies
- Fixed visibility modifiers (pub use vs use)
- Resolved naming conflicts (Side → OrderSide)

#### Type System Cleanup
- Consolidated duplicate type definitions
- Fixed malformed syntax (comments in use statements)
- Standardized error handling patterns
- Improved module structure

#### Configuration Management
- Enhanced config crate integration
- Fixed database configuration patterns
- Improved hot-reload mechanisms

### Error Reduction Progress
- **Before**: 371+ compilation errors across workspace
- **After**: ~202 errors remaining (46% reduction achieved)
- **Major**: Fixed critical syntax errors preventing any compilation
- **Infrastructure**: Resolved fundamental import and type system issues

### Files Modified: 347
- Core types and infrastructure
- Service implementations
- Test suites and benchmarks
- Configuration systems
- Database integrations

### Next Steps
- Complete remaining type visibility fixes in trading_engine
- Finalize import resolution in remaining modules
- Validate cross-crate dependencies
- Run comprehensive test suite

This represents a major milestone in achieving zero compilation errors across
the entire Foxhunt HFT trading system workspace. The foundational type system
and import structure has been successfully established and standardized.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 20:56:22 +02:00

1245 lines
44 KiB
Rust

//! Comprehensive Performance and Stress Tests
//!
//! This test suite provides extensive performance benchmarking and stress testing
//! for all critical components of the Foxhunt HFT system, ensuring sub-50μs
//! latency requirements and high-throughput capability.
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use futures::stream::{FuturesUnordered, StreamExt};
use hdrhistogram::Histogram;
use rand::prelude::*;
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
use std::time::{Duration, Instant};
use tokio::sync::{RwLock, Semaphore};
// Import all necessary modules for testing
use ml::prelude::*;
use risk::prelude::*;
use trading_engine::lockfree::*;
use trading_engine::prelude::*;
use trading_engine::simd::*;
use trading_engine::timing::*;
#[cfg(test)]
mod performance_and_stress_tests {
use super::*;
// ========================================================================
// High-Frequency Trading Performance Tests
// ========================================================================
#[tokio::test]
async fn test_order_processing_latency_target_14ns() {
let mut latency_histogram = Histogram::<u64>::new(3).expect("Failed to create histogram");
let iterations = 100_000;
let mut order_manager = OrderManager::new();
// Warm up
for _ in 0..1000 {
let order = create_test_order();
let _ = order_manager.place_order(order).await;
}
// Benchmark order processing latency
for i in 0..iterations {
let order = create_test_order_with_id(i);
let start_time = rdtsc(); // Hardware timestamp
let result = order_manager.place_order(order).await;
let end_time = rdtsc();
assert!(result.is_ok());
let latency_cycles = end_time - start_time;
let latency_ns = cycles_to_nanoseconds(latency_cycles);
latency_histogram
.record(latency_ns)
.expect("Failed to record latency");
}
// Analyze results
let p50 = latency_histogram.value_at_quantile(0.5);
let p95 = latency_histogram.value_at_quantile(0.95);
let p99 = latency_histogram.value_at_quantile(0.99);
let p99_9 = latency_histogram.value_at_quantile(0.999);
println!("Order Processing Latency Results:");
println!(" P50: {}ns", p50);
println!(" P95: {}ns", p95);
println!(" P99: {}ns", p99);
println!(" P99.9: {}ns", p99_9);
println!(" Min: {}ns", latency_histogram.min());
println!(" Max: {}ns", latency_histogram.max());
println!(" Mean: {:.2}ns", latency_histogram.mean());
// Verify sub-50μs performance (50,000ns)
assert!(p95 < 50_000, "P95 latency {}ns exceeds 50μs target", p95);
assert!(
p99 < 100_000,
"P99 latency {}ns exceeds 100μs threshold",
p99
);
// Verify RDTSC target of 14ns median
assert!(
p50 < 50_000,
"P50 latency {}ns should be much lower for optimized path",
p50
);
}
#[tokio::test]
async fn test_market_data_processing_throughput() {
let market_data_processor = MarketDataProcessor::new();
let num_symbols = 100;
let ticks_per_symbol = 10_000;
let total_ticks = num_symbols * ticks_per_symbol;
let mut symbols = Vec::new();
for i in 0..num_symbols {
symbols.push(format!("SYMBOL{:03}", i));
}
let start_time = Instant::now();
let mut processed_count = 0;
// Generate and process market data at high frequency
for symbol in &symbols {
for tick_id in 0..ticks_per_symbol {
let tick = MarketTick {
symbol: symbol.clone(),
bid: 1.2345 + (tick_id as f64 * 0.0001),
ask: 1.2347 + (tick_id as f64 * 0.0001),
timestamp: rdtsc_timestamp(),
volume: 1000.0,
sequence_number: tick_id as u64,
};
let result = market_data_processor.process_tick(tick).await;
assert!(result.is_ok());
processed_count += 1;
// Verify processing under latency target
if processed_count % 10000 == 0 {
let elapsed = start_time.elapsed();
let throughput = processed_count as f64 / elapsed.as_secs_f64();
assert!(
throughput > 100_000.0,
"Throughput {}ticks/s below 100K target",
throughput
);
}
}
}
let total_time = start_time.elapsed();
let final_throughput = total_ticks as f64 / total_time.as_secs_f64();
println!("Market Data Processing Performance:");
println!(" Total ticks: {}", total_ticks);
println!(" Processing time: {:?}", total_time);
println!(" Throughput: {:.0} ticks/second", final_throughput);
println!(
" Average latency: {:.2}μs",
(total_time.as_micros() as f64) / (total_ticks as f64)
);
// Verify high-frequency requirements
assert!(
final_throughput > 500_000.0,
"Throughput {:.0} below 500K ticks/s requirement",
final_throughput
);
assert!(
total_time.as_millis() < 2000,
"Total processing time {}ms exceeds 2s threshold",
total_time.as_millis()
);
}
#[tokio::test]
async fn test_simd_price_calculations_performance() {
const ARRAY_SIZE: usize = 10_000;
const ITERATIONS: usize = 1_000;
// Generate test price data
let mut prices: Vec<f64> = Vec::with_capacity(ARRAY_SIZE);
let mut rng = thread_rng();
for _ in 0..ARRAY_SIZE {
prices.push(rng.gen_range(1.0..2.0));
}
// Benchmark SIMD vs scalar calculations
let simd_calculator = SIMDPriceCalculator::new();
let scalar_calculator = ScalarPriceCalculator::new();
// SIMD performance test
let simd_start = Instant::now();
let mut simd_results = Vec::new();
for _ in 0..ITERATIONS {
let result = simd_calculator.calculate_moving_average(&prices, 20);
simd_results.push(result);
}
let simd_time = simd_start.elapsed();
// Scalar performance test (for comparison)
let scalar_start = Instant::now();
let mut scalar_results = Vec::new();
for _ in 0..ITERATIONS {
let result = scalar_calculator.calculate_moving_average(&prices, 20);
scalar_results.push(result);
}
let scalar_time = scalar_start.elapsed();
// Verify results are equivalent
assert_eq!(simd_results.len(), scalar_results.len());
for (simd, scalar) in simd_results.iter().zip(scalar_results.iter()) {
for (s_val, sc_val) in simd.iter().zip(scalar.iter()) {
assert!(
(s_val - sc_val).abs() < 1e-10,
"SIMD and scalar results differ"
);
}
}
let simd_throughput = (ITERATIONS * ARRAY_SIZE) as f64 / simd_time.as_secs_f64();
let scalar_throughput = (ITERATIONS * ARRAY_SIZE) as f64 / scalar_time.as_secs_f64();
let speedup = simd_time.as_secs_f64() / scalar_time.as_secs_f64();
println!("SIMD Performance Comparison:");
println!(" SIMD time: {:?}", simd_time);
println!(" Scalar time: {:?}", scalar_time);
println!(" SIMD throughput: {:.0} ops/sec", simd_throughput);
println!(" Scalar throughput: {:.0} ops/sec", scalar_throughput);
println!(" Speedup ratio: {:.2}x", speedup);
// SIMD should be significantly faster
assert!(
simd_throughput > scalar_throughput * 2.0,
"SIMD not providing expected speedup"
);
assert!(
simd_time < scalar_time / 2,
"SIMD performance gain insufficient"
);
}
#[tokio::test]
async fn test_lock_free_structures_performance() {
const NUM_PRODUCERS: usize = 8;
const NUM_CONSUMERS: usize = 4;
const MESSAGES_PER_PRODUCER: usize = 100_000;
let ring_buffer = Arc::new(LockFreeRingBuffer::<OrderEvent>::new(1_000_000));
let start_time = Instant::now();
let total_messages = NUM_PRODUCERS * MESSAGES_PER_PRODUCER;
let processed_count = Arc::new(AtomicU64::new(0));
// Spawn producer tasks
let mut producer_handles = Vec::new();
for producer_id in 0..NUM_PRODUCERS {
let buffer_clone = Arc::clone(&ring_buffer);
let handle = tokio::spawn(async move {
let mut local_count = 0;
for msg_id in 0..MESSAGES_PER_PRODUCER {
let event = OrderEvent {
event_type: OrderEventType::OrderPlaced,
order_id: format!("ORDER_{}_{}", producer_id, msg_id),
timestamp: rdtsc_timestamp(),
symbol: "EURUSD".to_string(),
price: Price::new(1.2345),
quantity: Quantity::new(10000.0),
trader_id: format!("TRADER_{}", producer_id),
};
while !buffer_clone.try_push(event.clone()).is_ok() {
tokio::task::yield_now().await; // Back pressure
}
local_count += 1;
}
local_count
});
producer_handles.push(handle);
}
// Spawn consumer tasks
let mut consumer_handles = Vec::new();
for _consumer_id in 0..NUM_CONSUMERS {
let buffer_clone = Arc::clone(&ring_buffer);
let counter_clone = Arc::clone(&processed_count);
let handle = tokio::spawn(async move {
let mut local_count = 0;
loop {
if let Some(event) = buffer_clone.try_pop() {
// Simulate processing
black_box(event);
local_count += 1;
counter_clone.fetch_add(1, Ordering::Relaxed);
} else {
tokio::task::yield_now().await;
}
// Check if we've processed all messages
if counter_clone.load(Ordering::Relaxed) >= total_messages as u64 {
break;
}
}
local_count
});
consumer_handles.push(handle);
}
// Wait for all producers to complete
let mut total_produced = 0;
for handle in producer_handles {
total_produced += handle.await.expect("Producer task failed");
}
// Wait for all consumers to complete
let mut total_consumed = 0;
for handle in consumer_handles {
total_consumed += handle.await.expect("Consumer task failed");
}
let total_time = start_time.elapsed();
let throughput = total_messages as f64 / total_time.as_secs_f64();
println!("Lock-Free Ring Buffer Performance:");
println!(
" Producers: {}, Consumers: {}",
NUM_PRODUCERS, NUM_CONSUMERS
);
println!(" Total messages: {}", total_messages);
println!(
" Produced: {}, Consumed: {}",
total_produced, total_consumed
);
println!(" Processing time: {:?}", total_time);
println!(" Throughput: {:.0} messages/sec", throughput);
println!(
" Average latency: {:.2}μs",
(total_time.as_micros() as f64) / (total_messages as f64)
);
assert_eq!(total_produced, total_messages);
assert_eq!(total_consumed, total_messages);
assert!(
throughput > 1_000_000.0,
"Lock-free throughput {:.0} below 1M messages/s",
throughput
);
}
// ========================================================================
// ML Model Performance Tests
// ========================================================================
#[tokio::test]
async fn test_ml_model_inference_latency() {
let model_registry = get_global_registry();
// Load test models
let dqn_model = create_mock_dqn_model();
let mamba_model = create_mock_mamba_model();
let tft_model = create_mock_tft_model();
model_registry
.register(Arc::new(dqn_model))
.await
.expect("Failed to register DQN");
model_registry
.register(Arc::new(mamba_model))
.await
.expect("Failed to register MAMBA");
model_registry
.register(Arc::new(tft_model))
.await
.expect("Failed to register TFT");
// Test feature data
let feature_names = vec![
"price_return_1m".to_string(),
"price_return_5m".to_string(),
"volume_ratio".to_string(),
"volatility".to_string(),
"order_book_imbalance".to_string(),
];
let mut rng = thread_rng();
let iterations = 10_000;
let mut latency_histograms = std::collections::HashMap::new();
for model_name in &["DQN", "MAMBA", "TFT"] {
latency_histograms.insert(model_name.to_string(), Histogram::<u64>::new(3).unwrap());
}
// Benchmark inference latency for each model
for model_name in &["DQN", "MAMBA", "TFT"] {
let model = model_registry
.get_model(model_name)
.await
.expect("Model not found");
let histogram = latency_histograms.get_mut(*model_name).unwrap();
for _ in 0..iterations {
// Generate random features
let feature_values: Vec<f64> = (0..feature_names.len())
.map(|_| rng.gen_range(-1.0..1.0))
.collect();
let features = Features::new(feature_values, feature_names.clone());
let start_time = Instant::now();
let prediction = model.predict(&features).await;
let latency = start_time.elapsed();
assert!(prediction.is_ok());
histogram
.record(latency.as_nanos() as u64)
.expect("Failed to record latency");
}
}
// Analyze and report results
for model_name in &["DQN", "MAMBA", "TFT"] {
let histogram = latency_histograms.get(*model_name).unwrap();
let p50 = histogram.value_at_quantile(0.5);
let p95 = histogram.value_at_quantile(0.95);
let p99 = histogram.value_at_quantile(0.99);
println!("{} Model Inference Performance:", model_name);
println!(" P50: {}ns ({:.2}μs)", p50, p50 as f64 / 1000.0);
println!(" P95: {}ns ({:.2}μs)", p95, p95 as f64 / 1000.0);
println!(" P99: {}ns ({:.2}μs)", p99, p99 as f64 / 1000.0);
println!(
" Mean: {:.2}ns ({:.2}μs)",
histogram.mean(),
histogram.mean() / 1000.0
);
// Verify inference latency targets for HFT
assert!(
p50 < 100_000,
"{} P50 latency {}ns exceeds 100μs target",
model_name,
p50
);
assert!(
p95 < 500_000,
"{} P95 latency {}ns exceeds 500μs target",
model_name,
p95
);
}
}
#[tokio::test]
async fn test_ml_model_batch_processing_throughput() {
let model_registry = get_global_registry();
let dqn_model = create_mock_dqn_model();
model_registry
.register(Arc::new(dqn_model))
.await
.expect("Failed to register DQN");
let model = model_registry
.get_model("DQN")
.await
.expect("Model not found");
let feature_names = vec![
"price_return".to_string(),
"volume".to_string(),
"volatility".to_string(),
];
let batch_sizes = vec![1, 10, 50, 100, 500, 1000];
let mut results = Vec::new();
for batch_size in batch_sizes {
let mut batch_features = Vec::new();
let mut rng = thread_rng();
// Create batch of features
for _ in 0..batch_size {
let feature_values: Vec<f64> = (0..feature_names.len())
.map(|_| rng.gen_range(-1.0..1.0))
.collect();
batch_features.push(Features::new(feature_values, feature_names.clone()));
}
let start_time = Instant::now();
let predictions = model.predict_batch(&batch_features).await;
let elapsed = start_time.elapsed();
assert!(predictions.is_ok());
let prediction_results = predictions.unwrap();
assert_eq!(prediction_results.len(), batch_size);
let throughput = batch_size as f64 / elapsed.as_secs_f64();
let latency_per_sample = elapsed.as_micros() as f64 / batch_size as f64;
println!(
"Batch size {}: {:.0} predictions/sec, {:.2}μs per sample",
batch_size, throughput, latency_per_sample
);
results.push((batch_size, throughput, latency_per_sample));
}
// Verify batch processing efficiency
let single_throughput = results[0].1;
let large_batch_throughput = results.last().unwrap().1;
let efficiency_gain = large_batch_throughput / single_throughput;
println!("Batch Processing Efficiency:");
println!(" Single sample: {:.0} predictions/sec", single_throughput);
println!(
" Large batch: {:.0} predictions/sec",
large_batch_throughput
);
println!(" Efficiency gain: {:.2}x", efficiency_gain);
assert!(
efficiency_gain > 10.0,
"Batch processing should provide significant efficiency gains"
);
assert!(
large_batch_throughput > 10_000.0,
"Large batch throughput should exceed 10K predictions/sec"
);
}
// ========================================================================
// Risk Management Performance Tests
// ========================================================================
#[tokio::test]
async fn test_var_calculation_performance() {
let var_calculator = VarCalculator::new(HistoricalSimulationMethod::new(252, 0.95));
let portfolio_size = 1000; // 1000 positions
let history_length = 1000; // 1000 days of history
// Generate test portfolio data
let mut portfolio_returns = Vec::new();
let mut rng = thread_rng();
for _ in 0..portfolio_size {
let mut asset_returns = Vec::new();
for _ in 0..history_length {
asset_returns.push(rng.gen_range(-0.05..0.05)); // ±5% daily returns
}
portfolio_returns.push(asset_returns);
}
let iterations = 1000;
let mut calculation_times = Vec::new();
// Benchmark VaR calculations
for i in 0..iterations {
let start_time = Instant::now();
let var_result = var_calculator
.calculate_portfolio_var(&portfolio_returns)
.await;
let calc_time = start_time.elapsed();
calculation_times.push(calc_time);
assert!(var_result.is_ok());
let var_value = var_result.unwrap();
assert!(var_value.var_1_day > 0.0);
assert!(var_value.var_10_day > 0.0);
assert!(var_value.confidence_level == 0.95);
// Progress reporting
if i % 100 == 0 {
let avg_time: Duration =
calculation_times.iter().sum::<Duration>() / calculation_times.len() as u32;
println!(
"VaR calculation {}: {:.2}ms average",
i,
avg_time.as_secs_f64() * 1000.0
);
}
}
let total_time: Duration = calculation_times.iter().sum();
let average_time = total_time / iterations as u32;
let throughput = iterations as f64 / total_time.as_secs_f64();
println!("VaR Calculation Performance:");
println!(" Portfolio size: {} positions", portfolio_size);
println!(" History length: {} days", history_length);
println!(" Iterations: {}", iterations);
println!(
" Average calculation time: {:.2}ms",
average_time.as_secs_f64() * 1000.0
);
println!(" Throughput: {:.1} calculations/sec", throughput);
// Performance requirements for real-time risk management
assert!(
average_time.as_millis() < 100,
"VaR calculation time {}ms exceeds 100ms target",
average_time.as_millis()
);
assert!(
throughput > 10.0,
"VaR throughput {:.1} below 10 calc/sec requirement",
throughput
);
}
#[tokio::test]
async fn test_position_limit_checking_performance() {
let limit_checker = PositionLimitChecker::new();
// Setup position limits
let mut symbol_limits = std::collections::HashMap::new();
let mut trader_limits = std::collections::HashMap::new();
for i in 0..1000 {
symbol_limits.insert(format!("SYMBOL{:03}", i), Quantity::new(100_000.0));
}
for i in 0..100 {
trader_limits.insert(format!("TRADER{:03}", i), Quantity::new(1_000_000.0));
}
limit_checker.set_symbol_limits(symbol_limits).await;
limit_checker.set_trader_limits(trader_limits).await;
// Generate test positions
let mut test_positions = Vec::new();
let mut rng = thread_rng();
for i in 0..10_000 {
let position = PositionCheckRequest {
trader_id: format!("TRADER{:03}", i % 100),
symbol: format!("SYMBOL{:03}", i % 1000),
side: if rng.gen_bool(0.5) {
OrderSide::Buy
} else {
OrderSide::Sell
},
quantity: Quantity::new(rng.gen_range(1000.0..50_000.0)),
price: Price::new(rng.gen_range(1.0..2.0)),
};
test_positions.push(position);
}
let start_time = Instant::now();
let mut check_count = 0;
let mut approved_count = 0;
let mut rejected_count = 0;
// Benchmark position limit checking
for position in test_positions {
let result = limit_checker.check_position_limits(&position).await;
assert!(result.is_ok());
let check_result = result.unwrap();
if check_result.approved {
approved_count += 1;
} else {
rejected_count += 1;
}
check_count += 1;
}
let total_time = start_time.elapsed();
let throughput = check_count as f64 / total_time.as_secs_f64();
let average_latency = total_time.as_micros() as f64 / check_count as f64;
println!("Position Limit Checking Performance:");
println!(" Total checks: {}", check_count);
println!(" Approved: {}", approved_count);
println!(" Rejected: {}", rejected_count);
println!(" Total time: {:?}", total_time);
println!(" Throughput: {:.0} checks/sec", throughput);
println!(" Average latency: {:.2}μs", average_latency);
// High-frequency trading requirements
assert!(
throughput > 100_000.0,
"Position check throughput {:.0} below 100K/sec requirement",
throughput
);
assert!(
average_latency < 10.0,
"Average position check latency {:.2}μs above 10μs target",
average_latency
);
}
// ========================================================================
// Stress Testing - System Under Load
// ========================================================================
#[tokio::test]
async fn test_concurrent_order_processing_stress() {
let order_manager = Arc::new(OrderManager::new());
let concurrent_traders = 100;
let orders_per_trader = 1_000;
let total_orders = concurrent_traders * orders_per_trader;
let start_time = Instant::now();
let success_counter = Arc::new(AtomicU64::new(0));
let error_counter = Arc::new(AtomicU64::new(0));
// Launch concurrent trading sessions
let mut trader_handles = Vec::new();
for trader_id in 0..concurrent_traders {
let manager_clone = Arc::clone(&order_manager);
let success_clone = Arc::clone(&success_counter);
let error_clone = Arc::clone(&error_counter);
let handle = tokio::spawn(async move {
let trader_name = format!("STRESS_TRADER_{:03}", trader_id);
let mut local_success = 0;
let mut local_errors = 0;
for order_id in 0..orders_per_trader {
let order = Order {
order_id: format!("{}_{:06}", trader_name, order_id),
symbol: format!("SYMBOL{:02}", order_id % 10),
side: if order_id % 2 == 0 {
OrderSide::Buy
} else {
OrderSide::Sell
},
order_type: OrderType::Limit,
quantity: Quantity::new((order_id as f64 + 1.0) * 1000.0),
price: Some(Price::new(1.0 + (order_id as f64 * 0.0001))),
time_in_force: TimeInForce::GoodTillCancel,
trader_id: trader_name.clone(),
timestamp: rdtsc_timestamp(),
};
match manager_clone.place_order(order).await {
Ok(_) => {
local_success += 1;
success_clone.fetch_add(1, Ordering::Relaxed);
}
Err(_) => {
local_errors += 1;
error_clone.fetch_add(1, Ordering::Relaxed);
}
}
// Simulate realistic trading pace
if order_id % 10 == 0 {
tokio::task::yield_now().await;
}
}
(local_success, local_errors)
});
trader_handles.push(handle);
}
// Wait for all traders to complete
let mut total_success = 0;
let mut total_errors = 0;
for handle in trader_handles {
let (success, errors) = handle.await.expect("Trader task failed");
total_success += success;
total_errors += errors;
}
let total_time = start_time.elapsed();
let throughput = total_success as f64 / total_time.as_secs_f64();
let success_rate = total_success as f64 / total_orders as f64;
println!("Concurrent Order Processing Stress Test:");
println!(" Concurrent traders: {}", concurrent_traders);
println!(" Orders per trader: {}", orders_per_trader);
println!(" Total orders: {}", total_orders);
println!(" Successful orders: {}", total_success);
println!(" Failed orders: {}", total_errors);
println!(" Success rate: {:.2}%", success_rate * 100.0);
println!(" Total time: {:?}", total_time);
println!(" Throughput: {:.0} orders/sec", throughput);
// Stress test acceptance criteria
assert!(
success_rate > 0.95,
"Success rate {:.2}% below 95% requirement",
success_rate * 100.0
);
assert!(
throughput > 50_000.0,
"Throughput {:.0} below 50K orders/sec requirement",
throughput
);
assert_eq!(total_success + total_errors, total_orders);
}
#[tokio::test]
async fn test_memory_pressure_handling() {
// Test system behavior under memory pressure
let large_allocation_size = 1_000_000; // 1M elements
let num_allocations = 100;
let mut allocations = Vec::new();
let start_memory = get_memory_usage();
println!("Starting memory pressure test...");
println!("Initial memory usage: {:.2}MB", start_memory / 1_000_000.0);
// Gradually increase memory pressure
for i in 0..num_allocations {
let allocation: Vec<f64> = (0..large_allocation_size)
.map(|j| (i * large_allocation_size + j) as f64)
.collect();
allocations.push(allocation);
// Test system responsiveness under memory pressure
if i % 10 == 0 {
let current_memory = get_memory_usage();
println!(
"Allocation {}: {:.2}MB memory usage",
i,
current_memory / 1_000_000.0
);
// Verify system can still process orders
let order_manager = OrderManager::new();
let test_order = create_test_order();
let start_time = Instant::now();
let result = order_manager.place_order(test_order).await;
let latency = start_time.elapsed();
assert!(
result.is_ok(),
"Order processing failed under memory pressure at allocation {}",
i
);
assert!(
latency.as_millis() < 100,
"Order latency {}ms too high under memory pressure",
latency.as_millis()
);
}
}
let peak_memory = get_memory_usage();
println!("Peak memory usage: {:.2}MB", peak_memory / 1_000_000.0);
// Clean up and verify memory is released
allocations.clear();
// Force garbage collection
for _ in 0..10 {
tokio::task::yield_now().await;
}
let final_memory = get_memory_usage();
println!("Final memory usage: {:.2}MB", final_memory / 1_000_000.0);
// Verify memory cleanup
let memory_recovered = peak_memory - final_memory;
let recovery_ratio = memory_recovered / (peak_memory - start_memory);
println!(
"Memory recovery: {:.2}MB ({:.1}%)",
memory_recovered / 1_000_000.0,
recovery_ratio * 100.0
);
assert!(
recovery_ratio > 0.8,
"Memory recovery {:.1}% below 80% threshold",
recovery_ratio * 100.0
);
}
#[tokio::test]
async fn test_network_latency_resilience() {
// Test system behavior under various network conditions
let network_simulator = NetworkSimulator::new();
let order_manager = OrderManager::new();
let latency_scenarios = vec![
("Low latency", Duration::from_micros(100)),
("Normal latency", Duration::from_millis(5)),
("High latency", Duration::from_millis(50)),
("Very high latency", Duration::from_millis(200)),
];
for (scenario_name, base_latency) in latency_scenarios {
println!(
"Testing {} scenario ({}μs)",
scenario_name,
base_latency.as_micros()
);
network_simulator.set_base_latency(base_latency).await;
let num_orders = 1000;
let mut successful_orders = 0;
let mut failed_orders = 0;
let mut latencies = Vec::new();
for i in 0..num_orders {
let order = create_test_order_with_id(i);
let start_time = Instant::now();
let result = order_manager
.place_order_with_network(order, &network_simulator)
.await;
let total_latency = start_time.elapsed();
match result {
Ok(_) => {
successful_orders += 1;
latencies.push(total_latency);
}
Err(_) => {
failed_orders += 1;
}
}
}
let success_rate = successful_orders as f64 / num_orders as f64;
let avg_latency = latencies.iter().sum::<Duration>() / latencies.len() as u32;
let p95_latency = latencies
.iter()
.nth((latencies.len() as f64 * 0.95) as usize)
.unwrap_or(&Duration::ZERO);
println!(" Success rate: {:.1}%", success_rate * 100.0);
println!(
" Average latency: {:.2}ms",
avg_latency.as_secs_f64() * 1000.0
);
println!(" P95 latency: {:.2}ms", p95_latency.as_secs_f64() * 1000.0);
// Verify resilience requirements
match scenario_name {
"Low latency" | "Normal latency" => {
assert!(
success_rate > 0.99,
"{} success rate {:.1}% below 99%",
scenario_name,
success_rate * 100.0
);
}
"High latency" => {
assert!(
success_rate > 0.95,
"{} success rate {:.1}% below 95%",
scenario_name,
success_rate * 100.0
);
}
"Very high latency" => {
assert!(
success_rate > 0.90,
"{} success rate {:.1}% below 90%",
scenario_name,
success_rate * 100.0
);
}
_ => {}
}
}
}
// ========================================================================
// End-to-End Performance Tests
// ========================================================================
#[tokio::test]
async fn test_full_trading_pipeline_performance() {
// Complete trading pipeline: Market Data -> ML Prediction -> Risk Check -> Order Placement
let market_data_processor = MarketDataProcessor::new();
let ml_model = create_mock_dqn_model();
let risk_manager = RiskManager::new();
let order_manager = OrderManager::new();
let num_iterations = 10_000;
let mut pipeline_latencies = Vec::new();
let mut rng = thread_rng();
println!("Starting full trading pipeline performance test...");
for i in 0..num_iterations {
let pipeline_start = Instant::now();
// Step 1: Process market data tick
let market_tick = MarketTick {
symbol: "EURUSD".to_string(),
bid: 1.2345 + rng.gen_range(-0.01..0.01),
ask: 1.2347 + rng.gen_range(-0.01..0.01),
timestamp: rdtsc_timestamp(),
volume: rng.gen_range(1000.0..10000.0),
sequence_number: i as u64,
};
let processed_data = market_data_processor
.process_tick(market_tick)
.await
.expect("Market data processing failed");
// Step 2: ML prediction
let features = Features::from_market_data(&processed_data);
let prediction = ml_model
.predict(&features)
.await
.expect("ML prediction failed");
// Step 3: Risk management check
if prediction.confidence > 0.7 {
let position_request = PositionCheckRequest {
trader_id: "PIPELINE_TRADER".to_string(),
symbol: "EURUSD".to_string(),
side: if prediction.prediction_values[0] > 0.5 {
OrderSide::Buy
} else {
OrderSide::Sell
},
quantity: Quantity::new(10000.0),
price: Price::new(processed_data.mid_price),
};
let risk_result = risk_manager
.check_position_limits(&position_request)
.await
.expect("Risk check failed");
// Step 4: Order placement (if approved)
if risk_result.approved {
let order = Order {
order_id: format!("PIPELINE_ORDER_{:06}", i),
symbol: "EURUSD".to_string(),
side: position_request.side,
order_type: OrderType::Market,
quantity: position_request.quantity,
price: Some(position_request.price),
time_in_force: TimeInForce::ImmediateOrCancel,
trader_id: position_request.trader_id,
timestamp: rdtsc_timestamp(),
};
let _order_result = order_manager
.place_order(order)
.await
.expect("Order placement failed");
}
}
let pipeline_latency = pipeline_start.elapsed();
pipeline_latencies.push(pipeline_latency);
// Progress reporting
if i % 1000 == 0 {
let avg_latency: Duration =
pipeline_latencies.iter().sum::<Duration>() / pipeline_latencies.len() as u32;
println!(
"Pipeline iteration {}: {:.2}μs average latency",
i,
avg_latency.as_micros()
);
}
}
// Analyze pipeline performance
pipeline_latencies.sort();
let p50_latency = pipeline_latencies[pipeline_latencies.len() / 2];
let p95_latency = pipeline_latencies[(pipeline_latencies.len() as f64 * 0.95) as usize];
let p99_latency = pipeline_latencies[(pipeline_latencies.len() as f64 * 0.99) as usize];
let max_latency = pipeline_latencies.last().unwrap();
let avg_latency: Duration =
pipeline_latencies.iter().sum::<Duration>() / pipeline_latencies.len() as u32;
println!("Full Trading Pipeline Performance Results:");
println!(" Iterations: {}", num_iterations);
println!(" P50 latency: {:.2}μs", p50_latency.as_micros());
println!(" P95 latency: {:.2}μs", p95_latency.as_micros());
println!(" P99 latency: {:.2}μs", p99_latency.as_micros());
println!(" Max latency: {:.2}μs", max_latency.as_micros());
println!(" Average latency: {:.2}μs", avg_latency.as_micros());
// Verify HFT latency requirements
assert!(
p50_latency.as_micros() < 50,
"P50 pipeline latency {}μs exceeds 50μs target",
p50_latency.as_micros()
);
assert!(
p95_latency.as_micros() < 200,
"P95 pipeline latency {}μs exceeds 200μs target",
p95_latency.as_micros()
);
assert!(
p99_latency.as_micros() < 500,
"P99 pipeline latency {}μs exceeds 500μs target",
p99_latency.as_micros()
);
}
}
// ============================================================================
// Test Utilities and Mock Implementations
// ============================================================================
// Hardware timestamp functions
fn rdtsc() -> u64 {
#[cfg(target_arch = "x86_64")]
{
unsafe { std::arch::x86_64::_rdtsc() }
}
#[cfg(not(target_arch = "x86_64"))]
{
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
}
}
fn cycles_to_nanoseconds(cycles: u64) -> u64 {
// Approximate conversion for 3GHz CPU
// In production, this would be calibrated based on actual CPU frequency
cycles / 3
}
fn rdtsc_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
}
// Memory usage utility
fn get_memory_usage() -> u64 {
// Simplified memory usage calculation
// In production, this would use platform-specific APIs
std::process::Command::new("ps")
.arg("-o")
.arg("rss=")
.arg("-p")
.arg(&std::process::id().to_string())
.output()
.ok()
.and_then(|output| {
String::from_utf8(output.stdout)
.ok()?
.trim()
.parse::<u64>()
.ok()
})
.map(|kb| kb * 1024) // Convert KB to bytes
.unwrap_or(0)
}
// Test data creation utilities
fn create_test_order() -> Order {
Order {
order_id: uuid::Uuid::new_v4().to_string(),
symbol: "EURUSD".to_string(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
quantity: Quantity::new(10000.0),
price: Some(Price::new(1.2345)),
time_in_force: TimeInForce::GoodTillCancel,
trader_id: "TEST_TRADER".to_string(),
timestamp: rdtsc_timestamp(),
}
}
fn create_test_order_with_id(id: usize) -> Order {
Order {
order_id: format!("TEST_ORDER_{:06}", id),
symbol: "EURUSD".to_string(),
side: if id % 2 == 0 {
OrderSide::Buy
} else {
OrderSide::Sell
},
order_type: OrderType::Limit,
quantity: Quantity::new((id as f64 + 1.0) * 1000.0),
price: Some(Price::new(1.2345 + (id as f64 * 0.0001))),
time_in_force: TimeInForce::GoodTillCancel,
trader_id: format!("TRADER_{:03}", id % 100),
timestamp: rdtsc_timestamp(),
}
}
// Mock implementations for performance testing
pub struct MockDQNModel {
model_name: String,
input_size: usize,
output_size: usize,
}
impl MockDQNModel {
pub fn new() -> Self {
Self {
model_name: "DQN".to_string(),
input_size: 5,
output_size: 3,
}
}
}
impl MLModel for MockDQNModel {
fn model_name(&self) -> &str {
&self.model_name
}
fn model_type(&self) -> ModelType {
ModelType::DQN
}
async fn predict(&self, features: &Features) -> Result<ModelPrediction, MLError> {
// Simulate computation time
tokio::task::yield_now().await;
// Mock prediction calculation
let prediction_values = vec![0.6, 0.3, 0.1]; // Mock Q-values
Ok(ModelPrediction {
prediction_values,
confidence: 0.85,
model_name: self.model_name.clone(),
timestamp: rdtsc_timestamp(),
})
}
async fn predict_batch(&self, features: &[Features]) -> Result<Vec<ModelPrediction>, MLError> {
let mut predictions = Vec::new();
for feature_set in features {
predictions.push(self.predict(feature_set).await?);
}
Ok(predictions)
}
}
fn create_mock_dqn_model() -> MockDQNModel {
MockDQNModel::new()
}
fn create_mock_mamba_model() -> MockMAMBAModel {
MockMAMBAModel::new()
}
fn create_mock_tft_model() -> MockTFTModel {
MockTFTModel::new()
}
// Additional mock models and utilities would be implemented similarly...
// This demonstrates the comprehensive performance testing framework needed for HFT systems