//! Complete Order Execution Lifecycle Validation Tests //! //! These tests validate the complete order execution lifecycle by testing: //! - Order creation, validation, and submission //! - Order routing through the trading engine //! - Execution reporting and position updates //! - Order modifications and cancellations //! - Multi-leg and complex order scenarios //! - End-to-end latency and performance validation //! //! This represents the most comprehensive test of the trading system's //! order execution capabilities from order entry to final settlement. #![allow(unused_crate_dependencies)] use std::env; use std::time::{Duration, Instant}; use std::collections::HashMap; use std::sync::Arc; use tokio::time::timeout; use tokio::sync::{RwLock, mpsc}; use tracing::{info, warn, error, debug}; use uuid::Uuid; use trading_engine::brokers::interactive_brokers::InteractiveBrokersClient; use trading_engine::brokers::icmarkets::ICMarketsClient; use trading_engine::brokers::config::{InteractiveBrokersConfig, ICMarketsConfig}; use trading_engine::trading::data_interface::{BrokerInterface, BrokerConnectionStatus, ExecutionReport, Position}; use trading_engine::prelude::{TradingOrder, OrderSide}; use trading_engine::trading_operations::{OrderType, OrderStatus}; use common::TimeInForce; /// Comprehensive order lifecycle tracker #[derive(Debug, Clone)] pub struct OrderLifecycleTracker { pub order_id: OrderId, pub broker_order_id: Option, pub creation_time: Instant, pub submission_time: Option, pub first_ack_time: Option, pub execution_time: Option, pub completion_time: Option, pub current_status: OrderStatus, pub executions: Vec, pub modifications: Vec<(Instant, TradingOrder)>, pub errors: Vec<(Instant, String)>, pub latency_metrics: LatencyMetrics, } #[derive(Debug, Clone, Default)] pub struct LatencyMetrics { pub submission_latency_us: Option, pub ack_latency_us: Option, pub execution_latency_us: Option, pub end_to_end_latency_us: Option, } impl OrderLifecycleTracker { pub fn new(order_id: OrderId) -> Self { Self { order_id, broker_order_id: None, creation_time: Instant::now(), submission_time: None, first_ack_time: None, execution_time: None, completion_time: None, current_status: OrderStatus::Pending, executions: Vec::new(), modifications: Vec::new(), errors: Vec::new(), latency_metrics: LatencyMetrics::default(), } } pub fn mark_submitted(&mut self, broker_order_id: String) { self.submission_time = Some(Instant::now()); self.broker_order_id = Some(broker_order_id); if let Some(submission_time) = self.submission_time { self.latency_metrics.submission_latency_us = Some( submission_time.duration_since(self.creation_time).as_micros() as u64 ); } } pub fn mark_acknowledged(&mut self) { self.first_ack_time = Some(Instant::now()); self.current_status = OrderStatus::Submitted; if let (Some(ack_time), Some(submission_time)) = (self.first_ack_time, self.submission_time) { self.latency_metrics.ack_latency_us = Some( ack_time.duration_since(submission_time).as_micros() as u64 ); } } pub fn add_execution(&mut self, execution: ExecutionReport) { if self.execution_time.is_none() { self.execution_time = Some(Instant::now()); if let Some(exec_time) = self.execution_time { self.latency_metrics.execution_latency_us = Some( exec_time.duration_since(self.creation_time).as_micros() as u64 ); } } // Update status based on execution match execution.status { core::brokers::ExecutionStatus::Filled { .. } => { self.current_status = OrderStatus::Filled; self.mark_completed(); } core::brokers::ExecutionStatus::PartiallyFilled { .. } => { self.current_status = OrderStatus::PartiallyFilled; } core::brokers::ExecutionStatus::Cancelled => { self.current_status = OrderStatus::Cancelled; self.mark_completed(); } core::brokers::ExecutionStatus::Rejected => { self.current_status = OrderStatus::Rejected; self.mark_completed(); } _ => {} } self.executions.push(execution); } pub fn add_modification(&mut self, modified_order: TradingOrder) { self.modifications.push((Instant::now(), modified_order)); } pub fn add_error(&mut self, error: String) { self.errors.push((Instant::now(), error)); } pub fn mark_completed(&mut self) { if self.completion_time.is_none() { self.completion_time = Some(Instant::now()); if let Some(completion_time) = self.completion_time { self.latency_metrics.end_to_end_latency_us = Some( completion_time.duration_since(self.creation_time).as_micros() as u64 ); } } } pub fn is_terminal_status(&self) -> bool { matches!(self.current_status, OrderStatus::Filled | OrderStatus::Cancelled | OrderStatus::Rejected) } pub fn get_total_filled_quantity(&self) -> Quantity { let total: f64 = self.executions.iter() .map(|exec| exec.filled_quantity.to_f64()) .sum(); Quantity::from_f64(total).unwrap_or_default() } pub fn get_average_execution_price(&self) -> Option { if self.executions.is_empty() { return None; } let total_value: f64 = self.executions.iter() .filter_map(|exec| { exec.execution_price.map(|price| price.to_f64() * exec.filled_quantity.to_f64() ) }) .sum(); let total_quantity: f64 = self.executions.iter() .map(|exec| exec.filled_quantity.to_f64()) .sum(); if total_quantity > 0.0 { Some(Price::from_f64(total_value / total_quantity).unwrap_or_default()) } else { None } } } /// Order lifecycle test manager #[derive(Debug)] pub struct OrderLifecycleManager { active_trackers: Arc>>, execution_receiver: Option>, performance_stats: Arc>, } #[derive(Debug, Default, Clone)] pub struct PerformanceStats { pub total_orders: u64, pub successful_orders: u64, pub failed_orders: u64, pub cancelled_orders: u64, pub average_submission_latency_us: f64, pub average_execution_latency_us: f64, pub average_end_to_end_latency_us: f64, pub max_latency_us: u64, pub min_latency_us: u64, } impl OrderLifecycleManager { pub fn new() -> Self { Self { active_trackers: Arc::new(RwLock::new(HashMap::new())), execution_receiver: None, performance_stats: Arc::new(RwLock::new(PerformanceStats::default())), } } pub async fn start_tracking(&self, order_id: OrderId) { let tracker = OrderLifecycleTracker::new(order_id.clone()); self.active_trackers.write().await.insert(order_id, tracker); } pub async fn update_submission(&self, order_id: &OrderId, broker_order_id: String) { if let Some(tracker) = self.active_trackers.write().await.get_mut(order_id) { tracker.mark_submitted(broker_order_id); } } pub async fn update_acknowledgment(&self, order_id: &OrderId) { if let Some(tracker) = self.active_trackers.write().await.get_mut(order_id) { tracker.mark_acknowledged(); } } pub async fn add_execution(&self, execution: ExecutionReport) { if let Some(tracker) = self.active_trackers.write().await.get_mut(&execution.order_id) { tracker.add_execution(execution); // Update performance stats if order completed if tracker.is_terminal_status() { self.update_performance_stats(tracker).await; } } } pub async fn add_modification(&self, order_id: &OrderId, modified_order: TradingOrder) { if let Some(tracker) = self.active_trackers.write().await.get_mut(order_id) { tracker.add_modification(modified_order); } } pub async fn add_error(&self, order_id: &OrderId, error: String) { if let Some(tracker) = self.active_trackers.write().await.get_mut(order_id) { tracker.add_error(error); } } pub async fn get_tracker(&self, order_id: &OrderId) -> Option { self.active_trackers.read().await.get(order_id).cloned() } pub async fn get_performance_stats(&self) -> PerformanceStats { self.performance_stats.read().await.clone() } async fn update_performance_stats(&self, tracker: &OrderLifecycleTracker) { let mut stats = self.performance_stats.write().await; stats.total_orders += 1; match tracker.current_status { OrderStatus::Filled => stats.successful_orders += 1, OrderStatus::Cancelled => stats.cancelled_orders += 1, _ => stats.failed_orders += 1, } // Update latency metrics if let Some(latency) = tracker.latency_metrics.submission_latency_us { let total = stats.average_submission_latency_us * (stats.total_orders - 1) as f64; stats.average_submission_latency_us = (total + latency as f64) / stats.total_orders as f64; } if let Some(latency) = tracker.latency_metrics.execution_latency_us { let total = stats.average_execution_latency_us * (stats.total_orders - 1) as f64; stats.average_execution_latency_us = (total + latency as f64) / stats.total_orders as f64; } if let Some(latency) = tracker.latency_metrics.end_to_end_latency_us { let total = stats.average_end_to_end_latency_us * (stats.total_orders - 1) as f64; stats.average_end_to_end_latency_us = (total + latency as f64) / stats.total_orders as f64; // Update min/max if stats.total_orders == 1 { stats.min_latency_us = latency; stats.max_latency_us = latency; } else { stats.min_latency_us = stats.min_latency_us.min(latency); stats.max_latency_us = stats.max_latency_us.max(latency); } } } } /// Helper function to create test trading order fn create_test_order(symbol: &str, side: OrderSide, quantity: i64, price: f64, order_type: OrderType) -> TradingOrder { TradingOrder { id: OrderId::new(), symbol: Symbol::new(symbol.to_string()), side, quantity: Quantity::from_f64(quantity as f64).unwrap_or_default(), price: Price::from_f64(price).unwrap_or_default(), order_type, time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), metadata: HashMap::new(), } } /// Helper function to create test IB configuration fn create_test_ib_config() -> InteractiveBrokersConfig { InteractiveBrokersConfig { enabled: true, host: env::var("FOXHUNT_IB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()), port: env::var("FOXHUNT_IB_PORT") .map(|p| p.parse().unwrap_or(7497)) .unwrap_or(7497), client_id: env::var("FOXHUNT_IB_CLIENT_ID") .map(|id| id.parse().unwrap_or(1)) .unwrap_or(1), account_id: env::var("FOXHUNT_IB_ACCOUNT_ID").ok(), connection_timeout_secs: 10, request_timeout_secs: 5, heartbeat_interval_secs: 30, max_reconnect_attempts: 2, paper_trading: true, } } #[tokio::test] async fn test_basic_order_lifecycle() { info!("🔄 Testing basic order lifecycle"); let manager = OrderLifecycleManager::new(); let order = create_test_order("AAPL", OrderSide::Buy, 100, 150.50, OrderType::Limit); let order_id = order.id.clone(); // Start tracking manager.start_tracking(order_id.clone()).await; // Verify initial state let initial_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(initial_tracker.current_status, OrderStatus::Pending); assert!(initial_tracker.broker_order_id.is_none()); assert!(initial_tracker.submission_time.is_none()); // Simulate order submission let broker_order_id = format!("BROKER_{}", Uuid::new_v4()); manager.update_submission(&order_id, broker_order_id.clone()).await; let submitted_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(submitted_tracker.broker_order_id.as_ref().unwrap(), &broker_order_id); assert!(submitted_tracker.submission_time.is_some()); assert!(submitted_tracker.latency_metrics.submission_latency_us.is_some()); // Simulate order acknowledgment manager.update_acknowledgment(&order_id).await; let ack_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(ack_tracker.current_status, OrderStatus::Submitted); assert!(ack_tracker.first_ack_time.is_some()); assert!(ack_tracker.latency_metrics.ack_latency_us.is_some()); // Simulate execution let execution = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("AAPL".to_string()), side: core::trading::data_interface::Side::Buy, quantity: Quantity::from_f64(100.0).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(100.0).unwrap_or_default()), execution_price: Some(Price::from_f64(150.45).unwrap_or_default()), filled_quantity: Quantity::from_f64(100.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(100.0).unwrap_or_default(), average_price: Some(Price::from_f64(150.45).unwrap_or_default()), remaining_quantity: Quantity::from_f64(0.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::Filled { filled_quantity: Quantity::from_f64(100.0).unwrap_or_default(), average_price: Price::from_f64(150.45).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(execution).await; let final_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(final_tracker.current_status, OrderStatus::Filled); assert_eq!(final_tracker.executions.len(), 1); assert!(final_tracker.completion_time.is_some()); assert!(final_tracker.latency_metrics.execution_latency_us.is_some()); assert!(final_tracker.latency_metrics.end_to_end_latency_us.is_some()); // Verify quantities let filled_qty = final_tracker.get_total_filled_quantity(); assert_eq!(filled_qty.to_f64(), 100.0); let avg_price = final_tracker.get_average_execution_price().unwrap(); assert!((avg_price.to_f64().unwrap() - 150.45).abs() < 0.01); // Check performance stats let stats = manager.get_performance_stats().await; assert_eq!(stats.total_orders, 1); assert_eq!(stats.successful_orders, 1); assert!(stats.average_end_to_end_latency_us > 0.0); info!("✅ Basic order lifecycle test completed"); info!(" End-to-end latency: {}Ξs", final_tracker.latency_metrics.end_to_end_latency_us.unwrap()); } #[tokio::test] async fn test_partial_fill_lifecycle() { info!("🔄 Testing partial fill order lifecycle"); let manager = OrderLifecycleManager::new(); let order = create_test_order("MSFT", OrderSide::Sell, 1000, 300.25, OrderType::Limit); let order_id = order.id.clone(); let broker_order_id = format!("BROKER_{}", Uuid::new_v4()); // Start tracking and submit manager.start_tracking(order_id.clone()).await; manager.update_submission(&order_id, broker_order_id.clone()).await; manager.update_acknowledgment(&order_id).await; // First partial fill let partial_execution1 = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("MSFT".to_string()), side: core::trading::data_interface::Side::Sell, quantity: Quantity::from_f64(1000.0).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(300.0).unwrap_or_default()), execution_price: Some(Price::from_f64(300.30).unwrap_or_default()), filled_quantity: Quantity::from_f64(300.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(300.0).unwrap_or_default(), average_price: Some(Price::from_f64(300.30).unwrap_or_default()), remaining_quantity: Quantity::from_f64(700.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::PartiallyFilled { filled_quantity: Quantity::from_f64(300.0).unwrap_or_default(), average_price: Price::from_f64(300.30).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC1_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(partial_execution1).await; let partial_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(partial_tracker.current_status, OrderStatus::PartiallyFilled); assert_eq!(partial_tracker.executions.len(), 1); assert_eq!(partial_tracker.get_total_filled_quantity().to_f64(), 300.0); // Second partial fill let partial_execution2 = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("MSFT".to_string()), side: core::trading::data_interface::Side::Sell, quantity: Quantity::from_f64(1000.0).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(400.0).unwrap_or_default()), execution_price: Some(Price::from_f64(300.20).unwrap_or_default()), filled_quantity: Quantity::from_f64(400.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(700.0).unwrap_or_default(), average_price: Some(Price::from_f64(300.24).unwrap_or_default()), remaining_quantity: Quantity::from_f64(300.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::PartiallyFilled { filled_quantity: Quantity::from_f64(400.0).unwrap_or_default(), average_price: Price::from_f64(300.24).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC2_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(partial_execution2).await; let partial_tracker2 = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(partial_tracker2.current_status, OrderStatus::PartiallyFilled); assert_eq!(partial_tracker2.executions.len(), 2); assert_eq!(partial_tracker2.get_total_filled_quantity().to_f64(), 700.0); // Calculate weighted average price let avg_price = partial_tracker2.get_average_execution_price().unwrap().to_f64().unwrap(); let expected_avg = (300.0 * 300.30 + 400.0 * 300.20) / 700.0; assert!((avg_price - expected_avg).abs() < 0.01, "Expected avg price {}, got {}", expected_avg, avg_price); // Final fill let final_execution = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("MSFT".to_string()), side: core::trading::data_interface::Side::Sell, quantity: Quantity::from_f64(1000.0).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(300.0).unwrap_or_default()), execution_price: Some(Price::from_f64(300.15).unwrap_or_default()), filled_quantity: Quantity::from_f64(300.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(1000.0).unwrap_or_default(), average_price: Some(Price::from_f64(300.22).unwrap_or_default()), remaining_quantity: Quantity::from_f64(0.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::Filled { filled_quantity: Quantity::from_f64(1000.0).unwrap_or_default(), average_price: Price::from_f64(300.22).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC3_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(final_execution).await; let final_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(final_tracker.current_status, OrderStatus::Filled); assert_eq!(final_tracker.executions.len(), 3); assert_eq!(final_tracker.get_total_filled_quantity().to_f64(), 1000.0); assert!(final_tracker.is_terminal_status()); info!("✅ Partial fill lifecycle test completed"); info!(" Total executions: {}", final_tracker.executions.len()); info!(" Final avg price: ${:.2}", final_tracker.get_average_execution_price().unwrap().to_f64().unwrap()); } #[tokio::test] async fn test_order_modification_lifecycle() { info!("🔄 Testing order modification lifecycle"); let manager = OrderLifecycleManager::new(); let original_order = create_test_order("GOOGL", OrderSide::Buy, 50, 2500.00, OrderType::Limit); let order_id = original_order.id.clone(); let broker_order_id = format!("BROKER_{}", Uuid::new_v4()); // Start tracking and submit manager.start_tracking(order_id.clone()).await; manager.update_submission(&order_id, broker_order_id.clone()).await; manager.update_acknowledgment(&order_id).await; // First modification (price change) let modified_order1 = create_test_order("GOOGL", OrderSide::Buy, 50, 2495.00, OrderType::Limit); manager.add_modification(&order_id, modified_order1).await; let tracker1 = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(tracker1.modifications.len(), 1); // Second modification (quantity change) let modified_order2 = create_test_order("GOOGL", OrderSide::Buy, 75, 2495.00, OrderType::Limit); manager.add_modification(&order_id, modified_order2).await; let tracker2 = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(tracker2.modifications.len(), 2); // Execution of modified order let execution = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("GOOGL".to_string()), side: core::trading::data_interface::Side::Buy, quantity: Quantity::from_f64(75.0).unwrap_or_default(), // Modified quantity executed_quantity: Some(Quantity::from_f64(75.0).unwrap_or_default()), execution_price: Some(Price::from_f64(2493.50).unwrap_or_default()), filled_quantity: Quantity::from_f64(75.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(75.0).unwrap_or_default(), average_price: Some(Price::from_f64(2493.50).unwrap_or_default()), remaining_quantity: Quantity::from_f64(0.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::Filled { filled_quantity: Quantity::from_f64(75.0).unwrap_or_default(), average_price: Price::from_f64(2493.50).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(execution).await; let final_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(final_tracker.current_status, OrderStatus::Filled); assert_eq!(final_tracker.modifications.len(), 2); assert_eq!(final_tracker.executions.len(), 1); assert_eq!(final_tracker.get_total_filled_quantity().to_f64(), 75.0); // Modified quantity info!("✅ Order modification lifecycle test completed"); info!(" Modifications made: {}", final_tracker.modifications.len()); info!(" Final fill quantity: {}", final_tracker.get_total_filled_quantity().to_f64()); } #[tokio::test] async fn test_order_cancellation_lifecycle() { info!("🔄 Testing order cancellation lifecycle"); let manager = OrderLifecycleManager::new(); let order = create_test_order("TSLA", OrderSide::Sell, 100, 800.00, OrderType::Limit); let order_id = order.id.clone(); let broker_order_id = format!("BROKER_{}", Uuid::new_v4()); // Start tracking and submit manager.start_tracking(order_id.clone()).await; manager.update_submission(&order_id, broker_order_id.clone()).await; manager.update_acknowledgment(&order_id).await; // Simulate cancellation let cancellation = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new("TSLA".to_string()), side: core::trading::data_interface::Side::Sell, quantity: Quantity::from_f64(100.0).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(0.0).unwrap_or_default()), execution_price: None, filled_quantity: Quantity::from_f64(0.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(0.0).unwrap_or_default(), average_price: None, remaining_quantity: Quantity::from_f64(100.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::Cancelled, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("CANCEL_{}", Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager.add_execution(cancellation).await; let final_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(final_tracker.current_status, OrderStatus::Cancelled); assert_eq!(final_tracker.executions.len(), 1); assert_eq!(final_tracker.get_total_filled_quantity().to_f64(), 0.0); assert!(final_tracker.is_terminal_status()); assert!(final_tracker.completion_time.is_some()); info!("✅ Order cancellation lifecycle test completed"); } #[tokio::test] async fn test_multiple_order_lifecycle_performance() { info!("🔄 Testing multiple order lifecycle performance"); let manager = OrderLifecycleManager::new(); let order_count = 100; let start_time = Instant::now(); // Create and track multiple orders concurrently let mut handles = Vec::new(); for i in 0..order_count { let manager_ref = &manager; let handle = tokio::spawn(async move { let order = create_test_order( &format!("STOCK{}", i % 20), if i % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell }, 100 + (i as i64 * 5), 100.0 + (i as f64 * 0.1), OrderType::Limit ); let order_id = order.id.clone(); let broker_order_id = format!("BROKER_{}_{}", i, Uuid::new_v4()); // Simulate full lifecycle manager_ref.start_tracking(order_id.clone()).await; // Small random delay to simulate real order submission tokio::time::sleep(Duration::from_micros(rand::random::() % 1000)).await; manager_ref.update_submission(&order_id, broker_order_id.clone()).await; manager_ref.update_acknowledgment(&order_id).await; // Simulate execution (90% success rate) if rand::random::() < 0.9 { let execution = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new(format!("STOCK{}", i % 20)), side: if i % 2 == 0 { core::trading::data_interface::Side::Buy } else { core::trading::data_interface::Side::Sell }, quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default()), execution_price: Some(Price::from_f64(100.0 + i as f64 * 0.1).unwrap_or_default()), filled_quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), cumulative_quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), average_price: Some(Price::from_f64(100.0 + i as f64 * 0.1).unwrap_or_default()), remaining_quantity: Quantity::from_f64(0.0).unwrap_or_default(), status: core::brokers::ExecutionStatus::Filled { filled_quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), average_price: Price::from_f64(100.0 + i as f64 * 0.1).unwrap_or_default(), }, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("EXEC_{}_{}", i, Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager_ref.add_execution(execution).await; Ok(()) } else { // Simulate cancellation let cancellation = ExecutionReport { order_id: order_id.clone(), broker_order_id: broker_order_id.clone(), symbol: Symbol::new(format!("STOCK{}", i % 20)), side: if i % 2 == 0 { core::trading::data_interface::Side::Buy } else { core::trading::data_interface::Side::Sell }, quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), executed_quantity: Some(Quantity::from_f64(0.0).unwrap_or_default()), execution_price: None, filled_quantity: Quantity::from_f64(0.0).unwrap_or_default(), cumulative_quantity: Quantity::from_f64(0.0).unwrap_or_default(), average_price: None, remaining_quantity: Quantity::from_f64((100 + i * 5) as f64).unwrap_or_default(), status: core::brokers::ExecutionStatus::Cancelled, timestamp: chrono::Utc::now(), venue: Some("NASDAQ".to_string()), broker_name: "TestBroker".to_string(), execution_id: format!("CANCEL_{}_{}", i, Uuid::new_v4()), commission: None, metadata: HashMap::new(), }; manager_ref.add_execution(cancellation).await; Err("Cancelled") } }); handles.push(handle); } // Wait for all orders to complete let results = futures::future::join_all(handles).await; let total_time = start_time.elapsed(); let mut successful_orders = 0; let mut failed_orders = 0; for result in results { match result { Ok(Ok(())) => successful_orders += 1, Ok(Err(_)) => failed_orders += 1, Err(e) => { error!("Task panicked: {}", e); failed_orders += 1; } } } // Get final performance statistics let stats = manager.get_performance_stats().await; info!("📊 Multiple order lifecycle performance results:"); info!(" Total orders processed: {}", order_count); info!(" Successful orders: {} ({}%)", successful_orders, (successful_orders * 100) / order_count); info!(" Failed/cancelled orders: {} ({}%)", failed_orders, (failed_orders * 100) / order_count); info!(" Total processing time: {:?}", total_time); info!(" Average time per order: {:?}", total_time / order_count); info!(" Performance statistics:"); info!(" Total tracked: {}", stats.total_orders); info!(" Successful: {}", stats.successful_orders); info!(" Cancelled: {}", stats.cancelled_orders); info!(" Failed: {}", stats.failed_orders); info!(" Avg submission latency: {:.2}Ξs", stats.average_submission_latency_us); info!(" Avg execution latency: {:.2}Ξs", stats.average_execution_latency_us); info!(" Avg end-to-end latency: {:.2}Ξs", stats.average_end_to_end_latency_us); info!(" Min latency: {}Ξs", stats.min_latency_us); info!(" Max latency: {}Ξs", stats.max_latency_us); // Performance assertions assert_eq!(stats.total_orders as u32, order_count); assert!(stats.successful_orders > 0, "Should have some successful orders"); assert!(stats.average_end_to_end_latency_us > 0.0, "Should record latency"); assert!(stats.average_end_to_end_latency_us < 100_000.0, "Latency should be reasonable (< 100ms)"); // Should process orders reasonably quickly let avg_time_per_order = total_time / order_count; assert!(avg_time_per_order < Duration::from_millis(10), "Average processing time too slow: {:?}", avg_time_per_order); info!("✅ Multiple order lifecycle performance test completed"); } #[tokio::test] async fn test_real_broker_order_lifecycle() { info!("🔄 Testing order lifecycle with real broker integration"); let manager = OrderLifecycleManager::new(); let config = create_test_ib_config(); let mut ib_client = InteractiveBrokersClient::new(config); info!("🔄 Attempting connection to IB for lifecycle testing"); // Try to connect (will gracefully fail in CI) let connection_result = timeout( Duration::from_secs(10), ib_client.connect() ).await; match connection_result { Ok(Ok(())) => { info!("✅ Connected to IB - testing real order lifecycle"); // Create test order let order = create_test_order("AAPL", OrderSide::Buy, 100, 150.50, OrderType::Limit); let order_id = order.id.clone(); // Start lifecycle tracking manager.start_tracking(order_id.clone()).await; // Submit order to real broker let start_time = Instant::now(); match ib_client.submit_order(&order).await { Ok(broker_order_id) => { let submission_time = start_time.elapsed(); info!("✅ Real order submitted: {} ({}Ξs)", broker_order_id, submission_time.as_micros()); manager.update_submission(&order_id, broker_order_id.clone()).await; manager.update_acknowledgment(&order_id).await; // Try to subscribe to executions match ib_client.subscribe_executions().await { Ok(mut rx) => { info!("✅ Subscribed to real executions"); // Wait for execution reports with timeout let execution_timeout = timeout( Duration::from_secs(5), rx.recv() ).await; match execution_timeout { Ok(Some(execution)) => { info!("✅ Received real execution report:"); info!(" Execution ID: {}", execution.execution_id); info!(" Status: {:?}", execution.status); info!(" Filled: {}", execution.filled_quantity); manager.add_execution(execution).await; let final_tracker = manager.get_tracker(&order_id).await.unwrap(); info!("📊 Real broker lifecycle metrics:"); info!(" Submission latency: {}Ξs", final_tracker.latency_metrics.submission_latency_us.unwrap_or(0)); info!(" End-to-end latency: {}Ξs", final_tracker.latency_metrics.end_to_end_latency_us.unwrap_or(0)); } Ok(None) => { info!(" Execution channel closed"); } Err(_) => { info!(" No execution received within timeout (normal for limit order)"); } } } Err(e) => { warn!("⚠ïļ Failed to subscribe to executions: {}", e); } } // Cancel the order to clean up let cancel_result = ib_client.cancel_order(&broker_order_id).await; match cancel_result { Ok(()) => { info!("✅ Real order cancelled successfully"); // Wait for cancellation confirmation tokio::time::sleep(Duration::from_millis(500)).await; } Err(e) => { warn!("⚠ïļ Failed to cancel real order: {}", e); } } // Get final tracker state let final_tracker = manager.get_tracker(&order_id).await.unwrap(); info!("📊 Final real order lifecycle state:"); info!(" Status: {:?}", final_tracker.current_status); info!(" Executions: {}", final_tracker.executions.len()); info!(" Errors: {}", final_tracker.errors.len()); } Err(e) => { warn!("⚠ïļ Real order submission failed: {}", e); manager.add_error(&order_id, e.to_string()).await; let error_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(error_tracker.errors.len(), 1); info!("✅ Error handling verified in lifecycle tracking"); } } let _ = ib_client.disconnect().await; } Ok(Err(e)) => { warn!("⚠ïļ IB connection failed (expected in CI): {}", e); info!(" Testing lifecycle error handling instead"); // Test error handling in lifecycle let order = create_test_order("AAPL", OrderSide::Buy, 100, 150.50, OrderType::Limit); let order_id = order.id.clone(); manager.start_tracking(order_id.clone()).await; manager.add_error(&order_id, e.to_string()).await; let error_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(error_tracker.errors.len(), 1); assert!(error_tracker.errors[0].1.contains("not connected") || error_tracker.errors[0].1.contains("not available")); info!("✅ Lifecycle error handling verified"); } Err(_) => { warn!("⚠ïļ IB connection timed out - testing offline lifecycle"); // Test lifecycle tracking without real broker let order = create_test_order("AAPL", OrderSide::Buy, 100, 150.50, OrderType::Limit); let order_id = order.id.clone(); manager.start_tracking(order_id.clone()).await; manager.add_error(&order_id, "Connection timeout".to_string()).await; let timeout_tracker = manager.get_tracker(&order_id).await.unwrap(); assert_eq!(timeout_tracker.errors.len(), 1); info!("✅ Offline lifecycle tracking verified"); } } info!("✅ Real broker order lifecycle test completed"); }