# Test Fixtures and Helpers Documentation ## Overview This module provides **high-performance, cached access** to real DBN market data for backtesting tests. All data is loaded once and cached in static memory, dramatically improving test execution speed. ## Architecture ``` tests/ ├── fixtures/ │ └── mod.rs # Cached data loading (singleton pattern) └── helpers.rs # Validation utilities ``` ## Performance Characteristics ### Without Caching (Naive Approach) - **Per test**: 5-10ms (DBN file I/O) - **100 tests**: 500-1000ms total - **Problem**: Repeated file reads, slow test suites ### With Caching (This Module) - **First test**: 5-10ms (one-time load) - **Subsequent tests**: ~0.1μs (memory read) - **100 tests**: ~10ms total after first load - **Benefit**: **50-100x faster** test execution ## Usage Guide ### 1. Basic Data Loading ```rust use fixtures::{get_es_fut_bars, get_nq_fut_bars, get_cl_fut_bars}; #[tokio::test] async fn test_with_real_data() -> anyhow::Result<()> { // Load ES.FUT data (cached after first call) let bars = get_es_fut_bars().await?; assert!(!bars.is_empty()); assert_eq!(bars[0].symbol, "ES.FUT"); assert!(bars.len() > 350); // ~390 bars for trading day Ok(()) } ``` ### 2. Multi-Symbol Testing ```rust use fixtures::get_multi_symbol_bars; #[tokio::test] async fn test_multiple_symbols() -> anyhow::Result<()> { // Load multiple symbols in parallel let symbols = vec!["ES.FUT", "NQ.FUT", "CL.FUT"]; let data = get_multi_symbol_bars(&symbols).await?; assert_eq!(data.len(), 3); assert!(data.contains_key("ES.FUT")); // Access data for each symbol let es_bars = &data["ES.FUT"]; let nq_bars = &data["NQ.FUT"]; Ok(()) } ``` ### 3. Regime-Specific Testing ```rust use fixtures::{get_regime_sample, RegimeType}; #[tokio::test] async fn test_trending_strategy() -> anyhow::Result<()> { // Get sample of trending market data let bars = get_regime_sample(RegimeType::Trending).await?; // Test trend-following strategy let signals = my_strategy.generate_signals(&bars); assert!(signals.len() > 0); Ok(()) } #[tokio::test] async fn test_ranging_strategy() -> anyhow::Result<()> { // Get sample of ranging/sideways market let bars = get_regime_sample(RegimeType::Ranging).await?; // Test mean-reversion strategy let signals = my_strategy.generate_signals(&bars); Ok(()) } ``` ### 4. Date-Specific Data ```rust use fixtures::get_bars_for_date; use chrono::NaiveDate; #[tokio::test] async fn test_specific_date() -> anyhow::Result<()> { 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?; // All bars from 2024-01-02 for bar in &bars { assert_eq!(bar.timestamp.date_naive(), date.date_naive()); } Ok(()) } ``` ## Data Validation Helpers ### 5. OHLCV Validation ```rust use helpers::assert_valid_ohlcv; #[test] fn test_data_quality() { let bars = load_my_data(); // Validates: // - High >= Low // - High >= Open, Close // - Low <= Open, Close // - All prices > 0 // - Volume >= 0 assert_valid_ohlcv(&bars); } ``` ### 6. Time Series Validation ```rust use helpers::{assert_chronological, assert_no_large_gaps}; #[test] fn test_timestamp_quality() { let bars = load_my_data(); // Ensure bars sorted by timestamp assert_chronological(&bars); // Ensure no gaps > 5 minutes (for 1-minute data) assert_no_large_gaps(&bars, 5); } ``` ### 7. Price Range Validation ```rust use helpers::assert_price_range; #[test] fn test_realistic_prices() { let bars = load_my_data(); // Validates prices within realistic range: // ES.FUT: 3000-6000 // NQ.FUT: 12000-20000 // CL.FUT: 50-100 assert_price_range(&bars, "ES.FUT"); } ``` ### 8. Trade Validation ```rust use helpers::{assert_valid_trade, assert_valid_trade_sequence}; #[test] fn test_backtest_trades() { let trades = run_backtest(); // Validate individual trade assert_valid_trade(&trades[0]); // Validate entire sequence: // - No overlapping trades // - Chronological order // - Valid PnL calculations assert_valid_trade_sequence(&trades); } ``` ### 9. Performance Metrics Validation ```rust use helpers::{assert_sharpe_bounds, assert_drawdown_bounds, assert_win_rate_valid}; #[test] fn test_metrics_realistic() { let metrics = calculate_performance_metrics(); // Sharpe ratio between -3.0 and 5.0 assert_sharpe_bounds(metrics.sharpe_ratio, -3.0, 5.0); // Max drawdown <= 50% assert_drawdown_bounds(metrics.max_drawdown, 50.0); // Win rate between 0% and 100% assert_win_rate_valid(metrics.win_rate); } ``` ### 10. Data Quality Reports ```rust use helpers::generate_quality_report; #[test] fn test_print_quality_report() { let bars = load_my_data(); let report = generate_quality_report(&bars); println!("{}", report); // Output: // === Data Quality Report === // // Total bars: 390 // Symbol: ES.FUT // Date range: 2024-01-02 00:00:00 to 2024-01-02 23:59:00 // // Price Statistics: // Min: 4705.25 // Max: 4748.75 // Avg: 4725.50 // Range: 0.92% // // Volatility: // Annualized: 18.5% // // Quality Checks: // OHLCV errors: 0 // Chronology errors: 0 // // === End Report === } ``` ## Available Data ### ES.FUT (E-mini S&P 500) - **File**: `ES.FUT_ohlcv-1m_2024-01-02.dbn` - **Bars**: ~390 (trading day) - **Size**: 95KB - **Price range**: 4700-4750 (typical 2024) - **Use case**: General strategy testing, high liquidity ### NQ.FUT (E-mini NASDAQ-100) - **File**: `NQ.FUT_ohlcv-1m_2024-01-02.dbn` - **Bars**: ~390 - **Size**: 93KB - **Price range**: 16500-16700 (typical 2024) - **Use case**: Tech sector strategies ### CL.FUT (WTI Crude Oil) - **File**: `CL.FUT_ohlcv-1m_2024-01-02.dbn` - **Bars**: ~1440 (24-hour trading) - **Size**: 1.5MB - **Price range**: 71-73 USD/barrel - **Use case**: Extended hours trading, energy strategies ## Regime Types ### RegimeType::Trending - **Characteristics**: Strong directional movement - **Detection**: Price change > 1.5% over 60 bars - **Use case**: Trend-following strategies ### RegimeType::Ranging - **Characteristics**: Bounded oscillation, sideways movement - **Detection**: Price range < 0.8% over 60 bars - **Use case**: Mean-reversion strategies ### RegimeType::Volatile - **Characteristics**: High price fluctuations - **Detection**: Standard deviation > 0.5% - **Use case**: Options strategies, volatility trading ### RegimeType::Stable - **Characteristics**: Low volatility, steady prices - **Detection**: Standard deviation < 0.3% - **Use case**: Low-risk strategies, carry trades ## Performance Tips ### ✅ DO ```rust // Reuse cached data across tests let bars = get_es_fut_bars().await?; // Use regime samples for targeted testing let trending = get_regime_sample(RegimeType::Trending).await?; // Validate data quality with helpers assert_valid_ohlcv(&bars); assert_chronological(&bars); ``` ### ❌ DON'T ```rust // Don't load DBN files directly (bypass cache) let data_source = DbnDataSource::new(file_mapping).await?; // SLOW // Don't generate synthetic data when real data available let fake_bars = generate_fake_bars(100); // LESS REALISTIC // Don't skip validation (catch bugs early) // (missing assert_valid_ohlcv check) ``` ## Testing Best Practices ### 1. Fast Unit Tests ```rust // Use small samples for fast iteration let bars = get_sample_real_data(50).await?; ``` ### 2. Comprehensive Integration Tests ```rust // Use full datasets for thorough testing let bars = get_es_fut_bars().await?; ``` ### 3. Regime-Specific Tests ```rust // Test each regime type separately let trending = get_regime_sample(RegimeType::Trending).await?; let ranging = get_regime_sample(RegimeType::Ranging).await?; ``` ### 4. Multi-Symbol Tests ```rust // Test portfolio strategies with multiple symbols let data = get_multi_symbol_bars(&["ES.FUT", "NQ.FUT"]).await?; ``` ## Thread Safety All fixtures use `once_cell::sync::Lazy` and `tokio::sync::RwLock` for safe concurrent access: ```rust // Multiple tests can access cache concurrently #[tokio::test] async fn test_1() { let bars = get_es_fut_bars().await?; // Thread-safe } #[tokio::test] async fn test_2() { let bars = get_es_fut_bars().await?; // Same cache, different test } ``` ## Migration Guide ### Before (Slow, No Cache) ```rust #[tokio::test] async fn test_old_way() { let mut file_mapping = HashMap::new(); file_mapping.insert( "ES.FUT".to_string(), "../../test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn".to_string() ); let data_source = DbnDataSource::new(file_mapping).await?; let bars = data_source.load_ohlcv_bars("ES.FUT").await?; // 5-10ms PER TEST } ``` ### After (Fast, Cached) ```rust #[tokio::test] async fn test_new_way() { let bars = get_es_fut_bars().await?; // ~0.1μs after first load } ``` ## Troubleshooting ### Cache Not Working? - Ensure using `get_es_fut_bars()` not direct DBN loading - Check `CACHED_ES_BARS` static is initialized ### File Not Found? - Verify DBN files exist: `ls test_data/real/databento/` - Check `get_project_root()` finds correct directory ### Test Failures? - Use `generate_quality_report()` to inspect data - Check validation helpers for detailed error messages ## Examples Directory See `services/backtesting_service/tests/` for full examples: - `dbn_integration_tests.rs` - DBN loading patterns - `strategy_execution.rs` - Strategy testing with real data - `performance_metrics.rs` - Metrics validation ## Contributing When adding new fixtures: 1. Add static cache variable 2. Implement cached loading function 3. Add unit tests 4. Update this documentation ## License Part of Foxhunt HFT Trading System - See project LICENSE