#![allow(unexpected_cfgs)] #![cfg(feature = "__trading_service_integration")] //! End-to-End Integration Tests for Trading Service //! //! Comprehensive E2E test scenarios covering: //! - Order placement -> Risk check -> Execution (complete flow) //! - Position tracking across multiple orders //! - PnL calculation with partial and full fills //! - Stop-loss triggers and automatic liquidation //! - Market order execution //! - Limit order matching //! - Order cancellation flows //! - Position close-out scenarios //! //! These tests use real PostgreSQL connections (not mocks) to validate //! complete integration between all service components. use anyhow::Result; use common::{OrderSide, OrderStatus, OrderType}; use sqlx::PgPool; use std::sync::Arc; use tonic::Request; use trading_service::proto::trading::{ trading_service_server::TradingService, CancelOrderRequest, GetOrderStatusRequest, GetPortfolioSummaryRequest, GetPositionsRequest, SubmitOrderRequest, }; use trading_service::repositories::*; use trading_service::repository_impls::*; use trading_service::services::trading::TradingServiceImpl; use trading_service::state::TradingServiceState; /// Setup test database connection pool async fn setup_test_db() -> Result { let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string() }); let pool = PgPool::connect(&database_url).await?; Ok(pool) } /// Setup complete trading service with real database repositories async fn setup_trading_service_with_db() -> Result { let pool = setup_test_db().await?; // Create repository implementations let trading_repo = Arc::new(PostgresTradingRepository::new(pool.clone())); let market_data_repo = Arc::new(PostgresMarketDataRepository::new(pool.clone())); let risk_repo = Arc::new(PostgresRiskRepository::new(pool.clone())); let config_repo = Arc::new(PostgresConfigRepository::new(pool.clone())); // Create event persistence for audit trail let event_persistence = Arc::new(trading_service::event_persistence::EventPersistence::new( pool.clone(), "trading_service_test".to_string(), std::process::id(), )); // Create state with repositories let state = Arc::new( TradingServiceState::new_with_repositories( trading_repo, market_data_repo, risk_repo, config_repo, event_persistence, None, // kill_switch None, // model_cache ) .await?, ); Ok(TradingServiceImpl::new(state)) } /// Clean up test orders for a specific account async fn cleanup_test_orders(pool: &PgPool, account_id: &str) -> Result<()> { sqlx::query("DELETE FROM orders WHERE account_id = $1") .bind(account_id) .execute(pool) .await?; sqlx::query("DELETE FROM positions WHERE account_id = $1") .bind(account_id) .execute(pool) .await?; sqlx::query("DELETE FROM executions WHERE account_id = $1") .bind(account_id) .execute(pool) .await?; Ok(()) } // ============================================================================ // E2E Test: Order Placement → Risk Check → Execution // ============================================================================ #[tokio::test] async fn test_e2e_order_placement_to_execution() -> Result<()> { println!("\n=== E2E Test: Order Placement → Risk Check → Execution ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_001"; cleanup_test_orders(&pool, account_id).await?; // 1. Place market order let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_result = response.into_inner(); println!(" 1. Order placed: {}", order_result.order_id); assert_eq!(order_result.status, OrderStatus::Submitted as i32); // 2. Verify risk check passed (implicit in successful submission) assert!(!order_result.order_id.is_empty()); println!(" 2. Risk check passed"); // 3. Verify order status let status_req = Request::new(GetOrderStatusRequest { order_id: order_result.order_id.clone(), }); let status_response = service.get_order_status(status_req).await?; let order_status = status_response.into_inner(); assert!(order_status.order.is_some()); println!( " 3. Order status verified: {:?}", order_status.order.unwrap().status ); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_limit_order_placement_and_matching() -> Result<()> { println!("\n=== E2E Test: Limit Order Placement and Matching ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_002"; cleanup_test_orders(&pool, account_id).await?; // Place limit order let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "GOOGL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 50.0, price: Some(150.50), stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order = response.into_inner(); println!(" Limit order placed at $150.50: {}", order.order_id); assert_eq!(order.status, OrderStatus::Submitted as i32); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Position Tracking Across Multiple Orders // ============================================================================ #[tokio::test] async fn test_e2e_position_tracking_multiple_orders() -> Result<()> { println!("\n=== E2E Test: Position Tracking Across Multiple Orders ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_003"; cleanup_test_orders(&pool, account_id).await?; // Place multiple orders for same symbol for i in 1..=3 { let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NVDA".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 10.0 * i as f64, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; println!(" Order {} placed: {}", i, response.into_inner().order_id); } // Get positions let pos_request = Request::new(GetPositionsRequest { account_id: Some(account_id.to_string()), symbol: Some("NVDA".to_string()), }); let positions = service.get_positions(pos_request).await?; println!( " Total positions: {}", positions.into_inner().positions.len() ); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_position_updates_with_fills() -> Result<()> { println!("\n=== E2E Test: Position Updates with Fills ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_004"; cleanup_test_orders(&pool, account_id).await?; // Buy order let buy_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "TSLA".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let buy_response = service.submit_order(buy_request).await?; let buy_order_id = buy_response.into_inner().order_id; println!(" Buy order placed: {}", buy_order_id); // Sell order (reduce position) let sell_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "TSLA".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Market as i32, quantity: 50.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let sell_response = service.submit_order(sell_request).await?; println!( " Sell order placed: {}", sell_response.into_inner().order_id ); // Verify position let pos_request = Request::new(GetPositionsRequest { account_id: Some(account_id.to_string()), symbol: Some("TSLA".to_string()), }); let positions = service.get_positions(pos_request).await?; let position_count = positions.into_inner().positions.len(); println!(" Net position count: {}", position_count); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: PnL Calculation with Partial/Full Fills // ============================================================================ #[tokio::test] async fn test_e2e_pnl_calculation_full_fill() -> Result<()> { println!("\n=== E2E Test: PnL Calculation with Full Fill ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_005"; cleanup_test_orders(&pool, account_id).await?; // Buy at lower price let buy_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AMD".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 100.0, price: Some(100.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let buy_response = service.submit_order(buy_request).await?; println!( " Buy order placed at $100: {}", buy_response.into_inner().order_id ); // Sell at higher price let sell_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AMD".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Limit as i32, quantity: 100.0, price: Some(110.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let sell_response = service.submit_order(sell_request).await?; println!( " Sell order placed at $110: {}", sell_response.into_inner().order_id ); // Get portfolio summary (includes PnL) let summary_request = Request::new(GetPortfolioSummaryRequest { account_id: account_id.to_string(), }); let summary = service.get_portfolio_summary(summary_request).await?; let portfolio = summary.into_inner(); println!(" Expected PnL: $1000 (100 shares × $10 profit)"); println!(" Realized PnL: ${}", portfolio.realized_pnl); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_pnl_calculation_partial_fill() -> Result<()> { println!("\n=== E2E Test: PnL Calculation with Partial Fill ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_006"; cleanup_test_orders(&pool, account_id).await?; // Place large limit order (may partially fill) let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 1000.0, price: Some(450.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!(" Large limit order placed: {}", order_id); // Check order status for partial fill let status_req = Request::new(GetOrderStatusRequest { order_id: order_id.clone(), }); let status = service.get_order_status(status_req).await?; if let Some(order) = status.into_inner().order { println!( " Order quantity: {}, Filled: {}", order.quantity, order.filled_quantity ); } cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Stop-Loss Trigger and Automatic Liquidation // ============================================================================ #[tokio::test] async fn test_e2e_stop_loss_trigger() -> Result<()> { println!("\n=== E2E Test: Stop-Loss Trigger ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_007"; cleanup_test_orders(&pool, account_id).await?; // Place stop-loss order let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "META".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Stop as i32, quantity: 50.0, price: None, stop_price: Some(350.0), // Trigger if price drops to 350 metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!( " Stop-loss order placed at trigger price $350: {}", order_id ); // Verify order exists let status_req = Request::new(GetOrderStatusRequest { order_id: order_id.clone(), }); let status = service.get_order_status(status_req).await?; assert!(status.into_inner().order.is_some()); println!(" Stop-loss order verified and active"); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_automatic_liquidation() -> Result<()> { println!("\n=== E2E Test: Automatic Liquidation ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_008"; cleanup_test_orders(&pool, account_id).await?; // Build position let buy_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NFLX".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let buy_response = service.submit_order(buy_request).await?; println!(" Position opened: {}", buy_response.into_inner().order_id); // Liquidation order (close entire position) let liquidation_request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NFLX".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Market as i32, quantity: 100.0, // Close entire position price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let liq_response = service.submit_order(liquidation_request).await?; println!( " Liquidation order submitted: {}", liq_response.into_inner().order_id ); // Verify position closed let pos_request = Request::new(GetPositionsRequest { account_id: Some(account_id.to_string()), symbol: Some("NFLX".to_string()), }); let positions = service.get_positions(pos_request).await?; let position_count = positions.into_inner().positions.len(); println!(" Position count after liquidation: {}", position_count); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Market Order Execution // ============================================================================ #[tokio::test] async fn test_e2e_market_order_immediate_execution() -> Result<()> { println!("\n=== E2E Test: Market Order Immediate Execution ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_009"; cleanup_test_orders(&pool, account_id).await?; let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "QQQ".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 50.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let start = std::time::Instant::now(); let response = service.submit_order(request).await?; let elapsed = start.elapsed(); let order = response.into_inner(); println!(" Market order executed in {:?}", elapsed); println!(" Order ID: {}", order.order_id); assert_eq!(order.status, OrderStatus::Submitted as i32); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_market_order_with_slippage() -> Result<()> { println!("\n=== E2E Test: Market Order with Slippage ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_010"; cleanup_test_orders(&pool, account_id).await?; // Large market order (may experience slippage) let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 5000.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!(" Large market order placed: {}", order_id); // Check execution details let status_req = Request::new(GetOrderStatusRequest { order_id }); let status = service.get_order_status(status_req).await?; if let Some(order) = status.into_inner().order { println!(" Order status: {:?}", order.status); if let Some(price) = order.price { println!(" Execution price: ${}", price); } } cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Limit Order Matching // ============================================================================ #[tokio::test] async fn test_e2e_limit_order_price_matching() -> Result<()> { println!("\n=== E2E Test: Limit Order Price Matching ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_011"; cleanup_test_orders(&pool, account_id).await?; // Buy limit at specific price let buy_limit = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 100.0, price: Some(180.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let buy_response = service.submit_order(buy_limit).await?; let buy_order_id = buy_response.into_inner().order_id; println!(" Buy limit order at $180: {}", buy_order_id); // Sell limit at higher price let sell_limit = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Limit as i32, quantity: 100.0, price: Some(185.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let sell_response = service.submit_order(sell_limit).await?; let sell_order_id = sell_response.into_inner().order_id; println!(" Sell limit order at $185: {}", sell_order_id); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_limit_order_queue_priority() -> Result<()> { println!("\n=== E2E Test: Limit Order Queue Priority ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_012"; cleanup_test_orders(&pool, account_id).await?; // Place multiple limit orders at same price (FIFO queue) for i in 1..=3 { let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "MSFT".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 50.0, price: Some(400.0), stop_price: None, metadata: { let mut map = std::collections::HashMap::new(); map.insert("queue_position".to_string(), i.to_string()); map }, }); let response = service.submit_order(request).await?; println!( " Limit order {} placed at $400: {}", i, response.into_inner().order_id ); } cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Order Cancellation // ============================================================================ #[tokio::test] async fn test_e2e_order_cancellation_before_fill() -> Result<()> { println!("\n=== E2E Test: Order Cancellation Before Fill ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_013"; cleanup_test_orders(&pool, account_id).await?; // Place limit order let submit_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "GOOGL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 25.0, price: Some(140.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let submit_response = service.submit_order(submit_req).await?; let order_id = submit_response.into_inner().order_id; println!(" Order placed: {}", order_id); // Cancel immediately let cancel_req = Request::new(CancelOrderRequest { order_id: order_id.clone(), account_id: account_id.to_string(), }); let cancel_response = service.cancel_order(cancel_req).await?; assert!(cancel_response.into_inner().success); println!(" Order cancelled successfully"); // Verify cancellation let status_req = Request::new(GetOrderStatusRequest { order_id }); let status = service.get_order_status(status_req).await?; if let Some(order) = status.into_inner().order { println!(" Final status: {:?}", order.status); } cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_bulk_order_cancellation() -> Result<()> { println!("\n=== E2E Test: Bulk Order Cancellation ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_014"; cleanup_test_orders(&pool, account_id).await?; let mut order_ids = Vec::new(); // Place multiple orders for i in 1..=5 { let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "TSLA".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 10.0, price: Some(250.0 + i as f64), stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; order_ids.push(order_id.clone()); println!(" Order {} placed: {}", i, order_id); } // Cancel all orders for (i, order_id) in order_ids.into_iter().enumerate() { let cancel_req = Request::new(CancelOrderRequest { order_id: order_id.clone(), account_id: account_id.to_string(), }); let cancel_response = service.cancel_order(cancel_req).await?; assert!(cancel_response.into_inner().success); println!(" Order {} cancelled", i + 1); } cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Position Close-Out // ============================================================================ #[tokio::test] async fn test_e2e_position_closeout_market_order() -> Result<()> { println!("\n=== E2E Test: Position Close-Out with Market Order ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_015"; cleanup_test_orders(&pool, account_id).await?; // Open position let open_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NVDA".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 200.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let open_response = service.submit_order(open_req).await?; println!(" Position opened: {}", open_response.into_inner().order_id); // Close position with market order let close_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NVDA".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Market as i32, quantity: 200.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let close_response = service.submit_order(close_req).await?; println!( " Position closed: {}", close_response.into_inner().order_id ); // Verify no open positions let pos_req = Request::new(GetPositionsRequest { account_id: Some(account_id.to_string()), symbol: Some("NVDA".to_string()), }); let positions = service.get_positions(pos_req).await?; println!( " Open positions: {}", positions.into_inner().positions.len() ); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_position_closeout_limit_order() -> Result<()> { println!("\n=== E2E Test: Position Close-Out with Limit Order ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_016"; cleanup_test_orders(&pool, account_id).await?; // Open position let open_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AMD".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 150.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let open_response = service.submit_order(open_req).await?; println!(" Position opened: {}", open_response.into_inner().order_id); // Close with limit order at target price let close_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AMD".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Limit as i32, quantity: 150.0, price: Some(120.0), // Target exit price stop_price: None, metadata: std::collections::HashMap::new(), }); let close_response = service.submit_order(close_req).await?; println!( " Close order placed at $120: {}", close_response.into_inner().order_id ); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Complex Multi-Leg Scenarios // ============================================================================ #[tokio::test] async fn test_e2e_multi_symbol_portfolio_management() -> Result<()> { println!("\n=== E2E Test: Multi-Symbol Portfolio Management ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_017"; cleanup_test_orders(&pool, account_id).await?; let symbols = vec!["AAPL", "GOOGL", "MSFT", "NVDA"]; // Build diversified portfolio for symbol in &symbols { let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: symbol.to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 50.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; println!( " {} position opened: {}", symbol, response.into_inner().order_id ); } // Get portfolio summary let summary_req = Request::new(GetPortfolioSummaryRequest { account_id: account_id.to_string(), }); let summary = service.get_portfolio_summary(summary_req).await?; let portfolio = summary.into_inner(); println!(" Portfolio value: ${}", portfolio.total_value); println!(" Number of positions: {}", symbols.len()); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_hedging_strategy() -> Result<()> { println!("\n=== E2E Test: Hedging Strategy ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_018"; cleanup_test_orders(&pool, account_id).await?; // Long position let long_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let long_response = service.submit_order(long_req).await?; println!( " Long SPY position: {}", long_response.into_inner().order_id ); // Hedge with short position let hedge_req = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::Limit as i32, quantity: 50.0, // Partial hedge price: Some(455.0), stop_price: None, metadata: std::collections::HashMap::new(), }); let hedge_response = service.submit_order(hedge_req).await?; println!( " Hedge order placed: {}", hedge_response.into_inner().order_id ); // Verify net position let pos_req = Request::new(GetPositionsRequest { account_id: Some(account_id.to_string()), symbol: Some("SPY".to_string()), }); let positions = service.get_positions(pos_req).await?; println!( " Net positions: {}", positions.into_inner().positions.len() ); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Concurrent Trading Operations // ============================================================================ #[tokio::test] async fn test_e2e_concurrent_multi_account_trading() -> Result<()> { println!("\n=== E2E Test: Concurrent Multi-Account Trading ==="); let service = Arc::new(setup_trading_service_with_db().await?); let pool = setup_test_db().await?; let accounts = vec![ "concurrent_account_1", "concurrent_account_2", "concurrent_account_3", ]; // Cleanup all accounts for account in &accounts { cleanup_test_orders(&pool, account).await?; } let mut handles = Vec::new(); // Concurrent trading from multiple accounts for account in accounts.clone() { let svc = service.clone(); let handle = tokio::spawn(async move { let request = Request::new(SubmitOrderRequest { account_id: account.to_string(), symbol: "QQQ".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); svc.submit_order(request).await }); handles.push(handle); } // Wait for all orders let mut success_count = 0; for handle in handles { if let Ok(Ok(response)) = handle.await { println!(" Order placed: {}", response.into_inner().order_id); success_count += 1; } } println!( " {}/{} concurrent orders succeeded", success_count, accounts.len() ); assert_eq!(success_count, accounts.len()); // Cleanup for account in &accounts { cleanup_test_orders(&pool, account).await?; } Ok(()) } #[tokio::test] async fn test_e2e_high_frequency_order_flow() -> Result<()> { println!("\n=== E2E Test: High-Frequency Order Flow ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "hft_account_001"; cleanup_test_orders(&pool, account_id).await?; let mut latencies = Vec::new(); // Simulate high-frequency order flow for i in 1..=50 { let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: if i % 2 == 0 { OrderSide::Buy as i32 } else { OrderSide::Sell as i32 }, order_type: OrderType::Market as i32, quantity: 1.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let start = std::time::Instant::now(); let _ = service.submit_order(request).await; let elapsed = start.elapsed(); latencies.push(elapsed); } // Calculate latency metrics latencies.sort(); let p50 = latencies[24]; let p95 = latencies[47]; let p99 = latencies[49]; println!(" HFT Latency Metrics (50 orders):"); println!(" ├─ P50: {:?}", p50); println!(" ├─ P95: {:?}", p95); println!(" └─ P99: {:?}", p99); cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Error Recovery and Edge Cases // ============================================================================ #[tokio::test] async fn test_e2e_duplicate_order_handling() -> Result<()> { println!("\n=== E2E Test: Duplicate Order Handling ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_019"; cleanup_test_orders(&pool, account_id).await?; let mut metadata = std::collections::HashMap::new(); metadata.insert( "client_order_id".to_string(), "duplicate_test_123".to_string(), ); // Submit first order let request1 = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 50.0, price: None, stop_price: None, metadata: metadata.clone(), }); let response1 = service.submit_order(request1).await?; println!(" First order: {}", response1.into_inner().order_id); // Submit duplicate (same client_order_id) let request2 = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 50.0, price: None, stop_price: None, metadata: metadata.clone(), }); let response2 = service.submit_order(request2).await?; println!(" Second order: {}", response2.into_inner().order_id); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_order_rejection_insufficient_margin() -> Result<()> { println!("\n=== E2E Test: Order Rejection - Insufficient Margin ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_020"; cleanup_test_orders(&pool, account_id).await?; // Attempt very large order (should fail margin check) let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 1_000_000.0, price: None, stop_price: None, metadata: std::collections::HashMap::new(), }); let result = service.submit_order(request).await; match result { Ok(_) => println!(" Order was accepted (unexpected)"), Err(status) => { println!(" Order rejected: {}", status.message()); assert!( status.message().contains("Risk violation") || status.message().contains("exceeds maximum") ); }, } cleanup_test_orders(&pool, account_id).await?; Ok(()) } // ============================================================================ // E2E Test: Advanced Order Types // ============================================================================ #[tokio::test] async fn test_e2e_iceberg_order_execution() -> Result<()> { println!("\n=== E2E Test: Iceberg Order Execution ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_021"; cleanup_test_orders(&pool, account_id).await?; // Iceberg order - large order with hidden quantity let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Iceberg as i32, quantity: 10000.0, // Total quantity price: Some(180.0), stop_price: None, metadata: { let mut map = std::collections::HashMap::new(); map.insert("visible_quantity".to_string(), "100".to_string()); map }, }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!( " Iceberg order placed: total 10000, visible 100: {}", order_id ); // Verify order let status_req = Request::new(GetOrderStatusRequest { order_id }); let status = service.get_order_status(status_req).await?; assert!(status.into_inner().order.is_some()); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_trailing_stop_order() -> Result<()> { println!("\n=== E2E Test: Trailing Stop Order ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_022"; cleanup_test_orders(&pool, account_id).await?; // Trailing stop order let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "NVDA".to_string(), side: OrderSide::Sell as i32, order_type: OrderType::TrailingStop as i32, quantity: 100.0, price: None, stop_price: Some(5.0), // Trail by $5 metadata: std::collections::HashMap::new(), }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!(" Trailing stop order placed (trail: $5): {}", order_id); // Verify order exists let status_req = Request::new(GetOrderStatusRequest { order_id }); let status = service.get_order_status(status_req).await?; assert!(status.into_inner().order.is_some()); println!(" Trailing stop order active and monitoring"); cleanup_test_orders(&pool, account_id).await?; Ok(()) } #[tokio::test] async fn test_e2e_order_execution_with_fees() -> Result<()> { println!("\n=== E2E Test: Order Execution with Fees ==="); let service = setup_trading_service_with_db().await?; let pool = setup_test_db().await?; let account_id = "e2e_account_023"; cleanup_test_orders(&pool, account_id).await?; // Place order with fee metadata let request = Request::new(SubmitOrderRequest { account_id: account_id.to_string(), symbol: "SPY".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, price: None, stop_price: None, metadata: { let mut map = std::collections::HashMap::new(); map.insert("commission_rate".to_string(), "0.0001".to_string()); map }, }); let response = service.submit_order(request).await?; let order_id = response.into_inner().order_id; println!(" Order with fees placed: {}", order_id); // Calculate expected fees let expected_fee = 100.0 * 450.0 * 0.0001; // qty * price * rate println!(" Expected commission: ${:.2}", expected_fee); // Verify order let status_req = Request::new(GetOrderStatusRequest { order_id }); let status = service.get_order_status(status_req).await?; assert!(status.into_inner().order.is_some()); cleanup_test_orders(&pool, account_id).await?; Ok(()) }