Files
foxhunt/services/backtesting_service/tests/fixtures
jgrusewski eae3c31e53 fix(clippy): Fix 6 unwrap_used violations in risk/data
Patterns applied:
- Pattern 2: Float comparison (2x: utils.rs, var_edge_cases_tests.rs)
- Pattern 7: Date/time construction (2x: production_streaming.rs, streaming.rs)
- Pattern 1: Duration/time ops (2x: rate limiter, semaphore)
- Pattern 4: Optional field access (1x: position_tracker.rs)

Changes:
- data/src/utils.rs: Float sort with NaN handling
- data/src/providers/benzinga/production_streaming.rs: Rate limiter + semaphore + date/time
- data/src/providers/benzinga/streaming.rs: Date/time construction
- risk/src/position_tracker.rs: Emergency fallback counter
- risk/tests/var_edge_cases_tests.rs: Test helper float sort

Test impact: 0 failures (182/182 passing)
Compilation: Clean (0 errors, 0 warnings)
Time: 25 min (44% under budget)
2025-10-23 14:58:32 +02:00
..

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

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

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

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

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

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

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

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

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

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

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

// 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

// 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

// Use small samples for fast iteration
let bars = get_sample_real_data(50).await?;

2. Comprehensive Integration Tests

// Use full datasets for thorough testing
let bars = get_es_fut_bars().await?;

3. Regime-Specific Tests

// 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

// 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:

// 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)

#[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)

#[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