- 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
863 lines
20 KiB
Markdown
863 lines
20 KiB
Markdown
# DBN Troubleshooting Guide
|
|
|
|
**Version**: 1.0
|
|
**Last Updated**: 2025-10-13
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Common Errors](#common-errors)
|
|
2. [Data Quality Issues](#data-quality-issues)
|
|
3. [Performance Problems](#performance-problems)
|
|
4. [File Format Issues](#file-format-issues)
|
|
5. [Integration Issues](#integration-issues)
|
|
6. [Debugging Tools](#debugging-tools)
|
|
|
|
---
|
|
|
|
## Common Errors
|
|
|
|
### Error: "DBN file not found"
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: DBN file not found: test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
```
|
|
|
|
**Cause**: File path is incorrect or relative paths are not resolving correctly.
|
|
|
|
**Solution 1**: Use workspace-relative paths
|
|
```rust
|
|
fn get_test_file_path() -> String {
|
|
let workspace_root = std::env::current_dir()
|
|
.unwrap()
|
|
.ancestors()
|
|
.find(|p| p.join("Cargo.toml").exists() && p.join("test_data").exists())
|
|
.expect("Could not find workspace root");
|
|
|
|
workspace_root
|
|
.join("test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn")
|
|
.to_string_lossy()
|
|
.to_string()
|
|
}
|
|
```
|
|
|
|
**Solution 2**: Use absolute paths
|
|
```rust
|
|
let file_path = "/home/jgrusewski/Work/foxhunt/test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn";
|
|
```
|
|
|
|
**Solution 3**: Verify file exists
|
|
```bash
|
|
ls -lh test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
# Should output: -rw-rw-r-- 1 user user 95K Oct 12 23:22 ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
```
|
|
|
|
---
|
|
|
|
### Error: "No DBN file configured for symbol"
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: No DBN file configured for symbol: UNKNOWN
|
|
```
|
|
|
|
**Cause**: Symbol not in file mapping.
|
|
|
|
**Solution**:
|
|
```rust
|
|
// Check available symbols first
|
|
let symbols = data_source.available_symbols();
|
|
println!("Available symbols: {:?}", symbols);
|
|
|
|
// Add missing symbol
|
|
data_source.add_symbol_mapping(
|
|
"UNKNOWN".to_string(),
|
|
"path/to/file.dbn".to_string()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### Error: "Invalid message length"
|
|
|
|
**Symptom**:
|
|
```
|
|
WARN Invalid message length: 0 at offset 1234
|
|
```
|
|
|
|
**Cause**: Corrupted DBN file or incorrect file format.
|
|
|
|
**Diagnosis**:
|
|
```bash
|
|
# Check file size (should be > 0)
|
|
ls -lh test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
|
|
# Check file is not empty
|
|
du -h test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
|
|
# Try to open with dbn CLI tool (if available)
|
|
dbn info test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
```
|
|
|
|
**Solution**: Re-download or regenerate the DBN file.
|
|
|
|
---
|
|
|
|
### Error: "Failed to decode DBN record"
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: Failed to decode DBN record at offset 5678
|
|
```
|
|
|
|
**Cause**: File format mismatch or version incompatibility.
|
|
|
|
**Solution**:
|
|
```rust
|
|
// Enable version upgrades
|
|
let mut decoder = DbnDecoder::from_file(file_path)?;
|
|
decoder.set_upgrade_policy(VersionUpgradePolicy::UpgradeToV3)?;
|
|
```
|
|
|
|
**Check DBN version compatibility**:
|
|
- Foxhunt uses `dbn` crate v0.9+
|
|
- Supports DBN format v1, v2, v3
|
|
- Automatic version upgrading enabled by default
|
|
|
|
---
|
|
|
|
## Data Quality Issues
|
|
|
|
### Issue: Unrealistic Prices
|
|
|
|
**Symptom**:
|
|
```
|
|
Bar 150: ES.FUT price 47.4275 outside realistic range (3000-6000)
|
|
```
|
|
|
|
**Cause**: Price encoding anomaly (7 decimal places instead of 9).
|
|
|
|
**Automatic Fix**: The system automatically detects and corrects these:
|
|
|
|
```rust
|
|
// Price anomaly detection (automatic in load_ohlcv_bars)
|
|
if pct_change > 0.5 && close_f64 < 1000.0 {
|
|
let corrected_close = close_f64 * 100.0;
|
|
if corrected_close >= 3000.0 && corrected_close <= 6000.0 {
|
|
// Apply 100x correction
|
|
open_f64 *= 100.0;
|
|
high_f64 *= 100.0;
|
|
low_f64 *= 100.0;
|
|
close_f64 = corrected_close;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Verification**:
|
|
```rust
|
|
// Check correction logs
|
|
RUST_LOG=debug cargo test -p backtesting_service test_load_real_dbn_file
|
|
|
|
// Look for lines like:
|
|
// DEBUG Applied 100x price correction at bar 150 (99% change, $47.43 -> $4742.75)
|
|
```
|
|
|
|
**Manual Validation**:
|
|
```rust
|
|
async fn validate_prices() -> Result<()> {
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
|
|
for (i, bar) in bars.iter().enumerate() {
|
|
let close_f64 = bar.close.to_string().parse::<f64>().unwrap();
|
|
|
|
if close_f64 < 3000.0 || close_f64 > 6000.0 {
|
|
eprintln!("WARN: Bar {} has unusual price: ${:.2}", i, close_f64);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: OHLCV Relationship Violations
|
|
|
|
**Symptom**:
|
|
```
|
|
Quality issue at bar 42: high < low
|
|
```
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
fn diagnose_ohlcv_issues(bars: &[MarketData]) {
|
|
for (i, bar) in bars.iter().enumerate() {
|
|
if bar.high < bar.low {
|
|
eprintln!("Bar {}: high ({}) < low ({})", i, bar.high, bar.low);
|
|
}
|
|
if bar.high < bar.open || bar.high < bar.close {
|
|
eprintln!("Bar {}: high not highest value", i);
|
|
}
|
|
if bar.low > bar.open || bar.low > bar.close {
|
|
eprintln!("Bar {}: low not lowest value", i);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Solution**: This indicates data corruption. Options:
|
|
1. Re-download the DBN file
|
|
2. Skip corrupted bars
|
|
3. Contact data provider
|
|
|
|
```rust
|
|
// Skip corrupted bars
|
|
let valid_bars: Vec<_> = bars.into_iter()
|
|
.filter(|bar| {
|
|
bar.high >= bar.low &&
|
|
bar.high >= bar.open &&
|
|
bar.high >= bar.close &&
|
|
bar.low <= bar.open &&
|
|
bar.low <= bar.close
|
|
})
|
|
.collect();
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Missing or Sparse Data
|
|
|
|
**Symptom**:
|
|
```
|
|
Expected ~390 bars, got 150
|
|
```
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
async fn analyze_data_density() -> Result<()> {
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
|
|
if bars.len() < 350 {
|
|
eprintln!("WARN: Expected ~390 bars (1-minute, full trading day), got {}", bars.len());
|
|
}
|
|
|
|
// Check for gaps
|
|
for i in 1..bars.len() {
|
|
let gap = bars[i].timestamp - bars[i-1].timestamp;
|
|
if gap > chrono::Duration::minutes(5) {
|
|
eprintln!("Data gap detected at bar {}: {} minute gap", i, gap.num_minutes());
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
**Common Causes**:
|
|
1. Partial day data (market opened late, closed early)
|
|
2. Data feed interruption
|
|
3. Filter applied during download
|
|
|
|
**Solution**: Verify data source coverage and re-download if needed.
|
|
|
|
---
|
|
|
|
### Issue: Timestamp Anomalies
|
|
|
|
**Symptom**:
|
|
```
|
|
Bar 200: Timestamp 2024-01-02 15:30:00 (jumped backwards)
|
|
```
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
fn check_timestamp_ordering(bars: &[MarketData]) {
|
|
for i in 1..bars.len() {
|
|
if bars[i].timestamp < bars[i-1].timestamp {
|
|
eprintln!(
|
|
"Timestamp ordering issue at bar {}: {} < {}",
|
|
i,
|
|
bars[i].timestamp,
|
|
bars[i-1].timestamp
|
|
);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Solution**: Sort bars after loading (already done by `load_ohlcv_bars`):
|
|
```rust
|
|
// Automatic sorting
|
|
bars.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Problems
|
|
|
|
### Issue: Slow Loading (>100ms for ~400 bars)
|
|
|
|
**Target**: <10ms for ~400 bars
|
|
**Actual**: >100ms
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
use std::time::Instant;
|
|
|
|
let start = Instant::now();
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
let duration = start.elapsed();
|
|
|
|
println!("Loaded {} bars in {:?} ({:.2}ms)",
|
|
bars.len(),
|
|
duration,
|
|
duration.as_secs_f64() * 1000.0
|
|
);
|
|
|
|
if duration.as_millis() > 100 {
|
|
eprintln!("WARN: Performance target missed");
|
|
}
|
|
```
|
|
|
|
**Common Causes & Solutions**:
|
|
|
|
#### 1. Cold File System Cache
|
|
|
|
**Solution**: Warm up cache first
|
|
```rust
|
|
// Warm-up run
|
|
let _ = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
|
|
// Timed run (will be faster)
|
|
let start = Instant::now();
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
let duration = start.elapsed();
|
|
```
|
|
|
|
#### 2. Disk I/O Bottleneck
|
|
|
|
**Check disk performance**:
|
|
```bash
|
|
# Test read speed
|
|
dd if=test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn of=/dev/null bs=1M
|
|
|
|
# Should be >100 MB/s on modern SSD
|
|
```
|
|
|
|
**Solution**: Use SSD storage, not HDD
|
|
|
|
#### 3. Debug Logging Overhead
|
|
|
|
**Disable verbose logging**:
|
|
```bash
|
|
# ❌ Slow (debug logging)
|
|
RUST_LOG=debug cargo run
|
|
|
|
# ✅ Fast (info or warn only)
|
|
RUST_LOG=info cargo run
|
|
```
|
|
|
|
#### 4. Memory Allocations
|
|
|
|
**Pre-allocate vectors**:
|
|
```rust
|
|
// ✅ Good (pre-allocated)
|
|
let mut bars = Vec::with_capacity(400);
|
|
|
|
// ❌ Slow (reallocates multiple times)
|
|
let mut bars = Vec::new();
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: High Memory Usage
|
|
|
|
**Symptom**: Process uses >1GB RAM for loading 10 files.
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
use sysinfo::{System, SystemExt, ProcessExt};
|
|
|
|
let mut sys = System::new_all();
|
|
sys.refresh_all();
|
|
|
|
if let Some(process) = sys.process(sysinfo::get_current_pid().unwrap()) {
|
|
println!("Memory usage: {} MB", process.memory() / 1024);
|
|
}
|
|
```
|
|
|
|
**Solutions**:
|
|
|
|
#### 1. Disable Caching
|
|
|
|
```rust
|
|
let data_source = DbnDataSource::new(file_mapping)
|
|
.await?
|
|
.with_cache_limit(0); // No caching
|
|
```
|
|
|
|
#### 2. Stream Processing
|
|
|
|
```rust
|
|
// Process files one at a time
|
|
for symbol in symbols {
|
|
let bars = data_source.load_ohlcv_bars(&symbol).await?;
|
|
process_bars(&bars);
|
|
// Bars dropped here, memory freed
|
|
}
|
|
```
|
|
|
|
#### 3. Limit LRU Cache Size
|
|
|
|
```rust
|
|
let data_source = DbnDataSource::new(file_mapping)
|
|
.await?
|
|
.with_cache_limit(3); // Only cache last 3 symbols
|
|
```
|
|
|
|
---
|
|
|
|
## File Format Issues
|
|
|
|
### Issue: Unknown File Format
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: Failed to create DBN decoder for file: unknown format
|
|
```
|
|
|
|
**Verify file format**:
|
|
```bash
|
|
# Check file magic bytes
|
|
xxd -l 16 test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
|
|
# Should start with DBN magic bytes (0x64 0x62 0x6e)
|
|
```
|
|
|
|
**Verify DBN schema**:
|
|
```bash
|
|
# If you have dbn CLI tool
|
|
dbn info test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
|
|
# Expected output:
|
|
# Schema: OHLCV-1m
|
|
# Compression: none
|
|
# Records: 390
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Unsupported Schema
|
|
|
|
**Symptom**:
|
|
```
|
|
WARN Unknown message type: 0x52
|
|
```
|
|
|
|
**Supported Schemas**:
|
|
- ✅ OHLCV-1m (1-minute bars) - **Fully supported**
|
|
- ✅ Trade messages - Supported via DbnParser
|
|
- ✅ Quote messages - Supported via DbnParser
|
|
- ❌ MBO (Market By Order) - Not yet implemented
|
|
- ❌ MBP (Market By Price) - Not yet implemented
|
|
|
|
**Workaround**: Convert unsupported schema to OHLCV using databento tools:
|
|
```bash
|
|
# Convert trades to OHLCV-1m
|
|
dbn convert --schema ohlcv-1m --interval 1m input.dbn output.dbn
|
|
```
|
|
|
|
---
|
|
|
|
## Integration Issues
|
|
|
|
### Issue: Repository Interface Not Working
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: the trait bound `DbnMarketDataRepository: MarketDataRepository` is not satisfied
|
|
```
|
|
|
|
**Solution**: Import the trait
|
|
```rust
|
|
use backtesting_service::repositories::MarketDataRepository;
|
|
|
|
// Now trait methods available
|
|
let data = repo.load_historical_data(&symbols, start_time, end_time).await?;
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Timestamp Format Mismatch
|
|
|
|
**Symptom**:
|
|
```
|
|
Error: Invalid timestamp: expected nanoseconds, got seconds
|
|
```
|
|
|
|
**DBN uses nanoseconds** (i64):
|
|
```rust
|
|
// ✅ Correct (nanoseconds since Unix epoch)
|
|
let start_time = 1704153600_000_000_000i64; // 2024-01-02 00:00:00
|
|
|
|
// ❌ Wrong (seconds)
|
|
let start_time = 1704153600i64;
|
|
```
|
|
|
|
**Convert DateTime to nanoseconds**:
|
|
```rust
|
|
use chrono::{TimeZone, Utc};
|
|
|
|
let dt = Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap();
|
|
let nanos = dt.timestamp_nanos_opt().unwrap();
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Symbol Mapping Not Working
|
|
|
|
**Symptom**:
|
|
```
|
|
Loaded 0 bars for BTC/USD (expected ES.FUT data)
|
|
```
|
|
|
|
**Diagnosis**:
|
|
```rust
|
|
// Check symbol mappings
|
|
let repo = DbnMarketDataRepository::new_with_mappings(
|
|
file_mapping,
|
|
symbol_mappings
|
|
).await?;
|
|
|
|
// Add debug logging
|
|
RUST_LOG=debug cargo run
|
|
// Look for: "Symbol mapping: BTC/USD -> ES.FUT"
|
|
```
|
|
|
|
**Solution**: Verify mapping configuration
|
|
```rust
|
|
let mut symbol_mappings = HashMap::new();
|
|
symbol_mappings.insert("BTC/USD".to_string(), "ES.FUT".to_string());
|
|
symbol_mappings.insert("ETH/USD".to_string(), "ES.FUT".to_string());
|
|
|
|
let repo = DbnMarketDataRepository::new_with_mappings(
|
|
file_mapping,
|
|
symbol_mappings // ← Make sure this is passed!
|
|
).await?;
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Tools
|
|
|
|
### Tool 1: Data Validation Script
|
|
|
|
```rust
|
|
// Save as: scripts/validate_dbn_data.rs
|
|
|
|
use backtesting_service::dbn_data_source::DbnDataSource;
|
|
use std::collections::HashMap;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
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?;
|
|
|
|
println!("=== DBN Data Validation Report ===");
|
|
println!("File: ES.FUT_ohlcv-1m_2024-01-02.dbn");
|
|
println!("Bars loaded: {}", bars.len());
|
|
|
|
// Check expected count
|
|
if bars.len() < 350 || bars.len() > 450 {
|
|
eprintln!("⚠️ WARN: Expected ~390 bars, got {}", bars.len());
|
|
} else {
|
|
println!("✅ Bar count in expected range (350-450)");
|
|
}
|
|
|
|
// Validate OHLCV relationships
|
|
let mut quality_issues = 0;
|
|
for (i, bar) in bars.iter().enumerate() {
|
|
if !(bar.high >= bar.low &&
|
|
bar.high >= bar.open &&
|
|
bar.high >= bar.close &&
|
|
bar.low <= bar.open &&
|
|
bar.low <= bar.close) {
|
|
quality_issues += 1;
|
|
if quality_issues <= 3 {
|
|
eprintln!("⚠️ Bar {} OHLCV violation", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if quality_issues == 0 {
|
|
println!("✅ All bars passed OHLCV validation");
|
|
} else {
|
|
eprintln!("❌ {} bars failed OHLCV validation", quality_issues);
|
|
}
|
|
|
|
// Check price ranges
|
|
let mut price_issues = 0;
|
|
for (i, bar) in bars.iter().enumerate() {
|
|
let close_f64 = bar.close.to_string().parse::<f64>().unwrap();
|
|
if close_f64 < 3000.0 || close_f64 > 6000.0 {
|
|
price_issues += 1;
|
|
if price_issues <= 3 {
|
|
eprintln!("⚠️ Bar {} unusual price: ${:.2}", i, close_f64);
|
|
}
|
|
}
|
|
}
|
|
|
|
if price_issues == 0 {
|
|
println!("✅ All prices in realistic range (3000-6000)");
|
|
} else {
|
|
eprintln!("❌ {} bars with unusual prices", price_issues);
|
|
}
|
|
|
|
// Check timestamp ordering
|
|
let mut timestamp_issues = 0;
|
|
for i in 1..bars.len() {
|
|
if bars[i].timestamp < bars[i-1].timestamp {
|
|
timestamp_issues += 1;
|
|
}
|
|
}
|
|
|
|
if timestamp_issues == 0 {
|
|
println!("✅ Timestamps properly ordered");
|
|
} else {
|
|
eprintln!("❌ {} timestamp ordering issues", timestamp_issues);
|
|
}
|
|
|
|
println!("\n=== Summary ===");
|
|
println!("First bar: {} @ {}", bars[0].close, bars[0].timestamp);
|
|
println!("Last bar: {} @ {}", bars[bars.len()-1].close, bars[bars.len()-1].timestamp);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Run with:
|
|
```bash
|
|
cargo run --bin validate_dbn_data
|
|
```
|
|
|
|
---
|
|
|
|
### Tool 2: Performance Profiler
|
|
|
|
```rust
|
|
// Save as: scripts/profile_dbn_loading.rs
|
|
|
|
use backtesting_service::dbn_data_source::DbnDataSource;
|
|
use std::collections::HashMap;
|
|
use std::time::Instant;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
println!("=== DBN Loading Performance Profile ===\n");
|
|
|
|
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()
|
|
);
|
|
|
|
// Test 1: Cold load
|
|
println!("Test 1: Cold load (first time)");
|
|
let data_source = DbnDataSource::new(file_mapping.clone()).await?;
|
|
let start = Instant::now();
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
let cold_duration = start.elapsed();
|
|
println!(" Duration: {:?} ({:.2}ms)", cold_duration, cold_duration.as_secs_f64() * 1000.0);
|
|
println!(" Bars: {}", bars.len());
|
|
|
|
// Test 2: Warm load
|
|
println!("\nTest 2: Warm load (cached in OS)");
|
|
let start = Instant::now();
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
let warm_duration = start.elapsed();
|
|
println!(" Duration: {:?} ({:.2}ms)", warm_duration, warm_duration.as_secs_f64() * 1000.0);
|
|
println!(" Bars: {}", bars.len());
|
|
|
|
// Test 3: With caching enabled
|
|
println!("\nTest 3: With LRU cache (immediate)");
|
|
let data_source = DbnDataSource::new(file_mapping.clone())
|
|
.await?
|
|
.with_cache_limit(10);
|
|
|
|
// Prime cache
|
|
let _ = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
|
|
let start = Instant::now();
|
|
let bars = data_source.load_ohlcv_bars("ES.FUT").await?;
|
|
let cached_duration = start.elapsed();
|
|
println!(" Duration: {:?} ({:.2}ms)", cached_duration, cached_duration.as_secs_f64() * 1000.0);
|
|
println!(" Bars: {}", bars.len());
|
|
|
|
// Performance summary
|
|
println!("\n=== Performance Summary ===");
|
|
println!("Target: <10ms");
|
|
println!("Cold load: {:.2}ms {}",
|
|
cold_duration.as_secs_f64() * 1000.0,
|
|
if cold_duration.as_millis() < 10 { "✅" } else { "⚠️" }
|
|
);
|
|
println!("Warm load: {:.2}ms {}",
|
|
warm_duration.as_secs_f64() * 1000.0,
|
|
if warm_duration.as_millis() < 10 { "✅" } else { "⚠️" }
|
|
);
|
|
println!("Cached load: {:.2}ms ✅",
|
|
cached_duration.as_secs_f64() * 1000.0
|
|
);
|
|
|
|
// Throughput
|
|
let bars_per_sec = (bars.len() as f64 / warm_duration.as_secs_f64()) as u64;
|
|
println!("\nThroughput: {} bars/sec", bars_per_sec);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Run with:
|
|
```bash
|
|
cargo run --bin profile_dbn_loading
|
|
```
|
|
|
|
---
|
|
|
|
### Tool 3: Data Explorer
|
|
|
|
```rust
|
|
// Save as: scripts/explore_dbn_data.rs
|
|
|
|
use backtesting_service::dbn_data_source::DbnDataSource;
|
|
use std::collections::HashMap;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let symbol = std::env::args().nth(1).unwrap_or_else(|| "ES.FUT".to_string());
|
|
let file_path = std::env::args().nth(2).unwrap_or_else(|| {
|
|
"test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn".to_string()
|
|
});
|
|
|
|
println!("=== DBN Data Explorer ===");
|
|
println!("Symbol: {}", symbol);
|
|
println!("File: {}\n", file_path);
|
|
|
|
let mut file_mapping = HashMap::new();
|
|
file_mapping.insert(symbol.clone(), file_path);
|
|
|
|
let data_source = DbnDataSource::new(file_mapping).await?;
|
|
let bars = data_source.load_ohlcv_bars(&symbol).await?;
|
|
|
|
println!("Total bars: {}", bars.len());
|
|
println!("Time range: {} to {}", bars[0].timestamp, bars[bars.len()-1].timestamp);
|
|
|
|
// Sample bars
|
|
println!("\n=== First 5 bars ===");
|
|
for (i, bar) in bars.iter().take(5).enumerate() {
|
|
println!("[{:03}] {} @ {} | O:{} H:{} L:{} C:{} V:{}",
|
|
i,
|
|
bar.symbol,
|
|
bar.timestamp.format("%Y-%m-%d %H:%M:%S"),
|
|
bar.open,
|
|
bar.high,
|
|
bar.low,
|
|
bar.close,
|
|
bar.volume
|
|
);
|
|
}
|
|
|
|
println!("\n=== Last 5 bars ===");
|
|
for (i, bar) in bars.iter().rev().take(5).rev().enumerate() {
|
|
let idx = bars.len() - 5 + i;
|
|
println!("[{:03}] {} @ {} | O:{} H:{} L:{} C:{} V:{}",
|
|
idx,
|
|
bar.symbol,
|
|
bar.timestamp.format("%Y-%m-%d %H:%M:%S"),
|
|
bar.open,
|
|
bar.high,
|
|
bar.low,
|
|
bar.close,
|
|
bar.volume
|
|
);
|
|
}
|
|
|
|
// Statistics
|
|
let closes: Vec<f64> = bars.iter()
|
|
.map(|b| b.close.to_string().parse().unwrap())
|
|
.collect();
|
|
|
|
let mean = closes.iter().sum::<f64>() / closes.len() as f64;
|
|
let min = closes.iter().cloned().fold(f64::INFINITY, f64::min);
|
|
let max = closes.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
|
|
|
println!("\n=== Statistics ===");
|
|
println!("Mean close: ${:.2}", mean);
|
|
println!("Min close: ${:.2}", min);
|
|
println!("Max close: ${:.2}", max);
|
|
println!("Range: ${:.2}", max - min);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Run with:
|
|
```bash
|
|
cargo run --bin explore_dbn_data ES.FUT test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Help
|
|
|
|
### Check Logs
|
|
|
|
```bash
|
|
# Enable debug logging
|
|
RUST_LOG=debug cargo test -p backtesting_service dbn_integration_tests
|
|
|
|
# Filter for DBN-specific logs
|
|
RUST_LOG=backtesting_service::dbn_data_source=debug cargo run
|
|
```
|
|
|
|
### Run Test Suite
|
|
|
|
```bash
|
|
# All DBN tests
|
|
cargo test -p backtesting_service dbn_integration_tests
|
|
|
|
# Specific test
|
|
cargo test -p backtesting_service test_load_real_dbn_file
|
|
|
|
# With output
|
|
cargo test -p backtesting_service test_load_real_dbn_file -- --nocapture
|
|
```
|
|
|
|
### File a Bug Report
|
|
|
|
Include:
|
|
1. Error message (full stack trace)
|
|
2. DBN file info (name, size, schema)
|
|
3. Code snippet (minimal reproduction)
|
|
4. System info (OS, Rust version)
|
|
5. Logs (with RUST_LOG=debug)
|
|
|
|
```bash
|
|
# Generate system info
|
|
rustc --version
|
|
cargo --version
|
|
uname -a
|
|
```
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-10-13
|
|
**Version**: 1.0
|