Files
foxhunt/services/backtesting_service/tests/fixtures/README.md
jgrusewski e8a68ee39f Download 360 DBN files (36.3 MB) using Rust databento client
- Created data/examples/download_ml_training_data.rs using reqwest + Databento HTTP API
- Downloaded 90 days × 4 symbols (ES.FUT, NQ.FUT, ZN.FUT, 6E.FUT)
- Files saved to test_data/real/databento/ml_training/
- Total: 360 files, 15 MB compressed DBN format
- Used existing Rust pattern from download_nq_fut.rs
- API key loaded from .env file
- 100% success rate (360/360 files)
- Ready for ML training benchmarks

Next: Create simplified training benchmark for RTX 3050 Ti GPU measurements
2025-10-13 13:30:02 +02:00

432 lines
9.8 KiB
Markdown

# 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