//! Fixtures and Helpers Integration Tests //! //! Tests the cached data loading and validation utilities. mod fixtures; mod helpers; use anyhow::Result; use fixtures::{get_es_fut_bars, get_nq_fut_bars, get_cl_fut_bars}; use fixtures::{get_bars_for_date, get_regime_sample, RegimeType}; use fixtures::get_multi_symbol_bars; use helpers::{assert_valid_ohlcv, assert_chronological, assert_price_range}; use helpers::{assert_no_large_gaps, calculate_volatility, generate_quality_report}; use std::time::Instant; // ============================================================================ // Cache Performance Tests // ============================================================================ #[tokio::test] async fn test_es_fut_cache_performance() -> Result<()> { println!("\n=== ES.FUT Cache Performance Test ==="); // First call (cold cache) let start = Instant::now(); let bars1 = get_es_fut_bars().await?; let cold_duration = start.elapsed(); println!("Cold cache: {:?} ({} bars)", cold_duration, bars1.len()); assert!(!bars1.is_empty(), "Should load ES.FUT bars"); assert!(bars1.len() > 350 && bars1.len() < 450, "Expected ~390 bars, got {}", bars1.len()); // Second call (warm cache) let start = Instant::now(); let bars2 = get_es_fut_bars().await?; let warm_duration = start.elapsed(); println!("Warm cache: {:?} ({} bars)", warm_duration, bars2.len()); assert_eq!(bars1.len(), bars2.len(), "Cache should return same data"); // Calculate speedup let speedup = cold_duration.as_nanos() as f64 / warm_duration.as_nanos().max(1) as f64; println!("Speedup: {:.1}x faster", speedup); // Warm should be significantly faster (at least 10x) assert!( speedup > 10.0, "Cached access should be >10x faster (got {:.1}x)", speedup ); Ok(()) } #[tokio::test] async fn test_nq_fut_cache_performance() -> Result<()> { println!("\n=== NQ.FUT Cache Performance Test ==="); let start = Instant::now(); let bars = get_nq_fut_bars().await?; let duration = start.elapsed(); println!("Loaded: {:?} ({} bars)", duration, bars.len()); assert!(!bars.is_empty()); assert_eq!(bars[0].symbol, "NQ.FUT"); Ok(()) } #[tokio::test] async fn test_cl_fut_cache_performance() -> Result<()> { println!("\n=== CL.FUT Cache Performance Test ==="); let start = Instant::now(); let bars = get_cl_fut_bars().await?; let duration = start.elapsed(); println!("Loaded: {:?} ({} bars)", duration, bars.len()); assert!(!bars.is_empty()); assert!(bars.len() > 1400, "CL.FUT has 24-hour trading, expected >1400 bars"); assert_eq!(bars[0].symbol, "CL.FUT"); Ok(()) } // ============================================================================ // Data Validation Tests // ============================================================================ #[tokio::test] async fn test_es_fut_data_validation() -> Result<()> { println!("\n=== ES.FUT Data Validation ==="); let bars = get_es_fut_bars().await?; // OHLCV validation assert_valid_ohlcv(&bars); println!("✓ OHLCV validation passed"); // Chronological ordering assert_chronological(&bars); println!("✓ Chronological validation passed"); // Price range (ES.FUT typical range) assert_price_range(&bars, "ES.FUT"); println!("✓ Price range validation passed"); // No large gaps (max 5 minutes for 1-minute data) assert_no_large_gaps(&bars, 5); println!("✓ Gap validation passed"); Ok(()) } #[tokio::test] async fn test_nq_fut_data_validation() -> Result<()> { println!("\n=== NQ.FUT Data Validation ==="); let bars = get_nq_fut_bars().await?; assert_valid_ohlcv(&bars); assert_chronological(&bars); assert_price_range(&bars, "NQ.FUT"); assert_no_large_gaps(&bars, 5); println!("✓ All validations passed"); Ok(()) } #[tokio::test] async fn test_cl_fut_data_validation() -> Result<()> { println!("\n=== CL.FUT Data Validation ==="); let bars = get_cl_fut_bars().await?; assert_valid_ohlcv(&bars); assert_chronological(&bars); assert_price_range(&bars, "CL.FUT"); assert_no_large_gaps(&bars, 2); // CL.FUT has tighter gaps (24-hour trading) println!("✓ All validations passed"); Ok(()) } // ============================================================================ // Filtered Data Access Tests // ============================================================================ #[tokio::test] async fn test_bars_for_date() -> Result<()> { println!("\n=== Date Filtering Test ==="); use chrono::NaiveDate; let date = NaiveDate::from_ymd_opt(2024, 1, 2) .unwrap() .and_hms_opt(0, 0, 0) .unwrap() .and_utc(); let bars = get_bars_for_date("ES.FUT", date).await?; assert!(!bars.is_empty(), "Should find bars for 2024-01-02"); println!("Found {} bars for 2024-01-02", bars.len()); // All bars should be from requested date for bar in &bars { assert_eq!(bar.timestamp.date_naive(), date.date_naive()); } println!("✓ All bars from correct date"); Ok(()) } #[tokio::test] async fn test_regime_trending() -> Result<()> { println!("\n=== Trending Regime Test ==="); let bars = get_regime_sample(RegimeType::Trending).await?; assert!(!bars.is_empty(), "Should find trending sample"); assert!(bars.len() >= 50, "Should have sufficient bars"); println!("Trending sample: {} bars", bars.len()); // Calculate price movement let first_price = bars[0].close.to_string().parse::().unwrap_or(0.0); let last_price = bars[bars.len()-1].close.to_string().parse::().unwrap_or(0.0); let change_pct = ((last_price - first_price) / first_price).abs() * 100.0; println!("Price change: {:.2}%", change_pct); println!("✓ Trending regime detected"); Ok(()) } #[tokio::test] async fn test_regime_ranging() -> Result<()> { println!("\n=== Ranging Regime Test ==="); let bars = get_regime_sample(RegimeType::Ranging).await?; assert!(!bars.is_empty(), "Should find ranging sample"); println!("Ranging sample: {} bars", bars.len()); Ok(()) } #[tokio::test] async fn test_regime_volatile() -> Result<()> { println!("\n=== Volatile Regime Test ==="); let bars = get_regime_sample(RegimeType::Volatile).await?; assert!(!bars.is_empty(), "Should find volatile sample"); let volatility = calculate_volatility(&bars); println!("Volatile sample: {} bars", bars.len()); println!("Annualized volatility: {:.2}%", volatility); Ok(()) } #[tokio::test] async fn test_regime_stable() -> Result<()> { println!("\n=== Stable Regime Test ==="); let bars = get_regime_sample(RegimeType::Stable).await?; assert!(!bars.is_empty(), "Should find stable sample"); let volatility = calculate_volatility(&bars); println!("Stable sample: {} bars", bars.len()); println!("Annualized volatility: {:.2}%", volatility); Ok(()) } // ============================================================================ // Multi-Symbol Tests // ============================================================================ #[tokio::test] async fn test_multi_symbol_loading() -> Result<()> { println!("\n=== Multi-Symbol Loading Test ==="); let start = Instant::now(); let symbols = vec!["ES.FUT", "NQ.FUT", "CL.FUT"]; let data = get_multi_symbol_bars(&symbols).await?; let duration = start.elapsed(); println!("Loaded {} symbols in {:?}", data.len(), duration); assert_eq!(data.len(), 3, "Should load all 3 symbols"); assert!(data.contains_key("ES.FUT")); assert!(data.contains_key("NQ.FUT")); assert!(data.contains_key("CL.FUT")); // Verify data quality for each symbol for (symbol, bars) in &data { assert!(!bars.is_empty(), "Symbol {} should have bars", symbol); assert_eq!(bars[0].symbol, *symbol); println!(" {}: {} bars", symbol, bars.len()); } println!("✓ All symbols loaded successfully"); Ok(()) } // ============================================================================ // Quality Report Tests // ============================================================================ #[tokio::test] async fn test_es_fut_quality_report() -> Result<()> { println!("\n=== ES.FUT Quality Report ==="); let bars = get_es_fut_bars().await?; let report = generate_quality_report(&bars); println!("{}", report); assert!(report.contains("ES.FUT")); assert!(report.contains("Total bars")); assert!(report.contains("Quality Checks")); Ok(()) } #[tokio::test] async fn test_nq_fut_quality_report() -> Result<()> { println!("\n=== NQ.FUT Quality Report ==="); let bars = get_nq_fut_bars().await?; let report = generate_quality_report(&bars); println!("{}", report); Ok(()) } #[tokio::test] async fn test_cl_fut_quality_report() -> Result<()> { println!("\n=== CL.FUT Quality Report ==="); let bars = get_cl_fut_bars().await?; let report = generate_quality_report(&bars); println!("{}", report); Ok(()) } // ============================================================================ // Thread Safety Tests // ============================================================================ #[tokio::test] async fn test_concurrent_cache_access() -> Result<()> { println!("\n=== Concurrent Cache Access Test ==="); let mut handles = vec![]; // Spawn 10 concurrent reads for i in 0..10 { handles.push(tokio::spawn(async move { let bars = get_es_fut_bars().await.unwrap(); (i, bars.len()) })); } // All should succeed let mut results = vec![]; for handle in handles { let (id, len) = handle.await?; results.push((id, len)); } println!("Concurrent reads: {}", results.len()); // All should return same data let first_len = results[0].1; for (id, len) in &results { assert_eq!(*len, first_len, "Task {} got different length", id); } println!("✓ All concurrent reads consistent"); Ok(()) } // ============================================================================ // Integration Tests // ============================================================================ #[tokio::test] async fn test_strategy_with_cached_data() -> Result<()> { println!("\n=== Strategy Integration Test ==="); // Load cached data let bars = get_es_fut_bars().await?; // Validate data assert_valid_ohlcv(&bars); assert_chronological(&bars); // Simple moving average crossover strategy simulation let mut signals = 0; for i in 20..bars.len() { let window = &bars[i-20..i]; let avg: f64 = window.iter() .map(|b| b.close.to_string().parse::().unwrap_or(0.0)) .sum::() / 20.0; let current = bars[i].close.to_string().parse::().unwrap_or(0.0); if current > avg * 1.001 { // 0.1% above average signals += 1; } } println!("Generated {} trading signals", signals); assert!(signals > 0, "Should generate some signals"); println!("✓ Strategy integration successful"); Ok(()) } #[tokio::test] async fn test_performance_comparison() -> Result<()> { println!("\n=== Performance Comparison ==="); // Test 1: Single symbol (cached) let start = Instant::now(); for _ in 0..100 { let _ = get_es_fut_bars().await?; } let cached_duration = start.elapsed(); println!("100 cached reads: {:?}", cached_duration); println!("Average per read: {:?}", cached_duration / 100); // Should be very fast (< 1ms total for 100 reads) assert!( cached_duration.as_millis() < 100, "100 cached reads should take < 100ms" ); println!("✓ Cache performance excellent"); Ok(()) }