- 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
359 lines
8.4 KiB
Markdown
359 lines
8.4 KiB
Markdown
# Test Fixtures Quick Start Guide
|
|
|
|
## TL;DR
|
|
|
|
**Before** (Slow, 5-10ms per test):
|
|
```rust
|
|
let mut file_mapping = HashMap::new();
|
|
file_mapping.insert("ES.FUT".to_string(), "path/to/ES.FUT.dbn".to_string());
|
|
let data_source = DbnDataSource::new(file_mapping).await?;
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
```
|
|
|
|
**After** (Fast, 0.1μs per test):
|
|
```rust
|
|
use fixtures::get_es_fut_bars;
|
|
|
|
let bars = get_es_fut_bars().await?; // That's it!
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Examples
|
|
|
|
### 1. Load Real Data (Fastest Way)
|
|
```rust
|
|
use fixtures::{get_es_fut_bars, get_nq_fut_bars, get_cl_fut_bars};
|
|
|
|
#[tokio::test]
|
|
async fn test_my_strategy() -> anyhow::Result<()> {
|
|
let bars = get_es_fut_bars().await?; // Cached, fast
|
|
// Test your code...
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 2. Validate Data Quality
|
|
```rust
|
|
use helpers::{assert_valid_ohlcv, assert_chronological};
|
|
|
|
#[test]
|
|
fn test_data_quality() {
|
|
let bars = load_data();
|
|
assert_valid_ohlcv(&bars); // Validates price relationships
|
|
assert_chronological(&bars); // Validates timestamp order
|
|
}
|
|
```
|
|
|
|
### 3. Test Specific Market Conditions
|
|
```rust
|
|
use fixtures::{get_regime_sample, RegimeType};
|
|
|
|
#[tokio::test]
|
|
async fn test_trending_strategy() -> anyhow::Result<()> {
|
|
let bars = get_regime_sample(RegimeType::Trending).await?;
|
|
// bars now contain trending market data
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### 4. Multi-Symbol Testing
|
|
```rust
|
|
use fixtures::get_multi_symbol_bars;
|
|
|
|
#[tokio::test]
|
|
async fn test_portfolio() -> anyhow::Result<()> {
|
|
let symbols = vec!["ES.FUT", "NQ.FUT"];
|
|
let data = get_multi_symbol_bars(&symbols).await?;
|
|
// data["ES.FUT"] → ES.FUT bars
|
|
// data["NQ.FUT"] → NQ.FUT bars
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## API Reference (One Page)
|
|
|
|
### Load Data
|
|
|
|
| Function | Returns | Use Case |
|
|
|----------|---------|----------|
|
|
| `get_es_fut_bars()` | Vec<MarketData> | E-mini S&P 500 (~390 bars) |
|
|
| `get_nq_fut_bars()` | Vec<MarketData> | E-mini NASDAQ-100 (~390 bars) |
|
|
| `get_cl_fut_bars()` | Vec<MarketData> | WTI Crude Oil (~1440 bars) |
|
|
| `get_multi_symbol_bars(&[symbols])` | HashMap<String, Vec> | Multiple symbols at once |
|
|
| `get_bars_for_date(symbol, date)` | Vec<MarketData> | Specific date only |
|
|
| `get_regime_sample(regime_type)` | Vec<MarketData> | Trending/Ranging/Volatile/Stable |
|
|
|
|
### Validate Data
|
|
|
|
| Function | Validates |
|
|
|----------|-----------|
|
|
| `assert_valid_ohlcv(&bars)` | High≥Low, Open/Close within range, positive prices |
|
|
| `assert_chronological(&bars)` | Timestamps sorted ascending |
|
|
| `assert_price_range(&bars, symbol)` | Realistic price ranges (ES: 3000-6000) |
|
|
| `assert_no_large_gaps(&bars, max_min)` | No gaps > N minutes |
|
|
|
|
### Validate Trades
|
|
|
|
| Function | Validates |
|
|
|----------|-----------|
|
|
| `assert_valid_trade(&trade)` | Exit > Entry time, positive prices, PnL correct |
|
|
| `assert_valid_trade_sequence(&trades)` | No overlaps, chronological order |
|
|
|
|
### Validate Metrics
|
|
|
|
| Function | Validates |
|
|
|----------|-----------|
|
|
| `assert_sharpe_bounds(sharpe, min, max)` | Sharpe ratio realistic (-3 to 5) |
|
|
| `assert_drawdown_bounds(dd, max)` | Drawdown ≤ max% |
|
|
| `assert_win_rate_valid(rate)` | Win rate 0-100% |
|
|
|
|
### Utilities
|
|
|
|
| Function | Returns | Use Case |
|
|
|----------|---------|----------|
|
|
| `calculate_volatility(&bars)` | f64 | Annualized volatility % |
|
|
| `generate_quality_report(&bars)` | String | Comprehensive data analysis |
|
|
|
|
---
|
|
|
|
## Regime Types
|
|
|
|
```rust
|
|
pub enum RegimeType {
|
|
Trending, // Strong directional movement (>1.5% change)
|
|
Ranging, // Bounded oscillation (<0.8% range)
|
|
Volatile, // High fluctuations (>0.5% std dev)
|
|
Stable, // Low volatility (<0.3% std dev)
|
|
}
|
|
```
|
|
|
|
**Usage**:
|
|
```rust
|
|
let trending = get_regime_sample(RegimeType::Trending).await?;
|
|
let ranging = get_regime_sample(RegimeType::Ranging).await?;
|
|
let volatile = get_regime_sample(RegimeType::Volatile).await?;
|
|
let stable = get_regime_sample(RegimeType::Stable).await?;
|
|
```
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
| Operation | Time | Notes |
|
|
|-----------|------|-------|
|
|
| First call | 5-10ms | Load from DBN file |
|
|
| Subsequent calls | ~0.1μs | Read from cache |
|
|
| 100 tests | ~10ms | 50-100x faster |
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Pattern 1: Basic Strategy Test
|
|
```rust
|
|
use fixtures::get_es_fut_bars;
|
|
use helpers::assert_valid_ohlcv;
|
|
|
|
#[tokio::test]
|
|
async fn test_my_strategy() -> anyhow::Result<()> {
|
|
let bars = get_es_fut_bars().await?;
|
|
assert_valid_ohlcv(&bars);
|
|
|
|
let signals = my_strategy.generate_signals(&bars);
|
|
assert!(!signals.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Pattern 2: Metrics Validation
|
|
```rust
|
|
use helpers::{assert_sharpe_bounds, assert_drawdown_bounds};
|
|
|
|
#[test]
|
|
fn test_backtest_metrics() {
|
|
let metrics = run_backtest();
|
|
|
|
assert_sharpe_bounds(metrics.sharpe, -3.0, 5.0);
|
|
assert_drawdown_bounds(metrics.max_dd, 50.0);
|
|
}
|
|
```
|
|
|
|
### Pattern 3: Data Quality Report
|
|
```rust
|
|
use fixtures::get_es_fut_bars;
|
|
use helpers::generate_quality_report;
|
|
|
|
#[tokio::test]
|
|
async fn test_print_report() -> anyhow::Result<()> {
|
|
let bars = get_es_fut_bars().await?;
|
|
println!("{}", generate_quality_report(&bars));
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Pattern 4: Multi-Regime Testing
|
|
```rust
|
|
use fixtures::{get_regime_sample, RegimeType};
|
|
|
|
#[tokio::test]
|
|
async fn test_all_regimes() -> anyhow::Result<()> {
|
|
for regime in &[
|
|
RegimeType::Trending,
|
|
RegimeType::Ranging,
|
|
RegimeType::Volatile,
|
|
RegimeType::Stable,
|
|
] {
|
|
let bars = get_regime_sample(*regime).await?;
|
|
test_strategy_on_regime(&bars, regime);
|
|
}
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### "File not found" error
|
|
```bash
|
|
# Check files exist
|
|
ls test_data/real/databento/*.dbn
|
|
|
|
# Should see:
|
|
# ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
# NQ.FUT_ohlcv-1m_2024-01-02.dbn
|
|
# CL.FUT_ohlcv-1m_2024-01-02.dbn
|
|
```
|
|
|
|
### Tests still slow
|
|
```rust
|
|
// ❌ Don't bypass cache
|
|
let data_source = DbnDataSource::new(file_mapping).await?;
|
|
|
|
// ✅ Use cached fixtures
|
|
let bars = get_es_fut_bars().await?;
|
|
```
|
|
|
|
### Need different date
|
|
```rust
|
|
// Not yet implemented (only 2024-01-02 available)
|
|
// Add more DBN files to test_data/real/databento/
|
|
```
|
|
|
|
---
|
|
|
|
## Import Cheatsheet
|
|
|
|
```rust
|
|
// At top of test file
|
|
mod fixtures;
|
|
mod helpers;
|
|
|
|
// In test functions
|
|
use fixtures::{
|
|
get_es_fut_bars, get_nq_fut_bars, get_cl_fut_bars,
|
|
get_regime_sample, RegimeType,
|
|
get_multi_symbol_bars, get_bars_for_date,
|
|
};
|
|
|
|
use helpers::{
|
|
assert_valid_ohlcv, assert_chronological, assert_price_range,
|
|
assert_valid_trade, assert_valid_trade_sequence,
|
|
assert_sharpe_bounds, assert_drawdown_bounds,
|
|
calculate_volatility, generate_quality_report,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Full Example Test File
|
|
|
|
```rust
|
|
//! My strategy tests
|
|
|
|
mod fixtures;
|
|
mod helpers;
|
|
|
|
use anyhow::Result;
|
|
use fixtures::{get_es_fut_bars, get_regime_sample, RegimeType};
|
|
use helpers::{assert_valid_ohlcv, assert_chronological};
|
|
|
|
#[tokio::test]
|
|
async fn test_strategy_trending() -> Result<()> {
|
|
// Load trending market data (cached)
|
|
let bars = get_regime_sample(RegimeType::Trending).await?;
|
|
|
|
// Validate data quality
|
|
assert_valid_ohlcv(&bars);
|
|
assert_chronological(&bars);
|
|
|
|
// Test strategy
|
|
let signals = my_trend_strategy.generate(&bars);
|
|
assert!(!signals.is_empty(), "Should generate signals in trend");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_strategy_ranging() -> Result<()> {
|
|
// Load ranging market data
|
|
let bars = get_regime_sample(RegimeType::Ranging).await?;
|
|
|
|
// Validate
|
|
assert_valid_ohlcv(&bars);
|
|
|
|
// Test
|
|
let signals = my_mean_reversion_strategy.generate(&bars);
|
|
assert!(!signals.is_empty(), "Should generate signals in range");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_full_day() -> Result<()> {
|
|
// Load full day of real data
|
|
let bars = get_es_fut_bars().await?;
|
|
|
|
// Comprehensive validation
|
|
assert_valid_ohlcv(&bars);
|
|
assert_chronological(&bars);
|
|
|
|
// Run full backtest
|
|
let results = backtest(&bars);
|
|
|
|
// Validate metrics
|
|
use helpers::{assert_sharpe_bounds, assert_drawdown_bounds};
|
|
assert_sharpe_bounds(results.sharpe, -3.0, 5.0);
|
|
assert_drawdown_bounds(results.max_dd, 50.0);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Copy pattern above** for your tests
|
|
2. **Replace manual DBN loading** with `get_es_fut_bars()`
|
|
3. **Add validation helpers** to catch bugs early
|
|
4. **Run tests** and enjoy 50-100x speedup!
|
|
|
|
---
|
|
|
|
## More Info
|
|
|
|
- Full documentation: `fixtures/README.md`
|
|
- Performance analysis: `fixtures/PERFORMANCE.md`
|
|
- Examples: `fixtures_tests.rs`
|
|
|
|
---
|
|
|
|
**Questions?** See comprehensive docs in `fixtures/README.md`
|
|
|
|
**Quick start**: Just call `get_es_fut_bars().await?` and you're done!
|