diff --git a/crates/trading_engine/src/small_batch_optimizer.rs b/crates/trading_engine/src/small_batch_optimizer.rs index c21d56eb4..202400f77 100644 --- a/crates/trading_engine/src/small_batch_optimizer.rs +++ b/crates/trading_engine/src/small_batch_optimizer.rs @@ -19,7 +19,7 @@ )] use crate::timing::HardwareTimestamp; -use common::{OrderSide, OrderType}; +use common::{FinancialPrice, FinancialQuantity, OrderSide, OrderType}; use std::sync::atomic::{AtomicU64, Ordering}; /// Maximum orders in a small batch for specialized processing @@ -59,10 +59,10 @@ pub struct OrderRequest { pub side: OrderSide, /// Order Type pub order_type: OrderType, - /// Quantity - pub quantity: f64, // Use f64 for SIMD operations - /// Price - pub price: f64, // Use f64 for SIMD operations + /// Quantity (typed fixed-point, converted to f64 at SIMD boundary) + pub quantity: FinancialQuantity, + /// Price (typed fixed-point, converted to f64 at SIMD boundary) + pub price: FinancialPrice, /// Timestamp Ns pub timestamp_ns: u64, } @@ -75,8 +75,8 @@ impl OrderRequest { symbol: &str, side: OrderSide, order_type: OrderType, - quantity: f64, - price: f64, + quantity: FinancialQuantity, + price: FinancialPrice, ) -> Self { Self { order_id, @@ -222,14 +222,14 @@ impl SmallBatchSimd { return Err("Batch size must be 1-4 orders for SIMD processing"); } - // Pad batch to 4 elements for SIMD + // Pad batch to 4 elements for SIMD (convert typed → f64 at SIMD boundary) let mut padded_count = 0; for (i, order) in orders.iter().enumerate() { if let Some(slot) = self.prices.get_mut(i) { - *slot = order.price; + *slot = order.price.to_f64(); } if let Some(slot) = self.quantities.get_mut(i) { - *slot = order.quantity; + *slot = order.quantity.to_f64(); } if let Some(slot) = self.timestamps.get_mut(i) { *slot = order.timestamp_ns; @@ -408,10 +408,10 @@ impl SmallBatchProcessor { // Process with SIMD simd_ops.process_batch(&valid_orders)?; - // Calculate total notional (simplified for demonstration) + // Calculate total notional (convert typed values to f64 at SIMD boundary) let total_notional: f64 = valid_orders .iter() - .map(|order| order.price * order.quantity) + .map(|order| order.price.to_f64() * order.quantity.to_f64()) .sum(); Ok(BatchProcessingResult { @@ -428,13 +428,13 @@ impl SmallBatchProcessor { for &order_opt in self.orders.get(..self.batch_size).unwrap_or(&[]) { if let Some(order) = order_opt { - // Validate order - if order.price <= 0.0 || order.quantity <= 0.0 { + // Validate order (typed values are always valid — NaN/Inf rejected at construction) + if !order.price.is_positive() || !order.quantity.is_positive() { return Err("Invalid order parameters"); } - // Calculate notional - let notional = order.price * order.quantity; + // Calculate notional (convert to f64 at computation boundary) + let notional = order.price.to_f64() * order.quantity.to_f64(); if notional > 1_000_000_000.0 { return Err("Notional value too large"); } @@ -586,6 +586,14 @@ mod tests { assert!(!processor.is_full()); } + fn test_price(val: f64) -> FinancialPrice { + FinancialPrice::from_f64(val).unwrap_or(FinancialPrice::ZERO) + } + + fn test_qty(val: f64) -> FinancialQuantity { + FinancialQuantity::from_f64(val).unwrap_or(FinancialQuantity::ZERO) + } + #[test] fn test_order_request_creation() { let order = OrderRequest::new( @@ -593,15 +601,15 @@ mod tests { "BTCUSD", OrderSide::Buy, OrderType::Limit, - 1.5, - 50000.0, + test_qty(1.5), + test_price(50000.0), ); assert_eq!(order.order_id, 12345); assert_eq!(order.side, OrderSide::Buy); assert_eq!(order.order_type, OrderType::Limit); - assert_eq!(order.quantity, 1.5); - assert_eq!(order.price, 50000.0); + assert!((order.quantity.to_f64() - 1.5).abs() < 1e-6); + assert!((order.price.to_f64() - 50000.0).abs() < 1e-6); assert!(order.timestamp_ns > 0); } @@ -609,9 +617,9 @@ mod tests { fn test_add_orders_to_batch() { let mut processor = SmallBatchProcessor::new(); - let order1 = OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, 1.0, 50000.0); + let order1 = OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0)); let order2 = - OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Market, 2.0, 3000.0); + OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Market, test_qty(2.0), test_price(3000.0)); assert!(processor.add_order(order1).is_ok()); assert_eq!(processor.batch_size(), 1); @@ -631,8 +639,8 @@ mod tests { "BTCUSD", OrderSide::Buy, OrderType::Limit, - 1.0, - 50000.0 + i as f64, + test_qty(1.0), + test_price(50000.0 + i as f64), ); processor .add_order(order) @@ -683,8 +691,8 @@ mod tests { "BTCUSD", OrderSide::Buy, OrderType::Limit, - 1.0, - 50000.0, + test_qty(1.0), + test_price(50000.0), ); assert!(processor.add_order(order).is_ok()); } @@ -697,8 +705,8 @@ mod tests { "ETHUSD", OrderSide::Sell, OrderType::Market, - 1.0, - 3000.0, + test_qty(1.0), + test_price(3000.0), ); assert!(processor.add_order(overflow_order).is_err()); } @@ -709,8 +717,8 @@ mod tests { let mut simd_ops = SmallBatchSimd::new().expect("Test: Failed to create SIMD ops"); let orders = vec![ - OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, 1.0, 50000.0), - OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Limit, 2.0, 3000.0), + OrderRequest::new(1, "BTCUSD", OrderSide::Buy, OrderType::Limit, test_qty(1.0), test_price(50000.0)), + OrderRequest::new(2, "ETHUSD", OrderSide::Sell, OrderType::Limit, test_qty(2.0), test_price(3000.0)), ]; assert!(simd_ops.process_batch(&orders).is_ok());