#![allow( clippy::tests_outside_test_module, clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::str_to_string, clippy::string_to_string, clippy::assertions_on_result_states, clippy::assertions_on_constants, clippy::let_underscore_must_use, clippy::use_debug, clippy::doc_markdown, clippy::shadow_unrelated, clippy::shadow_reuse, clippy::similar_names, clippy::clone_on_copy, clippy::get_unwrap, clippy::modulo_arithmetic, clippy::integer_division, clippy::non_ascii_literal, clippy::useless_vec, clippy::useless_format, clippy::wildcard_enum_match_arm, clippy::manual_range_contains, clippy::const_is_empty, clippy::needless_range_loop, clippy::field_reassign_with_default, clippy::items_after_test_module, clippy::missing_const_for_fn, unused_imports, unused_variables, unused_mut, unused_assignments, unused_comparisons, unused_must_use, dead_code, )] //! Comprehensive position manager tests targeting 95% coverage //! Tests for trading/position_manager.rs module covering all 13 public functions use chrono::Utc; use common::{OrderId, OrderSide}; use rust_decimal::Decimal; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; use trading_engine::trading::position_manager::PositionManager; use trading_engine::trading_operations::{ExecutionResult, LiquidityFlag}; // ============================================================================ // Helper Functions // ============================================================================ fn create_test_execution( symbol: String, quantity: Decimal, price: Decimal, side: OrderSide, ) -> ExecutionResult { ExecutionResult { order_id: OrderId::new(), symbol, side, executed_quantity: quantity, execution_price: price, commission: Decimal::from_str("0.01").unwrap(), execution_time: Utc::now(), liquidity_flag: LiquidityFlag::Maker, } } // ============================================================================ // PositionManager::new() Tests // ============================================================================ #[cfg(test)] mod position_manager_creation_tests { use super::*; #[test] fn test_position_manager_new_creates_valid_instance() { let pm = PositionManager::new(); let positions = pm.get_positions(None).unwrap(); assert_eq!(positions.len(), 0); } #[test] fn test_position_manager_default_creates_valid_instance() { let pm = PositionManager::default(); let positions = pm.get_positions(None).unwrap(); assert_eq!(positions.len(), 0); } #[test] fn test_multiple_position_managers_independent() { let pm1 = PositionManager::new(); let pm2 = PositionManager::new(); let exec1 = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm1.update_position(&exec1).unwrap(); let positions1 = pm1.get_positions(None).unwrap(); let positions2 = pm2.get_positions(None).unwrap(); assert_eq!(positions1.len(), 1); assert_eq!(positions2.len(), 0); } } // ============================================================================ // PositionManager::update_position() Tests // ============================================================================ #[cfg(test)] mod update_position_tests { use super::*; #[test] fn test_update_position_new_buy() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("AAPL").unwrap(); assert_eq!(position.quantity, Decimal::from_str("100").unwrap()); } #[test] fn test_update_position_new_sell() { let pm = PositionManager::new(); let exec = create_test_execution( "MSFT".to_string(), Decimal::from_str("50").unwrap(), Decimal::from_str("300.00").unwrap(), OrderSide::Sell, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("MSFT").unwrap(); assert_eq!(position.quantity, Decimal::from_str("-50").unwrap()); } #[test] fn test_update_position_increasing_long() { let pm = PositionManager::new(); // First buy let exec1 = create_test_execution( "GOOGL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("2800.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); // Second buy at different price let exec2 = create_test_execution( "GOOGL".to_string(), Decimal::from_str("50").unwrap(), Decimal::from_str("2850.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec2).unwrap(); let position = pm.get_position("GOOGL").unwrap(); assert_eq!(position.quantity, Decimal::from_str("150").unwrap()); // Average cost should be weighted: (100*2800 + 50*2850) / 150 = 2816.67 assert!(position.avg_cost > Decimal::from_str("2800").unwrap()); assert!(position.avg_cost < Decimal::from_str("2850").unwrap()); } #[test] fn test_update_position_reducing_long() { let pm = PositionManager::new(); // Buy 100 shares let exec1 = create_test_execution( "TSLA".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("700.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); // Sell 40 shares let exec2 = create_test_execution( "TSLA".to_string(), Decimal::from_str("40").unwrap(), Decimal::from_str("720.00").unwrap(), OrderSide::Sell, ); pm.update_position(&exec2).unwrap(); let position = pm.get_position("TSLA").unwrap(); assert_eq!(position.quantity, Decimal::from_str("60").unwrap()); // Realized P&L should be positive: 40 * (720 - 700) = 800 assert!(position.realized_pnl > Decimal::ZERO); } #[test] fn test_update_position_closing_position() { let pm = PositionManager::new(); // Buy 100 shares let exec1 = create_test_execution( "AMZN".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("3200.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); // Sell all 100 shares let exec2 = create_test_execution( "AMZN".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("3250.00").unwrap(), OrderSide::Sell, ); pm.update_position(&exec2).unwrap(); let position = pm.get_position("AMZN").unwrap(); assert_eq!(position.quantity, Decimal::ZERO); // Realized P&L: 100 * (3250 - 3200) = 5000 assert_eq!(position.realized_pnl, Decimal::from_str("5000").unwrap()); } #[test] fn test_update_position_reversing_position() { let pm = PositionManager::new(); // Buy 100 shares let exec1 = create_test_execution( "NVDA".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("500.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); // Sell 150 shares (closing long and opening short) let exec2 = create_test_execution( "NVDA".to_string(), Decimal::from_str("150").unwrap(), Decimal::from_str("520.00").unwrap(), OrderSide::Sell, ); pm.update_position(&exec2).unwrap(); let position = pm.get_position("NVDA").unwrap(); assert_eq!(position.quantity, Decimal::from_str("-50").unwrap()); assert!(position.realized_pnl > Decimal::ZERO); } #[test] fn test_update_position_fractional_shares() { let pm = PositionManager::new(); let exec = create_test_execution( "BRK.B".to_string(), Decimal::from_str("0.5").unwrap(), Decimal::from_str("350.00").unwrap(), OrderSide::Buy, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("BRK.B").unwrap(); assert_eq!(position.quantity, Decimal::from_str("0.5").unwrap()); } #[test] fn test_update_position_concurrent_updates() { let pm = Arc::new(PositionManager::new()); let handles: Vec<_> = (0..10) .map(|_i| { let pm_clone = Arc::clone(&pm); std::thread::spawn(move || { let exec = create_test_execution( "SPY".to_string(), Decimal::from_str("10").unwrap(), Decimal::from_str("450.00").unwrap(), OrderSide::Buy, ); pm_clone.update_position(&exec) }) }) .collect(); for handle in handles { assert!(handle.join().unwrap().is_ok()); } let position = pm.get_position("SPY").unwrap(); assert_eq!(position.quantity, Decimal::from_str("100").unwrap()); } } // ============================================================================ // PositionManager::get_position() Tests // ============================================================================ #[cfg(test)] mod get_position_tests { use super::*; #[test] fn test_get_position_existing() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); let position = pm.get_position("AAPL"); assert!(position.is_some()); assert_eq!(position.unwrap().symbol, "AAPL"); } #[test] fn test_get_position_nonexistent() { let pm = PositionManager::new(); let position = pm.get_position("NONEXISTENT"); assert!(position.is_none()); } #[test] fn test_get_position_case_sensitive() { let pm = PositionManager::new(); let exec = create_test_execution( "aapl".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); assert!(pm.get_position("aapl").is_some()); assert!(pm.get_position("AAPL").is_none()); } } // ============================================================================ // PositionManager::get_positions() Tests // ============================================================================ #[cfg(test)] mod get_positions_tests { use super::*; #[test] fn test_get_positions_empty() { let pm = PositionManager::new(); let positions = pm.get_positions(None).unwrap(); assert_eq!(positions.len(), 0); } #[test] fn test_get_positions_multiple() { let pm = PositionManager::new(); let symbols = vec!["AAPL", "MSFT", "GOOGL"]; for symbol in &symbols { let exec = create_test_execution( symbol.to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("100.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); } let positions = pm.get_positions(None).unwrap(); assert_eq!(positions.len(), 3); } #[test] fn test_get_positions_with_filter() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); let positions = pm.get_positions(Some("AAPL".to_string())).unwrap(); assert_eq!(positions.len(), 1); assert_eq!(positions[0].symbol, "AAPL"); } } // ============================================================================ // PositionManager::update_market_values() Tests // ============================================================================ #[cfg(test)] mod update_market_values_tests { use super::*; #[test] fn test_update_market_values_existing_position() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); let result = pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()); assert!(result.is_ok()); let position = pm.get_position("AAPL").unwrap(); // Unrealized P&L should be: 100 * (160 - 150) = 1000 assert_eq!(position.unrealized_pnl, Decimal::from_str("1000").unwrap()); } #[test] fn test_update_market_values_nonexistent_position() { let pm = PositionManager::new(); let result = pm.update_market_values("NONEXISTENT", Decimal::from_str("100.00").unwrap()); assert!(result.is_err()); } #[test] fn test_update_market_values_price_decrease() { let pm = PositionManager::new(); let exec = create_test_execution( "TSLA".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("700.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("TSLA", Decimal::from_str("680.00").unwrap()) .unwrap(); let position = pm.get_position("TSLA").unwrap(); // Unrealized P&L should be negative: 100 * (680 - 700) = -2000 assert_eq!(position.unrealized_pnl, Decimal::from_str("-2000").unwrap()); } } // ============================================================================ // PositionManager::update_market_values_batch() Tests // ============================================================================ #[cfg(test)] mod update_market_values_batch_tests { use super::*; #[test] fn test_update_market_values_batch_multiple() { let pm = PositionManager::new(); // Create multiple positions for symbol in &["AAPL", "MSFT", "GOOGL"] { let exec = create_test_execution( symbol.to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("100.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); } let mut market_prices = HashMap::new(); market_prices.insert("AAPL".to_string(), Decimal::from_str("110.00").unwrap()); market_prices.insert("MSFT".to_string(), Decimal::from_str("105.00").unwrap()); market_prices.insert("GOOGL".to_string(), Decimal::from_str("115.00").unwrap()); pm.update_market_values_batch(market_prices).unwrap(); let aapl = pm.get_position("AAPL").unwrap(); assert_eq!(aapl.unrealized_pnl, Decimal::from_str("1000").unwrap()); let msft = pm.get_position("MSFT").unwrap(); assert_eq!(msft.unrealized_pnl, Decimal::from_str("500").unwrap()); } #[test] fn test_update_market_values_batch_empty() { let pm = PositionManager::new(); let market_prices = HashMap::new(); let result = pm.update_market_values_batch(market_prices); assert!(result.is_ok()); } #[test] fn test_update_market_values_batch_partial_positions() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); let mut market_prices = HashMap::new(); market_prices.insert("AAPL".to_string(), Decimal::from_str("160.00").unwrap()); market_prices.insert("MSFT".to_string(), Decimal::from_str("300.00").unwrap()); // No position let result = pm.update_market_values_batch(market_prices); assert!(result.is_ok()); let aapl = pm.get_position("AAPL").unwrap(); assert_eq!(aapl.unrealized_pnl, Decimal::from_str("1000").unwrap()); } } // ============================================================================ // Portfolio Value Tests // ============================================================================ #[cfg(test)] mod portfolio_value_tests { use super::*; #[test] fn test_get_total_portfolio_value_empty() { let pm = PositionManager::new(); let total = pm.get_total_portfolio_value(); assert_eq!(total, Decimal::ZERO); } #[test] fn test_get_total_portfolio_value_with_positions() { let pm = PositionManager::new(); let exec1 = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let total = pm.get_total_portfolio_value(); // Market value: 100 * 160 = 16000 assert_eq!(total, Decimal::from_str("16000").unwrap()); } #[test] fn test_get_total_unrealized_pnl_empty() { let pm = PositionManager::new(); let total = pm.get_total_unrealized_pnl(); assert_eq!(total, Decimal::ZERO); } #[test] fn test_get_total_unrealized_pnl_with_positions() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let total = pm.get_total_unrealized_pnl(); assert_eq!(total, Decimal::from_str("1000").unwrap()); } #[test] fn test_get_total_realized_pnl_empty() { let pm = PositionManager::new(); let total = pm.get_total_realized_pnl(); assert_eq!(total, Decimal::ZERO); } #[test] fn test_get_total_realized_pnl_after_trades() { let pm = PositionManager::new(); // Buy and sell to realize profit let exec1 = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec1).unwrap(); let exec2 = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("160.00").unwrap(), OrderSide::Sell, ); pm.update_position(&exec2).unwrap(); let total = pm.get_total_realized_pnl(); assert_eq!(total, Decimal::from_str("1000").unwrap()); } } // ============================================================================ // Close Position Tests // ============================================================================ #[cfg(test)] mod close_position_tests { use super::*; #[test] fn test_close_position_existing() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); let result = pm.close_position("AAPL"); assert!(result.is_ok()); let closed = result.unwrap(); assert!(closed.is_some()); // Position should no longer exist assert!(pm.get_position("AAPL").is_none()); } #[test] fn test_close_position_nonexistent() { let pm = PositionManager::new(); let result = pm.close_position("NONEXISTENT"); assert!(result.is_ok()); assert!(result.unwrap().is_none()); } } // ============================================================================ // Risk Management Tests // ============================================================================ #[cfg(test)] mod risk_management_tests { use super::*; #[test] fn test_get_positions_exceeding_limits_none() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let exceeding = pm.get_positions_exceeding_limits(Decimal::from_str("20000").unwrap()); assert_eq!(exceeding.len(), 0); } #[test] fn test_get_positions_exceeding_limits_some() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let exceeding = pm.get_positions_exceeding_limits(Decimal::from_str("10000").unwrap()); assert_eq!(exceeding.len(), 1); assert_eq!(exceeding[0].symbol, "AAPL"); } #[test] fn test_calculate_concentration_risk_empty() { let pm = PositionManager::new(); let risk = pm.calculate_concentration_risk(); assert_eq!(risk.len(), 0); } #[test] fn test_calculate_concentration_risk_single_position() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let risk = pm.calculate_concentration_risk(); assert_eq!(risk.len(), 1); assert!(risk.contains_key("AAPL")); // Single position = 100% concentration assert!((risk["AAPL"] - 1.0).abs() < 0.001); } #[test] fn test_calculate_concentration_risk_multiple_positions() { let pm = PositionManager::new(); // Two equal positions for symbol in &["AAPL", "MSFT"] { let exec = create_test_execution( symbol.to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values(symbol, Decimal::from_str("160.00").unwrap()) .unwrap(); } let risk = pm.calculate_concentration_risk(); assert_eq!(risk.len(), 2); // Each position = 50% concentration assert!((risk["AAPL"] - 0.5).abs() < 0.001); assert!((risk["MSFT"] - 0.5).abs() < 0.001); } #[test] fn test_get_position_stats() { let pm = PositionManager::new(); let exec = create_test_execution( "AAPL".to_string(), Decimal::from_str("100").unwrap(), Decimal::from_str("150.00").unwrap(), OrderSide::Buy, ); pm.update_position(&exec).unwrap(); pm.update_market_values("AAPL", Decimal::from_str("160.00").unwrap()) .unwrap(); let stats = pm.get_position_stats(); assert_eq!(stats.total_positions, 1); assert_eq!(stats.long_positions, 1); assert_eq!(stats.short_positions, 0); } } // ============================================================================ // Edge Cases and Stress Tests // ============================================================================ #[cfg(test)] mod edge_case_tests { use super::*; #[test] fn test_position_manager_with_zero_price_execution() { let pm = PositionManager::new(); let exec = create_test_execution( "TEST".to_string(), Decimal::from_str("100").unwrap(), Decimal::ZERO, OrderSide::Buy, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("TEST").unwrap(); assert_eq!(position.avg_cost, Decimal::ZERO); } #[test] fn test_position_manager_concurrent_access() { let pm = Arc::new(PositionManager::new()); let handles: Vec<_> = (0..20) .map(|i| { let pm_clone = Arc::clone(&pm); let symbol = format!("SYM{}", i % 5); std::thread::spawn(move || { let exec = create_test_execution( symbol, Decimal::from_str("10").unwrap(), Decimal::from_str("100.00").unwrap(), OrderSide::Buy, ); pm_clone.update_position(&exec) }) }) .collect(); for handle in handles { assert!(handle.join().unwrap().is_ok()); } let positions = pm.get_positions(None).unwrap(); assert_eq!(positions.len(), 5); } #[test] fn test_position_manager_very_large_quantities() { let pm = PositionManager::new(); let exec = create_test_execution( "INDEX".to_string(), Decimal::from_str("1000000").unwrap(), Decimal::from_str("1.00").unwrap(), OrderSide::Buy, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("INDEX").unwrap(); assert_eq!(position.quantity, Decimal::from_str("1000000").unwrap()); } #[test] fn test_position_manager_very_high_prices() { let pm = PositionManager::new(); let exec = create_test_execution( "BRK.A".to_string(), Decimal::from_str("1").unwrap(), Decimal::from_str("500000.00").unwrap(), OrderSide::Buy, ); let result = pm.update_position(&exec); assert!(result.is_ok()); let position = pm.get_position("BRK.A").unwrap(); assert_eq!(position.avg_cost, Decimal::from_str("500000.00").unwrap()); } }