//! Risk Limit Enforcement Integration Tests //! //! This module provides comprehensive integration tests for risk limit enforcement //! within the Foxhunt HFT system, including position limits, exposure limits, //! drawdown protection, and real-time risk monitoring. #![allow(unused_crate_dependencies)] use std::collections::HashMap; use std::sync::{Arc, atomic::{AtomicU64, AtomicBool, Ordering}}; use std::time::{Duration, Instant}; use tokio::sync::{RwLock, Mutex}; use uuid::Uuid; use serde_json::json; use chrono::{DateTime, Utc}; use tli::prelude::*; use crate::fixtures::{IntegrationTestConfig, TestEnvironment, TestMetricsCollector}; use crate::mocks::{MockTradingService, MockRiskService, TestDatabaseManager}; /// Risk limit enforcement integration tests pub struct RiskEnforcementTests { client_suite: TliClientSuite, mock_trading_service: MockTradingService, mock_risk_service: MockRiskService, test_db: TestDatabaseManager, risk_manager: Arc, position_tracker: Arc, metrics: Arc, config: IntegrationTestConfig, test_accounts: Arc>>, } /// Risk enforcement performance metrics #[derive(Debug, Default)] pub struct RiskMetrics { pub risk_check_latency: AtomicU64, pub position_update_latency: AtomicU64, pub limit_breach_detection_latency: AtomicU64, pub risk_checks_performed: AtomicU64, pub orders_rejected: AtomicU64, pub positions_liquidated: AtomicU64, pub limit_breaches_detected: AtomicU64, pub emergency_stops_triggered: AtomicU64, } impl RiskMetrics { pub fn new() -> Self { Self::default() } pub fn record_risk_check(&self, latency_ns: u64) { self.risk_check_latency.store(latency_ns, Ordering::Relaxed); self.risk_checks_performed.fetch_add(1, Ordering::Relaxed); } pub fn record_position_update(&self, latency_ns: u64) { self.position_update_latency.store(latency_ns, Ordering::Relaxed); } pub fn record_limit_breach(&self, latency_ns: u64) { self.limit_breach_detection_latency.store(latency_ns, Ordering::Relaxed); self.limit_breaches_detected.fetch_add(1, Ordering::Relaxed); } pub fn record_order_rejection(&self) { self.orders_rejected.fetch_add(1, Ordering::Relaxed); } pub fn record_position_liquidation(&self) { self.positions_liquidated.fetch_add(1, Ordering::Relaxed); } pub fn record_emergency_stop(&self) { self.emergency_stops_triggered.fetch_add(1, Ordering::Relaxed); } pub fn get_summary(&self) -> serde_json::Value { json!({ "risk_check_latency_ns": self.risk_check_latency.load(Ordering::Relaxed), "position_update_latency_ns": self.position_update_latency.load(Ordering::Relaxed), "limit_breach_detection_latency_ns": self.limit_breach_detection_latency.load(Ordering::Relaxed), "risk_checks_performed": self.risk_checks_performed.load(Ordering::Relaxed), "orders_rejected": self.orders_rejected.load(Ordering::Relaxed), "positions_liquidated": self.positions_liquidated.load(Ordering::Relaxed), "limit_breaches_detected": self.limit_breaches_detected.load(Ordering::Relaxed), "emergency_stops_triggered": self.emergency_stops_triggered.load(Ordering::Relaxed) }) } } /// Test account for risk enforcement testing #[derive(Debug, Clone)] pub struct TestAccount { pub account_id: String, pub balance: Decimal, pub available_balance: Decimal, pub position_limits: HashMap, // Symbol -> Max position size pub exposure_limit: Decimal, pub daily_loss_limit: Decimal, pub max_drawdown_pct: Decimal, pub leverage_limit: Decimal, pub positions: HashMap, pub daily_pnl: Decimal, pub max_daily_drawdown: Decimal, } impl TestAccount { pub fn new(account_id: &str, balance: Decimal) -> Self { Self { account_id: account_id.to_string(), balance, available_balance: balance, position_limits: HashMap::new(), exposure_limit: balance * Decimal::new(5, 0), // 5x leverage limit daily_loss_limit: balance * Decimal::new(10, 2), // 10% daily loss limit max_drawdown_pct: Decimal::new(20, 2), // 20% max drawdown leverage_limit: Decimal::new(10, 0), // 10x max leverage positions: HashMap::new(), daily_pnl: Decimal::ZERO, max_daily_drawdown: Decimal::ZERO, } } pub fn set_position_limit(&mut self, symbol: &str, limit: Decimal) { self.position_limits.insert(symbol.to_string(), limit); } pub fn get_position_limit(&self, symbol: &str) -> Option { self.position_limits.get(symbol).copied() } pub fn get_current_exposure(&self) -> Decimal { self.positions.values() .map(|pos| pos.quantity.abs() * pos.average_price) .sum() } pub fn update_position(&mut self, symbol: &str, quantity: Decimal, price: Decimal) { let position = self.positions.entry(symbol.to_string()).or_insert_with(|| { Position { symbol: symbol.to_string(), quantity: Decimal::ZERO, average_price: Decimal::ZERO, unrealized_pnl: Decimal::ZERO, realized_pnl: Decimal::ZERO, } }); // Update position quantity and average price if position.quantity.is_zero() { position.quantity = quantity; position.average_price = price; } else if position.quantity.is_sign_positive() == quantity.is_sign_positive() { // Adding to position let total_cost = position.quantity * position.average_price + quantity * price; position.quantity += quantity; if !position.quantity.is_zero() { position.average_price = total_cost / position.quantity; } } else { // Reducing or reversing position let reduction = quantity.abs().min(position.quantity.abs()); let realized = reduction * (price - position.average_price) * if position.quantity.is_sign_positive() { Decimal::ONE } else { -Decimal::ONE }; position.realized_pnl += realized; position.quantity += quantity; if position.quantity.is_zero() { position.average_price = Decimal::ZERO; } } } } /// Position information #[derive(Debug, Clone)] pub struct Position { pub symbol: String, pub quantity: Decimal, pub average_price: Decimal, pub unrealized_pnl: Decimal, pub realized_pnl: Decimal, } impl RiskEnforcementTests { /// Create new risk enforcement tests instance pub async fn new(config: IntegrationTestConfig) -> TliResult { let test_env = TestEnvironment::new(config.clone()).await?; // Initialize mock services let mock_trading_service = MockTradingService::new().await?; let mock_risk_service = MockRiskService::new().await?; let test_db = TestDatabaseManager::new(&config.test_db_url).await?; // Initialize risk management components let risk_config = RiskManagerConfig { max_position_check_latency_ns: config.max_risk_latency_ns, enable_real_time_monitoring: true, position_limit_buffer_pct: Decimal::new(5, 2), // 5% buffer exposure_limit_buffer_pct: Decimal::new(10, 2), // 10% buffer emergency_liquidation_threshold_pct: Decimal::new(95, 2), // 95% of limit }; let risk_manager = Arc::new(RiskManager::new(risk_config).await?); let position_tracker = Arc::new(PositionTracker::new().await?); // Create TLI client suite let client_suite = TliClientBuilder::new() .with_service_endpoint( "trading_service".to_string(), format!("http://localhost:{}", mock_trading_service.port()) ) .with_service_endpoint( "risk_service".to_string(), format!("http://localhost:{}", mock_risk_service.port()) ) .with_trading_config(TradingClientConfig::default()) .build() .await?; Ok(Self { client_suite, mock_trading_service, mock_risk_service, test_db, risk_manager, position_tracker, metrics: Arc::new(RiskMetrics::new()), config, test_accounts: Arc::new(RwLock::new(HashMap::new())), }) } /// Test position limit enforcement pub async fn test_position_limit_enforcement(&mut self) -> TliResult { let mut test_result = TestResult::new("position_limit_enforcement"); let start_time = Instant::now(); println!("๐Ÿ”„ Testing position limit enforcement..."); // Setup test account with position limits let account_id = "POSITION_LIMIT_TEST"; let mut test_account = TestAccount::new(account_id, Decimal::new(100000, 0)); // $100,000 test_account.set_position_limit("AAPL", Decimal::new(1000, 0)); // 1,000 shares max test_account.set_position_limit("GOOGL", Decimal::new(100, 0)); // 100 shares max // Register account with risk manager self.risk_manager.register_account(account_id, &test_account).await?; self.test_accounts.write().await.insert(account_id.to_string(), test_account); // Test 1: Order within position limit should be accepted let within_limit_start = Instant::now(); let within_limit_order = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 500.0, // Within 1,000 limit client_order_id: "within_limit_order".to_string(), account_id: Some(account_id.to_string()), ..Default::default() }; let within_limit_result = if let Some(trading_client) = &self.client_suite.trading_client { trading_client.submit_order(within_limit_order).await } else { return Err(TliError::Other("Trading client not available".to_string())); }; let risk_check_latency = within_limit_start.elapsed().as_nanos() as u64; self.metrics.record_risk_check(risk_check_latency); test_result.add_assertion( "Order within position limit accepted", within_limit_result.is_ok() ); test_result.add_assertion( &format!("Risk check latency < {}ยตs (got {}ns)", self.config.max_risk_latency_ns / 1000, risk_check_latency), risk_check_latency < self.config.max_risk_latency_ns ); // Update position after successful order if within_limit_result.is_ok() { let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { account.update_position("AAPL", Decimal::new(500, 0), Decimal::new(150, 0)); } } // Test 2: Order that would exceed position limit should be rejected let exceed_limit_start = Instant::now(); let exceed_limit_order = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 600.0, // Would exceed 1,000 limit (500 existing + 600 = 1,100) client_order_id: "exceed_limit_order".to_string(), account_id: Some(account_id.to_string()), ..Default::default() }; let exceed_limit_result = if let Some(trading_client) = &self.client_suite.trading_client { trading_client.submit_order(exceed_limit_order).await } else { return Err(TliError::Other("Trading client not available".to_string())); }; let rejection_latency = exceed_limit_start.elapsed().as_nanos() as u64; self.metrics.record_risk_check(rejection_latency); let order_rejected = exceed_limit_result.is_err(); if order_rejected { self.metrics.record_order_rejection(); } test_result.add_assertion( "Order exceeding position limit rejected", order_rejected ); test_result.add_assertion( &format!("Risk rejection latency < {}ยตs (got {}ns)", self.config.max_risk_latency_ns / 1000, rejection_latency), rejection_latency < self.config.max_risk_latency_ns ); // Test 3: Order that exactly reaches limit should be accepted let exact_limit_order = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 500.0, // Exactly reaches 1,000 limit client_order_id: "exact_limit_order".to_string(), account_id: Some(account_id.to_string()), ..Default::default() }; let exact_limit_result = if let Some(trading_client) = &self.client_suite.trading_client { trading_client.submit_order(exact_limit_order).await } else { return Err(TliError::Other("Trading client not available".to_string())); }; test_result.add_assertion( "Order exactly at position limit accepted", exact_limit_result.is_ok() ); test_result.set_passed(test_result.assertions.iter().all(|a| a.passed)); test_result.execution_time = start_time.elapsed(); // Store position limit test metadata test_result.metadata.insert("within_limit_latency_ns".to_string(), json!(risk_check_latency)); test_result.metadata.insert("rejection_latency_ns".to_string(), json!(rejection_latency)); test_result.metadata.insert("orders_rejected".to_string(), json!(self.metrics.orders_rejected.load(Ordering::Relaxed))); println!("โœ… Position limit enforcement test completed"); Ok(test_result) } /// Test exposure limit enforcement pub async fn test_exposure_limit_enforcement(&mut self) -> TliResult { let mut test_result = TestResult::new("exposure_limit_enforcement"); let start_time = Instant::now(); println!("๐Ÿ”„ Testing exposure limit enforcement..."); // Setup test account with exposure limits let account_id = "EXPOSURE_LIMIT_TEST"; let balance = Decimal::new(50000, 0); // $50,000 let mut test_account = TestAccount::new(account_id, balance); test_account.exposure_limit = balance * Decimal::new(3, 0); // 3x leverage = $150,000 max exposure self.risk_manager.register_account(account_id, &test_account).await?; self.test_accounts.write().await.insert(account_id.to_string(), test_account); // Test 1: Build position within exposure limit let symbols_and_prices = vec![ ("AAPL", 150.0, 300.0), // $45,000 exposure ("GOOGL", 2500.0, 20.0), // $50,000 exposure ]; let mut total_exposure = Decimal::ZERO; for (symbol, price, quantity) in &symbols_and_prices { let exposure_start = Instant::now(); let order = SubmitOrderRequest { symbol: symbol.to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: *quantity, price: Some(*price), client_order_id: format!("exposure_order_{}", symbol), account_id: Some(account_id.to_string()), ..Default::default() }; if let Some(trading_client) = &self.client_suite.trading_client { let result = trading_client.submit_order(order).await; let exposure_check_latency = exposure_start.elapsed().as_nanos() as u64; self.metrics.record_risk_check(exposure_check_latency); if result.is_ok() { total_exposure += Decimal::new(*quantity as i64, 0) * Decimal::new((*price * 100.0) as i64, 2); // Update account position let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { account.update_position(symbol, Decimal::new(*quantity as i64, 0), Decimal::new((*price * 100.0) as i64, 2)); } } test_result.add_assertion( &format!("Order for {} within exposure limit accepted", symbol), result.is_ok() ); } } // Test 2: Order that would exceed exposure limit should be rejected let exceed_exposure_start = Instant::now(); let exceed_order = SubmitOrderRequest { symbol: "TSLA".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 300.0, // At $200/share = $60,000, would exceed remaining limit price: Some(200.0), client_order_id: "exceed_exposure_order".to_string(), account_id: Some(account_id.to_string()), ..Default::default() }; let exceed_result = if let Some(trading_client) = &self.client_suite.trading_client { trading_client.submit_order(exceed_order).await } else { return Err(TliError::Other("Trading client not available".to_string())); }; let exposure_rejection_latency = exceed_exposure_start.elapsed().as_nanos() as u64; self.metrics.record_risk_check(exposure_rejection_latency); let exposure_order_rejected = exceed_result.is_err(); if exposure_order_rejected { self.metrics.record_order_rejection(); } test_result.add_assertion( "Order exceeding exposure limit rejected", exposure_order_rejected ); test_result.add_assertion( &format!("Exposure check latency < {}ยตs (got {}ns)", self.config.max_risk_latency_ns / 1000, exposure_rejection_latency), exposure_rejection_latency < self.config.max_risk_latency_ns ); // Test 3: Real-time exposure monitoring let monitoring_start = Instant::now(); // Simulate price movements that increase exposure let price_updates = vec![ ("AAPL", 160.0), // +6.67% increase ("GOOGL", 2700.0), // +8% increase ]; for (symbol, new_price) in price_updates { // Update position with new market price let position_update_start = Instant::now(); let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { if let Some(position) = account.positions.get_mut(symbol) { let old_value = position.quantity * position.average_price; let new_value = position.quantity * Decimal::new((new_price * 100.0) as i64, 2); position.unrealized_pnl = new_value - old_value; } } let position_update_latency = position_update_start.elapsed().as_nanos() as u64; self.metrics.record_position_update(position_update_latency); // Check if exposure limit is breached let current_exposure = { let accounts = self.test_accounts.read().await; accounts.get(account_id).map(|acc| acc.get_current_exposure()).unwrap_or(Decimal::ZERO) }; let exposure_limit = balance * Decimal::new(3, 0); if current_exposure > exposure_limit { let breach_latency = monitoring_start.elapsed().as_nanos() as u64; self.metrics.record_limit_breach(breach_latency); } } test_result.add_assertion( "Real-time exposure monitoring active", self.metrics.risk_checks_performed.load(Ordering::Relaxed) > 0 ); test_result.set_passed(test_result.assertions.iter().all(|a| a.passed)); test_result.execution_time = start_time.elapsed(); println!("โœ… Exposure limit enforcement test completed"); Ok(test_result) } /// Test drawdown protection mechanisms pub async fn test_drawdown_protection(&mut self) -> TliResult { let mut test_result = TestResult::new("drawdown_protection"); let start_time = Instant::now(); println!("๐Ÿ”„ Testing drawdown protection mechanisms..."); // Setup test account with drawdown limits let account_id = "DRAWDOWN_TEST"; let initial_balance = Decimal::new(100000, 0); // $100,000 let mut test_account = TestAccount::new(account_id, initial_balance); test_account.daily_loss_limit = initial_balance * Decimal::new(5, 2); // 5% daily loss limit test_account.max_drawdown_pct = Decimal::new(10, 2); // 10% max drawdown self.risk_manager.register_account(account_id, &test_account).await?; self.test_accounts.write().await.insert(account_id.to_string(), test_account); // Build initial profitable position let initial_order = SubmitOrderRequest { symbol: "PROFIT_STOCK".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 1000.0, price: Some(100.0), client_order_id: "initial_position".to_string(), account_id: Some(account_id.to_string()), ..Default::default() }; if let Some(trading_client) = &self.client_suite.trading_client { let _ = trading_client.submit_order(initial_order).await; } // Update position to be profitable initially { let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { account.update_position("PROFIT_STOCK", Decimal::new(1000, 0), Decimal::new(10000, 2)); account.daily_pnl = Decimal::new(5000, 0); // $5,000 profit } } // Simulate adverse price movements causing losses let loss_scenarios = vec![ ("PROFIT_STOCK", 95.0, "2% loss"), // Position value drops to $95,000 ("PROFIT_STOCK", 90.0, "5% loss"), // Position value drops to $90,000 ("PROFIT_STOCK", 85.0, "8% loss"), // Position value drops to $85,000 ]; for (symbol, new_price, scenario) in loss_scenarios { let drawdown_check_start = Instant::now(); // Update position with loss let mut current_pnl = Decimal::ZERO; { let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { if let Some(position) = account.positions.get_mut(symbol) { let new_value = position.quantity * Decimal::new((new_price * 100.0) as i64, 2); let cost_basis = position.quantity * position.average_price; position.unrealized_pnl = new_value - cost_basis; current_pnl = position.unrealized_pnl; // Update daily PnL account.daily_pnl = position.unrealized_pnl; // Track maximum drawdown if account.daily_pnl < account.max_daily_drawdown { account.max_daily_drawdown = account.daily_pnl; } } } } // Check if drawdown limits are breached let daily_loss_pct = current_pnl.abs() / initial_balance * Decimal::new(100, 0); let max_drawdown_pct = { let accounts = self.test_accounts.read().await; accounts.get(account_id) .map(|acc| acc.max_daily_drawdown.abs() / initial_balance * Decimal::new(100, 0)) .unwrap_or(Decimal::ZERO) }; let drawdown_check_latency = drawdown_check_start.elapsed().as_nanos() as u64; // Test if new orders are blocked when approaching limits if daily_loss_pct > Decimal::new(4, 2) { // Above 4% loss let risk_order = SubmitOrderRequest { symbol: "RISKY_STOCK".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: Some(50.0), client_order_id: format!("risk_order_{}", scenario.replace(" ", "_")), account_id: Some(account_id.to_string()), ..Default::default() }; let risk_order_result = if let Some(trading_client) = &self.client_suite.trading_client { trading_client.submit_order(risk_order).await } else { return Err(TliError::Other("Trading client not available".to_string())); }; let order_blocked = risk_order_result.is_err(); if order_blocked { self.metrics.record_order_rejection(); } test_result.add_assertion( &format!("New order blocked during {} scenario", scenario), order_blocked ); } // Record breach detection if limits exceeded if daily_loss_pct > Decimal::new(5, 2) || max_drawdown_pct > Decimal::new(10, 2) { self.metrics.record_limit_breach(drawdown_check_latency); test_result.add_assertion( &format!("Drawdown limit breach detected for {}", scenario), true ); } self.metrics.record_risk_check(drawdown_check_latency); println!("๐Ÿ“Š {} scenario: Daily PnL = {:.2}%, Max Drawdown = {:.2}%", scenario, daily_loss_pct, max_drawdown_pct); } // Test emergency liquidation trigger let emergency_start = Instant::now(); // Simulate severe loss that triggers emergency liquidation { let mut accounts = self.test_accounts.write().await; if let Some(account) = accounts.get_mut(account_id) { account.daily_pnl = initial_balance * Decimal::new(-12, 2); // -12% loss (exceeds 10% limit) account.max_daily_drawdown = account.daily_pnl; } } // Check if emergency stop is triggered let emergency_triggered = self.risk_manager.check_emergency_conditions(account_id).await?; if emergency_triggered { self.metrics.record_emergency_stop(); let emergency_latency = emergency_start.elapsed().as_nanos() as u64; self.metrics.record_limit_breach(emergency_latency); } test_result.add_assertion( "Emergency liquidation triggered for severe drawdown", emergency_triggered ); test_result.add_assertion( "Drawdown monitoring latency acceptable", self.metrics.limit_breach_detection_latency.load(Ordering::Relaxed) < self.config.max_risk_latency_ns * 2 ); test_result.set_passed(test_result.assertions.iter().all(|a| a.passed)); test_result.execution_time = start_time.elapsed(); println!("โœ… Drawdown protection test completed"); Ok(test_result) } /// Test real-time risk monitoring performance pub async fn test_real_time_risk_monitoring(&mut self) -> TliResult { let mut test_result = TestResult::new("real_time_risk_monitoring"); let start_time = Instant::now(); println!("๐Ÿ”„ Testing real-time risk monitoring performance..."); // Setup multiple test accounts for stress testing let account_count = 10; let orders_per_account = 50; for i in 0..account_count { let account_id = format!("MONITOR_TEST_{}", i); let test_account = TestAccount::new(&account_id, Decimal::new(50000, 0)); self.risk_manager.register_account(&account_id, &test_account).await?; self.test_accounts.write().await.insert(account_id, test_account); } // Generate concurrent order flow to stress test risk monitoring let mut order_tasks = Vec::new(); let monitoring_start = Instant::now(); for account_idx in 0..account_count { let account_id = format!("MONITOR_TEST_{}", account_idx); let client_suite = self.client_suite.clone(); let metrics = Arc::clone(&self.metrics); let task = tokio::spawn(async move { let mut successful_orders = 0; let mut rejected_orders = 0; for order_idx in 0..orders_per_account { let order_start = Instant::now(); let order = SubmitOrderRequest { symbol: format!("STOCK_{}", order_idx % 5), side: if order_idx % 2 == 0 { OrderSide::Buy as i32 } else { OrderSide::Sell as i32 }, order_type: OrderType::Market as i32, quantity: 10.0 + (order_idx as f64), price: Some(100.0 + (order_idx as f64 * 0.1)), client_order_id: format!("monitor_order_{}_{}", account_idx, order_idx), account_id: Some(account_id.clone()), ..Default::default() }; if let Some(trading_client) = &client_suite.trading_client { match trading_client.submit_order(order).await { Ok(_) => successful_orders += 1, Err(_) => rejected_orders += 1, } } let order_latency = order_start.elapsed().as_nanos() as u64; metrics.record_risk_check(order_latency); // Small delay to simulate realistic order flow tokio::time::sleep(Duration::from_millis(1)).await; } (successful_orders, rejected_orders) }); order_tasks.push(task); } // Wait for all order tasks to complete let task_results: Vec<_> = futures::future::join_all(order_tasks).await; let monitoring_duration = monitoring_start.elapsed(); // Collect results let mut total_successful = 0; let mut total_rejected = 0; let mut task_errors = 0; for result in task_results { match result { Ok((successful, rejected)) => { total_successful += successful; total_rejected += rejected; } Err(_) => task_errors += 1, } } let total_orders = account_count * orders_per_account; let total_processed = total_successful + total_rejected; let processing_rate = total_processed as f64 / monitoring_duration.as_secs_f64(); // Performance assertions test_result.add_assertion( &format!("All {} orders processed", total_orders), total_processed == total_orders && task_errors == 0 ); test_result.add_assertion( &format!("Processing rate > {} orders/sec (got {:.0})", self.config.min_throughput_ops_per_sec, processing_rate), processing_rate > self.config.min_throughput_ops_per_sec ); let avg_risk_check_latency = self.metrics.risk_check_latency.load(Ordering::Relaxed); test_result.add_assertion( &format!("Average risk check latency < {}ยตs (got {}ns)", self.config.max_risk_latency_ns / 1000, avg_risk_check_latency), avg_risk_check_latency < self.config.max_risk_latency_ns ); test_result.add_assertion( "Risk monitoring system stable under load", task_errors == 0 ); test_result.set_passed(test_result.assertions.iter().all(|a| a.passed)); test_result.execution_time = start_time.elapsed(); // Store performance metadata test_result.metadata.insert("total_orders".to_string(), json!(total_orders)); test_result.metadata.insert("successful_orders".to_string(), json!(total_successful)); test_result.metadata.insert("rejected_orders".to_string(), json!(total_rejected)); test_result.metadata.insert("processing_rate_ops_per_sec".to_string(), json!(processing_rate)); test_result.metadata.insert("avg_latency_ns".to_string(), json!(avg_risk_check_latency)); println!("โœ… Real-time risk monitoring test completed: {:.0} orders/sec", processing_rate); Ok(test_result) } /// Run all risk enforcement integration tests pub async fn run_all_tests(&mut self) -> TliResult { let mut test_suite = TestSuite::new("risk_enforcement_integration"); println!("๐Ÿš€ Starting risk limit enforcement integration tests..."); // Run individual test methods let tests = vec![ self.test_position_limit_enforcement().await, self.test_exposure_limit_enforcement().await, self.test_drawdown_protection().await, self.test_real_time_risk_monitoring().await, ]; // Collect results for test_result in tests { match test_result { Ok(result) => { test_suite.add_test_result(result); } Err(e) => { let mut error_result = TestResult::new("risk_enforcement_test_error"); error_result.add_error(format!("Test execution failed: {}", e)); test_suite.add_test_result(error_result); } } } // Calculate overall success test_suite.set_passed(test_suite.passed_tests == test_suite.total_tests); // Add risk metrics to test suite metadata let metrics_summary = self.metrics.get_summary(); test_suite.metadata.insert("risk_metrics".to_string(), metrics_summary); println!("๐Ÿ Risk limit enforcement integration tests completed: {}/{} passed", test_suite.passed_tests, test_suite.total_tests); Ok(test_suite) } } /// Test result structure #[derive(Debug, Clone)] pub struct TestResult { pub name: String, pub passed: bool, pub execution_time: Duration, pub assertions: Vec, pub errors: Vec, pub metadata: HashMap, } impl TestResult { pub fn new(name: &str) -> Self { Self { name: name.to_string(), passed: false, execution_time: Duration::default(), assertions: Vec::new(), errors: Vec::new(), metadata: HashMap::new(), } } pub fn add_assertion(&mut self, description: &str, passed: bool) { self.assertions.push(Assertion { description: description.to_string(), passed, }); } pub fn add_error(&mut self, error: String) { self.errors.push(error); } pub fn set_passed(&mut self, passed: bool) { self.passed = passed; } } /// Individual test assertion #[derive(Debug, Clone)] pub struct Assertion { pub description: String, pub passed: bool, } /// Test suite containing multiple test results #[derive(Debug, Clone)] pub struct TestSuite { pub name: String, pub tests: Vec, pub passed_tests: usize, pub total_tests: usize, pub passed: bool, pub execution_time: Duration, pub metadata: HashMap, } impl TestSuite { pub fn new(name: &str) -> Self { Self { name: name.to_string(), tests: Vec::new(), passed_tests: 0, total_tests: 0, passed: false, execution_time: Duration::default(), metadata: HashMap::new(), } } pub fn add_test_result(&mut self, test: TestResult) { if test.passed { self.passed_tests += 1; } self.total_tests += 1; self.tests.push(test); } pub fn set_passed(&mut self, passed: bool) { self.passed = passed; } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_risk_metrics() { let metrics = RiskMetrics::new(); metrics.record_risk_check(25_000); // 25ยตs metrics.record_order_rejection(); metrics.record_limit_breach(50_000); // 50ยตs let summary = metrics.get_summary(); assert_eq!(summary["risk_checks_performed"].as_u64().unwrap(), 1); assert_eq!(summary["orders_rejected"].as_u64().unwrap(), 1); assert_eq!(summary["limit_breaches_detected"].as_u64().unwrap(), 1); } #[test] fn test_account_position_limits() { let mut account = TestAccount::new("TEST", Decimal::new(10000, 0)); account.set_position_limit("AAPL", Decimal::new(1000, 0)); assert_eq!(account.get_position_limit("AAPL"), Some(Decimal::new(1000, 0))); assert_eq!(account.get_position_limit("GOOGL"), None); } #[test] fn test_position_updates() { let mut account = TestAccount::new("TEST", Decimal::new(10000, 0)); // Initial position account.update_position("AAPL", Decimal::new(100, 0), Decimal::new(15000, 2)); assert_eq!(account.positions["AAPL"].quantity, Decimal::new(100, 0)); assert_eq!(account.positions["AAPL"].average_price, Decimal::new(15000, 2)); // Add to position account.update_position("AAPL", Decimal::new(50, 0), Decimal::new(16000, 2)); assert_eq!(account.positions["AAPL"].quantity, Decimal::new(150, 0)); } }