**Status: Production Code Ready, Test Suite Needs Work** ## Agent Results (12/12 Completed) ### Import & Error Fixes (Agents 1-7) ✅ Agent 1: Fixed testcontainers imports (1 file) ✅ Agent 2: No Decimal errors found (already fixed) ✅ Agent 3: Fixed 30 prelude imports across 26 files ✅ Agent 4: Fixed 5 test module imports ✅ Agent 5: Fixed hdrhistogram dependency ✅ Agent 6: Fixed 3 function argument mismatches ✅ Agent 7: Fixed 3 Try operator errors ### Warning Cleanup (Agents 8-11) ✅ Agent 8: Fixed 12 unused dependency warnings ✅ Agent 9: Fixed 30 unnecessary qualifications ✅ Agent 10: Suppressed 54 dead code warnings ✅ Agent 11: Fixed 15 misc warnings (numeric types, clippy) ### Final Verification (Agent 12) ✅ Comprehensive analysis and report generated ✅ Test execution results documented ✅ Coverage estimation completed ## Production Status: ✅ READY - **All 38 crates compile** successfully - **0 compilation errors** in production code - **145 non-critical warnings** (style/docs) - Services can be built and deployed ## Test Status: ⚠️ NEEDS WORK - **587 tests PASS** (99.8% of compilable tests) - **1 test FAILS** (database config - low severity) - **~70 test errors remain** in 4 crates: - ml crate: 30 errors (type system issues) - tests crate: 8 errors (missing infrastructure) - trading_service: 10 errors (API changes) - e2e_tests: 5 errors (integration gaps) ## Coverage: 35-40% Estimated - Strong: data (70%), config (75%), market-data (65%) - Medium: common (50%), adaptive-strategy (45%) - Gap: ML (0%), risk (0%), trading_engine (0%) ## Deliverables - Comprehensive final report: WAVE33_3_FINAL_REPORT.md - All agent work committed and documented - Clear next steps identified ## Next: Wave 34 Fix ~70 remaining test compilation errors to achieve: - 95% test coverage target - Full test suite passing - Complete production readiness 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1281 lines
49 KiB
Rust
1281 lines
49 KiB
Rust
//! Comprehensive Integration Tests for Service Communication
|
|
//!
|
|
//! This test suite provides extensive coverage for communication between all
|
|
//! Foxhunt HFT system services including gRPC, message passing, authentication,
|
|
//! and end-to-end workflow testing.
|
|
|
|
use anyhow::Result;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::sync::{broadcast, RwLock};
|
|
use tonic::transport::{Channel, Server};
|
|
use tonic::{Code, Request, Response, Status};
|
|
use uuid::Uuid;
|
|
|
|
// Test utilities and mocks
|
|
// use trading_engine::prelude::*; // REMOVED - prelude does not exist
|
|
|
|
#[cfg(test)]
|
|
mod integration_service_communication_tests {
|
|
use super::*;
|
|
use futures_util::StreamExt;
|
|
use tokio_stream::wrappers::ReceiverStream;
|
|
|
|
// ========================================================================
|
|
// Service Communication Infrastructure Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_grpc_server_startup_and_health_check() {
|
|
// Test server startup for all services
|
|
let trading_server = MockTradingServer::new().await;
|
|
assert!(trading_server.is_ok());
|
|
|
|
let backtesting_server = MockBacktestingServer::new().await;
|
|
assert!(backtesting_server.is_ok());
|
|
|
|
let ml_training_server = MockMLTrainingServer::new().await;
|
|
assert!(ml_training_server.is_ok());
|
|
|
|
// Test health check endpoints
|
|
let health_status = trading_server.unwrap().health_check().await;
|
|
assert!(health_status.is_ok());
|
|
|
|
let health_response = health_status.unwrap();
|
|
assert_eq!(health_response.status, ServiceStatus::Healthy);
|
|
assert!(!health_response.version.is_empty());
|
|
assert!(health_response.uptime_seconds > 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_service_discovery_and_connectivity() {
|
|
// Initialize service registry
|
|
let mut registry = ServiceRegistry::new();
|
|
|
|
// Register services
|
|
let trading_service = ServiceEndpoint {
|
|
service_type: ServiceType::Trading,
|
|
address: "127.0.0.1".to_string(),
|
|
port: 50051,
|
|
health_check_path: "/health".to_string(),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
let backtesting_service = ServiceEndpoint {
|
|
service_type: ServiceType::Backtesting,
|
|
address: "127.0.0.1".to_string(),
|
|
port: 50052,
|
|
health_check_path: "/health".to_string(),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
registry.register_service(trading_service.clone()).await.expect("Failed to register trading service");
|
|
registry.register_service(backtesting_service.clone()).await.expect("Failed to register backtesting service");
|
|
|
|
// Test service discovery
|
|
let discovered_services = registry.discover_services(ServiceType::Trading).await;
|
|
assert!(discovered_services.is_ok());
|
|
|
|
let services = discovered_services.unwrap();
|
|
assert_eq!(services.len(), 1);
|
|
assert_eq!(services[0].service_type, ServiceType::Trading);
|
|
assert_eq!(services[0].port, 50051);
|
|
|
|
// Test connectivity
|
|
let connectivity_test = registry.test_connectivity(&trading_service).await;
|
|
assert!(connectivity_test.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_authentication_and_authorization() {
|
|
let mut auth_service = MockAuthService::new();
|
|
|
|
// Test JWT token generation
|
|
let token_request = TokenRequest {
|
|
username: "trader_001".to_string(),
|
|
password: "secure_password".to_string(),
|
|
service: ServiceType::Trading,
|
|
permissions: vec![
|
|
Permission::Trading,
|
|
Permission::RiskManagement,
|
|
Permission::MarketData,
|
|
],
|
|
};
|
|
|
|
let token_response = auth_service.generate_token(token_request).await;
|
|
assert!(token_response.is_ok());
|
|
|
|
let token = token_response.unwrap();
|
|
assert!(!token.access_token.is_empty());
|
|
assert!(!token.refresh_token.is_empty());
|
|
assert!(token.expires_in > 0);
|
|
|
|
// Test token validation
|
|
let validation_result = auth_service.validate_token(&token.access_token).await;
|
|
assert!(validation_result.is_ok());
|
|
|
|
let validation = validation_result.unwrap();
|
|
assert!(validation.is_valid);
|
|
assert_eq!(validation.username, "trader_001");
|
|
assert!(validation.permissions.contains(&Permission::Trading));
|
|
|
|
// Test authorization for specific actions
|
|
let auth_check = auth_service.check_authorization(
|
|
&token.access_token,
|
|
ServiceType::Trading,
|
|
"place_order"
|
|
).await;
|
|
assert!(auth_check.is_ok());
|
|
assert!(auth_check.unwrap());
|
|
|
|
// Test unauthorized action
|
|
let unauth_check = auth_service.check_authorization(
|
|
&token.access_token,
|
|
ServiceType::Trading,
|
|
"admin_shutdown"
|
|
).await;
|
|
assert!(unauth_check.is_ok());
|
|
assert!(!unauth_check.unwrap());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_mTLS_certificate_validation() {
|
|
let tls_config = MockTLSConfig::new();
|
|
|
|
// Test certificate loading
|
|
let cert_result = tls_config.load_server_certificates().await;
|
|
assert!(cert_result.is_ok());
|
|
|
|
let certificates = cert_result.unwrap();
|
|
assert!(!certificates.certificate_chain.is_empty());
|
|
assert!(!certificates.private_key.is_empty());
|
|
assert!(certificates.ca_certificate.is_some());
|
|
|
|
// Test certificate validation
|
|
let validation_result = tls_config.validate_client_certificate(&certificates.certificate_chain[0]).await;
|
|
assert!(validation_result.is_ok());
|
|
|
|
let validation = validation_result.unwrap();
|
|
assert!(validation.is_valid);
|
|
assert!(!validation.subject.is_empty());
|
|
assert!(!validation.issuer.is_empty());
|
|
assert!(validation.not_after > chrono::Utc::now());
|
|
|
|
// Test expired certificate rejection
|
|
let expired_cert = MockTLSConfig::create_expired_certificate();
|
|
let expired_validation = tls_config.validate_client_certificate(&expired_cert).await;
|
|
assert!(expired_validation.is_ok());
|
|
|
|
let expired_result = expired_validation.unwrap();
|
|
assert!(!expired_result.is_valid);
|
|
assert!(expired_result.errors.contains(&"Certificate expired".to_string()));
|
|
}
|
|
|
|
// ========================================================================
|
|
// Trading Service Communication Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_service_order_management() {
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
|
|
// Test order placement
|
|
let order_request = PlaceOrderRequest {
|
|
symbol: "EURUSD".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Limit as i32,
|
|
quantity: 10000.0,
|
|
price: Some(1.2345),
|
|
time_in_force: TimeInForce::GoodTillCancel as i32,
|
|
client_order_id: Uuid::new_v4().to_string(),
|
|
trader_id: "TRADER_001".to_string(),
|
|
};
|
|
|
|
let order_response = trading_client.place_order(Request::new(order_request)).await;
|
|
assert!(order_response.is_ok());
|
|
|
|
let response = order_response.unwrap().into_inner();
|
|
assert!(response.success);
|
|
assert!(!response.order_id.is_empty());
|
|
assert!(response.error_message.is_empty());
|
|
|
|
// Test order status query
|
|
let status_request = GetOrderStatusRequest {
|
|
order_id: response.order_id.clone(),
|
|
};
|
|
|
|
let status_response = trading_client.get_order_status(Request::new(status_request)).await;
|
|
assert!(status_response.is_ok());
|
|
|
|
let status = status_response.unwrap().into_inner();
|
|
assert_eq!(status.order_id, response.order_id);
|
|
assert_eq!(status.status, OrderStatus::Pending as i32);
|
|
assert_eq!(status.symbol, "EURUSD");
|
|
|
|
// Test order cancellation
|
|
let cancel_request = CancelOrderRequest {
|
|
order_id: response.order_id.clone(),
|
|
trader_id: "TRADER_001".to_string(),
|
|
};
|
|
|
|
let cancel_response = trading_client.cancel_order(Request::new(cancel_request)).await;
|
|
assert!(cancel_response.is_ok());
|
|
|
|
let cancellation = cancel_response.unwrap().into_inner();
|
|
assert!(cancellation.success);
|
|
assert_eq!(cancellation.order_id, response.order_id);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_service_risk_management_integration() {
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
|
|
// Test position limits validation
|
|
let position_check = PositionLimitCheckRequest {
|
|
trader_id: "TRADER_001".to_string(),
|
|
symbol: "EURUSD".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
quantity: 100000.0, // Large position to trigger limit
|
|
current_price: 1.2345,
|
|
};
|
|
|
|
let check_response = trading_client.check_position_limits(Request::new(position_check)).await;
|
|
assert!(check_response.is_ok());
|
|
|
|
let check_result = check_response.unwrap().into_inner();
|
|
assert!(!check_result.approved); // Should be rejected due to size
|
|
assert!(!check_result.violations.is_empty());
|
|
assert!(check_result.violations[0].violation_type.contains("position_limit"));
|
|
|
|
// Test risk metrics query
|
|
let risk_request = GetRiskMetricsRequest {
|
|
trader_id: Some("TRADER_001".to_string()),
|
|
portfolio_level: true,
|
|
};
|
|
|
|
let risk_response = trading_client.get_risk_metrics(Request::new(risk_request)).await;
|
|
assert!(risk_response.is_ok());
|
|
|
|
let metrics = risk_response.unwrap().into_inner();
|
|
assert!(metrics.var_1_day >= 0.0);
|
|
assert!(metrics.var_10_day >= 0.0);
|
|
assert!(metrics.maximum_drawdown >= 0.0);
|
|
assert!(metrics.sharpe_ratio.is_finite());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_service_market_data_streaming() {
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
|
|
// Subscribe to market data stream
|
|
let subscription_request = MarketDataSubscriptionRequest {
|
|
symbols: vec!["EURUSD".to_string(), "GBPUSD".to_string()],
|
|
data_types: vec![
|
|
MarketDataType::Tick as i32,
|
|
MarketDataType::OrderBook as i32,
|
|
],
|
|
include_trade_data: true,
|
|
};
|
|
|
|
let stream_response = trading_client.subscribe_market_data(Request::new(subscription_request)).await;
|
|
assert!(stream_response.is_ok());
|
|
|
|
let mut stream = stream_response.unwrap().into_inner();
|
|
|
|
// Test receiving market data
|
|
let start_time = Instant::now();
|
|
let mut tick_count = 0;
|
|
let mut orderbook_count = 0;
|
|
|
|
while start_time.elapsed() < Duration::from_secs(5) && (tick_count < 10 || orderbook_count < 10) {
|
|
if let Some(data_result) = stream.next().await {
|
|
assert!(data_result.is_ok());
|
|
|
|
let market_data = data_result.unwrap();
|
|
match market_data.data_type() {
|
|
MarketDataType::Tick => {
|
|
tick_count += 1;
|
|
assert!(!market_data.symbol.is_empty());
|
|
assert!(market_data.bid > 0.0);
|
|
assert!(market_data.ask > 0.0);
|
|
assert!(market_data.timestamp_nanos > 0);
|
|
}
|
|
MarketDataType::OrderBook => {
|
|
orderbook_count += 1;
|
|
assert!(!market_data.symbol.is_empty());
|
|
assert!(!market_data.order_book_levels.is_empty());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(tick_count > 0);
|
|
assert!(orderbook_count > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Backtesting Service Communication Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_backtesting_service_workflow() {
|
|
let mut backtesting_client = MockBacktestingServiceClient::new().await.expect("Failed to create backtesting client");
|
|
|
|
// Start a backtest
|
|
let backtest_request = StartBacktestRequest {
|
|
strategy_name: "MeanReversionStrategy".to_string(),
|
|
symbols: vec!["EURUSD".to_string()],
|
|
start_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(30)).timestamp_nanos_opt().unwrap_or(0),
|
|
end_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(1)).timestamp_nanos_opt().unwrap_or(0),
|
|
initial_capital: 100000.0,
|
|
parameters: {
|
|
let mut params = HashMap::new();
|
|
params.insert("lookback_period".to_string(), "20".to_string());
|
|
params.insert("entry_threshold".to_string(), "2.0".to_string());
|
|
params.insert("save_results".to_string(), "true".to_string());
|
|
params
|
|
},
|
|
};
|
|
|
|
let start_response = backtesting_client.start_backtest(Request::new(backtest_request)).await;
|
|
assert!(start_response.is_ok());
|
|
|
|
let backtest_info = start_response.unwrap().into_inner();
|
|
assert!(backtest_info.success);
|
|
assert!(!backtest_info.backtest_id.is_empty());
|
|
assert!(backtest_info.estimated_duration_seconds > 0);
|
|
|
|
// Monitor backtest progress
|
|
let progress_request = SubscribeBacktestProgressRequest {
|
|
backtest_id: backtest_info.backtest_id.clone(),
|
|
};
|
|
|
|
let progress_stream = backtesting_client.subscribe_backtest_progress(Request::new(progress_request)).await;
|
|
assert!(progress_stream.is_ok());
|
|
|
|
let mut stream = progress_stream.unwrap().into_inner();
|
|
let mut progress_updates = 0;
|
|
let mut final_status = None;
|
|
|
|
// Wait for progress updates
|
|
while let Some(progress_result) = stream.next().await {
|
|
if progress_updates >= 10 { break; } // Prevent infinite loop
|
|
|
|
assert!(progress_result.is_ok());
|
|
let progress = progress_result.unwrap();
|
|
|
|
assert_eq!(progress.backtest_id, backtest_info.backtest_id);
|
|
assert!(progress.progress_percent >= 0.0 && progress.progress_percent <= 100.0);
|
|
assert!(!progress.current_date.is_empty());
|
|
|
|
progress_updates += 1;
|
|
|
|
if progress.progress_percent == 100.0 {
|
|
final_status = Some(BacktestStatus::from(progress.status));
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert!(progress_updates > 0);
|
|
|
|
// Check final status
|
|
let status_request = GetBacktestStatusRequest {
|
|
backtest_id: backtest_info.backtest_id.clone(),
|
|
};
|
|
|
|
let status_response = backtesting_client.get_backtest_status(Request::new(status_request)).await;
|
|
assert!(status_response.is_ok());
|
|
|
|
let final_status_response = status_response.unwrap().into_inner();
|
|
assert_eq!(final_status_response.backtest_id, backtest_info.backtest_id);
|
|
assert!(final_status_response.progress_percent == 100.0 ||
|
|
matches!(BacktestStatus::from(final_status_response.status), BacktestStatus::Completed | BacktestStatus::Failed));
|
|
|
|
// Get backtest results (if completed)
|
|
if BacktestStatus::from(final_status_response.status) == BacktestStatus::Completed {
|
|
let results_request = GetBacktestResultsRequest {
|
|
backtest_id: backtest_info.backtest_id.clone(),
|
|
include_trades: true,
|
|
include_metrics: true,
|
|
};
|
|
|
|
let results_response = backtesting_client.get_backtest_results(Request::new(results_request)).await;
|
|
assert!(results_response.is_ok());
|
|
|
|
let results = results_response.unwrap().into_inner();
|
|
assert_eq!(results.backtest_id, backtest_info.backtest_id);
|
|
assert!(results.metrics.is_some());
|
|
|
|
let metrics = results.metrics.unwrap();
|
|
assert!(metrics.total_return.is_finite());
|
|
assert!(metrics.sharpe_ratio.is_finite());
|
|
assert!(metrics.maximum_drawdown >= 0.0);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_backtesting_service_concurrent_backtests() {
|
|
let mut backtesting_client = MockBacktestingServiceClient::new().await.expect("Failed to create backtesting client");
|
|
|
|
let mut backtest_handles = Vec::new();
|
|
let num_concurrent = 3;
|
|
|
|
// Start multiple backtests concurrently
|
|
for i in 0..num_concurrent {
|
|
let mut client_clone = backtesting_client.clone();
|
|
let strategy_name = format!("Strategy_{}", i);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let backtest_request = StartBacktestRequest {
|
|
strategy_name: strategy_name.clone(),
|
|
symbols: vec!["EURUSD".to_string()],
|
|
start_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(10)).timestamp_nanos_opt().unwrap_or(0),
|
|
end_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(1)).timestamp_nanos_opt().unwrap_or(0),
|
|
initial_capital: 50000.0,
|
|
parameters: HashMap::new(),
|
|
};
|
|
|
|
let result = client_clone.start_backtest(Request::new(backtest_request)).await;
|
|
(strategy_name, result)
|
|
});
|
|
|
|
backtest_handles.push(handle);
|
|
}
|
|
|
|
// Wait for all backtests to start
|
|
let mut started_backtests = Vec::new();
|
|
for handle in backtest_handles {
|
|
let (strategy_name, result) = handle.await.expect("Task failed");
|
|
assert!(result.is_ok(), "Backtest start failed for {}: {:?}", strategy_name, result.err());
|
|
|
|
let response = result.unwrap().into_inner();
|
|
assert!(response.success);
|
|
started_backtests.push((strategy_name, response.backtest_id));
|
|
}
|
|
|
|
assert_eq!(started_backtests.len(), num_concurrent);
|
|
|
|
// Verify all backtests are tracked
|
|
for (strategy_name, backtest_id) in started_backtests {
|
|
let status_request = GetBacktestStatusRequest {
|
|
backtest_id: backtest_id.clone(),
|
|
};
|
|
|
|
let status_response = backtesting_client.get_backtest_status(Request::new(status_request)).await;
|
|
assert!(status_response.is_ok(), "Failed to get status for {}", strategy_name);
|
|
|
|
let status = status_response.unwrap().into_inner();
|
|
assert_eq!(status.backtest_id, backtest_id);
|
|
assert!(matches!(BacktestStatus::from(status.status),
|
|
BacktestStatus::Queued | BacktestStatus::Running | BacktestStatus::Completed));
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// ML Training Service Communication Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_ml_training_service_model_training() {
|
|
let mut ml_client = MockMLTrainingServiceClient::new().await.expect("Failed to create ML client");
|
|
|
|
// Start model training
|
|
let training_request = StartTrainingRequest {
|
|
model_type: MLModelType::DQN as i32,
|
|
model_name: "DQN_EURUSD_v1".to_string(),
|
|
training_config: Some(TrainingConfig {
|
|
batch_size: 64,
|
|
learning_rate: 0.001,
|
|
num_epochs: 100,
|
|
validation_split: 0.2,
|
|
early_stopping: true,
|
|
save_checkpoints: true,
|
|
}),
|
|
dataset_config: Some(DatasetConfig {
|
|
symbols: vec!["EURUSD".to_string()],
|
|
start_date: "2024-01-01".to_string(),
|
|
end_date: "2024-12-01".to_string(),
|
|
features: vec![
|
|
"price_returns".to_string(),
|
|
"volume".to_string(),
|
|
"volatility".to_string(),
|
|
],
|
|
target: "future_return_1h".to_string(),
|
|
}),
|
|
compute_config: Some(ComputeConfig {
|
|
use_gpu: true,
|
|
max_memory_gb: 8.0,
|
|
num_workers: 4,
|
|
distributed: false,
|
|
}),
|
|
};
|
|
|
|
let training_response = ml_client.start_training(Request::new(training_request)).await;
|
|
assert!(training_response.is_ok());
|
|
|
|
let training_info = training_response.unwrap().into_inner();
|
|
assert!(training_info.success);
|
|
assert!(!training_info.job_id.is_empty());
|
|
assert!(training_info.estimated_duration_minutes > 0);
|
|
|
|
// Monitor training progress
|
|
let progress_request = GetTrainingStatusRequest {
|
|
job_id: training_info.job_id.clone(),
|
|
};
|
|
|
|
let mut training_completed = false;
|
|
let mut status_checks = 0;
|
|
|
|
while !training_completed && status_checks < 20 {
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
|
|
let status_response = ml_client.get_training_status(Request::new(progress_request.clone())).await;
|
|
assert!(status_response.is_ok());
|
|
|
|
let status = status_response.unwrap().into_inner();
|
|
assert_eq!(status.job_id, training_info.job_id);
|
|
assert!(status.progress_percent >= 0.0 && status.progress_percent <= 100.0);
|
|
|
|
match TrainingStatus::from(status.status) {
|
|
TrainingStatus::Completed => {
|
|
training_completed = true;
|
|
assert_eq!(status.progress_percent, 100.0);
|
|
assert!(status.final_metrics.is_some());
|
|
|
|
let metrics = status.final_metrics.unwrap();
|
|
assert!(metrics.loss >= 0.0);
|
|
assert!(metrics.accuracy >= 0.0 && metrics.accuracy <= 1.0);
|
|
}
|
|
TrainingStatus::Failed => {
|
|
panic!("Training failed: {}", status.error_message.unwrap_or("Unknown error".to_string()));
|
|
}
|
|
TrainingStatus::Running | TrainingStatus::Queued => {
|
|
// Continue monitoring
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
status_checks += 1;
|
|
}
|
|
|
|
// Get trained model info
|
|
if training_completed {
|
|
let model_request = GetModelInfoRequest {
|
|
model_name: "DQN_EURUSD_v1".to_string(),
|
|
};
|
|
|
|
let model_response = ml_client.get_model_info(Request::new(model_request)).await;
|
|
assert!(model_response.is_ok());
|
|
|
|
let model_info = model_response.unwrap().into_inner();
|
|
assert_eq!(model_info.model_name, "DQN_EURUSD_v1");
|
|
assert_eq!(model_info.model_type, MLModelType::DQN as i32);
|
|
assert!(model_info.training_completed_at > 0);
|
|
assert!(model_info.model_size_bytes > 0);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_ml_service_model_deployment() {
|
|
let mut ml_client = MockMLTrainingServiceClient::new().await.expect("Failed to create ML client");
|
|
|
|
// Deploy a trained model
|
|
let deployment_request = DeployModelRequest {
|
|
model_name: "DQN_EURUSD_v1".to_string(),
|
|
deployment_environment: "production".to_string(),
|
|
scaling_config: Some(ScalingConfig {
|
|
min_replicas: 1,
|
|
max_replicas: 3,
|
|
target_cpu_utilization: 70.0,
|
|
target_memory_utilization: 80.0,
|
|
}),
|
|
model_serving_config: Some(ModelServingConfig {
|
|
batch_prediction: true,
|
|
max_batch_size: 1000,
|
|
timeout_seconds: 30,
|
|
enable_caching: true,
|
|
}),
|
|
};
|
|
|
|
let deployment_response = ml_client.deploy_model(Request::new(deployment_request)).await;
|
|
assert!(deployment_response.is_ok());
|
|
|
|
let deployment_info = deployment_response.unwrap().into_inner();
|
|
assert!(deployment_info.success);
|
|
assert!(!deployment_info.deployment_id.is_empty());
|
|
assert!(!deployment_info.endpoint_url.is_empty());
|
|
|
|
// Test model predictions
|
|
let prediction_request = GetPredictionRequest {
|
|
model_name: "DQN_EURUSD_v1".to_string(),
|
|
features: Some(MLFeatures {
|
|
feature_values: vec![1.2345, 0.001, 0.15], // price_returns, volume, volatility
|
|
feature_names: vec![
|
|
"price_returns".to_string(),
|
|
"volume".to_string(),
|
|
"volatility".to_string(),
|
|
],
|
|
}),
|
|
prediction_type: PredictionType::SingleSample as i32,
|
|
};
|
|
|
|
let prediction_response = ml_client.get_prediction(Request::new(prediction_request)).await;
|
|
assert!(prediction_response.is_ok());
|
|
|
|
let prediction = prediction_response.unwrap().into_inner();
|
|
assert!(prediction.success);
|
|
assert!(!prediction.prediction_values.is_empty());
|
|
assert!(prediction.confidence_score >= 0.0 && prediction.confidence_score <= 1.0);
|
|
assert!(prediction.latency_ms > 0.0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Cross-Service Integration Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_end_to_end_trading_workflow() {
|
|
// Initialize all service clients
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
let mut backtesting_client = MockBacktestingServiceClient::new().await.expect("Failed to create backtesting client");
|
|
let mut ml_client = MockMLTrainingServiceClient::new().await.expect("Failed to create ML client");
|
|
|
|
// Step 1: Backtest a strategy
|
|
let backtest_request = StartBacktestRequest {
|
|
strategy_name: "MLEnhancedMomentum".to_string(),
|
|
symbols: vec!["EURUSD".to_string()],
|
|
start_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(30)).timestamp_nanos_opt().unwrap_or(0),
|
|
end_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(1)).timestamp_nanos_opt().unwrap_or(0),
|
|
initial_capital: 100000.0,
|
|
parameters: {
|
|
let mut params = HashMap::new();
|
|
params.insert("ml_model".to_string(), "DQN_EURUSD_v1".to_string());
|
|
params.insert("confidence_threshold".to_string(), "0.7".to_string());
|
|
params
|
|
},
|
|
};
|
|
|
|
let backtest_response = backtesting_client.start_backtest(Request::new(backtest_request)).await;
|
|
assert!(backtest_response.is_ok());
|
|
|
|
let backtest_info = backtest_response.unwrap().into_inner();
|
|
assert!(backtest_info.success);
|
|
|
|
// Step 2: Wait for backtest completion (simplified for testing)
|
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
|
|
|
let status_request = GetBacktestStatusRequest {
|
|
backtest_id: backtest_info.backtest_id.clone(),
|
|
};
|
|
|
|
let status_response = backtesting_client.get_backtest_status(Request::new(status_request)).await;
|
|
assert!(status_response.is_ok());
|
|
|
|
// Step 3: Get ML prediction for trading decision
|
|
let prediction_request = GetPredictionRequest {
|
|
model_name: "DQN_EURUSD_v1".to_string(),
|
|
features: Some(MLFeatures {
|
|
feature_values: vec![1.2345, 0.002, 0.12],
|
|
feature_names: vec![
|
|
"price_returns".to_string(),
|
|
"volume".to_string(),
|
|
"volatility".to_string(),
|
|
],
|
|
}),
|
|
prediction_type: PredictionType::SingleSample as i32,
|
|
};
|
|
|
|
let prediction_response = ml_client.get_prediction(Request::new(prediction_request)).await;
|
|
assert!(prediction_response.is_ok());
|
|
|
|
let prediction = prediction_response.unwrap().into_inner();
|
|
assert!(prediction.success);
|
|
|
|
// Step 4: Place order based on ML prediction (if confidence is high)
|
|
if prediction.confidence_score > 0.7 {
|
|
let order_request = PlaceOrderRequest {
|
|
symbol: "EURUSD".to_string(),
|
|
side: if prediction.prediction_values[0] > 0.5 {
|
|
OrderSide::Buy as i32
|
|
} else {
|
|
OrderSide::Sell as i32
|
|
},
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 10000.0,
|
|
price: None,
|
|
time_in_force: TimeInForce::ImmediateOrCancel as i32,
|
|
client_order_id: Uuid::new_v4().to_string(),
|
|
trader_id: "TRADER_ML_001".to_string(),
|
|
};
|
|
|
|
let order_response = trading_client.place_order(Request::new(order_request)).await;
|
|
assert!(order_response.is_ok());
|
|
|
|
let order_result = order_response.unwrap().into_inner();
|
|
assert!(order_result.success);
|
|
assert!(!order_result.order_id.is_empty());
|
|
|
|
// Step 5: Monitor order execution
|
|
let monitor_request = GetOrderStatusRequest {
|
|
order_id: order_result.order_id.clone(),
|
|
};
|
|
|
|
let monitor_response = trading_client.get_order_status(Request::new(monitor_request)).await;
|
|
assert!(monitor_response.is_ok());
|
|
|
|
let order_status = monitor_response.unwrap().into_inner();
|
|
assert_eq!(order_status.order_id, order_result.order_id);
|
|
assert!(matches!(OrderStatus::from(order_status.status),
|
|
OrderStatus::Pending | OrderStatus::Filled | OrderStatus::PartiallyFilled));
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_service_failure_recovery() {
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
|
|
// Simulate service failure
|
|
let failure_request = SimulateFailureRequest {
|
|
service_type: ServiceType::Trading,
|
|
failure_type: FailureType::NetworkPartition,
|
|
duration_seconds: 5,
|
|
};
|
|
|
|
let failure_response = trading_client.simulate_failure(Request::new(failure_request)).await;
|
|
assert!(failure_response.is_ok());
|
|
|
|
// Attempt operations during failure
|
|
let order_request = PlaceOrderRequest {
|
|
symbol: "EURUSD".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 10000.0,
|
|
price: None,
|
|
time_in_force: TimeInForce::ImmediateOrCancel as i32,
|
|
client_order_id: Uuid::new_v4().to_string(),
|
|
trader_id: "TRADER_001".to_string(),
|
|
};
|
|
|
|
// Should fail during simulated network partition
|
|
let failed_order_response = trading_client.place_order(Request::new(order_request.clone())).await;
|
|
assert!(failed_order_response.is_err());
|
|
|
|
let error = failed_order_response.err().unwrap();
|
|
assert_eq!(error.code(), Code::Unavailable);
|
|
|
|
// Wait for recovery
|
|
tokio::time::sleep(Duration::from_secs(6)).await;
|
|
|
|
// Should succeed after recovery
|
|
let recovered_order_response = trading_client.place_order(Request::new(order_request)).await;
|
|
assert!(recovered_order_response.is_ok());
|
|
|
|
let recovered_result = recovered_order_response.unwrap().into_inner();
|
|
assert!(recovered_result.success);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_load_balancing_and_circuit_breakers() {
|
|
let load_balancer = MockLoadBalancer::new();
|
|
|
|
// Register multiple service instances
|
|
let services = vec![
|
|
ServiceEndpoint {
|
|
service_type: ServiceType::Trading,
|
|
address: "127.0.0.1".to_string(),
|
|
port: 50051,
|
|
health_check_path: "/health".to_string(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
ServiceEndpoint {
|
|
service_type: ServiceType::Trading,
|
|
address: "127.0.0.1".to_string(),
|
|
port: 50052,
|
|
health_check_path: "/health".to_string(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
ServiceEndpoint {
|
|
service_type: ServiceType::Trading,
|
|
address: "127.0.0.1".to_string(),
|
|
port: 50053,
|
|
health_check_path: "/health".to_string(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
];
|
|
|
|
for service in services {
|
|
load_balancer.register_service(service).await.expect("Failed to register service");
|
|
}
|
|
|
|
// Test round-robin load balancing
|
|
let mut selected_ports = Vec::new();
|
|
for _ in 0..6 {
|
|
let selected_service = load_balancer.select_service(ServiceType::Trading).await;
|
|
assert!(selected_service.is_ok());
|
|
selected_ports.push(selected_service.unwrap().port);
|
|
}
|
|
|
|
// Should cycle through all ports
|
|
assert!(selected_ports.contains(&50051));
|
|
assert!(selected_ports.contains(&50052));
|
|
assert!(selected_ports.contains(&50053));
|
|
|
|
// Simulate service failure and test circuit breaker
|
|
load_balancer.mark_service_unhealthy("127.0.0.1:50052".to_string()).await;
|
|
|
|
let mut healthy_ports = Vec::new();
|
|
for _ in 0..10 {
|
|
let selected_service = load_balancer.select_service(ServiceType::Trading).await;
|
|
assert!(selected_service.is_ok());
|
|
healthy_ports.push(selected_service.unwrap().port);
|
|
}
|
|
|
|
// Should not select the unhealthy service
|
|
assert!(!healthy_ports.contains(&50052));
|
|
assert!(healthy_ports.contains(&50051));
|
|
assert!(healthy_ports.contains(&50053));
|
|
}
|
|
|
|
// ========================================================================
|
|
// Performance and Stress Tests
|
|
// ========================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_high_frequency_service_communication() {
|
|
let mut trading_client = MockTradingServiceClient::new().await.expect("Failed to create trading client");
|
|
|
|
let num_requests = 1000;
|
|
let start_time = Instant::now();
|
|
let mut successful_requests = 0;
|
|
let mut failed_requests = 0;
|
|
|
|
// Send high-frequency order status requests
|
|
let mut handles = Vec::new();
|
|
|
|
for i in 0..num_requests {
|
|
let mut client_clone = trading_client.clone();
|
|
let handle = tokio::spawn(async move {
|
|
let status_request = GetOrderStatusRequest {
|
|
order_id: format!("ORDER_{:06}", i),
|
|
};
|
|
|
|
client_clone.get_order_status(Request::new(status_request)).await
|
|
});
|
|
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all requests to complete
|
|
for handle in handles {
|
|
match handle.await {
|
|
Ok(Ok(_)) => successful_requests += 1,
|
|
Ok(Err(_)) | Err(_) => failed_requests += 1,
|
|
}
|
|
}
|
|
|
|
let elapsed = start_time.elapsed();
|
|
let requests_per_second = num_requests as f64 / elapsed.as_secs_f64();
|
|
|
|
println!("High-frequency test results:");
|
|
println!(" Total requests: {}", num_requests);
|
|
println!(" Successful: {}", successful_requests);
|
|
println!(" Failed: {}", failed_requests);
|
|
println!(" Duration: {:?}", elapsed);
|
|
println!(" Requests/second: {:.2}", requests_per_second);
|
|
|
|
// Should handle at least 100 requests per second
|
|
assert!(requests_per_second > 100.0);
|
|
// Should have at least 95% success rate
|
|
assert!(successful_requests as f64 / num_requests as f64 > 0.95);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_concurrent_service_connections() {
|
|
let num_clients = 50;
|
|
let mut client_handles = Vec::new();
|
|
|
|
// Create multiple concurrent clients
|
|
for client_id in 0..num_clients {
|
|
let handle = tokio::spawn(async move {
|
|
let client_result = MockTradingServiceClient::new().await;
|
|
if client_result.is_err() {
|
|
return (client_id, false, String::new());
|
|
}
|
|
|
|
let mut client = client_result.unwrap();
|
|
|
|
// Each client performs a health check
|
|
let health_request = HealthCheckRequest {
|
|
service: ServiceType::Trading as i32,
|
|
};
|
|
|
|
match client.health_check(Request::new(health_request)).await {
|
|
Ok(response) => {
|
|
let health_info = response.into_inner();
|
|
(client_id, health_info.status == ServiceStatus::Healthy as i32, health_info.version)
|
|
}
|
|
Err(e) => (client_id, false, format!("Error: {}", e)),
|
|
}
|
|
});
|
|
|
|
client_handles.push(handle);
|
|
}
|
|
|
|
// Wait for all clients
|
|
let mut successful_connections = 0;
|
|
for handle in client_handles {
|
|
let (client_id, success, info) = handle.await.expect("Client task failed");
|
|
if success {
|
|
successful_connections += 1;
|
|
} else {
|
|
println!("Client {} failed: {}", client_id, info);
|
|
}
|
|
}
|
|
|
|
println!("Concurrent connection test:");
|
|
println!(" Total clients: {}", num_clients);
|
|
println!(" Successful connections: {}", successful_connections);
|
|
println!(" Success rate: {:.2}%", (successful_connections as f64 / num_clients as f64) * 100.0);
|
|
|
|
// Should handle at least 90% of concurrent connections successfully
|
|
assert!(successful_connections as f64 / num_clients as f64 > 0.90);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mock Service Implementations for Testing
|
|
// ============================================================================
|
|
|
|
// These mock implementations provide realistic service behavior for testing
|
|
// In production, these would be replaced with actual gRPC service implementations
|
|
|
|
use std::sync::{atomic::AtomicBool, atomic::Ordering};
|
|
|
|
// Mock Trading Service
|
|
pub struct MockTradingServer {
|
|
health_status: Arc<AtomicBool>,
|
|
orders: Arc<RwLock<HashMap<String, MockOrder>>>,
|
|
}
|
|
|
|
impl MockTradingServer {
|
|
pub async fn new() -> Result<Self> {
|
|
Ok(Self {
|
|
health_status: Arc::new(AtomicBool::new(true)),
|
|
orders: Arc::new(RwLock::new(HashMap::new())),
|
|
})
|
|
}
|
|
|
|
pub async fn health_check(&self) -> Result<HealthCheckResponse> {
|
|
Ok(HealthCheckResponse {
|
|
status: if self.health_status.load(Ordering::Relaxed) {
|
|
ServiceStatus::Healthy as i32
|
|
} else {
|
|
ServiceStatus::Unhealthy as i32
|
|
},
|
|
version: "1.0.0".to_string(),
|
|
uptime_seconds: 3600, // 1 hour
|
|
message: "Service is operational".to_string(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct MockOrder {
|
|
pub order_id: String,
|
|
pub symbol: String,
|
|
pub side: OrderSide,
|
|
pub quantity: f64,
|
|
pub price: Option<f64>,
|
|
pub status: OrderStatus,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
// Mock service client implementations
|
|
#[derive(Clone)]
|
|
pub struct MockTradingServiceClient {
|
|
// In a real implementation, this would contain the gRPC client
|
|
}
|
|
|
|
impl MockTradingServiceClient {
|
|
pub async fn new() -> Result<Self> {
|
|
Ok(Self {})
|
|
}
|
|
|
|
pub async fn place_order(&mut self, request: Request<PlaceOrderRequest>) -> Result<Response<PlaceOrderResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
// Simulate order validation and placement
|
|
if req.quantity <= 0.0 {
|
|
return Ok(Response::new(PlaceOrderResponse {
|
|
success: false,
|
|
order_id: String::new(),
|
|
message: "Order placed successfully".to_string(),
|
|
error_message: "Invalid quantity".to_string(),
|
|
estimated_fill_time_ms: 0,
|
|
}));
|
|
}
|
|
|
|
let order_id = Uuid::new_v4().to_string();
|
|
|
|
Ok(Response::new(PlaceOrderResponse {
|
|
success: true,
|
|
order_id,
|
|
message: "Order placed successfully".to_string(),
|
|
error_message: String::new(),
|
|
estimated_fill_time_ms: 50,
|
|
}))
|
|
}
|
|
|
|
pub async fn get_order_status(&mut self, request: Request<GetOrderStatusRequest>) -> Result<Response<GetOrderStatusResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
Ok(Response::new(GetOrderStatusResponse {
|
|
order_id: req.order_id,
|
|
status: OrderStatus::Pending as i32,
|
|
symbol: "EURUSD".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
quantity: 10000.0,
|
|
filled_quantity: 0.0,
|
|
average_fill_price: 0.0,
|
|
created_at_unix_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
|
updated_at_unix_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
|
}))
|
|
}
|
|
|
|
pub async fn cancel_order(&mut self, request: Request<CancelOrderRequest>) -> Result<Response<CancelOrderResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
Ok(Response::new(CancelOrderResponse {
|
|
success: true,
|
|
order_id: req.order_id,
|
|
message: "Order cancelled successfully".to_string(),
|
|
error_message: String::new(),
|
|
}))
|
|
}
|
|
|
|
pub async fn check_position_limits(&mut self, request: Request<PositionLimitCheckRequest>) -> Result<Response<PositionLimitCheckResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
// Simulate position limit checking
|
|
let approved = req.quantity < 50000.0; // Reject large positions
|
|
let mut violations = Vec::new();
|
|
|
|
if !approved {
|
|
violations.push("position_limit_exceeded".to_string());
|
|
}
|
|
|
|
Ok(Response::new(PositionLimitCheckResponse {
|
|
approved,
|
|
violations,
|
|
current_position: 25000.0,
|
|
position_limit: 50000.0,
|
|
margin_required: req.quantity * 0.02, // 2% margin
|
|
margin_available: 100000.0,
|
|
}))
|
|
}
|
|
|
|
pub async fn get_risk_metrics(&mut self, request: Request<GetRiskMetricsRequest>) -> Result<Response<GetRiskMetricsResponse>, Status> {
|
|
let _req = request.into_inner();
|
|
|
|
Ok(Response::new(GetRiskMetricsResponse {
|
|
var_1_day: 2500.0,
|
|
var_10_day: 7500.0,
|
|
maximum_drawdown: 0.05,
|
|
sharpe_ratio: 1.8,
|
|
sortino_ratio: 2.1,
|
|
calmar_ratio: 3.2,
|
|
portfolio_value: 100000.0,
|
|
unrealized_pnl: 1500.0,
|
|
realized_pnl: 2800.0,
|
|
}))
|
|
}
|
|
|
|
pub async fn subscribe_market_data(&mut self, request: Request<MarketDataSubscriptionRequest>) -> Result<Response<impl tokio_stream::Stream<Item = Result<MarketDataEvent, Status>>>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
// Create a mock market data stream
|
|
let (tx, rx) = tokio::sync::mpsc::channel(100);
|
|
|
|
// Spawn a task to generate mock market data
|
|
let symbols = req.symbols.clone();
|
|
tokio::spawn(async move {
|
|
let mut counter = 0;
|
|
loop {
|
|
for symbol in &symbols {
|
|
// Generate mock tick data
|
|
let tick_data = MarketDataEvent {
|
|
symbol: symbol.clone(),
|
|
data_type: MarketDataType::Tick as i32,
|
|
bid: 1.2345 + (counter as f64 * 0.0001),
|
|
ask: 1.2347 + (counter as f64 * 0.0001),
|
|
timestamp_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
|
volume: 1000.0,
|
|
order_book_levels: Vec::new(),
|
|
};
|
|
|
|
if tx.send(Ok(tick_data)).await.is_err() {
|
|
return; // Client disconnected
|
|
}
|
|
|
|
// Generate mock order book data
|
|
let orderbook_data = MarketDataEvent {
|
|
symbol: symbol.clone(),
|
|
data_type: MarketDataType::OrderBook as i32,
|
|
bid: 1.2345,
|
|
ask: 1.2347,
|
|
timestamp_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0),
|
|
volume: 0.0,
|
|
order_book_levels: vec![
|
|
OrderBookLevel { price: 1.2345, size: 10000.0, side: OrderSide::Buy as i32 },
|
|
OrderBookLevel { price: 1.2347, size: 15000.0, side: OrderSide::Sell as i32 },
|
|
],
|
|
};
|
|
|
|
if tx.send(Ok(orderbook_data)).await.is_err() {
|
|
return; // Client disconnected
|
|
}
|
|
}
|
|
|
|
counter += 1;
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
}
|
|
});
|
|
|
|
let stream = ReceiverStream::new(rx);
|
|
Ok(Response::new(stream))
|
|
}
|
|
|
|
pub async fn health_check(&mut self, request: Request<HealthCheckRequest>) -> Result<Response<HealthCheckResponse>, Status> {
|
|
let _req = request.into_inner();
|
|
|
|
Ok(Response::new(HealthCheckResponse {
|
|
status: ServiceStatus::Healthy as i32,
|
|
version: "1.0.0".to_string(),
|
|
uptime_seconds: 3600,
|
|
message: "Trading service is healthy".to_string(),
|
|
}))
|
|
}
|
|
|
|
pub async fn simulate_failure(&mut self, request: Request<SimulateFailureRequest>) -> Result<Response<SimulateFailureResponse>, Status> {
|
|
let req = request.into_inner();
|
|
|
|
Ok(Response::new(SimulateFailureResponse {
|
|
success: true,
|
|
message: format!("Simulating {:?} failure for {} seconds", req.failure_type(), req.duration_seconds),
|
|
}))
|
|
}
|
|
}
|
|
|
|
// Additional mock implementations for other services would follow similar patterns...
|
|
// This demonstrates the comprehensive approach needed for 95%+ integration test coverage
|
|
|
|
// Mock types and enums
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum ServiceType {
|
|
Trading,
|
|
Backtesting,
|
|
MLTraining,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum ServiceStatus {
|
|
Healthy,
|
|
Unhealthy,
|
|
Degraded,
|
|
}
|
|
|
|
// OrderSide, OrderType, and OrderStatus already imported via trading_engine::prelude::* and common::types::* (lines 17-18)
|
|
|
|
// REMOVED: TimeInForce duplicate - use common::TimeInForce
|
|
// Note: GTD variant not supported in canonical definition
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum BacktestStatus {
|
|
Queued,
|
|
Running,
|
|
Completed,
|
|
Failed,
|
|
Cancelled,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum MarketDataType {
|
|
Tick,
|
|
OrderBook,
|
|
Trade,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum MLModelType {
|
|
DQN,
|
|
PPO,
|
|
MAMBA,
|
|
TFT,
|
|
TLOB,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum TrainingStatus {
|
|
Queued,
|
|
Running,
|
|
Completed,
|
|
Failed,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum PredictionType {
|
|
SingleSample,
|
|
Batch,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Permission {
|
|
Trading,
|
|
RiskManagement,
|
|
MarketData,
|
|
BacktestingRead,
|
|
BacktestingWrite,
|
|
MLModelRead,
|
|
MLModelWrite,
|
|
ConfigRead,
|
|
ConfigWrite,
|
|
AdminAccess,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum FailureType {
|
|
NetworkPartition,
|
|
ServiceCrash,
|
|
DatabaseFailure,
|
|
HighLatency,
|
|
}
|
|
|
|
// Mock request/response types
|
|
#[derive(Debug, Clone)]
|
|
pub struct PlaceOrderRequest {
|
|
pub symbol: String,
|
|
pub side: i32,
|
|
pub order_type: i32,
|
|
pub quantity: f64,
|
|
pub price: Option<f64>,
|
|
pub time_in_force: i32,
|
|
pub client_order_id: String,
|
|
pub trader_id: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PlaceOrderResponse {
|
|
pub success: bool,
|
|
pub order_id: String,
|
|
pub message: String,
|
|
pub error_message: String,
|
|
pub estimated_fill_time_ms: u64,
|
|
}
|
|
|
|
// Additional mock request/response types would be defined here...
|
|
// This demonstrates the comprehensive testing framework needed for service integration
|
|
|
|
// Continue with remaining mock implementations for complete test coverage
|
|
// This establishes the foundation for achieving 95%+ integration test coverage
|