//! HFT Specific Test Utilities //! //! Specialized testing utilities for high frequency trading components //! with focus on performance, latency, and financial accuracy. use super::test_safety::{TestError, TestResult}; use ::rust_decimal::Decimal; use chrono::{DateTime, Utc}; use std::collections::VecDeque; use std::time::{Duration, Instant}; /// Performance measurement utilities for HFT testing pub mod performance { use super::*; /// Latency measurement with statistical analysis /// /// Collects latency measurements and provides statistical analysis /// including average, percentiles, min, and max values. #[derive(Debug)] pub struct LatencyMeasurement { /// Collection of latency measurements measurements: VecDeque, /// Maximum number of samples to retain max_samples: usize, } impl LatencyMeasurement { /// Create a new latency measurement collector /// /// # Arguments /// * `max_samples` - Maximum number of samples to retain in memory pub fn new(max_samples: usize) -> Self { Self { measurements: VecDeque::with_capacity(max_samples), max_samples, } } /// Record a new latency measurement /// /// If the maximum number of samples is reached, the oldest sample is removed. /// /// # Arguments /// * `latency` - The latency duration to record pub fn record(&mut self, latency: Duration) { if self.measurements.len() >= self.max_samples { self.measurements.pop_front(); } self.measurements.push_back(latency); } /// Calculate the average latency across all recorded measurements /// /// # Returns /// * `Some(Duration)` - The average latency if measurements exist /// /// * `None` - If no measurements have been recorded pub fn average(&self) -> Option { if self.measurements.is_empty() { return None; } let total_nanos: u64 = self.measurements.iter().map(|d| d.as_nanos() as u64).sum(); Some(Duration::from_nanos( total_nanos / self.measurements.len() as u64, )) } /// Calculate the specified percentile latency /// /// # Arguments /// * `p` - Percentile value between 0.0 and 1.0 (e.g., 0.99 for 99th percentile) /// /// # Returns /// * `Some(Duration)` - The percentile latency if measurements exist /// /// * `None` - If no measurements have been recorded pub fn percentile(&self, p: f64) -> Option { if self.measurements.is_empty() { return None; } let mut sorted: Vec = self.measurements.iter().copied().collect(); sorted.sort(); let index = ((sorted.len() as f64 - 1.0) * p).round() as usize; sorted.get(index).copied() } /// Get the maximum latency from all recorded measurements /// /// # Returns /// * `Some(Duration)` - The maximum latency if measurements exist /// /// * `None` - If no measurements have been recorded pub fn max(&self) -> Option { self.measurements.iter().max().copied() } /// Get the minimum latency from all recorded measurements /// /// # Returns /// * `Some(Duration)` - The minimum latency if measurements exist /// /// * `None` - If no measurements have been recorded pub fn min(&self) -> Option { self.measurements.iter().min().copied() } } /// Measure operation latency with safety checks /// /// Executes an operation and measures its execution time with proper error handling. /// /// # Arguments /// * `operation` - The async operation to measure /// /// * `context` - Description of the operation for error reporting /// /// # Returns /// * `Ok((T, Duration))` - The operation result and its execution time /// /// * `Err(TestError)` - If the operation fails pub async fn measure_latency( operation: F, context: &str, ) -> TestResult<(T, Duration)> where F: FnOnce() -> Fut, Fut: std::future::Future>, { let start = Instant::now(); let result = operation().await?; let latency = start.elapsed(); println!("Operation '{}' completed in {:?}", context, latency); Ok((result, latency)) } /// Validate HFT latency requirements (sub-microsecond) /// /// Checks if a measured latency meets HFT performance requirements. /// /// # Arguments /// * `latency` - The measured latency to validate /// /// * `max_allowed` - Maximum allowed latency for HFT operations /// * `context` - Description of the operation for error reporting /// /// # Returns /// * `Ok(())` - If latency meets requirements /// /// * `Err(TestError)` - If latency exceeds maximum allowed pub fn validate_hft_latency( latency: Duration, max_allowed: Duration, context: &str, ) -> TestResult<()> { if latency > max_allowed { return Err(TestError::assertion(format!( "{}: Latency {:?} exceeds HFT requirement of {:?}", context, latency, max_allowed ))); } Ok(()) } /// Benchmark operation with warmup and multiple iterations /// /// Performs a comprehensive benchmark with warmup phase and statistical measurement. /// /// # Arguments /// * `operation` - The operation to benchmark (must be cloneable for multiple runs) /// /// * `warmup_iterations` - Number of warmup iterations to run before measurement /// * `measurement_iterations` - Number of iterations to measure for statistics /// /// * `context` - Description of the operation for error reporting /// /// # Returns /// * `Ok(LatencyMeasurement)` - Statistical analysis of the benchmark results /// /// * `Err(TestError)` - If any iteration fails pub async fn benchmark_operation( operation: F, warmup_iterations: usize, measurement_iterations: usize, context: &str, ) -> TestResult where F: Fn() -> Fut + Clone, Fut: std::future::Future>, { // Warmup phase for _ in 0..warmup_iterations { operation().await.map_err(|e| { TestError::setup(format!("Benchmark warmup failed for {}: {}", context, e)) })?; } // Measurement phase let mut measurements = LatencyMeasurement::new(measurement_iterations); for _ in 0..measurement_iterations { let (_, latency) = measure_latency(operation.clone(), context).await?; measurements.record(latency); } Ok(measurements) } } /// Financial accuracy testing utilities pub mod financial { use super::*; /// Precision threshold for financial calculations /// /// This constant defines the maximum acceptable difference between /// two financial amounts for them to be considered equal. pub const FINANCIAL_PRECISION: f64 = 1e-8; /// Safe comparison of financial amounts with precision handling /// /// Compares two Decimal values for equality within the financial precision threshold. /// /// This is essential for testing financial calculations where floating-point precision /// errors can cause exact equality comparisons to fail. /// /// # Arguments /// * `left` - First decimal value to compare /// /// * `right` - Second decimal value to compare /// * `context` - Description of the comparison for error reporting /// /// # Returns /// * `Ok(())` - If values are equal within precision /// /// * `Err(TestError)` - If values differ beyond acceptable precision pub fn assert_decimal_eq(left: Decimal, right: Decimal, context: &str) -> TestResult<()> { let diff = (left - right).abs(); let precision_decimal = Decimal::try_from(FINANCIAL_PRECISION) .map_err(|_| TestError::setup("Failed to create precision decimal"))?; if diff > precision_decimal { return Err(TestError::assertion(format!( "{}: Financial amounts not equal within precision\n left: {}\n right: {}\n diff: {}\n max_allowed: {}", context, left, right, diff, precision_decimal ))); } Ok(()) } /// Generate test prices with realistic market behavior /// /// Creates a sequence of realistic price movements for testing market data processing. /// /// Uses random walk with specified volatility to simulate real market conditions. /// /// # Arguments /// * `base_price` - Starting price for the sequence /// /// * `count` - Number of price points to generate /// * `volatility` - Volatility as a percentage (e.g., 0.02 for 2% volatility) /// /// # Returns /// * `Ok(Vec)` - Sequence of generated prices /// /// * `Err(TestError)` - If price generation fails pub fn generate_realistic_prices( base_price: Decimal, count: usize, volatility: f64, ) -> TestResult> { use rand::Rng; let mut rng = rand::thread_rng(); let mut prices = Vec::with_capacity(count); let mut current_price = base_price; for _ in 0..count { let change_pct = rng.gen_range(-volatility..volatility); let change_decimal = Decimal::try_from(change_pct / 100.0) .map_err(|_| TestError::setup("Failed to create change decimal"))?; let change = current_price * change_decimal; current_price = (current_price + change).max(Decimal::try_from(0.01).unwrap_or_default()); prices.push(current_price); } Ok(prices) } } /// Market data testing utilities pub mod market_data { use super::*; /// Mock market data tick for testing /// /// Represents a single market data tick with price, volume, and optional bid/ask spread. /// /// Used for testing market data processing and trading algorithms. #[derive(Debug, Clone)] pub struct TestTick { /// Trading symbol (e.g., "BTCUSD", "AAPL") pub symbol: String, /// UTC timestamp of the tick pub timestamp: DateTime, /// Last traded price pub price: Decimal, /// Volume traded at this price pub volume: Decimal, /// Best bid price (optional) pub bid: Option, /// Best ask price (optional) pub ask: Option, } impl TestTick { /// Create a new test tick with basic price and volume /// /// # Arguments /// * `symbol` - Trading symbol for this tick /// /// * `price` - Last traded price /// * `volume` - Volume traded pub fn new(symbol: &str, price: Decimal, volume: Decimal) -> Self { Self { symbol: symbol.to_string(), timestamp: Utc::now(), price, volume, bid: None, ask: None, } } /// Add bid/ask spread to the tick /// /// # Arguments /// * `bid` - Best bid price /// /// * `ask` - Best ask price /// /// # Returns /// /// Self with bid/ask prices set pub fn with_spread(mut self, bid: Decimal, ask: Decimal) -> Self { self.bid = Some(bid); self.ask = Some(ask); self } } /// Generate realistic market data stream for testing /// /// Provides a configurable market data generator that produces realistic /// price movements and tick patterns for testing trading algorithms. #[derive(Debug)] pub struct TestMarketDataStream { /// List of symbols to generate data for symbols: Vec, /// Base prices for each symbol base_prices: std::collections::HashMap, /// Tick generation rate in Hz tick_rate_hz: u64, } impl TestMarketDataStream { /// Create a new market data stream generator /// /// # Arguments /// * `symbols` - List of trading symbols to generate data for /// /// * `tick_rate_hz` - Rate of tick generation in Hz pub fn new(symbols: Vec, tick_rate_hz: u64) -> Self { let mut base_prices = std::collections::HashMap::new(); for symbol in &symbols { // Set realistic base prices for different asset types let base_price = match symbol.as_str() { s if s.contains("USD") => Decimal::try_from(1.2).unwrap_or_default(), s if s.starts_with("BTC") => Decimal::try_from(45000.0).unwrap_or_default(), _ => Decimal::try_from(100.0).unwrap_or_default(), // Default stock price }; base_prices.insert(symbol.clone(), base_price); } Self { symbols, base_prices, tick_rate_hz, } } /// Generate market data ticks for the specified duration /// /// Creates realistic market data with small price variations and consistent timing. /// /// # Arguments /// * `duration` - How long to generate data for /// /// # Returns /// * `Ok(Vec)` - Generated market data ticks /// /// * `Err(TestError)` - If tick generation fails pub async fn generate_ticks(&self, duration: Duration) -> TestResult> { let total_ticks = (duration.as_secs_f64() * self.tick_rate_hz as f64) as usize; let mut ticks = Vec::with_capacity(total_ticks); let tick_interval = Duration::from_nanos(1_000_000_000 / self.tick_rate_hz); for i in 0..total_ticks { for symbol in &self.symbols { let base_price = self.base_prices.get(symbol).ok_or_else(|| { TestError::setup(format!("No base price for symbol {}", symbol)) })?; // Add small random variation let variation = (i as f64 * 0.001).sin() * 0.001; // Small deterministic variation let variation_decimal = Decimal::try_from(variation).unwrap_or_default(); let price = *base_price * (Decimal::ONE + variation_decimal); let volume = Decimal::from((100 + i) % 1000); let tick = TestTick::new(symbol, price, volume); ticks.push(tick); } // Simulate real-time tick generation if i % 100 == 0 { tokio::time::sleep(tick_interval).await; } } Ok(ticks) } } /// Validate market data consistency /// /// Performs comprehensive validation of a tick sequence including: /// - Timestamp ordering /// /// - Realistic price movements (no more than 50% jumps) /// - Data integrity checks /// /// # Arguments /// * `ticks` - Sequence of ticks to validate /// /// * `context` - Description for error reporting /// /// # Returns /// * `Ok(())` - If all validation checks pass /// /// * `Err(TestError)` - If any validation fails pub fn validate_tick_sequence(ticks: &[TestTick], context: &str) -> TestResult<()> { if ticks.is_empty() { return Err(TestError::assertion(format!( "{}: Empty tick sequence", context ))); } // Check timestamp ordering for window in ticks.windows(2) { if window[1].timestamp < window[0].timestamp { return Err(TestError::assertion(format!( "{}: Timestamps out of order: {} -> {}", context, window[0].timestamp, window[1].timestamp ))); } } // Check for unrealistic price movements (>50% in single tick) for window in ticks.windows(2) { if window[0].symbol == window[1].symbol { let price_change = ((window[1].price - window[0].price) / window[0].price).abs(); let fifty_percent = Decimal::try_from(0.5).unwrap_or_default(); if price_change > fifty_percent { return Err(TestError::assertion(format!( "{}: Unrealistic price movement in {}: {} -> {} ({:.2}%)", context, window[0].symbol, window[0].price, window[1].price, price_change * Decimal::from(100) ))); } } } Ok(()) } } /// Order management testing utilities pub mod orders { use super::*; // Import order types from common crate pub use common::trading::{OrderSide, OrderType}; pub use common::types::OrderStatus; /// Mock order for testing /// /// Represents a trading order with full lifecycle tracking including /// partial fills, status transitions, and average fill price calculation. #[derive(Debug, Clone)] pub struct TestOrder { /// Unique order identifier pub id: String, /// Trading symbol (e.g., "AAPL", "BTCUSD") pub symbol: String, /// Order side (Buy or Sell) pub side: OrderSide, /// Total order quantity pub quantity: Decimal, /// Limit price (None for market orders) pub price: Option, /// Order type (Market, Limit, etc.) pub order_type: OrderType, /// Current order status pub status: OrderStatus, /// Timestamp when order was created pub created_at: DateTime, /// Quantity that has been filled pub filled_quantity: Decimal, /// Volume-weighted average fill price pub avg_fill_price: Option, } impl TestOrder { /// Create a new market order /// /// # Arguments /// * `symbol` - Trading symbol for the order /// /// * `side` - Order side (Buy or Sell) /// * `quantity` - Order quantity /// /// # Returns /// /// A new market order with New status pub fn new_market_order(symbol: &str, side: OrderSide, quantity: Decimal) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), symbol: symbol.to_string(), side, quantity, price: None, order_type: OrderType::Market, status: OrderStatus::New, created_at: Utc::now(), filled_quantity: Decimal::ZERO, avg_fill_price: None, } } /// Create a new limit order /// /// # Arguments /// * `symbol` - Trading symbol for the order /// /// * `side` - Order side (Buy or Sell) /// * `quantity` - Order quantity /// /// * `price` - Limit price /// /// # Returns /// /// A new limit order with New status pub fn new_limit_order( symbol: &str, side: OrderSide, quantity: Decimal, price: Decimal, ) -> Self { Self { id: uuid::Uuid::new_v4().to_string(), symbol: symbol.to_string(), side, quantity, price: Some(price), order_type: OrderType::Limit, status: OrderStatus::New, created_at: Utc::now(), filled_quantity: Decimal::ZERO, avg_fill_price: None, } } /// Simulate a fill event for this order /// /// Updates the filled quantity, average fill price, and order status. /// /// Validates that fills don't exceed the order quantity. /// /// # Arguments /// * `fill_quantity` - Quantity being filled /// /// * `fill_price` - Price at which the fill occurred /// /// # Returns /// * `Ok(())` - If the fill is valid and processed /// /// * `Err(TestError)` - If the fill would exceed order quantity or is invalid pub fn simulate_fill( &mut self, fill_quantity: Decimal, fill_price: Decimal, ) -> TestResult<()> { if fill_quantity <= Decimal::ZERO { return Err(TestError::assertion("Fill quantity must be positive")); } if self.filled_quantity + fill_quantity > self.quantity { return Err(TestError::assertion(format!( "Fill quantity {} would exceed order quantity {}", fill_quantity, self.quantity ))); } // Update average fill price if self.filled_quantity == Decimal::ZERO { self.avg_fill_price = Some(fill_price); } else { let current_avg = self.avg_fill_price.unwrap_or_default(); let total_value = current_avg * self.filled_quantity + fill_price * fill_quantity; let new_total_quantity = self.filled_quantity + fill_quantity; self.avg_fill_price = Some(total_value / new_total_quantity); } self.filled_quantity += fill_quantity; // Update status if self.filled_quantity == self.quantity { self.status = OrderStatus::Filled; } else { self.status = OrderStatus::PartiallyFilled; } Ok(()) } } /// Validate order lifecycle transitions /// /// Performs comprehensive validation of order state consistency: /// - Filled quantity doesn't exceed order quantity /// /// - Order status matches fill state /// - Average fill prices are reasonable /// /// # Arguments /// * `orders` - Collection of orders to validate /// /// * `context` - Description for error reporting /// /// # Returns /// * `Ok(())` - If all orders pass validation /// /// * `Err(TestError)` - If any order has inconsistent state pub fn validate_order_lifecycle(orders: &[TestOrder], context: &str) -> TestResult<()> { for order in orders { // Validate filled quantity doesn't exceed order quantity if order.filled_quantity > order.quantity { return Err(TestError::assertion(format!( "{}: Order {} filled quantity {} exceeds order quantity {}", context, order.id, order.filled_quantity, order.quantity ))); } // Validate status consistency match order.status { OrderStatus::Filled if order.filled_quantity != order.quantity => { return Err(TestError::assertion(format!( "{}: Order {} marked as filled but quantities don't match: {} != {}", context, order.id, order.filled_quantity, order.quantity ))); }, OrderStatus::PartiallyFilled if order.filled_quantity == Decimal::ZERO => { return Err(TestError::assertion(format!( "{}: Order {} marked as partially filled but no quantity filled", context, order.id ))); }, _ => {}, } } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_latency_measurement() -> TestResult<()> { let mut measurement = performance::LatencyMeasurement::new(100); for i in 1..=10 { measurement.record(Duration::from_nanos(i * 1000)); } let avg = measurement.average().expect("Should have average"); assert!(avg > Duration::from_nanos(5000)); assert!(avg < Duration::from_nanos(6000)); Ok(()) } #[tokio::test] async fn test_financial_precision() -> TestResult<()> { let price1 = Decimal::try_from(100.12345678).unwrap_or_default(); let price2 = Decimal::try_from(100.12345679).unwrap_or_default(); // Should pass within precision financial::assert_decimal_eq(price1, price2, "price comparison")?; Ok(()) } #[tokio::test] async fn test_market_data_generation() -> TestResult<()> { let stream = market_data::TestMarketDataStream::new( vec!["AAPL".to_string(), "GOOGL".to_string()], 1000, // 1000 Hz ); let ticks = stream.generate_ticks(Duration::from_millis(10)).await?; assert!(!ticks.is_empty()); market_data::validate_tick_sequence(&ticks, "test market data")?; Ok(()) } #[tokio::test] async fn test_order_simulation() -> TestResult<()> { let mut order = orders::TestOrder::new_limit_order( "AAPL", orders::OrderSide::Buy, Decimal::from(100), Decimal::try_from(150.0).unwrap_or_default(), ); // Simulate partial fill order.simulate_fill( Decimal::from(50), Decimal::try_from(149.5).unwrap_or_default(), )?; assert_eq!(order.status, orders::OrderStatus::PartiallyFilled); assert_eq!(order.filled_quantity, Decimal::from(50)); // Complete the fill order.simulate_fill( Decimal::from(50), Decimal::try_from(150.5).unwrap_or_default(), )?; assert_eq!(order.status, orders::OrderStatus::Filled); assert_eq!(order.filled_quantity, Decimal::from(100)); Ok(()) } }