//! Mock Services for Foxhunt HFT Trading System Testing //! //! This module provides mock implementations of all external services //! for isolated testing without dependencies on real services. use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use tokio::sync::{mpsc, Mutex, RwLock}; use tokio::time::sleep; use uuid::Uuid; use super::test_config::TestConfig; // ============================================================================= // MOCK TRADING SERVICE // ============================================================================= /// Mock trading service for testing trading operations #[derive(Debug, Clone)] pub struct MockTradingService { _config: TestConfig, orders: Arc>>, positions: Arc>>, order_events: Arc>>, latency_ms: u64, failure_rate: f64, next_order_id: Arc>, } impl MockTradingService { pub fn new(config: TestConfig) -> Self { let (sender, _receiver) = mpsc::unbounded_channel(); let latency_ms = config.services.mock_latency_ms; let failure_rate = config.services.mock_failure_rate; Self { latency_ms, failure_rate, _config: config, orders: Arc::new(RwLock::new(HashMap::new())), positions: Arc::new(RwLock::new(HashMap::new())), order_events: Arc::new(Mutex::new(sender)), next_order_id: Arc::new(RwLock::new(1)), } } /// Submit a new order pub async fn submit_order( &self, request: MockOrderRequest, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let order_id = { let mut next_id = self.next_order_id.write().await; let id = *next_id; *next_id += 1; id }; let order = MockOrder { id: Uuid::new_v4(), order_id, symbol: request.symbol.clone(), side: request.side, quantity: request.quantity, price: request.price, order_type: request.order_type, status: MockOrderStatus::Pending, filled_quantity: Decimal::ZERO, average_fill_price: Decimal::ZERO, created_at: Utc::now(), updated_at: Utc::now(), }; // Store the order self.orders.write().await.insert(order.id, order.clone()); // Simulate order processing in background let service = self.clone(); let order_id = order.id; tokio::spawn(async move { service.process_order(order_id).await; }); Ok(MockOrderResponse { order_id: order.id, status: MockOrderStatus::Pending, message: "Order submitted successfully".to_string(), }) } /// Cancel an existing order pub async fn cancel_order( &self, order_id: Uuid, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let mut orders = self.orders.write().await; if let Some(order) = orders.get_mut(&order_id) { if matches!( order.status, MockOrderStatus::Pending | MockOrderStatus::PartiallyFilled ) { order.status = MockOrderStatus::Cancelled; order.updated_at = Utc::now(); Ok(MockOrderResponse { order_id, status: MockOrderStatus::Cancelled, message: "Order cancelled successfully".to_string(), }) } else { Err(MockServiceError::InvalidOperation(format!( "Cannot cancel order in status: {:?}", order.status ))) } } else { Err(MockServiceError::OrderNotFound(order_id)) } } /// Get order status pub async fn get_order_status(&self, order_id: Uuid) -> Result { self.simulate_latency().await; self.simulate_failure()?; let orders = self.orders.read().await; orders .get(&order_id) .cloned() .ok_or(MockServiceError::OrderNotFound(order_id)) } /// Get all orders for a symbol pub async fn get_orders_for_symbol( &self, symbol: &str, ) -> Result, MockServiceError> { self.simulate_latency().await; self.simulate_failure()?; let orders = self.orders.read().await; let filtered_orders: Vec = orders .values() .filter(|order| order.symbol == symbol) .cloned() .collect(); Ok(filtered_orders) } /// Get current positions pub async fn get_positions(&self) -> Result, MockServiceError> { self.simulate_latency().await; self.simulate_failure()?; let positions = self.positions.read().await; Ok(positions.values().cloned().collect()) } /// Process order asynchronously (simulate fill) async fn process_order(&self, order_id: Uuid) { // Wait a bit before processing sleep(Duration::from_millis(100 + self.latency_ms)).await; let mut orders = self.orders.write().await; if let Some(order) = orders.get_mut(&order_id) { // Simulate partial fill first order.status = MockOrderStatus::PartiallyFilled; order.filled_quantity = order.quantity / Decimal::from(2); // Fill 50% order.average_fill_price = order.price; order.updated_at = Utc::now(); // Send event self.send_order_event(order.clone()).await; // Wait a bit more drop(orders); // Release lock sleep(Duration::from_millis(200)).await; // Complete the fill let mut orders = self.orders.write().await; if let Some(order) = orders.get_mut(&order_id) { order.status = MockOrderStatus::Filled; order.filled_quantity = order.quantity; order.updated_at = Utc::now(); // Update position self.update_position(order).await; // Send final event self.send_order_event(order.clone()).await; } } } /// Update position based on filled order async fn update_position(&self, order: &MockOrder) { let mut positions = self.positions.write().await; let position_key = order.symbol.clone(); let position = positions .entry(position_key) .or_insert_with(|| MockPosition { symbol: order.symbol.clone(), quantity: Decimal::ZERO, average_price: Decimal::ZERO, market_value: Decimal::ZERO, unrealized_pnl: Decimal::ZERO, updated_at: Utc::now(), }); // Update position quantity and average price let old_quantity = position.quantity; let old_value = old_quantity * position.average_price; let order_quantity = match order.side { MockOrderSide::Buy => order.filled_quantity, MockOrderSide::Sell => -order.filled_quantity, }; let new_quantity = old_quantity + order_quantity; let order_value = order.filled_quantity * order.average_fill_price; if new_quantity != Decimal::ZERO { let new_total_value = match order.side { MockOrderSide::Buy => old_value + order_value, MockOrderSide::Sell => old_value - order_value, }; position.average_price = new_total_value / new_quantity.abs(); } else { position.average_price = Decimal::ZERO; } position.quantity = new_quantity; position.market_value = position.quantity * order.average_fill_price; // Assume market price = fill price position.unrealized_pnl = (order.average_fill_price - position.average_price) * position.quantity; position.updated_at = Utc::now(); } /// Send order event async fn send_order_event(&self, order: MockOrder) { let event = MockOrderEvent { order_id: order.id, symbol: order.symbol, status: order.status, filled_quantity: order.filled_quantity, timestamp: Utc::now(), }; if let Ok(sender) = self.order_events.try_lock() { let _ = sender.send(event); } } async fn simulate_latency(&self) { if self.latency_ms > 0 { sleep(Duration::from_millis(self.latency_ms)).await; } } fn simulate_failure(&self) -> Result<(), MockServiceError> { if self.failure_rate > 0.0 { use rand::Rng; let mut rng = rand::thread_rng(); if rng.gen::() < self.failure_rate { return Err(MockServiceError::SimulatedFailure); } } Ok(()) } } // ============================================================================= // MOCK ML TRAINING SERVICE // ============================================================================= /// Mock ML training service for testing ML operations #[derive(Debug, Clone)] pub struct MockMLTrainingService { _config: TestConfig, training_jobs: Arc>>, models: Arc>>, latency_ms: u64, failure_rate: f64, } impl MockMLTrainingService { pub fn new(config: TestConfig) -> Self { let latency_ms = config.services.mock_latency_ms; let failure_rate = config.services.mock_failure_rate; Self { latency_ms, failure_rate, _config: config, training_jobs: Arc::new(RwLock::new(HashMap::new())), models: Arc::new(RwLock::new(HashMap::new())), } } /// Start a new training job pub async fn start_training( &self, request: MockTrainingRequest, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let job_id = Uuid::new_v4(); let job = MockTrainingJob { id: job_id, model_name: request.model_name.clone(), model_type: request.model_type, status: MockTrainingStatus::Running, progress: 0.0, start_time: Utc::now(), end_time: None, metrics: HashMap::new(), error_message: None, }; self.training_jobs.write().await.insert(job_id, job.clone()); // Simulate training in background let service = self.clone(); tokio::spawn(async move { service.simulate_training(job_id).await; }); Ok(MockTrainingResponse { job_id, status: MockTrainingStatus::Running, message: "Training job started".to_string(), }) } /// Get training job status pub async fn get_training_status( &self, job_id: Uuid, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let jobs = self.training_jobs.read().await; jobs.get(&job_id) .cloned() .ok_or(MockServiceError::JobNotFound(job_id)) } /// Get available models pub async fn get_models(&self) -> Result, MockServiceError> { self.simulate_latency().await; self.simulate_failure()?; let models = self.models.read().await; Ok(models.values().cloned().collect()) } /// Run inference with a model pub async fn run_inference( &self, request: MockInferenceRequest, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; // Simulate inference calculation let prediction = match request.model_name.as_str() { "momentum_model" => 0.75, // Bullish "mean_reversion_model" => -0.25, // Bearish _ => 0.0, // Neutral }; Ok(MockInferenceResponse { prediction, confidence: 0.85, features_used: request.features.len(), inference_time_ms: self.latency_ms, }) } /// Simulate training process async fn simulate_training(&self, job_id: Uuid) { let training_duration = Duration::from_secs(5); // Simulate 5 second training let update_interval = Duration::from_millis(500); let total_updates = training_duration.as_millis() / update_interval.as_millis(); for i in 0..total_updates { sleep(update_interval).await; let progress = (i as f64 + 1.0) / total_updates as f64; // Update job progress if let Ok(mut jobs) = self.training_jobs.try_write() { if let Some(job) = jobs.get_mut(&job_id) { job.progress = progress; // Add some metrics job.metrics .insert("loss".to_string(), 1.0 - (progress * 0.8)); job.metrics .insert("accuracy".to_string(), 0.5 + (progress * 0.4)); if progress >= 1.0 { job.status = MockTrainingStatus::Completed; job.end_time = Some(Utc::now()); // Save the trained model let model = MockModel { name: job.model_name.clone(), model_type: job.model_type, version: "1.0.0".to_string(), accuracy: job.metrics.get("accuracy").copied().unwrap_or(0.9), created_at: Utc::now(), file_path: format!("/models/{}_v1.0.0.bin", job.model_name), }; if let Ok(mut models) = self.models.try_write() { models.insert(model.name.clone(), model); } } } } } } async fn simulate_latency(&self) { if self.latency_ms > 0 { sleep(Duration::from_millis(self.latency_ms)).await; } } fn simulate_failure(&self) -> Result<(), MockServiceError> { if self.failure_rate > 0.0 { use rand::Rng; let mut rng = rand::thread_rng(); if rng.gen::() < self.failure_rate { return Err(MockServiceError::SimulatedFailure); } } Ok(()) } } // ============================================================================= // MOCK BACKTESTING SERVICE // ============================================================================= /// Mock backtesting service for testing strategy backtests #[derive(Debug, Clone)] pub struct MockBacktestingService { _config: TestConfig, backtests: Arc>>, latency_ms: u64, failure_rate: f64, } impl MockBacktestingService { pub fn new(config: TestConfig) -> Self { let latency_ms = config.services.mock_latency_ms; let failure_rate = config.services.mock_failure_rate; Self { latency_ms, failure_rate, _config: config, backtests: Arc::new(RwLock::new(HashMap::new())), } } /// Start a new backtest pub async fn start_backtest( &self, request: MockBacktestRequest, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let backtest_id = Uuid::new_v4(); let backtest = MockBacktest { id: backtest_id, strategy_name: request.strategy_name.clone(), symbols: request.symbols, start_date: request.start_date, end_date: request.end_date, status: MockBacktestStatus::Running, progress: 0.0, results: None, start_time: Utc::now(), end_time: None, }; self.backtests.write().await.insert(backtest_id, backtest); // Simulate backtest in background let service = self.clone(); tokio::spawn(async move { service.simulate_backtest(backtest_id).await; }); Ok(MockBacktestResponse { backtest_id, status: MockBacktestStatus::Running, message: "Backtest started".to_string(), }) } /// Get backtest status pub async fn get_backtest_status( &self, backtest_id: Uuid, ) -> Result { self.simulate_latency().await; self.simulate_failure()?; let backtests = self.backtests.read().await; backtests .get(&backtest_id) .cloned() .ok_or(MockServiceError::BacktestNotFound(backtest_id)) } /// Get all backtests pub async fn get_backtests(&self) -> Result, MockServiceError> { self.simulate_latency().await; self.simulate_failure()?; let backtests = self.backtests.read().await; Ok(backtests.values().cloned().collect()) } /// Simulate backtest process async fn simulate_backtest(&self, backtest_id: Uuid) { let backtest_duration = Duration::from_secs(3); // Simulate 3 second backtest let update_interval = Duration::from_millis(300); let total_updates = backtest_duration.as_millis() / update_interval.as_millis(); for i in 0..total_updates { sleep(update_interval).await; let progress = (i as f64 + 1.0) / total_updates as f64; if let Ok(mut backtests) = self.backtests.try_write() { if let Some(backtest) = backtests.get_mut(&backtest_id) { backtest.progress = progress; if progress >= 1.0 { backtest.status = MockBacktestStatus::Completed; backtest.end_time = Some(Utc::now()); // Generate mock results backtest.results = Some(MockBacktestResults { total_return: 0.15, // 15% return annualized_return: 0.12, volatility: 0.18, sharpe_ratio: 0.67, max_drawdown: 0.08, win_rate: 0.58, total_trades: 1250, profit_factor: 1.35, }); } } } } } async fn simulate_latency(&self) { if self.latency_ms > 0 { sleep(Duration::from_millis(self.latency_ms)).await; } } fn simulate_failure(&self) -> Result<(), MockServiceError> { if self.failure_rate > 0.0 { use rand::Rng; let mut rng = rand::thread_rng(); if rng.gen::() < self.failure_rate { return Err(MockServiceError::SimulatedFailure); } } Ok(()) } } // ============================================================================= // DATA STRUCTURES // ============================================================================= /// Mock order structure #[derive(Debug, Clone)] pub struct MockOrder { pub id: Uuid, pub order_id: u64, pub symbol: String, pub side: MockOrderSide, pub quantity: Decimal, pub price: Decimal, pub order_type: MockOrderType, pub status: MockOrderStatus, pub filled_quantity: Decimal, pub average_fill_price: Decimal, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockOrderSide { Buy, Sell, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockOrderType { Market, Limit, Stop, StopLimit, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockOrderStatus { Pending, PartiallyFilled, Filled, Cancelled, Rejected, } /// Mock position structure #[derive(Debug, Clone)] pub struct MockPosition { pub symbol: String, pub quantity: Decimal, pub average_price: Decimal, pub market_value: Decimal, pub unrealized_pnl: Decimal, pub updated_at: DateTime, } /// Mock order request #[derive(Debug, Clone)] pub struct MockOrderRequest { pub symbol: String, pub side: MockOrderSide, pub quantity: Decimal, pub price: Decimal, pub order_type: MockOrderType, } /// Mock order response #[derive(Debug, Clone)] pub struct MockOrderResponse { pub order_id: Uuid, pub status: MockOrderStatus, pub message: String, } /// Mock order event #[derive(Debug, Clone)] pub struct MockOrderEvent { pub order_id: Uuid, pub symbol: String, pub status: MockOrderStatus, pub filled_quantity: Decimal, pub timestamp: DateTime, } /// Mock training job #[derive(Debug, Clone)] pub struct MockTrainingJob { pub id: Uuid, pub model_name: String, pub model_type: MockModelType, pub status: MockTrainingStatus, pub progress: f64, pub start_time: DateTime, pub end_time: Option>, pub metrics: HashMap, pub error_message: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockModelType { Mamba, Transformer, DQN, PPO, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockTrainingStatus { Running, Completed, Failed, Cancelled, } /// Mock model #[derive(Debug, Clone)] pub struct MockModel { pub name: String, pub model_type: MockModelType, pub version: String, pub accuracy: f64, pub created_at: DateTime, pub file_path: String, } /// Mock training request #[derive(Debug, Clone)] pub struct MockTrainingRequest { pub model_name: String, pub model_type: MockModelType, pub data_path: String, pub hyperparameters: HashMap, } /// Mock training response #[derive(Debug, Clone)] pub struct MockTrainingResponse { pub job_id: Uuid, pub status: MockTrainingStatus, pub message: String, } /// Mock inference request #[derive(Debug, Clone)] pub struct MockInferenceRequest { pub model_name: String, pub features: Vec, } /// Mock inference response #[derive(Debug, Clone)] pub struct MockInferenceResponse { pub prediction: f64, pub confidence: f64, pub features_used: usize, pub inference_time_ms: u64, } /// Mock backtest #[derive(Debug, Clone)] pub struct MockBacktest { pub id: Uuid, pub strategy_name: String, pub symbols: Vec, pub start_date: DateTime, pub end_date: DateTime, pub status: MockBacktestStatus, pub progress: f64, pub results: Option, pub start_time: DateTime, pub end_time: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MockBacktestStatus { Running, Completed, Failed, Cancelled, } /// Mock backtest results #[derive(Debug, Clone)] pub struct MockBacktestResults { pub total_return: f64, pub annualized_return: f64, pub volatility: f64, pub sharpe_ratio: f64, pub max_drawdown: f64, pub win_rate: f64, pub total_trades: u64, pub profit_factor: f64, } /// Mock backtest request #[derive(Debug, Clone)] pub struct MockBacktestRequest { pub strategy_name: String, pub symbols: Vec, pub start_date: DateTime, pub end_date: DateTime, pub initial_capital: Decimal, } /// Mock backtest response #[derive(Debug, Clone)] pub struct MockBacktestResponse { pub backtest_id: Uuid, pub status: MockBacktestStatus, pub message: String, } /// Mock service errors #[derive(Debug, Clone, thiserror::Error)] pub enum MockServiceError { #[error("Order not found: {0}")] OrderNotFound(Uuid), #[error("Job not found: {0}")] JobNotFound(Uuid), #[error("Backtest not found: {0}")] BacktestNotFound(Uuid), #[error("Invalid operation: {0}")] InvalidOperation(String), #[error("Simulated failure")] SimulatedFailure, #[error("Service unavailable")] ServiceUnavailable, } // ============================================================================= // SERVICE FACTORY // ============================================================================= /// Factory for creating mock services #[derive(Debug)] pub struct MockServiceFactory { config: TestConfig, } impl MockServiceFactory { pub fn new(config: TestConfig) -> Self { Self { config } } pub fn create_trading_service(&self) -> MockTradingService { MockTradingService::new(self.config.clone()) } pub fn create_ml_training_service(&self) -> MockMLTrainingService { MockMLTrainingService::new(self.config.clone()) } pub fn create_backtesting_service(&self) -> MockBacktestingService { MockBacktestingService::new(self.config.clone()) } /// Create all services pub fn create_all_services( &self, ) -> ( MockTradingService, MockMLTrainingService, MockBacktestingService, ) { ( self.create_trading_service(), self.create_ml_training_service(), self.create_backtesting_service(), ) } } #[cfg(test)] mod tests { use super::*; use crate::fixtures::TEST_EQUITY_1; #[tokio::test] async fn test_mock_trading_service() { let config = TestConfig::for_unit_tests(); let service = MockTradingService::new(config); let request = MockOrderRequest { symbol: TEST_EQUITY_1.to_string(), side: MockOrderSide::Buy, quantity: Decimal::from(100), price: Decimal::from(150), order_type: MockOrderType::Limit, }; let response = service.submit_order(request).await.unwrap(); assert_eq!(response.status, MockOrderStatus::Pending); // Wait for processing tokio::time::sleep(Duration::from_millis(500)).await; let order = service.get_order_status(response.order_id).await.unwrap(); assert_eq!(order.status, MockOrderStatus::Filled); assert_eq!(order.filled_quantity, order.quantity); } #[tokio::test] async fn test_mock_ml_training_service() { let config = TestConfig::for_unit_tests(); let service = MockMLTrainingService::new(config); let request = MockTrainingRequest { model_name: "test_model".to_string(), model_type: MockModelType::Transformer, data_path: "/data/test.csv".to_string(), hyperparameters: HashMap::new(), }; let response = service.start_training(request).await.unwrap(); assert_eq!(response.status, MockTrainingStatus::Running); // Wait for training to complete tokio::time::sleep(Duration::from_secs(6)).await; let job = service.get_training_status(response.job_id).await.unwrap(); assert_eq!(job.status, MockTrainingStatus::Completed); assert_eq!(job.progress, 1.0); } #[tokio::test] async fn test_mock_backtesting_service() { let config = TestConfig::for_unit_tests(); let service = MockBacktestingService::new(config); let request = MockBacktestRequest { strategy_name: "test_strategy".to_string(), symbols: vec![TEST_EQUITY_1.to_string()], start_date: Utc::now() - chrono::Duration::days(30), end_date: Utc::now(), initial_capital: Decimal::from(100000), }; let response = service.start_backtest(request).await.unwrap(); assert_eq!(response.status, MockBacktestStatus::Running); // Wait for backtest to complete tokio::time::sleep(Duration::from_secs(4)).await; let backtest = service .get_backtest_status(response.backtest_id) .await .unwrap(); assert_eq!(backtest.status, MockBacktestStatus::Completed); assert!(backtest.results.is_some()); } #[tokio::test] async fn test_service_factory() { let config = TestConfig::for_unit_tests(); let factory = MockServiceFactory::new(config); let (trading, ml, backtesting) = factory.create_all_services(); // Test that all services are created assert_eq!(trading.latency_ms, 0); assert_eq!(ml.latency_ms, 0); assert_eq!(backtesting.latency_ms, 0); } #[tokio::test] async fn test_failure_simulation() { let mut config = TestConfig::for_unit_tests(); config.services.mock_failure_rate = 1.0; // 100% failure rate let service = MockTradingService::new(config); let request = MockOrderRequest { symbol: TEST_EQUITY_1.to_string(), side: MockOrderSide::Buy, quantity: Decimal::from(100), price: Decimal::from(150), order_type: MockOrderType::Limit, }; let result = service.submit_order(request).await; assert!(result.is_err()); assert!(matches!( result.unwrap_err(), MockServiceError::SimulatedFailure )); } }