#![allow( unused_comparisons, clippy::absurd_extreme_comparisons, clippy::impossible_comparisons )] //! Comprehensive Unit Tests for Utils Module //! //! This test suite validates all utility functions including order validation, //! risk calculations, metrics tracking, position management, and helper functions. use trading_service::utils::*; // ============================================================================ // Order Validation Tests // ============================================================================ #[test] fn test_order_validator_default() { let validator = validation::OrderValidator::default(); // Default values should be reasonable assert!(validator.validate_order_size(100.0).is_ok()); assert!(validator.validate_symbol("ES.FUT").is_ok()); } #[test] fn test_order_validator_size_valid() { let validator = validation::OrderValidator::new( 1000.0, // max 1.0, // min 5.0, // price deviation false, // symbol validation None, // allowed symbols ); // Valid sizes assert!(validator.validate_order_size(1.0).is_ok()); assert!(validator.validate_order_size(500.0).is_ok()); assert!(validator.validate_order_size(1000.0).is_ok()); } #[test] fn test_order_validator_size_below_minimum() { let validator = validation::OrderValidator::new(1000.0, 10.0, 5.0, false, None); let result = validator.validate_order_size(5.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("below minimum")); } #[test] fn test_order_validator_size_above_maximum() { let validator = validation::OrderValidator::new(1000.0, 1.0, 5.0, false, None); let result = validator.validate_order_size(2000.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("exceeds maximum")); } #[test] fn test_order_validator_size_negative() { let validator = validation::OrderValidator::default(); let result = validator.validate_order_size(-10.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("positive")); } #[test] fn test_order_validator_size_zero() { let validator = validation::OrderValidator::default(); let result = validator.validate_order_size(0.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("positive")); } #[test] fn test_order_validator_price_valid() { let validator = validation::OrderValidator::new(1000.0, 1.0, 5.0, false, None); // Price within 5% of market price assert!(validator.validate_price(100.0, 100.0).is_ok()); // Exact match assert!(validator.validate_price(104.0, 100.0).is_ok()); // +4% deviation assert!(validator.validate_price(96.0, 100.0).is_ok()); // -4% deviation assert!(validator.validate_price(105.0, 100.0).is_ok()); // +5% deviation (edge) assert!(validator.validate_price(95.0, 100.0).is_ok()); // -5% deviation (edge) } #[test] fn test_order_validator_price_exceeds_deviation() { let validator = validation::OrderValidator::new(1000.0, 1.0, 5.0, false, None); // Price deviates >5% from market price let result = validator.validate_price(110.0, 100.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("deviation")); assert!(err_msg.contains("exceeds maximum")); } #[test] fn test_order_validator_price_negative() { let validator = validation::OrderValidator::default(); let result = validator.validate_price(-50.0, 100.0); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("Price must be positive")); } #[test] fn test_order_validator_price_zero() { let validator = validation::OrderValidator::default(); let result = validator.validate_price(0.0, 100.0); assert!(result.is_err()); } #[test] fn test_order_validator_symbol_validation_disabled() { let validator = validation::OrderValidator::new(1000.0, 1.0, 5.0, false, None); // All symbols valid when validation disabled assert!(validator.validate_symbol("ES.FUT").is_ok()); assert!(validator.validate_symbol("INVALID").is_ok()); assert!(validator.validate_symbol("ANYTHING").is_ok()); } #[test] fn test_order_validator_symbol_validation_enabled() { let allowed = vec![ "ES.FUT".to_string(), "NQ.FUT".to_string(), "ZN.FUT".to_string(), ]; let validator = validation::OrderValidator::new(1000.0, 1.0, 5.0, true, Some(allowed)); // Valid symbols assert!(validator.validate_symbol("ES.FUT").is_ok()); assert!(validator.validate_symbol("NQ.FUT").is_ok()); assert!(validator.validate_symbol("ZN.FUT").is_ok()); // Invalid symbols let result = validator.validate_symbol("INVALID"); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("not in allowed list")); } #[test] fn test_order_validator_symbol_empty() { let validator = validation::OrderValidator::default(); let result = validator.validate_symbol(""); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("cannot be empty")); } #[test] fn test_order_validator_order_type_market_valid() { let validator = validation::OrderValidator::default(); // Market orders must use IOC or FOK assert!(validator.validate_order_type("MARKET", "IOC").is_ok()); assert!(validator.validate_order_type("MARKET", "FOK").is_ok()); } #[test] fn test_order_validator_order_type_market_invalid() { let validator = validation::OrderValidator::default(); // Market orders cannot use GTC let result = validator.validate_order_type("MARKET", "GTC"); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("must use IOC or FOK")); } #[test] fn test_order_validator_order_type_limit_valid() { let validator = validation::OrderValidator::default(); // Limit orders can use any TIF assert!(validator.validate_order_type("LIMIT", "IOC").is_ok()); assert!(validator.validate_order_type("LIMIT", "FOK").is_ok()); assert!(validator.validate_order_type("LIMIT", "GTC").is_ok()); assert!(validator.validate_order_type("LIMIT", "DAY").is_ok()); } #[test] fn test_order_validator_order_type_stop_valid() { let validator = validation::OrderValidator::default(); assert!(validator.validate_order_type("STOP", "GTC").is_ok()); assert!(validator.validate_order_type("STOP_LIMIT", "DAY").is_ok()); } #[test] fn test_order_validator_order_type_invalid() { let validator = validation::OrderValidator::default(); let result = validator.validate_order_type("INVALID_TYPE", "GTC"); assert!(result.is_err()); let err_msg = format!("{:?}", result.unwrap_err()); assert!(err_msg.contains("Invalid order type")); } // ============================================================================ // Risk Calculation Tests // ============================================================================ #[test] fn test_risk_calculator_default() { let calculator = risk::TradingRiskCalculator::default(); let risk = calculator.calculate_position_risk(50_000.0, 200_000.0); assert_eq!(risk.position_value, 50_000.0); assert_eq!(risk.portfolio_value, 200_000.0); assert_eq!(risk.position_ratio, 0.25); // 25% assert!(!risk.is_over_limit); // 50k < 100k default limit } #[test] fn test_risk_calculator_position_within_limit() { let calculator = risk::TradingRiskCalculator::new(100_000.0); let risk = calculator.calculate_position_risk(75_000.0, 300_000.0); assert_eq!(risk.position_value, 75_000.0); assert_eq!(risk.position_ratio, 0.25); // 75k/300k = 25% assert_eq!(risk.risk_score, 0.75); // 75k/100k = 75% assert!(!risk.is_over_limit); } #[test] fn test_risk_calculator_position_over_limit() { let calculator = risk::TradingRiskCalculator::new(100_000.0); let risk = calculator.calculate_position_risk(150_000.0, 500_000.0); assert_eq!(risk.position_value, 150_000.0); assert_eq!(risk.position_ratio, 0.30); // 150k/500k = 30% assert_eq!(risk.risk_score, 1.0); // High risk (over limit) assert!(risk.is_over_limit); } #[test] fn test_risk_calculator_zero_portfolio() { let calculator = risk::TradingRiskCalculator::new(100_000.0); let risk = calculator.calculate_position_risk(50_000.0, 0.0); assert_eq!(risk.position_ratio, 0.0); // Avoid division by zero assert_eq!(risk.risk_score, 0.5); // 50k/100k = 50% } // ============================================================================ // Monitoring Tests // ============================================================================ #[test] fn test_trading_metrics_new() { let metrics = monitoring::TradingMetrics::new(); let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.order_count, 0); assert_eq!(snapshot.fill_count, 0); assert_eq!(snapshot.cancel_count, 0); assert_eq!(snapshot.reject_count, 0); assert_eq!(snapshot.fill_rate, 0.0); } #[test] fn test_trading_metrics_record_order() { let metrics = monitoring::TradingMetrics::new(); metrics.record_order(); metrics.record_order(); metrics.record_order(); let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.order_count, 3); } #[test] fn test_trading_metrics_record_fill() { let metrics = monitoring::TradingMetrics::new(); metrics.record_fill(); metrics.record_fill(); let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.fill_count, 2); } #[test] fn test_trading_metrics_fill_rate() { let metrics = monitoring::TradingMetrics::new(); metrics.record_order(); metrics.record_order(); metrics.record_order(); metrics.record_order(); // 4 orders metrics.record_fill(); metrics.record_fill(); // 2 fills let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.fill_rate, 0.5); // 2/4 = 50% } #[test] fn test_trading_metrics_record_cancel() { let metrics = monitoring::TradingMetrics::new(); metrics.record_cancel(); metrics.record_cancel(); metrics.record_cancel(); let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.cancel_count, 3); } #[test] fn test_trading_metrics_record_reject() { let metrics = monitoring::TradingMetrics::new(); metrics.record_reject(); let snapshot = metrics.get_snapshot(); assert_eq!(snapshot.reject_count, 1); } #[test] fn test_trading_metrics_uptime() { let metrics = monitoring::TradingMetrics::new(); std::thread::sleep(std::time::Duration::from_millis(100)); let snapshot = metrics.get_snapshot(); assert!(snapshot.uptime_seconds >= 0); // At least 0 seconds } // ============================================================================ // Portfolio Position Tests // ============================================================================ #[test] fn test_position_new() { let position = portfolio::Position::new(); assert_eq!(position.quantity, 0.0); assert_eq!(position.avg_price, 0.0); assert_eq!(position.realized_pnl, 0.0); } #[test] fn test_position_open_long() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); assert_eq!(position.quantity, 100.0); assert_eq!(position.avg_price, 50.0); assert_eq!(position.realized_pnl, 0.0); } #[test] fn test_position_add_to_long() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); // 100 @ $50 position.update(50.0, 60.0); // +50 @ $60 assert_eq!(position.quantity, 150.0); // Avg price = (100*50 + 50*60) / 150 = (5000 + 3000) / 150 = 53.33 assert!((position.avg_price - 53.333333).abs() < 0.01); assert_eq!(position.realized_pnl, 0.0); // No closed trades } #[test] fn test_position_reduce_long() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); // Open 100 @ $50 position.update(-30.0, 55.0); // Close 30 @ $55 assert_eq!(position.quantity, 70.0); assert_eq!(position.avg_price, 50.0); // Avg price unchanged // Realized PnL = 30 * (55 - 50) = $150 assert!((position.realized_pnl - 150.0).abs() < 0.01); } #[test] fn test_position_close_long() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); // Open 100 @ $50 position.update(-100.0, 60.0); // Close 100 @ $60 assert_eq!(position.quantity, 0.0); assert_eq!(position.avg_price, 0.0); // Reset after close // Realized PnL = 100 * (60 - 50) = $1000 assert!((position.realized_pnl - 1000.0).abs() < 0.01); } #[test] fn test_position_open_short() { let mut position = portfolio::Position::new(); position.update(-100.0, 50.0); assert_eq!(position.quantity, -100.0); assert_eq!(position.avg_price, 50.0); assert_eq!(position.realized_pnl, 0.0); } #[test] fn test_position_reduce_short() { let mut position = portfolio::Position::new(); position.update(-100.0, 50.0); // Short 100 @ $50 position.update(30.0, 45.0); // Cover 30 @ $45 assert_eq!(position.quantity, -70.0); assert_eq!(position.avg_price, 50.0); // Realized PnL = 30 * (50 - 45) = $150 (profit on short cover) assert!((position.realized_pnl - 150.0).abs() < 0.01); } #[test] fn test_position_unrealized_pnl_long() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); // Market price rises to $55 let unrealized = position.unrealized_pnl(55.0); assert_eq!(unrealized, 500.0); // 100 * (55 - 50) = $500 // Market price falls to $45 let unrealized = position.unrealized_pnl(45.0); assert_eq!(unrealized, -500.0); // 100 * (45 - 50) = -$500 } #[test] fn test_position_unrealized_pnl_short() { let mut position = portfolio::Position::new(); position.update(-100.0, 50.0); // Market price falls to $45 (profit for short) let unrealized = position.unrealized_pnl(45.0); assert_eq!(unrealized, 500.0); // -100 * (45 - 50) = $500 // Market price rises to $55 (loss for short) let unrealized = position.unrealized_pnl(55.0); assert_eq!(unrealized, -500.0); // -100 * (55 - 50) = -$500 } #[test] fn test_position_zero_quantity_update() { let mut position = portfolio::Position::new(); position.update(100.0, 50.0); // Update with zero quantity (no-op) position.update(0.0, 60.0); assert_eq!(position.quantity, 100.0); assert_eq!(position.avg_price, 50.0); } // ============================================================================ // Helper Function Tests // ============================================================================ #[test] fn test_generate_order_id() { let id1 = helpers::generate_order_id(); let id2 = helpers::generate_order_id(); // IDs should start with "ORD_" assert!(id1.starts_with("ORD_")); assert!(id2.starts_with("ORD_")); // IDs should be unique assert_ne!(id1, id2); } #[test] fn test_generate_order_id_format() { let id = helpers::generate_order_id(); // Format: ORD_{timestamp}_{counter} let parts: Vec<&str> = id.split('_').collect(); assert_eq!(parts.len(), 3); assert_eq!(parts[0], "ORD"); assert_eq!(parts[1].len(), 16); // Timestamp hex (16 chars) assert_eq!(parts[2].len(), 8); // Counter hex (8 chars) } #[test] fn test_align_price_to_tick() { // Test with tick size 0.01 assert!((helpers::align_price_to_tick(100.567, 0.01) - 100.57).abs() < 1e-10); assert!((helpers::align_price_to_tick(100.563, 0.01) - 100.56).abs() < 1e-10); assert!((helpers::align_price_to_tick(100.565, 0.01) - 100.57).abs() < 1e-10); // Round up // Test with tick size 0.25 assert!((helpers::align_price_to_tick(100.30, 0.25) - 100.25).abs() < 1e-10); assert!((helpers::align_price_to_tick(100.40, 0.25) - 100.50).abs() < 1e-10); // Test with tick size 1.0 assert!((helpers::align_price_to_tick(100.6, 1.0) - 101.0).abs() < 1e-10); } #[test] fn test_align_price_to_tick_zero_tick_size() { // Zero tick size should return original price let price = 100.567; assert_eq!(helpers::align_price_to_tick(price, 0.0), price); } #[test] fn test_calculate_order_value() { assert_eq!(helpers::calculate_order_value(100.0, 50.0), 5000.0); assert_eq!(helpers::calculate_order_value(50.0, 123.45), 6172.5); assert_eq!(helpers::calculate_order_value(-100.0, 50.0), 5000.0); // Abs value } #[test] fn test_format_price_stock() { // Stock/commodity (2 decimals) assert_eq!(helpers::format_price(123.456789, "AAPL"), "123.46"); assert_eq!(helpers::format_price(50.001, "ES.FUT"), "50.00"); } #[test] fn test_format_price_forex() { // Forex pair (5 decimals) - 6 chars, all alphabetic assert_eq!(helpers::format_price(1.234567, "EURUSD"), "1.23457"); assert_eq!(helpers::format_price(0.987654, "GBPUSD"), "0.98765"); } #[test] fn test_is_market_open() { // This test depends on current time, so we just verify it doesn't panic let _is_open = helpers::is_market_open(); // Cannot assert specific value due to time dependency }