//! ICMarkets FIX 4.4 Real Integration Validation Tests //! //! These tests validate the REAL ICMarkets FIX 4.4 integration by testing: //! - TCP connection establishment to ICMarkets FIX endpoint //! - FIX 4.4 protocol logon and session management //! - Order submission, modification, and cancellation workflows //! - Execution report processing and position tracking //! - FIX sequence number management and error recovery //! //! NOTE: These tests are designed to gracefully handle connection failures //! in CI environments while validating real broker integration functionality. #![allow(unused_crate_dependencies)] use std::env; use std::time::Duration; use std::collections::HashMap; use tokio::time::timeout; use tracing::{info, warn, error}; use trading_engine::brokers::icmarkets::{ICMarketsClient, FixMessageBuilder, FixMessage, FixMessageType, FixSequenceManager}; use trading_engine::brokers::config::ICMarketsConfig; use trading_engine::trading::data_interface::{BrokerInterface, BrokerConnectionStatus}; use trading_engine::prelude::{TradingOrder, OrderSide}; use trading_engine::trading_operations::OrderType; use common::TimeInForce; /// Helper function to create test ICMarkets configuration fn create_test_icmarkets_config() -> ICMarketsConfig { ICMarketsConfig { enabled: true, fix_endpoint: env::var("FOXHUNT_IC_FIX_ENDPOINT") .unwrap_or_else(|_| "demo1.p.ctrader.com".to_string()), fix_port: env::var("FOXHUNT_IC_FIX_PORT") .map(|p| p.parse().unwrap_or(5034)) .unwrap_or(5034), sender_comp_id: env::var("FOXHUNT_IC_SENDER_COMP_ID") .unwrap_or_else(|_| "FOXHUNT_TEST".to_string()), target_comp_id: env::var("FOXHUNT_IC_TARGET_COMP_ID") .unwrap_or_else(|_| "ICMARKETS".to_string()), rest_base_url: env::var("FOXHUNT_IC_REST_BASE_URL") .unwrap_or_else(|_| "https://api-demo.ctrader.com".to_string()), rate_limit_per_minute: 60, username: env::var("FOXHUNT_IC_USERNAME").ok(), password: env::var("FOXHUNT_IC_PASSWORD").ok(), account_id: env::var("FOXHUNT_IC_ACCOUNT_ID").ok(), } } /// Helper function to create test trading order fn create_test_order(symbol: &str, side: OrderSide, quantity: i64, price: f64) -> TradingOrder { TradingOrder { id: OrderId::new(), symbol: Symbol::new(symbol.to_string()), side, quantity: Quantity::from_f64(quantity as f64).unwrap_or_default(), price: Price::from_f64(price).unwrap_or_default(), order_type: OrderType::Limit, time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), metadata: HashMap::new(), } } #[tokio::test] async fn test_icmarkets_client_creation() { let config = create_test_icmarkets_config(); let client = ICMarketsClient::new(config); // Verify initial state assert_eq!(client.broker_name(), "ICMarkets_FIX44"); assert_eq!(client.connection_status(), BrokerConnectionStatus::Disconnected); assert!(!client.is_connected()); info!("✅ ICMarkets client creation test passed"); } #[tokio::test] async fn test_fix_message_protocol() { info!("🔄 Testing FIX 4.4 message protocol"); // Test FIX message construction let logon_msg = FixMessageBuilder::new(FixMessageType::Logon) .add_header("FOXHUNT_TEST", "ICMARKETS", 1) .add_field(98, "0") // EncryptMethod (None) .add_field(108, "30") // HeartBtInt (30 seconds) .add_field(553, "test_user") // Username .add_field(554, "test_password") // Password .build(); assert!(logon_msg.contains("8=FIX.4.4")); // BeginString assert!(logon_msg.contains("35=A")); // MsgType (Logon) assert!(logon_msg.contains("49=FOXHUNT_TEST")); // SenderCompID assert!(logon_msg.contains("56=ICMARKETS")); // TargetCompID assert!(logon_msg.contains("34=1")); // MsgSeqNum assert!(logon_msg.contains("98=0")); // EncryptMethod assert!(logon_msg.contains("108=30")); // HeartBtInt assert!(logon_msg.contains("553=test_user")); // Username assert!(logon_msg.contains("554=test_password")); // Password assert!(logon_msg.ends_with("10=")); // Checksum placeholder info!("✅ FIX logon message construction test passed"); // Test order message construction let order_msg = FixMessageBuilder::new(FixMessageType::NewOrderSingle) .add_header("FOXHUNT_TEST", "ICMARKETS", 2) .add_field(11, "ORDER123") // ClOrdID .add_field(55, "EURUSD") // Symbol .add_field(54, "1") // Side (Buy) .add_field(38, "100000") // OrderQty .add_field(40, "2") // OrdType (Limit) .add_field(44, "1.1250") // Price .add_field(59, "0") // TimeInForce (Day) .build(); assert!(order_msg.contains("35=D")); // MsgType (NewOrderSingle) assert!(order_msg.contains("11=ORDER123")); // ClOrdID assert!(order_msg.contains("55=EURUSD")); // Symbol assert!(order_msg.contains("54=1")); // Side assert!(order_msg.contains("38=100000")); // OrderQty assert!(order_msg.contains("40=2")); // OrdType assert!(order_msg.contains("44=1.1250")); // Price assert!(order_msg.contains("59=0")); // TimeInForce info!("✅ FIX order message construction test passed"); // Test message parsing let test_message = "8=FIX.4.4\x019=49\x0135=A\x0149=ICMARKETS\x0156=FOXHUNT_TEST\x0134=1\x0198=0\x01108=30\x0110=123\x01"; let parsed = FixMessage::parse(test_message).unwrap(); assert_eq!(parsed.get_field(8), Some(&"FIX.4.4".to_string())); // BeginString assert_eq!(parsed.get_field(35), Some(&"A".to_string())); // MsgType assert_eq!(parsed.get_field(49), Some(&"ICMARKETS".to_string())); // SenderCompID assert_eq!(parsed.get_field(56), Some(&"FOXHUNT_TEST".to_string())); // TargetCompID assert_eq!(parsed.get_field(34), Some(&"1".to_string())); // MsgSeqNum assert_eq!(parsed.msg_type, Some(FixMessageType::Logon)); info!("✅ FIX message parsing test passed"); // Test execution report parsing let exec_report = "8=FIX.4.4\x019=150\x0135=8\x0149=ICMARKETS\x0156=FOXHUNT_TEST\x0134=2\x0152=20231201-12:30:45\x0111=ORDER123\x0117=EXEC001\x0120=0\x01150=F\x0139=2\x0155=EURUSD\x0154=1\x0138=100000\x0114=100000\x016=1.1250\x01151=0\x0110=234\x01"; let exec_parsed = FixMessage::parse(exec_report).unwrap(); assert_eq!(exec_parsed.msg_type, Some(FixMessageType::ExecutionReport)); assert_eq!(exec_parsed.get_field(11), Some(&"ORDER123".to_string())); // ClOrdID assert_eq!(exec_parsed.get_field(17), Some(&"EXEC001".to_string())); // ExecID assert_eq!(exec_parsed.get_field(150), Some(&"F".to_string())); // ExecType (Trade) assert_eq!(exec_parsed.get_field(39), Some(&"2".to_string())); // OrdStatus (Filled) assert_eq!(exec_parsed.get_field(55), Some(&"EURUSD".to_string())); // Symbol assert_eq!(exec_parsed.get_field_as_f64(14), Some(100000.0)); // LastQty assert_eq!(exec_parsed.get_field_as_f64(6), Some(1.1250)); // AvgPx info!("✅ FIX execution report parsing test passed"); info!("✅ FIX 4.4 message protocol validation completed"); } #[tokio::test] async fn test_fix_sequence_management() { info!("🔄 Testing FIX sequence number management"); let seq_mgr = FixSequenceManager::new(); // Test outgoing sequence numbers assert_eq!(seq_mgr.get_next_outgoing(), 1); assert_eq!(seq_mgr.get_next_outgoing(), 2); assert_eq!(seq_mgr.get_next_outgoing(), 3); // Test incoming sequence validation assert!(seq_mgr.validate_incoming(1)); // Expected sequence assert!(seq_mgr.validate_incoming(2)); // Next expected assert!(!seq_mgr.validate_incoming(4)); // Gap detected assert!(seq_mgr.validate_incoming(3)); // Back to expected // Test sequence reset seq_mgr.reset(); assert_eq!(seq_mgr.get_next_outgoing(), 1); assert!(seq_mgr.validate_incoming(1)); info!("✅ FIX sequence management test passed"); } #[tokio::test] async fn test_icmarkets_connection_attempt() { let config = create_test_icmarkets_config(); let mut client = ICMarketsClient::new(config.clone()); info!("🔄 Attempting connection to ICMarkets FIX at {}:{}", config.fix_endpoint, config.fix_port); // Attempt connection with timeout let connection_result = timeout( Duration::from_secs(15), client.connect() ).await; match connection_result { Ok(Ok(())) => { info!("✅ Successfully connected to ICMarkets FIX!"); // Verify connection status assert!(client.is_connected()); assert_eq!(client.connection_status(), BrokerConnectionStatus::Connected); // Test heartbeat let heartbeat_result = client.send_heartbeat().await; match heartbeat_result { Ok(()) => info!("✅ Heartbeat successful"), Err(e) => warn!("⚠ïļ Heartbeat failed: {}", e), } // Get account info let account_info = client.get_account_info().await; match account_info { Ok(info) => { info!("✅ Account info retrieved:"); for (key, value) in info { info!(" {}: {}", key, value); } } Err(e) => warn!("⚠ïļ Failed to get account info: {}", e), } // Clean disconnection let disconnect_result = client.disconnect().await; match disconnect_result { Ok(()) => info!("✅ Disconnected cleanly"), Err(e) => warn!("⚠ïļ Disconnect error: {}", e), } } Ok(Err(e)) => { warn!("⚠ïļ ICMarkets connection failed (expected in CI): {}", e); info!(" This is normal if credentials are not configured"); // Verify we're still in disconnected state assert!(!client.is_connected()); assert_eq!(client.connection_status(), BrokerConnectionStatus::Disconnected); } Err(_) => { warn!("⚠ïļ ICMarkets connection timed out (expected in CI)"); info!(" This is normal if FIX endpoint is not accessible"); } } info!("✅ ICMarkets connection test completed (graceful handling verified)"); } #[tokio::test] async fn test_icmarkets_order_submission_workflow() { let config = create_test_icmarkets_config(); let mut client = ICMarketsClient::new(config); // Try to connect (may fail in CI) let connection_result = timeout( Duration::from_secs(10), client.connect() ).await; match connection_result { Ok(Ok(())) => { info!("✅ Connected to ICMarkets for order testing"); // Create test orders for Forex pairs let test_orders = vec![ create_test_order("EURUSD", OrderSide::Buy, 100000, 1.1250), // 1 lot EUR/USD create_test_order("GBPUSD", OrderSide::Sell, 50000, 1.2750), // 0.5 lot GBP/USD create_test_order("USDJPY", OrderSide::Buy, 100000, 149.50), // 1 lot USD/JPY ]; for (i, order) in test_orders.into_iter().enumerate() { info!("🔄 Submitting test order {}: {} {} units of {}", i + 1, order.side, order.quantity, order.symbol); let submit_result = client.submit_order(order).await; match submit_result { Ok(broker_order_id) => { info!("✅ Order submitted successfully: {}", broker_order_id); // Wait a moment for order processing tokio::time::sleep(Duration::from_millis(1000)).await; // Check order status let status_result = client.get_order_status(&broker_order_id).await; match status_result { Ok(status) => { info!(" Order status: {:?}", status); } Err(e) => { warn!(" Failed to get order status: {}", e); } } // Test order cancellation let cancel_result = client.cancel_order(&broker_order_id).await; match cancel_result { Ok(()) => { info!("✅ Order cancelled successfully"); } Err(e) => { warn!("⚠ïļ Order cancellation failed: {}", e); } } } Err(e) => { warn!("⚠ïļ Order submission failed: {}", e); info!(" This may be normal if using demo account"); } } } // Test position retrieval let positions_result = client.get_positions().await; match positions_result { Ok(positions) => { info!("✅ Retrieved {} positions", positions.len()); for position in positions { info!(" Position: {} {} units @ {}", position.symbol, position.quantity, position.average_price); } } Err(e) => { warn!("⚠ïļ Failed to get positions: {}", e); } } // Test execution subscription let execution_result = client.subscribe_executions().await; match execution_result { Ok(mut rx) => { info!("✅ Execution subscription established"); // Wait briefly for any execution reports let timeout_result = timeout( Duration::from_secs(2), rx.recv() ).await; match timeout_result { Ok(Some(execution)) => { info!("✅ Received execution report:"); info!(" Order ID: {}", execution.order_id); info!(" Symbol: {}", execution.symbol); info!(" Side: {:?}", execution.side); info!(" Quantity: {}", execution.quantity); info!(" Status: {:?}", execution.status); } Ok(None) => { info!(" Execution channel closed"); } Err(_) => { info!(" No executions received (normal for test)"); } } } Err(e) => { warn!("⚠ïļ Failed to subscribe to executions: {}", e); } } let _ = client.disconnect().await; } Ok(Err(e)) => { warn!("⚠ïļ Cannot test orders - ICMarkets not connected: {}", e); info!(" Testing order validation logic instead..."); // Test order validation without connection let test_order = create_test_order("EURUSD", OrderSide::Buy, 100000, 1.1250); let submit_result = client.submit_order(&test_order).await; // Should fail with "not connected" error match submit_result { Ok(_) => { error!("❌ Order unexpectedly succeeded without connection"); panic!("Order should fail when not connected"); } Err(e) => { info!("✅ Order properly failed when not connected: {}", e); assert!(e.to_string().to_lowercase().contains("not logged on") || e.to_string().to_lowercase().contains("not available")); } } } Err(_) => { warn!("⚠ïļ ICMarkets connection timed out - testing offline validation"); // Test that orders fail properly when not connected let test_order = create_test_order("EURUSD", OrderSide::Buy, 100000, 1.1250); let submit_result = client.submit_order(&test_order).await; match submit_result { Ok(_) => { error!("❌ Order unexpectedly succeeded without connection"); } Err(e) => { info!("✅ Order properly failed when not connected: {}", e); } } } } info!("✅ ICMarkets order workflow test completed"); } #[tokio::test] async fn test_icmarkets_order_modification() { let config = create_test_icmarkets_config(); let mut client = ICMarketsClient::new(config); // Try to connect let connection_result = timeout( Duration::from_secs(10), client.connect() ).await; if let Ok(Ok(())) = connection_result { info!("✅ Connected to ICMarkets for order modification testing"); // Submit an initial order let original_order = create_test_order("EURUSD", OrderSide::Buy, 100000, 1.1250); match client.submit_order(&original_order).await { Ok(broker_order_id) => { info!("✅ Original order submitted: {}", broker_order_id); // Wait for order to be processed tokio::time::sleep(Duration::from_millis(1000)).await; // Create modified order (different price and quantity) let modified_order = create_test_order("EURUSD", OrderSide::Buy, 150000, 1.1240); // Test order modification (FIX OrderCancelReplaceRequest) let modify_result = client.modify_order(&broker_order_id, &modified_order).await; match modify_result { Ok(()) => { info!("✅ Order modification successful"); // Verify the modification took effect let status_result = client.get_order_status(&broker_order_id).await; match status_result { Ok(status) => { info!(" Modified order status: {:?}", status); } Err(e) => { warn!(" Failed to get modified order status: {}", e); } } } Err(e) => { warn!("⚠ïļ Order modification failed: {}", e); info!(" This may be normal depending on order state"); } } // Clean up - cancel the order let _ = client.cancel_order(&broker_order_id).await; } Err(e) => { warn!("⚠ïļ Cannot test modification - order submission failed: {}", e); } } let _ = client.disconnect().await; } else { warn!("⚠ïļ Cannot test modification - ICMarkets not connected"); info!(" Testing modification validation without connection..."); // Test modification without connection let test_order = create_test_order("EURUSD", OrderSide::Buy, 100000, 1.1250); let modify_result = client.modify_order("fake_order_id", &test_order).await; match modify_result { Ok(()) => { error!("❌ Modification unexpectedly succeeded without connection"); } Err(e) => { info!("✅ Modification properly failed when not connected: {}", e); } } } info!("✅ ICMarkets order modification test completed"); } #[tokio::test] async fn test_icmarkets_session_management() { let config = create_test_icmarkets_config(); let client = ICMarketsClient::new(config); info!("🔄 Testing ICMarkets FIX session management"); // Test reconnection without initial connection let reconnect_result = client.reconnect().await; match reconnect_result { Ok(()) => { info!("✅ Reconnection succeeded"); // Verify connection state assert!(client.is_connected()); // Test session state after reconnection let account_info = client.get_account_info().await; match account_info { Ok(info) => { info!("✅ Session established - account info available"); info!(" Session state: {}", info.get("session_state").unwrap_or(&"unknown".to_string())); } Err(e) => { warn!("⚠ïļ Session not fully established: {}", e); } } // Test multiple rapid reconnections (session recovery) for i in 1..=3 { let rapid_reconnect = timeout( Duration::from_secs(5), client.reconnect() ).await; match rapid_reconnect { Ok(Ok(())) => { info!("✅ Rapid reconnection {} succeeded", i); } Ok(Err(e)) => { warn!("⚠ïļ Rapid reconnection {} failed: {}", i, e); } Err(_) => { warn!("⚠ïļ Rapid reconnection {} timed out", i); } } // Small delay between attempts tokio::time::sleep(Duration::from_millis(100)).await; } } Err(e) => { warn!("⚠ïļ Reconnection failed (expected in CI): {}", e); info!(" This is normal if ICMarkets FIX endpoint is not accessible"); } } info!("✅ ICMarkets session management test completed"); } #[tokio::test] async fn test_icmarkets_error_handling() { let config = create_test_icmarkets_config(); let client = ICMarketsClient::new(config); info!("🔄 Testing ICMarkets error handling"); // Test operations without connection let test_order = create_test_order("INVALID_SYMBOL", OrderSide::Buy, 0, -1.0); // Test invalid order submission let submit_result = client.submit_order(&test_order).await; match submit_result { Ok(_) => { error!("❌ Invalid order unexpectedly succeeded"); } Err(e) => { info!("✅ Invalid order properly rejected: {}", e); } } // Test cancellation of non-existent order let cancel_result = client.cancel_order("non_existent_order").await; match cancel_result { Ok(()) => { warn!("⚠ïļ Cancellation of non-existent order unexpectedly succeeded"); } Err(e) => { info!("✅ Cancellation of non-existent order properly failed: {}", e); } } // Test getting status of non-existent order let status_result = client.get_order_status("non_existent_order").await; match status_result { Ok(status) => { warn!("⚠ïļ Got status for non-existent order: {:?}", status); } Err(e) => { info!("✅ Status check for non-existent order properly failed: {}", e); } } // Test operations with invalid configuration let mut invalid_config = create_test_icmarkets_config(); invalid_config.fix_port = 0; // Invalid port invalid_config.username = None; // Missing credentials invalid_config.password = None; let invalid_client = ICMarketsClient::new(invalid_config); let invalid_connect_result = timeout( Duration::from_secs(5), async move { let mut client = invalid_client; client.connect().await } ).await; match invalid_connect_result { Ok(Ok(())) => { error!("❌ Connection with invalid config unexpectedly succeeded"); } Ok(Err(e)) => { info!("✅ Connection with invalid config properly failed: {}", e); } Err(_) => { info!("✅ Connection with invalid config properly timed out"); } } info!("✅ ICMarkets error handling test completed"); } #[tokio::test] async fn test_icmarkets_performance_characteristics() { let config = create_test_icmarkets_config(); let client = ICMarketsClient::new(config); info!("🔄 Testing ICMarkets performance characteristics"); // Test client creation performance let start = std::time::Instant::now(); let iterations = 100; for _ in 0..iterations { let test_config = create_test_icmarkets_config(); let _test_client = ICMarketsClient::new(test_config); } let creation_time = start.elapsed(); let avg_creation_time = creation_time / iterations; info!("✅ Client creation performance:"); info!(" {} iterations in {:?}", iterations, creation_time); info!(" Average: {:?} per client", avg_creation_time); // Should be very fast (under 1ms per creation) assert!(avg_creation_time < Duration::from_millis(1), "Client creation too slow: {:?}", avg_creation_time); // Test FIX message construction performance let msg_start = std::time::Instant::now(); let msg_iterations = 1000; for i in 0..msg_iterations { let _msg = FixMessageBuilder::new(FixMessageType::NewOrderSingle) .add_header("FOXHUNT_TEST", "ICMARKETS", i + 1) .add_field(11, &format!("ORDER{}", i)) // ClOrdID .add_field(55, "EURUSD") // Symbol .add_field(54, "1") // Side (Buy) .add_field(38, "100000") // OrderQty .add_field(40, "2") // OrdType (Limit) .add_field(44, "1.1250") // Price .add_field(59, "0") // TimeInForce (Day) .build(); } let msg_time = msg_start.elapsed(); let avg_msg_time = msg_time / msg_iterations; info!("✅ FIX message construction performance:"); info!(" {} iterations in {:?}", msg_iterations, msg_time); info!(" Average: {:?} per message", avg_msg_time); // Should be very fast (under 20Ξs per message) assert!(avg_msg_time < Duration::from_micros(20), "FIX message construction too slow: {:?}", avg_msg_time); // Test FIX message parsing performance let parse_start = std::time::Instant::now(); let parse_iterations = 1000; let test_exec_report = "8=FIX.4.4\x019=150\x0135=8\x0149=ICMARKETS\x0156=FOXHUNT_TEST\x0134=2\x0152=20231201-12:30:45\x0111=ORDER123\x0117=EXEC001\x0120=0\x01150=F\x0139=2\x0155=EURUSD\x0154=1\x0138=100000\x0114=100000\x016=1.1250\x01151=0\x0110=234\x01"; for _ in 0..parse_iterations { let _parsed = FixMessage::parse(test_exec_report).unwrap(); } let parse_time = parse_start.elapsed(); let avg_parse_time = parse_time / parse_iterations; info!("✅ FIX message parsing performance:"); info!(" {} iterations in {:?}", parse_iterations, parse_time); info!(" Average: {:?} per message", avg_parse_time); // Should be very fast (under 10Ξs per message) assert!(avg_parse_time < Duration::from_micros(10), "FIX message parsing too slow: {:?}", avg_parse_time); info!("✅ ICMarkets performance characteristics test completed"); } /// Integration test helper for ICMarkets configuration validation #[tokio::test] async fn test_icmarkets_configuration_validation() { info!("🔄 Testing ICMarkets configuration validation"); // Test valid configuration let valid_config = create_test_icmarkets_config(); let client = ICMarketsClient::new(valid_config); assert_eq!(client.broker_name(), "ICMarkets_FIX44"); // Test configuration with different FIX endpoints let test_configs = vec![ // Demo endpoints ("demo1.p.ctrader.com", 5034), ("demo2.p.ctrader.com", 5034), // Live endpoints (would fail without proper credentials) ("h4.p.ctrader.com", 5034), ("h8.p.ctrader.com", 5034), ("h12.p.ctrader.com", 5034), ("h16.p.ctrader.com", 5034), ]; for (endpoint, port) in test_configs { let mut config = create_test_icmarkets_config(); config.fix_endpoint = endpoint.to_string(); config.fix_port = port; let test_client = ICMarketsClient::new(config); assert_eq!(test_client.broker_name(), "ICMarkets_FIX44"); info!("✅ Configuration valid for {}:{}", endpoint, port); } // Test different comp IDs let comp_id_configs = vec![ ("FOXHUNT_PROD", "ICMARKETS"), ("FOXHUNT_DEMO", "ICMARKETS"), ("FOXHUNT_TEST", "ICMARKETS"), ]; for (sender_id, target_id) in comp_id_configs { let mut config = create_test_icmarkets_config(); config.sender_comp_id = sender_id.to_string(); config.target_comp_id = target_id.to_string(); let test_client = ICMarketsClient::new(config); assert_eq!(test_client.broker_name(), "ICMarkets_FIX44"); info!("✅ Configuration valid for comp IDs: {} -> {}", sender_id, target_id); } info!("✅ ICMarkets configuration validation completed"); } #[tokio::test] async fn test_forex_specific_order_handling() { info!("🔄 Testing Forex-specific order handling"); let config = create_test_icmarkets_config(); let client = ICMarketsClient::new(config); // Test major currency pairs let forex_pairs = vec![ ("EURUSD", 1.1250, 100000), // 1 lot EUR/USD ("GBPUSD", 1.2750, 50000), // 0.5 lot GBP/USD ("USDJPY", 149.50, 100000), // 1 lot USD/JPY ("AUDUSD", 0.6750, 100000), // 1 lot AUD/USD ("USDCAD", 1.3250, 100000), // 1 lot USD/CAD ("NZDUSD", 0.6150, 100000), // 1 lot NZD/USD ("EURGBP", 0.8750, 100000), // 1 lot EUR/GBP ("EURJPY", 163.25, 100000), // 1 lot EUR/JPY ]; for (symbol, price, quantity) in forex_pairs { let order = create_test_order(symbol, OrderSide::Buy, quantity, price); // Test order validation (should work without connection) info!("Testing order for {}: {} {} @ {}", symbol, order.side, quantity, price); // Verify order structure assert_eq!(order.symbol.to_string(), symbol); assert_eq!(order.quantity.to_i64().unwrap_or(0), quantity); assert_eq!(order.price.to_f64(), price); assert_eq!(order.order_type, OrderType::Limit); info!("✅ Order structure valid for {}", symbol); } // Test pip calculations for different pairs let pip_tests = vec![ ("EURUSD", 1.1250, 1.1251, 1.0), // 4-decimal pair ("USDJPY", 149.50, 149.51, 1.0), // 2-decimal pair ("EURJPY", 163.25, 163.26, 1.0), // 2-decimal pair ]; for (symbol, price1, price2, expected_pips) in pip_tests { let pip_diff = if symbol.contains("JPY") { (price2 - price1) * 100.0 // JPY pairs have 2 decimal places } else { (price2 - price1) * 10000.0 // Major pairs have 4 decimal places }; assert!((pip_diff - expected_pips).abs() < 0.001, "Pip calculation failed for {}: expected {}, got {}", symbol, expected_pips, pip_diff); info!("✅ Pip calculation correct for {}: {} pips", symbol, pip_diff); } info!("✅ Forex-specific order handling test completed"); }