//! Order execution unit tests //! //! Tests order routing, execution algorithms, and venue selection //! with focus on best execution and latency optimization. use core::types::prelude::*; // CANONICAL TYPE IMPORTS - Use types::prelude::Decimal use std::collections::HashMap; use std::time::{Duration, Instant}; #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_smart_order_router_venue_selection() { let mut router = SmartOrderRouter::new(); // Add venues with different characteristics router.add_venue(Venue { id: "NYSE".to_string(), latency: Duration::from_millis(2), fee_rate: Decimal::from_str("0.0005").unwrap(), // 0.05% liquidity_score: 95, reliability_score: 99, }); router.add_venue(Venue { id: "NASDAQ".to_string(), latency: Duration::from_millis(1), fee_rate: Decimal::from_str("0.0007").unwrap(), // 0.07% liquidity_score: 90, reliability_score: 98, }); router.add_venue(Venue { id: "DARK_POOL".to_string(), latency: Duration::from_millis(5), fee_rate: Decimal::from_str("0.0002").unwrap(), // 0.02% liquidity_score: 70, reliability_score: 95, }); // Small order - should prefer low latency let small_order = Order { id: OrderId::from("SMALL-001"), symbol: Symbol::new("AAPL".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Market, quantity: Quantity::new(Decimal::from(100)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: None, time_in_force: TimeInForce::IOC, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("CLIENT-001".to_string()), }; let small_routing = router.select_venue(&small_order, VenueSelectionStrategy::LatencyOptimized).await .expect("Should select venue for small order"); assert_eq!(small_routing.venue_id, "NASDAQ"); // Lowest latency // Large order - should prefer liquidity let large_order = Order { id: OrderId::from("LARGE-001"), symbol: Symbol::new("AAPL".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Limit, quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: Some(Price::new(Decimal::from(150)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("CLIENT-001".to_string()), }; let large_routing = router.select_venue(&large_order, VenueSelectionStrategy::LiquidityOptimized).await .expect("Should select venue for large order"); assert_eq!(large_routing.venue_id, "NYSE"); // Highest liquidity } #[tokio::test] async fn test_twap_execution_algorithm() { let mut twap = TwapExecutor::new( Duration::from_secs(300), // 5 minutes 20 // 20 child orders ); let parent_order = Order { id: OrderId::from("TWAP-PARENT"), symbol: Symbol::new("MSFT".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Limit, quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: Some(Price::new(Decimal::from(300)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("INST-001".to_string()), }; let execution_plan = twap.create_execution_plan(&parent_order).await .expect("Should create TWAP execution plan"); // Should create 20 child orders assert_eq!(execution_plan.child_orders.len(), 20); // Each child order should be 500 shares (10000 / 20) for child_order in &execution_plan.child_orders { assert_eq!(child_order.quantity.value(), Decimal::from(500)); } // Should have proper timing intervals (15 seconds each) let expected_interval = Duration::from_secs(15); // 300 seconds / 20 orders for i in 1..execution_plan.execution_schedule.len() { let interval = execution_plan.execution_schedule[i] - execution_plan.execution_schedule[i-1]; let tolerance = Duration::from_millis(100); assert!((interval - expected_interval) < tolerance); } } #[tokio::test] async fn test_vwap_execution_algorithm() { let mut vwap = VwapExecutor::new(); // Mock historical volume profile let volume_profile = vec![ (chrono::NaiveTime::from_hms_opt(9, 30, 0).unwrap(), Decimal::from(1000)), // Market open (chrono::NaiveTime::from_hms_opt(10, 0, 0).unwrap(), Decimal::from(800)), (chrono::NaiveTime::from_hms_opt(11, 0, 0).unwrap(), Decimal::from(600)), (chrono::NaiveTime::from_hms_opt(14, 0, 0).unwrap(), Decimal::from(700)), // Lunch time (chrono::NaiveTime::from_hms_opt(15, 30, 0).unwrap(), Decimal::from(1200)), // Market close ]; vwap.set_volume_profile(Symbol::new("GOOGL".to_string()).unwrap(), volume_profile); let parent_order = Order { id: OrderId::from("VWAP-PARENT"), symbol: Symbol::new("GOOGL".to_string()).unwrap(), side: Side::Sell, order_type: OrderType::Limit, quantity: Quantity::new(Decimal::from(5000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: Some(Price::new(Decimal::from(2500)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("FUND-001".to_string()), }; let execution_plan = vwap.create_execution_plan(&parent_order).await .expect("Should create VWAP execution plan"); // Should weight execution based on historical volume // Market open and close should have larger orders let open_order = execution_plan.child_orders.iter() .find(|o| o.id.value().contains("09:30")) .expect("Should have market open order"); let close_order = execution_plan.child_orders.iter() .find(|o| o.id.value().contains("15:30")) .expect("Should have market close order"); let midday_order = execution_plan.child_orders.iter() .find(|o| o.id.value().contains("11:00")) .expect("Should have midday order"); // Open and close orders should be larger than midday assert!(open_order.quantity.value() > midday_order.quantity.value()); assert!(close_order.quantity.value() > midday_order.quantity.value()); } #[tokio::test] async fn test_implementation_shortfall_execution() { let mut is_executor = ImplementationShortfallExecutor::new( Decimal::from_str("0.02").unwrap(), // 2% risk aversion Duration::from_secs(1800) // 30 minutes ); let parent_order = Order { id: OrderId::from("IS-PARENT"), symbol: Symbol::new("TSLA".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Market, quantity: Quantity::new(Decimal::from(2000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: None, time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("HEDGE-001".to_string()), }; // Set market conditions let market_conditions = MarketConditions { volatility: Decimal::from_str("0.25").unwrap(), // 25% annualized spread: Decimal::from_str("0.10").unwrap(), // $0.10 spread temporary_impact_rate: Decimal::from_str("0.001").unwrap(), permanent_impact_rate: Decimal::from_str("0.0005").unwrap(), }; is_executor.set_market_conditions(market_conditions); let execution_plan = is_executor.create_execution_plan(&parent_order).await .expect("Should create IS execution plan"); // Should optimize trade-off between market impact and timing risk assert!(!execution_plan.child_orders.is_empty()); // In high volatility, should execute faster to reduce timing risk let total_execution_time = execution_plan.execution_schedule.last().unwrap() - execution_plan.execution_schedule.first().unwrap(); assert!(total_execution_time < Duration::from_secs(1800)); // Less than max time assert!(total_execution_time > Duration::from_secs(60)); // But not too fast } #[tokio::test] async fn test_iceberg_order_execution() { let mut iceberg = IcebergExecutor::new( Quantity::new(Decimal::from(500)).map_err(|e| format!("Failed to create iceberg quantity: {}", e)).unwrap(), // Show 500 shares at a time Decimal::from_str("0.01").unwrap() // 1% price improvement threshold ); let parent_order = Order { id: OrderId::from("ICEBERG-PARENT"), symbol: Symbol::new("AMZN".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Limit, quantity: Quantity::new(Decimal::from(5000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: Some(Price::new(Decimal::from(3000)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("CLIENT-001".to_string()), }; let initial_slice = iceberg.get_initial_slice(&parent_order).await .expect("Should get initial iceberg slice"); // Initial slice should be clip size assert_eq!(initial_slice.quantity.value(), Decimal::from(500)); assert_eq!(initial_slice.price, parent_order.price); // Simulate partial fill let fill = Fill { id: FillId::new("FILL-ICE-001".to_string()), order_id: initial_slice.id.clone(), trade_id: TradeId::new("TRADE-ICE-001".to_string()), symbol: parent_order.symbol.clone(), side: parent_order.side, quantity: Quantity::new(Decimal::from(300)).map_err(|e| format!("Failed to create fill quantity: {}", e)).unwrap(), price: Price::new(Decimal::from(3000)).unwrap(), timestamp: chrono::Utc::now(), commission: None, }; let next_slice = iceberg.handle_fill(&parent_order, &fill).await .expect("Should handle iceberg fill"); assert!(next_slice.is_some()); let next = next_slice.unwrap(); // Should replenish to show full clip size again assert_eq!(next.quantity.value(), Decimal::from(500)); // 200 remaining + 300 new } #[tokio::test] async fn test_execution_quality_measurement() { let mut quality_tracker = ExecutionQualityTracker::new(); // Record executions with different quality metrics let execution1 = ExecutionResult { order_id: OrderId::from("EXEC-001"), symbol: Symbol::new("AAPL".to_string()).unwrap(), expected_price: Price::new(Decimal::from(150)).unwrap(), executed_price: Price::new(Decimal::from_str("150.05").unwrap()).unwrap(), quantity: Quantity::new(Decimal::from(1000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), execution_time: Duration::from_millis(50), venue: "NYSE".to_string(), timestamp: chrono::Utc::now(), }; let execution2 = ExecutionResult { order_id: OrderId::from("EXEC-002"), symbol: Symbol::new("AAPL".to_string()).unwrap(), expected_price: Price::new(Decimal::from(150)).unwrap(), executed_price: Price::new(Decimal::from_str("149.98").unwrap()).unwrap(), quantity: Quantity::new(Decimal::from(500)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), execution_time: Duration::from_millis(25), venue: "NASDAQ".to_string(), timestamp: chrono::Utc::now(), }; quality_tracker.record_execution(execution1); quality_tracker.record_execution(execution2); let quality_metrics = quality_tracker.calculate_metrics(Duration::from_secs(3600)).await .expect("Should calculate quality metrics"); // Should calculate average slippage let expected_avg_slippage = (Decimal::from_str("0.05").unwrap() + Decimal::from_str("-0.02").unwrap()) / Decimal::from(2); assert_eq!(quality_metrics.average_slippage, expected_avg_slippage); // Should track fill rates and timing assert_eq!(quality_metrics.total_executions, 2); assert!(quality_metrics.average_execution_time < Duration::from_millis(50)); } #[tokio::test] async fn test_multi_venue_execution() { let mut multi_venue = MultiVenueExecutor::new(); multi_venue.add_venue("NYSE", 0.3); // 30% allocation multi_venue.add_venue("NASDAQ", 0.4); // 40% allocation multi_venue.add_venue("BATS", 0.2); // 20% allocation multi_venue.add_venue("IEX", 0.1); // 10% allocation let large_order = Order { id: OrderId::from("MULTI-VENUE"), symbol: Symbol::new("SPY".to_string()).unwrap(), side: Side::Buy, order_type: OrderType::Limit, quantity: Quantity::new(Decimal::from(10000)).map_err(|e| format!("Failed to create test quantity: {}", e)).unwrap(), price: Some(Price::new(Decimal::from(400)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("INSTITUTION".to_string()), }; let venue_orders = multi_venue.split_order(&large_order).await .expect("Should split order across venues"); assert_eq!(venue_orders.len(), 4); // Check allocations let nyse_order = venue_orders.iter().find(|o| o.venue_id == "NYSE").unwrap(); let nasdaq_order = venue_orders.iter().find(|o| o.venue_id == "NASDAQ").unwrap(); let bats_order = venue_orders.iter().find(|o| o.venue_id == "BATS").unwrap(); let iex_order = venue_orders.iter().find(|o| o.venue_id == "IEX").unwrap(); assert_eq!(nyse_order.quantity.value(), Decimal::from(3000)); // 30% assert_eq!(nasdaq_order.quantity.value(), Decimal::from(4000)); // 40% assert_eq!(bats_order.quantity.value(), Decimal::from(2000)); // 20% assert_eq!(iex_order.quantity.value(), Decimal::from(1000)); // 10% // Total should match original order let total_quantity: Decimal = venue_orders.iter() .map(|o| o.quantity.value()) .sum(); assert_eq!(total_quantity, large_order.quantity.value()); } #[tokio::test] async fn test_latency_measurement() { let mut latency_tracker = LatencyTracker::new(); let start_time = Instant::now(); // Simulate order lifecycle with timing points let order_id = OrderId::from("LATENCY-TEST"); latency_tracker.mark_order_sent(&order_id, start_time); let ack_time = start_time + Duration::from_micros(500); // 500μs to ack latency_tracker.mark_order_ack(&order_id, ack_time); let fill_time = start_time + Duration::from_millis(2); // 2ms to fill latency_tracker.mark_order_fill(&order_id, fill_time); let metrics = latency_tracker.get_metrics(&order_id).await .expect("Should get latency metrics"); assert_eq!(metrics.order_to_ack, Duration::from_micros(500)); assert_eq!(metrics.order_to_fill, Duration::from_millis(2)); assert_eq!(metrics.ack_to_fill, Duration::from_millis(2) - Duration::from_micros(500)); } } // Production implementations for testing #[derive(Debug, Clone)] struct Venue { id: String, latency: Duration, fee_rate: Decimal, liquidity_score: u32, reliability_score: u32, } #[derive(Debug)] struct SmartOrderRouter { venues: Vec, } #[derive(Debug)] enum VenueSelectionStrategy { LatencyOptimized, LiquidityOptimized, CostOptimized, Balanced, } #[derive(Debug)] struct VenueRoutingDecision { venue_id: String, expected_latency: Duration, expected_fee: Money, confidence_score: u32, } impl SmartOrderRouter { fn new() -> Self { Self { venues: Vec::new(), } } fn add_venue(&mut self, venue: Venue) { self.venues.push(venue); } async fn select_venue(&self, order: &Order, strategy: VenueSelectionStrategy) -> Result> { if self.venues.is_empty() { return Err("No venues available".into()); } let best_venue = match strategy { VenueSelectionStrategy::LatencyOptimized => { self.venues.iter().min_by_key(|v| v.latency).unwrap() }, VenueSelectionStrategy::LiquidityOptimized => { self.venues.iter().max_by_key(|v| v.liquidity_score).unwrap() }, VenueSelectionStrategy::CostOptimized => { self.venues.iter().min_by_key(|v| v.fee_rate).unwrap() }, VenueSelectionStrategy::Balanced => { // Simple scoring: combine latency, liquidity, and cost self.venues.iter().max_by_key(|v| { let latency_score = 100 - (v.latency.as_millis() as u32); let cost_score = 100 - (v.fee_rate * Decimal::from(10000)).to_u32().unwrap_or(0); latency_score + v.liquidity_score + cost_score + v.reliability_score }).unwrap() } }; let order_value = order.quantity.value() * order.price.as_ref().unwrap_or(&Price::new(Decimal::from(100)).unwrap()).value(); Ok(VenueRoutingDecision { venue_id: best_venue.id.clone(), expected_latency: best_venue.latency, expected_fee: Money::new(order_value * best_venue.fee_rate, Currency::USD), confidence_score: best_venue.reliability_score, }) } } #[derive(Debug)] struct TwapExecutor { duration: Duration, num_slices: usize, } #[derive(Debug)] struct ExecutionPlan { child_orders: Vec, execution_schedule: Vec>, } impl TwapExecutor { fn new(duration: Duration, num_slices: usize) -> Self { Self { duration, num_slices } } async fn create_execution_plan(&self, parent_order: &Order) -> Result> { let slice_size = parent_order.quantity.value() / Decimal::from(self.num_slices); let slice_interval = self.duration / self.num_slices as u32; let mut child_orders = Vec::new(); let mut execution_schedule = Vec::new(); let start_time = chrono::Utc::now(); for i in 0..self.num_slices { let child_order = Order { id: OrderId::new(format!("{}-TWAP-{}", parent_order.id.value(), i)), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: parent_order.order_type, quantity: Quantity::new(slice_size).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(), price: parent_order.price.clone(), time_in_force: TimeInForce::IOC, timestamp: parent_order.timestamp, status: OrderStatus::New, client_id: parent_order.client_id.clone(), }; child_orders.push(child_order); execution_schedule.push(start_time + chrono::Duration::from_std(slice_interval * i as u32).unwrap()); } Ok(ExecutionPlan { child_orders, execution_schedule, }) } } #[derive(Debug)] struct VwapExecutor { volume_profiles: HashMap>, } impl VwapExecutor { fn new() -> Self { Self { volume_profiles: HashMap::new(), } } fn set_volume_profile(&mut self, symbol: Symbol, profile: Vec<(chrono::NaiveTime, Decimal)>) { self.volume_profiles.insert(symbol, profile); } async fn create_execution_plan(&self, parent_order: &Order) -> Result> { let profile = self.volume_profiles.get(&parent_order.symbol) .ok_or("No volume profile for symbol")?; let total_volume: Decimal = profile.iter().map(|(_, vol)| *vol).sum(); let mut child_orders = Vec::new(); let mut execution_schedule = Vec::new(); let base_date = chrono::Utc::now().date_naive(); for (time, historical_volume) in profile { let weight = historical_volume / total_volume; let slice_quantity = parent_order.quantity.value() * weight; if slice_quantity > Decimal::ZERO { let child_order = Order { id: OrderId::new(format!("{}-VWAP-{}", parent_order.id.value(), time.format("%H:%M"))), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: parent_order.order_type, quantity: Quantity::new(slice_quantity).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(), price: parent_order.price.clone(), time_in_force: TimeInForce::IOC, timestamp: parent_order.timestamp, status: OrderStatus::New, client_id: parent_order.client_id.clone(), }; child_orders.push(child_order); let execution_time = chrono::DateTime::from_naive_utc_and_offset( base_date.and_time(*time), chrono::Utc ); execution_schedule.push(execution_time); } } Ok(ExecutionPlan { child_orders, execution_schedule, }) } } #[derive(Debug)] struct ImplementationShortfallExecutor { risk_aversion: Decimal, max_duration: Duration, } #[derive(Debug)] struct MarketConditions { volatility: Decimal, spread: Decimal, temporary_impact_rate: Decimal, permanent_impact_rate: Decimal, } impl ImplementationShortfallExecutor { fn new(risk_aversion: Decimal, max_duration: Duration) -> Self { Self { risk_aversion, max_duration } } fn set_market_conditions(&mut self, _conditions: MarketConditions) { // Store market conditions for optimization } async fn create_execution_plan(&self, parent_order: &Order) -> Result> { // Simplified IS optimization - in practice would solve for optimal trajectory let num_slices = if parent_order.quantity.value() > Decimal::from(5000) { 10 } else { 5 }; let execution_duration = self.max_duration / 2; // Execute faster in volatile markets let slice_size = parent_order.quantity.value() / Decimal::from(num_slices); let slice_interval = execution_duration / num_slices as u32; let mut child_orders = Vec::new(); let mut execution_schedule = Vec::new(); let start_time = chrono::Utc::now(); for i in 0..num_slices { let child_order = Order { id: OrderId::new(format!("{}-IS-{}", parent_order.id.value(), i)), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: OrderType::Limit, // Use limit orders for better control quantity: Quantity::new(slice_size).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(), price: parent_order.price.clone(), time_in_force: TimeInForce::IOC, timestamp: parent_order.timestamp, status: OrderStatus::New, client_id: parent_order.client_id.clone(), }; child_orders.push(child_order); execution_schedule.push(start_time + chrono::Duration::from_std(slice_interval * i as u32).unwrap()); } Ok(ExecutionPlan { child_orders, execution_schedule, }) } } #[derive(Debug)] struct IcebergExecutor { clip_size: Quantity, price_improvement_threshold: Decimal, } impl IcebergExecutor { fn new(clip_size: Quantity, price_improvement_threshold: Decimal) -> Self { Self { clip_size, price_improvement_threshold } } async fn get_initial_slice(&self, parent_order: &Order) -> Result> { let slice_quantity = std::cmp::min(parent_order.quantity.value(), self.clip_size.value()); Ok(Order { id: OrderId::new(format!("{}-ICE-0", parent_order.id.value())), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: parent_order.order_type, quantity: Quantity::new(slice_quantity).map_err(|e| format!("Failed to create slice quantity: {}", e)).unwrap(), price: parent_order.price.clone(), time_in_force: TimeInForce::Day, timestamp: parent_order.timestamp, status: OrderStatus::New, client_id: parent_order.client_id.clone(), }) } async fn handle_fill(&self, parent_order: &Order, _fill: &Fill) -> Result, Box> { // Replenish the visible quantity Ok(Some(Order { id: OrderId::new(format!("{}-ICE-REFILL", parent_order.id.value())), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: parent_order.order_type, quantity: self.clip_size.clone(), price: parent_order.price.clone(), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: parent_order.client_id.clone(), })) } } #[derive(Debug)] struct ExecutionResult { order_id: OrderId, symbol: Symbol, expected_price: Price, executed_price: Price, quantity: Quantity, execution_time: Duration, venue: String, timestamp: chrono::DateTime, } #[derive(Debug)] struct ExecutionQualityMetrics { average_slippage: Decimal, total_executions: usize, average_execution_time: Duration, fill_rate: Decimal, venue_performance: HashMap, } #[derive(Debug)] struct VenueMetrics { average_slippage: Decimal, execution_count: usize, average_latency: Duration, } #[derive(Debug)] struct ExecutionQualityTracker { executions: Vec, } impl ExecutionQualityTracker { fn new() -> Self { Self { executions: Vec::new(), } } fn record_execution(&mut self, execution: ExecutionResult) { self.executions.push(execution); } async fn calculate_metrics(&self, period: Duration) -> Result> { let cutoff_time = chrono::Utc::now() - chrono::Duration::from_std(period).unwrap(); let recent_executions: Vec<_> = self.executions.iter() .filter(|e| e.timestamp > cutoff_time) .collect(); if recent_executions.is_empty() { return Err("No executions in specified period".into()); } // Calculate average slippage let total_slippage: Decimal = recent_executions.iter() .map(|e| e.executed_price.value() - e.expected_price.value()) .sum(); let average_slippage = total_slippage / Decimal::from(recent_executions.len()); // Calculate average execution time let total_time: Duration = recent_executions.iter() .map(|e| e.execution_time) .sum(); let average_execution_time = total_time / recent_executions.len() as u32; // Build venue performance map let mut venue_performance = HashMap::new(); let mut venue_groups: HashMap> = HashMap::new(); for exec in &recent_executions { venue_groups.entry(exec.venue.clone()).or_default().push(exec); } for (venue, execs) in venue_groups { let venue_slippage: Decimal = execs.iter() .map(|e| e.executed_price.value() - e.expected_price.value()) .sum::() / Decimal::from(execs.len()); let venue_latency = execs.iter() .map(|e| e.execution_time) .sum::() / execs.len() as u32; venue_performance.insert(venue, VenueMetrics { average_slippage: venue_slippage, execution_count: execs.len(), average_latency: venue_latency, }); } Ok(ExecutionQualityMetrics { average_slippage, total_executions: recent_executions.len(), average_execution_time, fill_rate: Decimal::ONE, // Simplified - assume all filled venue_performance, }) } } #[derive(Debug)] struct MultiVenueExecutor { venue_allocations: HashMap, } #[derive(Debug)] struct VenueOrder { venue_id: String, order: Order, quantity: Quantity, } impl MultiVenueExecutor { fn new() -> Self { Self { venue_allocations: HashMap::new(), } } fn add_venue(&mut self, venue_id: &str, allocation: f64) { self.venue_allocations.insert(venue_id.to_string(), allocation); } async fn split_order(&self, parent_order: &Order) -> Result, Box> { let mut venue_orders = Vec::new(); for (venue_id, &allocation) in &self.venue_allocations { let venue_quantity = parent_order.quantity.value() * Decimal::from_f64(allocation).map_err(|e| format!("Failed to convert allocation to Decimal: {}", e)).unwrap(); if venue_quantity > Decimal::ZERO { let venue_order = Order { id: OrderId::new(format!("{}-{}", parent_order.id.value(), venue_id)), symbol: parent_order.symbol.clone(), side: parent_order.side, order_type: parent_order.order_type, quantity: Quantity::new(venue_quantity).map_err(|e| format!("Failed to create venue quantity: {}", e)).unwrap(), price: parent_order.price.clone(), time_in_force: parent_order.time_in_force, timestamp: parent_order.timestamp, status: OrderStatus::New, client_id: parent_order.client_id.clone(), }; venue_orders.push(VenueOrder { venue_id: venue_id.clone(), order: venue_order, quantity: Quantity::new(venue_quantity).map_err(|e| format!("Failed to create venue quantity: {}", e)).unwrap(), }); } } Ok(venue_orders) } } #[derive(Debug)] struct LatencyMetrics { order_to_ack: Duration, order_to_fill: Duration, ack_to_fill: Duration, } #[derive(Debug)] struct LatencyTracker { timing_points: HashMap, } #[derive(Debug)] struct TimingPoints { order_sent: Instant, order_ack: Option, order_fill: Option, } impl LatencyTracker { fn new() -> Self { Self { timing_points: HashMap::new(), } } fn mark_order_sent(&mut self, order_id: &OrderId, time: Instant) { self.timing_points.insert(order_id.clone(), TimingPoints { order_sent: time, order_ack: None, order_fill: None, }); } fn mark_order_ack(&mut self, order_id: &OrderId, time: Instant) { if let Some(points) = self.timing_points.get_mut(order_id) { points.order_ack = Some(time); } } fn mark_order_fill(&mut self, order_id: &OrderId, time: Instant) { if let Some(points) = self.timing_points.get_mut(order_id) { points.order_fill = Some(time); } } async fn get_metrics(&self, order_id: &OrderId) -> Result> { let points = self.timing_points.get(order_id) .ok_or("Order not found in timing points")?; let order_to_ack = points.order_ack .ok_or("Order acknowledgment not recorded")? .duration_since(points.order_sent); let order_to_fill = points.order_fill .ok_or("Order fill not recorded")? .duration_since(points.order_sent); let ack_to_fill = points.order_fill.unwrap() .duration_since(points.order_ack.unwrap()); Ok(LatencyMetrics { order_to_ack, order_to_fill, ack_to_fill, }) } }