//! Integration tests for SharedMLStrategy //! //! Validates that ONE SINGLE SYSTEM works for both trading and backtesting services. use common::ml_strategy::{ MLSignal, SharedMLConfig, SharedMLStrategy, SignalAction, StrategyPerformance, }; use common::{MarketRegime, Symbol}; /// Test that SharedMLStrategy can be created and initialized #[tokio::test] async fn test_create_shared_strategy() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); let perf = strategy.get_performance().await; assert_eq!(perf.total_signals, 0); assert_eq!(perf.win_rate, 0.0); } /// Test signal generation with valid features #[tokio::test] async fn test_generate_signal_valid_features() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); strategy.set_symbol(Symbol::from("ES.FUT")).await; // Create 256-dimensional feature vector (matching ML system) let features: Vec = (0..256).map(|i| (i as f64) / 256.0).collect(); let signal = strategy .generate_signal(features) .await .expect("Should generate signal"); // Validate signal properties assert_eq!(signal.symbol, Symbol::from("ES.FUT")); assert!(signal.confidence >= 0.0 && signal.confidence <= 1.0); assert!(matches!( signal.action, SignalAction::Buy | SignalAction::Sell | SignalAction::Hold )); } /// Test that both trading and backtesting can use the same instance #[tokio::test] async fn test_shared_instance_multiple_services() { use std::sync::Arc; // Create ONE SINGLE SYSTEM let strategy = Arc::new( SharedMLStrategy::new() .await .expect("Should create strategy"), ); strategy.set_symbol(Symbol::from("NQ.FUT")).await; // Simulate trading service using the strategy let trading_strategy = Arc::clone(&strategy); let trading_handle = tokio::spawn(async move { let features: Vec = vec![0.5; 256]; trading_strategy .generate_signal(features) .await .expect("Trading service should generate signal") }); // Simulate backtesting service using the same strategy let backtesting_strategy = Arc::clone(&strategy); let backtesting_handle = tokio::spawn(async move { let features: Vec = vec![0.6; 256]; backtesting_strategy .generate_signal(features) .await .expect("Backtesting service should generate signal") }); // Both services should succeed let trading_signal = trading_handle .await .expect("Trading task should complete"); let backtesting_signal = backtesting_handle .await .expect("Backtesting task should complete"); assert_eq!(trading_signal.symbol, Symbol::from("NQ.FUT")); assert_eq!(backtesting_signal.symbol, Symbol::from("NQ.FUT")); // Verify performance tracking shows both signals let perf = strategy.get_performance().await; assert_eq!(perf.total_signals, 2); } /// Test regime detection #[tokio::test] async fn test_regime_detection() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); // Update with bullish market data let regime1 = strategy .update_regime(100.0, 1000.0) .await .expect("Should update regime"); // Update with bearish market data let regime2 = strategy .update_regime(95.0, 1200.0) .await .expect("Should update regime"); // Both should be valid regimes assert!(matches!( regime1, MarketRegime::Unknown | MarketRegime::Bull | MarketRegime::Bear | MarketRegime::Sideways | MarketRegime::HighVolatility )); assert!(matches!( regime2, MarketRegime::Unknown | MarketRegime::Bull | MarketRegime::Bear | MarketRegime::Sideways | MarketRegime::HighVolatility )); } /// Test outcome recording and performance tracking #[tokio::test] async fn test_outcome_recording() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); strategy.set_symbol(Symbol::from("ZN.FUT")).await; // Generate signals let features: Vec = vec![0.5; 256]; let _signal1 = strategy .generate_signal(features.clone()) .await .expect("Should generate signal 1"); let _signal2 = strategy .generate_signal(features.clone()) .await .expect("Should generate signal 2"); // Record outcomes strategy .record_outcome(0.05) .await .expect("Should record positive outcome"); strategy .record_outcome(-0.02) .await .expect("Should record negative outcome"); let perf = strategy.get_performance().await; assert_eq!(perf.total_signals, 2); assert!(perf.win_rate >= 0.0 && perf.win_rate <= 1.0); } /// Test configuration validation #[tokio::test] async fn test_invalid_configuration() { // Invalid confidence threshold (>1.0) let config1 = SharedMLConfig { min_confidence: 1.5, ..Default::default() }; let result1 = SharedMLStrategy::with_config(config1).await; assert!( result1.is_err(), "Should reject min_confidence > 1.0" ); // Invalid risk tolerance let config2 = SharedMLConfig { risk_tolerance: 2.0, ..Default::default() }; let result2 = SharedMLStrategy::with_config(config2).await; assert!( result2.is_err(), "Should reject risk_tolerance > 1.0" ); } /// Test confidence threshold filtering #[tokio::test] async fn test_confidence_threshold() { let config = SharedMLConfig { min_confidence: 0.95, // Very high threshold ..Default::default() }; let strategy = SharedMLStrategy::with_config(config) .await .expect("Should create strategy"); let features: Vec = vec![0.5; 256]; let signal = strategy .generate_signal(features) .await .expect("Should generate signal"); // With such a high threshold, should return Hold // (ensemble confidence unlikely to be 0.95+) assert_eq!(signal.action, SignalAction::Hold); } /// Test position sizing #[tokio::test] async fn test_position_sizing() { let config = SharedMLConfig { max_position_size: common::Quantity::from(100), risk_tolerance: 0.5, min_confidence: 0.6, ..Default::default() }; let strategy = SharedMLStrategy::with_config(config) .await .expect("Should create strategy"); let features: Vec = vec![0.7; 256]; let signal = strategy .generate_signal(features) .await .expect("Should generate signal"); // Position size should be: // - <= max_position_size (100) // - Adjusted by risk_tolerance (0.5) // - Adjusted by confidence assert!(signal.position_size.to_f64() > 0.0); assert!(signal.position_size.to_f64() <= 100.0); } /// Test performance metrics aggregation #[tokio::test] async fn test_performance_metrics() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); strategy.set_symbol(Symbol::from("6E.FUT")).await; let features: Vec = vec![0.5; 256]; // Generate multiple signals for _ in 0..10 { let _ = strategy .generate_signal(features.clone()) .await .expect("Should generate signal"); } let perf = strategy.get_performance().await; // Validate metrics assert_eq!(perf.total_signals, 10); assert!(!perf.signals_by_action.is_empty()); assert!(perf.average_confidence >= 0.0); assert!(perf.average_confidence <= 1.0); } /// Test concurrent signal generation (thread safety) #[tokio::test] async fn test_concurrent_signal_generation() { use std::sync::Arc; let strategy = Arc::new( SharedMLStrategy::new() .await .expect("Should create strategy"), ); strategy.set_symbol(Symbol::from("CL.FUT")).await; // Spawn multiple concurrent tasks let mut handles = Vec::new(); for i in 0..20 { let strategy_clone = Arc::clone(&strategy); let handle = tokio::spawn(async move { let features: Vec = vec![0.5 + (i as f64) * 0.01; 256]; strategy_clone .generate_signal(features) .await .expect("Should generate signal") }); handles.push(handle); } // Wait for all tasks for handle in handles { let signal = handle.await.expect("Task should complete"); assert_eq!(signal.symbol, Symbol::from("CL.FUT")); } // Verify all signals were tracked let perf = strategy.get_performance().await; assert_eq!(perf.total_signals, 20); } /// Test empty features rejection #[tokio::test] async fn test_empty_features_rejection() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); let empty_features: Vec = Vec::new(); let result = strategy.generate_signal(empty_features).await; assert!( result.is_err(), "Should reject empty features" ); } /// Test regime persistence across signal generations #[tokio::test] async fn test_regime_persistence() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); // Set regime let regime = strategy .update_regime(100.0, 1000.0) .await .expect("Should update regime"); // Get regime let retrieved_regime = strategy.get_regime().await; assert_eq!(regime, retrieved_regime); // Generate signal - should include the regime let features: Vec = vec![0.5; 256]; let signal = strategy .generate_signal(features) .await .expect("Should generate signal"); assert_eq!(signal.regime, regime); } /// Test model vote transparency #[tokio::test] async fn test_model_vote_transparency() { let strategy = SharedMLStrategy::new() .await .expect("Should create strategy"); let features: Vec = vec![0.5; 256]; let signal = strategy .generate_signal(features) .await .expect("Should generate signal"); // Should have votes from all 6 models (or hold signal with no votes) if signal.action != SignalAction::Hold { assert_eq!( signal.model_votes.len(), 6, "Should have 6 model votes" ); // Validate each vote for vote in &signal.model_votes { assert!(!vote.model_name.is_empty()); assert!(vote.confidence >= 0.0 && vote.confidence <= 1.0); assert!(vote.weight >= 0.0 && vote.weight <= 1.0); } } } /// Test strategy with custom regime config #[tokio::test] async fn test_custom_regime_config() { use ml::ensemble::RegimeConfig; let config = SharedMLConfig { regime_config: RegimeConfig { trend_lookback: 30, volatility_window: 15, trend_threshold: 0.03, volatility_threshold: 2.0, min_data_points: 10, }, ..Default::default() }; let strategy = SharedMLStrategy::with_config(config) .await .expect("Should create strategy with custom regime config"); // Update regime let regime = strategy .update_regime(100.0, 1000.0) .await .expect("Should update regime"); assert!(matches!( regime, MarketRegime::Unknown | MarketRegime::Bull | MarketRegime::Bear | MarketRegime::Sideways | MarketRegime::HighVolatility )); }