Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade Files updated: - Cargo.lock: Dependency resolution for Tonic 0.14.2 - All build.rs: Updated for tonic-prost-build - Proto files: Regenerated with tonic-prost 0.14 - Examples/tests: Updated for new gRPC API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
649 lines
24 KiB
Rust
649 lines
24 KiB
Rust
//! Broker Integration Tests
|
|
//!
|
|
//! Comprehensive test suite for real broker connectivity and trading operations.
|
|
//! Tests Interactive Brokers TWS, ICMarkets FIX, and broker failover scenarios.
|
|
#![allow(unused_crate_dependencies)]
|
|
|
|
use std::time::{Duration, Instant};
|
|
use tokio::time::timeout;
|
|
// Note: These broker types should be imported from actual crate when available
|
|
// use data::brokers::{InteractiveBrokers, ICMarkets, BrokerManager};
|
|
use risk::{RiskEngine, PositionTracker};
|
|
// Simple test configuration for this file
|
|
#[derive(Debug, Clone)]
|
|
struct UnifiedTestConfig {
|
|
initial_capital: common::prelude::Decimal,
|
|
enable_logging: bool,
|
|
}
|
|
|
|
fn create_test_config() -> UnifiedTestConfig {
|
|
UnifiedTestConfig {
|
|
initial_capital: common::prelude::Decimal::from(100000),
|
|
enable_logging: false,
|
|
}
|
|
}
|
|
|
|
/// Configuration for broker testing
|
|
#[derive(Debug, Clone)]
|
|
pub struct BrokerTestConfig {
|
|
pub connection_timeout: Duration,
|
|
pub order_execution_timeout: Duration,
|
|
pub max_order_latency: Duration,
|
|
pub max_position_sync_time: Duration,
|
|
pub test_symbol: Symbol,
|
|
pub test_quantity: Quantity,
|
|
pub enable_real_trading: bool,
|
|
pub demo_mode: bool,
|
|
}
|
|
|
|
impl Default for BrokerTestConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
connection_timeout: Duration::from_secs(30),
|
|
order_execution_timeout: Duration::from_secs(10),
|
|
max_order_latency: Duration::from_millis(100),
|
|
max_position_sync_time: Duration::from_secs(5),
|
|
test_symbol: Symbol::new("EURUSD").unwrap(),
|
|
test_quantity: Quantity::new(1000).unwrap(),
|
|
enable_real_trading: false, // Safety: disable real trading by default
|
|
demo_mode: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Broker connection status
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum ConnectionStatus {
|
|
Connected,
|
|
Disconnected,
|
|
Connecting,
|
|
Error(String),
|
|
}
|
|
|
|
/// Order execution result
|
|
#[derive(Debug, Clone)]
|
|
pub struct OrderExecutionResult {
|
|
pub order_id: String,
|
|
pub execution_time: Duration,
|
|
pub filled_quantity: Quantity,
|
|
pub average_price: Price,
|
|
pub status: OrderStatus,
|
|
pub broker_fees: Price,
|
|
}
|
|
|
|
/// Position synchronization result
|
|
#[derive(Debug, Clone)]
|
|
pub struct PositionSyncResult {
|
|
pub symbol: Symbol,
|
|
pub broker_position: Quantity,
|
|
pub system_position: Quantity,
|
|
pub sync_time: Duration,
|
|
pub discrepancy: Quantity,
|
|
}
|
|
|
|
/// Broker test suite
|
|
pub struct BrokerTestSuite {
|
|
config: BrokerTestConfig,
|
|
broker_manager: BrokerManager,
|
|
risk_engine: RiskEngine,
|
|
position_tracker: PositionTracker,
|
|
}
|
|
|
|
impl BrokerTestSuite {
|
|
pub async fn new(config: BrokerTestConfig) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
|
let unified_config = create_test_config();
|
|
let broker_manager = BrokerManager::new(unified_config.broker.clone()).await?;
|
|
let risk_engine = RiskEngine::new(unified_config.risk.clone()).await?;
|
|
let position_tracker = PositionTracker::new().await?;
|
|
|
|
Ok(Self {
|
|
config,
|
|
broker_manager,
|
|
risk_engine,
|
|
position_tracker,
|
|
})
|
|
}
|
|
|
|
pub async fn test_broker_connection(&mut self, broker_name: &str) -> Result<ConnectionStatus, Box<dyn std::error::Error + Send + Sync>> {
|
|
let connection_future = self.broker_manager.connect(broker_name);
|
|
let result = timeout(self.config.connection_timeout, connection_future).await;
|
|
|
|
match result {
|
|
Ok(Ok(_)) => {
|
|
// Verify connection by requesting account info
|
|
let account_info = self.broker_manager.get_account_info(broker_name).await?;
|
|
if account_info.is_connected {
|
|
Ok(ConnectionStatus::Connected)
|
|
} else {
|
|
Ok(ConnectionStatus::Disconnected)
|
|
}
|
|
}
|
|
Ok(Err(e)) => Ok(ConnectionStatus::Error(e.to_string())),
|
|
Err(_) => Ok(ConnectionStatus::Error("Connection timeout".to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn test_order_execution(
|
|
&mut self,
|
|
broker_name: &str,
|
|
order: &Order
|
|
) -> Result<OrderExecutionResult, Box<dyn std::error::Error + Send + Sync>> {
|
|
let start_time = Instant::now();
|
|
|
|
// Submit order through broker
|
|
let execution_future = self.broker_manager.submit_order(broker_name, order);
|
|
let execution_result = timeout(self.config.order_execution_timeout, execution_future).await??;
|
|
|
|
let execution_time = start_time.elapsed();
|
|
|
|
// Validate execution latency
|
|
assert!(
|
|
execution_time <= self.config.max_order_latency,
|
|
"Order execution latency {}ms exceeds maximum {}ms",
|
|
execution_time.as_millis(),
|
|
self.config.max_order_latency.as_millis()
|
|
);
|
|
|
|
Ok(OrderExecutionResult {
|
|
order_id: execution_result.order_id,
|
|
execution_time,
|
|
filled_quantity: execution_result.filled_quantity,
|
|
average_price: execution_result.average_price,
|
|
status: execution_result.status,
|
|
broker_fees: execution_result.fees,
|
|
})
|
|
}
|
|
|
|
pub async fn test_position_synchronization(
|
|
&mut self,
|
|
broker_name: &str,
|
|
symbol: &Symbol
|
|
) -> Result<PositionSyncResult, Box<dyn std::error::Error + Send + Sync>> {
|
|
let start_time = Instant::now();
|
|
|
|
// Get broker position
|
|
let broker_position = self.broker_manager.get_position(broker_name, symbol).await?;
|
|
|
|
// Get system position
|
|
let system_position = self.position_tracker.get_position(symbol).await?;
|
|
|
|
let sync_time = start_time.elapsed();
|
|
|
|
// Calculate discrepancy
|
|
let discrepancy = Quantity::new(
|
|
(broker_position.value() - system_position.value()).abs()
|
|
)?;
|
|
|
|
// Validate sync time
|
|
assert!(
|
|
sync_time <= self.config.max_position_sync_time,
|
|
"Position sync time {}ms exceeds maximum {}ms",
|
|
sync_time.as_millis(),
|
|
self.config.max_position_sync_time.as_millis()
|
|
);
|
|
|
|
Ok(PositionSyncResult {
|
|
symbol: symbol.clone(),
|
|
broker_position,
|
|
system_position,
|
|
sync_time,
|
|
discrepancy,
|
|
})
|
|
}
|
|
|
|
pub async fn test_market_data_feed(&mut self, broker_name: &str) -> Result<Duration, Box<dyn std::error::Error + Send + Sync>> {
|
|
let start_time = Instant::now();
|
|
|
|
// Subscribe to market data
|
|
self.broker_manager.subscribe_market_data(broker_name, &self.config.test_symbol).await?;
|
|
|
|
// Wait for first market data update
|
|
let market_data = self.broker_manager.get_market_data(&self.config.test_symbol).await?;
|
|
|
|
let latency = start_time.elapsed();
|
|
|
|
// Validate market data quality
|
|
assert!(market_data.bid > Price::zero(), "Invalid bid price");
|
|
assert!(market_data.ask > Price::zero(), "Invalid ask price");
|
|
assert!(market_data.ask >= market_data.bid, "Ask price below bid price");
|
|
|
|
Ok(latency)
|
|
}
|
|
|
|
async fn create_test_order(&self, side: OrderSide) -> Result<Order, Box<dyn std::error::Error + Send + Sync>> {
|
|
let current_price = self.broker_manager.get_current_price(&self.config.test_symbol).await?;
|
|
|
|
// Create order slightly away from market to avoid immediate execution in demo
|
|
let order_price = match side {
|
|
OrderSide::Buy => current_price - Price::new(0.0001)?,
|
|
OrderSide::Sell => current_price + Price::new(0.0001)?,
|
|
};
|
|
|
|
Ok(Order {
|
|
id: format!("test_order_{}", chrono::Utc::now().timestamp_nanos()),
|
|
symbol: self.config.test_symbol.clone(),
|
|
side,
|
|
order_type: OrderType::Limit,
|
|
quantity: self.config.test_quantity,
|
|
price: Some(order_price),
|
|
stop_price: None,
|
|
time_in_force: TimeInForce::GoodTillCancel,
|
|
created_at: std::time::SystemTime::now(),
|
|
updated_at: std::time::SystemTime::now(),
|
|
status: OrderStatus::PendingNew,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_interactive_brokers_connection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config).await?;
|
|
|
|
let connection_status = test_suite.test_broker_connection("interactive_brokers").await?;
|
|
|
|
match connection_status {
|
|
ConnectionStatus::Connected => {
|
|
println!("✅ Interactive Brokers: Connected successfully");
|
|
}
|
|
ConnectionStatus::Error(msg) if msg.contains("TWS not running") => {
|
|
println!("⚠️ Interactive Brokers: TWS not running (expected in CI)");
|
|
return Ok(()); // Skip test if TWS not available
|
|
}
|
|
other => {
|
|
panic!("Interactive Brokers connection failed: {:?}", other);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_icmarkets_connection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config).await?;
|
|
|
|
let connection_status = test_suite.test_broker_connection("icmarkets").await?;
|
|
|
|
match connection_status {
|
|
ConnectionStatus::Connected => {
|
|
println!("✅ ICMarkets: Connected successfully");
|
|
}
|
|
ConnectionStatus::Error(msg) if msg.contains("credentials") => {
|
|
println!("⚠️ ICMarkets: No credentials configured (expected in CI)");
|
|
return Ok(()); // Skip test if credentials not available
|
|
}
|
|
other => {
|
|
panic!("ICMarkets connection failed: {:?}", other);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_order_lifecycle_interactive_brokers() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let mut config = BrokerTestConfig::default();
|
|
config.demo_mode = true; // Ensure demo mode for safety
|
|
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
// Skip if broker not available
|
|
let connection_status = test_suite.test_broker_connection("interactive_brokers").await?;
|
|
if connection_status != ConnectionStatus::Connected {
|
|
println!("⚠️ Skipping order test - Interactive Brokers not connected");
|
|
return Ok(());
|
|
}
|
|
|
|
// Test buy order
|
|
let buy_order = test_suite.create_test_order(OrderSide::Buy).await?;
|
|
let buy_result = test_suite.test_order_execution("interactive_brokers", &buy_order).await?;
|
|
|
|
assert!(
|
|
buy_result.execution_time <= config.max_order_latency,
|
|
"Buy order execution time {}ms exceeds limit",
|
|
buy_result.execution_time.as_millis()
|
|
);
|
|
|
|
// Test sell order
|
|
let sell_order = test_suite.create_test_order(OrderSide::Sell).await?;
|
|
let sell_result = test_suite.test_order_execution("interactive_brokers", &sell_order).await?;
|
|
|
|
assert!(
|
|
sell_result.execution_time <= config.max_order_latency,
|
|
"Sell order execution time {}ms exceeds limit",
|
|
sell_result.execution_time.as_millis()
|
|
);
|
|
|
|
println!("✅ Interactive Brokers Order Lifecycle: Buy={}ms, Sell={}ms",
|
|
buy_result.execution_time.as_millis(),
|
|
sell_result.execution_time.as_millis());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_order_lifecycle_icmarkets() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let mut config = BrokerTestConfig::default();
|
|
config.demo_mode = true; // Ensure demo mode for safety
|
|
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
// Skip if broker not available
|
|
let connection_status = test_suite.test_broker_connection("icmarkets").await?;
|
|
if connection_status != ConnectionStatus::Connected {
|
|
println!("⚠️ Skipping order test - ICMarkets not connected");
|
|
return Ok(());
|
|
}
|
|
|
|
// Test market order execution speed
|
|
let market_order = Order {
|
|
id: format!("market_test_{}", chrono::Utc::now().timestamp_nanos()),
|
|
symbol: config.test_symbol.clone(),
|
|
side: OrderSide::Buy,
|
|
order_type: OrderType::Market,
|
|
quantity: config.test_quantity,
|
|
price: None,
|
|
stop_price: None,
|
|
time_in_force: TimeInForce::ImmediateOrCancel,
|
|
created_at: std::time::SystemTime::now(),
|
|
updated_at: std::time::SystemTime::now(),
|
|
status: OrderStatus::PendingNew,
|
|
};
|
|
|
|
let result = test_suite.test_order_execution("icmarkets", &market_order).await?;
|
|
|
|
assert!(
|
|
result.execution_time <= Duration::from_millis(50),
|
|
"ICMarkets market order too slow: {}ms",
|
|
result.execution_time.as_millis()
|
|
);
|
|
|
|
println!("✅ ICMarkets Order Execution: {}ms market order",
|
|
result.execution_time.as_millis());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_position_synchronization() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
let brokers = vec!["interactive_brokers", "icmarkets"];
|
|
|
|
for broker_name in brokers {
|
|
let connection_status = test_suite.test_broker_connection(broker_name).await?;
|
|
if connection_status != ConnectionStatus::Connected {
|
|
println!("⚠️ Skipping position sync for {} - not connected", broker_name);
|
|
continue;
|
|
}
|
|
|
|
let sync_result = test_suite.test_position_synchronization(broker_name, &config.test_symbol).await?;
|
|
|
|
assert!(
|
|
sync_result.sync_time <= config.max_position_sync_time,
|
|
"{} position sync too slow: {}ms",
|
|
broker_name, sync_result.sync_time.as_millis()
|
|
);
|
|
|
|
// Allow small discrepancies (rounding, different precision)
|
|
assert!(
|
|
sync_result.discrepancy.value().abs() <= 1,
|
|
"{} position discrepancy too large: {} vs {}",
|
|
broker_name, sync_result.broker_position.value(), sync_result.system_position.value()
|
|
);
|
|
|
|
println!("✅ {}: Position sync {}ms, discrepancy={}",
|
|
broker_name, sync_result.sync_time.as_millis(), sync_result.discrepancy.value());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_market_data_feeds() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config).await?;
|
|
|
|
let brokers = vec!["interactive_brokers", "icmarkets"];
|
|
|
|
for broker_name in brokers {
|
|
let connection_status = test_suite.test_broker_connection(broker_name).await?;
|
|
if connection_status != ConnectionStatus::Connected {
|
|
println!("⚠️ Skipping market data test for {} - not connected", broker_name);
|
|
continue;
|
|
}
|
|
|
|
let data_latency = test_suite.test_market_data_feed(broker_name).await?;
|
|
|
|
assert!(
|
|
data_latency <= Duration::from_millis(500),
|
|
"{} market data latency too high: {}ms",
|
|
broker_name, data_latency.as_millis()
|
|
);
|
|
|
|
println!("✅ {}: Market data latency {}ms",
|
|
broker_name, data_latency.as_millis());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_broker_failover() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
// Test primary broker
|
|
let primary_status = test_suite.test_broker_connection("interactive_brokers").await?;
|
|
let backup_status = test_suite.test_broker_connection("icmarkets").await?;
|
|
|
|
if primary_status == ConnectionStatus::Connected {
|
|
println!("✅ Primary broker (Interactive Brokers) available");
|
|
|
|
// Test failover scenario
|
|
test_suite.broker_manager.simulate_disconnect("interactive_brokers").await?;
|
|
|
|
// Verify automatic failover to backup
|
|
let order = test_suite.create_test_order(OrderSide::Buy).await?;
|
|
let result = test_suite.broker_manager.submit_order_with_failover(&order).await?;
|
|
|
|
assert!(result.broker_used == "icmarkets" || backup_status != ConnectionStatus::Connected,
|
|
"Failover should use backup broker when primary unavailable");
|
|
|
|
println!("✅ Broker failover working: Primary → Backup");
|
|
} else if backup_status == ConnectionStatus::Connected {
|
|
println!("✅ Backup broker (ICMarkets) available as primary");
|
|
} else {
|
|
println!("⚠️ No brokers available for failover testing");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_risk_integration_with_brokers() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
// Test order rejection by risk engine
|
|
let large_order = Order {
|
|
id: format!("risk_test_{}", chrono::Utc::now().timestamp_nanos()),
|
|
symbol: config.test_symbol.clone(),
|
|
side: OrderSide::Buy,
|
|
order_type: OrderType::Market,
|
|
quantity: Quantity::new(1_000_000)?, // Intentionally large
|
|
price: None,
|
|
stop_price: None,
|
|
time_in_force: TimeInForce::ImmediateOrCancel,
|
|
created_at: std::time::SystemTime::now(),
|
|
updated_at: std::time::SystemTime::now(),
|
|
status: OrderStatus::PendingNew,
|
|
};
|
|
|
|
// Risk engine should reject this order
|
|
let risk_result = test_suite.risk_engine.validate_order(&large_order).await?;
|
|
assert!(!risk_result.is_valid, "Risk engine should reject oversized order");
|
|
|
|
// Test normal order approval
|
|
let normal_order = test_suite.create_test_order(OrderSide::Buy).await?;
|
|
let risk_result = test_suite.risk_engine.validate_order(&normal_order).await?;
|
|
assert!(risk_result.is_valid, "Risk engine should approve normal order");
|
|
|
|
println!("✅ Risk-Broker Integration: Order validation working");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_concurrent_broker_operations() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let test_suite = std::sync::Arc::new(tokio::sync::Mutex::new(BrokerTestSuite::new(config.clone()).await?));
|
|
|
|
// Test concurrent operations
|
|
let mut tasks = vec![];
|
|
let num_concurrent = 5;
|
|
|
|
for i in 0..num_concurrent {
|
|
let suite = test_suite.clone();
|
|
let config = config.clone();
|
|
|
|
tasks.push(tokio::spawn(async move {
|
|
let mut suite = suite.lock().await;
|
|
|
|
// Test concurrent market data requests
|
|
let start = Instant::now();
|
|
let market_data = suite.broker_manager.get_market_data(&config.test_symbol).await;
|
|
let duration = start.elapsed();
|
|
|
|
(i, market_data.is_ok(), duration)
|
|
}));
|
|
}
|
|
|
|
let results = futures::future::join_all(tasks).await;
|
|
|
|
for result in results {
|
|
let (task_id, success, duration) = result?;
|
|
assert!(success, "Concurrent operation {} failed", task_id);
|
|
assert!(
|
|
duration <= Duration::from_millis(200),
|
|
"Concurrent operation {} too slow: {}ms",
|
|
task_id, duration.as_millis()
|
|
);
|
|
}
|
|
|
|
println!("✅ Concurrent Broker Operations: {} parallel requests completed", num_concurrent);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_broker_error_handling() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config).await?;
|
|
|
|
// Test handling of invalid symbols
|
|
let invalid_symbol = Symbol::new("INVALID_SYMBOL")?;
|
|
let result = test_suite.broker_manager.get_market_data(&invalid_symbol).await;
|
|
assert!(result.is_err(), "Should reject invalid symbol");
|
|
|
|
// Test handling of malformed orders
|
|
let invalid_order = Order {
|
|
id: "invalid".to_string(),
|
|
symbol: Symbol::new("EURUSD")?,
|
|
side: OrderSide::Buy,
|
|
order_type: OrderType::Limit,
|
|
quantity: Quantity::new(-100)?, // Invalid negative quantity
|
|
price: Some(Price::new(-1.0)?), // Invalid negative price
|
|
stop_price: None,
|
|
time_in_force: TimeInForce::GoodTillCancel,
|
|
created_at: std::time::SystemTime::now(),
|
|
updated_at: std::time::SystemTime::now(),
|
|
status: OrderStatus::PendingNew,
|
|
};
|
|
|
|
let result = test_suite.broker_manager.submit_order("any_broker", &invalid_order).await;
|
|
assert!(result.is_err(), "Should reject invalid order");
|
|
|
|
println!("✅ Broker Error Handling: Invalid inputs properly rejected");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_comprehensive_broker_validation() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let config = BrokerTestConfig::default();
|
|
let mut test_suite = BrokerTestSuite::new(config.clone()).await?;
|
|
|
|
let brokers = vec!["interactive_brokers", "icmarkets"];
|
|
let mut connected_brokers = 0;
|
|
let mut total_execution_time = Duration::ZERO;
|
|
let mut total_sync_time = Duration::ZERO;
|
|
|
|
for broker_name in &brokers {
|
|
let connection_status = test_suite.test_broker_connection(broker_name).await?;
|
|
|
|
if connection_status == ConnectionStatus::Connected {
|
|
connected_brokers += 1;
|
|
|
|
// Test order execution if connected
|
|
let test_order = test_suite.create_test_order(OrderSide::Buy).await?;
|
|
if let Ok(execution_result) = test_suite.test_order_execution(broker_name, &test_order).await {
|
|
total_execution_time += execution_result.execution_time;
|
|
|
|
assert!(
|
|
execution_result.execution_time <= config.max_order_latency,
|
|
"{} execution time {}ms exceeds limit",
|
|
broker_name, execution_result.execution_time.as_millis()
|
|
);
|
|
}
|
|
|
|
// Test position synchronization
|
|
if let Ok(sync_result) = test_suite.test_position_synchronization(broker_name, &config.test_symbol).await {
|
|
total_sync_time += sync_result.sync_time;
|
|
|
|
assert!(
|
|
sync_result.sync_time <= config.max_position_sync_time,
|
|
"{} sync time {}ms exceeds limit",
|
|
broker_name, sync_result.sync_time.as_millis()
|
|
);
|
|
}
|
|
|
|
// Test market data feed
|
|
if let Ok(data_latency) = test_suite.test_market_data_feed(broker_name).await {
|
|
assert!(
|
|
data_latency <= Duration::from_millis(500),
|
|
"{} market data latency {}ms too high",
|
|
broker_name, data_latency.as_millis()
|
|
);
|
|
}
|
|
|
|
println!("✅ {}: All tests passed", broker_name);
|
|
} else {
|
|
println!("⚠️ {}: Not available for testing", broker_name);
|
|
}
|
|
}
|
|
|
|
// Overall system validation
|
|
if connected_brokers > 0 {
|
|
let avg_execution_time = total_execution_time / connected_brokers as u32;
|
|
let avg_sync_time = total_sync_time / connected_brokers as u32;
|
|
|
|
assert!(
|
|
avg_execution_time <= config.max_order_latency,
|
|
"Average execution time {}ms exceeds limit",
|
|
avg_execution_time.as_millis()
|
|
);
|
|
|
|
println!("🎯 COMPREHENSIVE BROKER VALIDATION PASSED");
|
|
println!(" Connected Brokers: {}/{}", connected_brokers, brokers.len());
|
|
println!(" Average Execution Time: {}ms", avg_execution_time.as_millis());
|
|
println!(" Average Sync Time: {}ms", avg_sync_time.as_millis());
|
|
println!(" All broker integrations meet production requirements");
|
|
} else {
|
|
println!("⚠️ No brokers available for comprehensive testing");
|
|
}
|
|
|
|
Ok(())
|
|
} |