- 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
199 lines
6.9 KiB
Rust
199 lines
6.9 KiB
Rust
//! DBN Statistical Analysis Example
|
|
//!
|
|
//! This example demonstrates advanced statistical analysis and data transformation
|
|
//! features of the DBN repository.
|
|
//!
|
|
//! ## Usage
|
|
//!
|
|
//! ```bash
|
|
//! cargo run --example dbn_statistical_analysis
|
|
//! ```
|
|
|
|
use backtesting_service::dbn_repository::DbnMarketDataRepository;
|
|
use std::collections::HashMap;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
println!("=== DBN Statistical Analysis Example ===\n");
|
|
|
|
// 1. Setup repository
|
|
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 repo = DbnMarketDataRepository::new(file_mapping).await?;
|
|
|
|
// 2. Load all data
|
|
println!("Loading data...");
|
|
let symbols = vec!["ES.FUT".to_string()];
|
|
let start_time = 1704153600_000_000_000i64; // 2024-01-02 00:00:00
|
|
let end_time = 1704240000_000_000_000i64; // 2024-01-03 00:00:00
|
|
|
|
let bars = repo.load_historical_data(&symbols, start_time, end_time).await?;
|
|
println!("✅ Loaded {} bars\n", bars.len());
|
|
|
|
// 3. Summary Statistics
|
|
println!("=== Summary Statistics ===");
|
|
let stats = repo.generate_summary_stats(&bars);
|
|
|
|
println!("Count: {}", stats.get("count").unwrap());
|
|
println!("Mean Close: ${:.2}", stats.get("mean_close").unwrap());
|
|
println!("Std Close: ${:.2}", stats.get("std_close").unwrap());
|
|
println!("Min Close: ${:.2}", stats.get("min_close").unwrap());
|
|
println!("Max Close: ${:.2}", stats.get("max_close").unwrap());
|
|
println!("Mean Volume: {:.0}", stats.get("mean_volume").unwrap());
|
|
println!("Total Volume: {:.0}", stats.get("total_volume").unwrap());
|
|
|
|
// 4. Rolling Window Statistics
|
|
println!("\n=== Rolling 20-Bar Window Statistics ===");
|
|
let window_size = 20;
|
|
let rolling_stats = repo.calculate_rolling_stats(&bars, window_size);
|
|
|
|
println!("Window size: {} bars", window_size);
|
|
println!("Windows calculated: {}", rolling_stats.len());
|
|
|
|
// Display last 5 windows
|
|
println!("\nLast 5 windows:");
|
|
for (i, (mean, std, min, max)) in rolling_stats.iter().rev().take(5).enumerate() {
|
|
println!(
|
|
" Window {}: mean=${:.2}, std=${:.2}, range=[${:.2}, ${:.2}]",
|
|
rolling_stats.len() - i,
|
|
mean,
|
|
std,
|
|
min,
|
|
max
|
|
);
|
|
}
|
|
|
|
// 5. Bar Resampling
|
|
println!("\n=== Bar Resampling ===");
|
|
|
|
// Resample to different timeframes
|
|
for target_minutes in [5, 15, 60] {
|
|
let resampled = repo.resample_bars(&bars, target_minutes)?;
|
|
let reduction = (1.0 - (resampled.len() as f64 / bars.len() as f64)) * 100.0;
|
|
|
|
println!(
|
|
"{:>2}-minute bars: {:>3} bars ({:.1}% reduction)",
|
|
target_minutes,
|
|
resampled.len(),
|
|
reduction
|
|
);
|
|
|
|
// Show first resampled bar
|
|
if let Some(first) = resampled.first() {
|
|
println!(
|
|
" First: {} @ {} | O={} H={} L={} C={} V={}",
|
|
first.symbol,
|
|
first.timestamp.format("%H:%M"),
|
|
first.open,
|
|
first.high,
|
|
first.low,
|
|
first.close,
|
|
first.volume
|
|
);
|
|
}
|
|
}
|
|
|
|
// 6. Regime Detection (Simple Heuristic)
|
|
println!("\n=== Regime Detection ===");
|
|
|
|
let regime_types = ["trending", "ranging", "volatile", "stable"];
|
|
for regime_type in regime_types.iter() {
|
|
match repo.load_regime_samples(regime_type, 5, &symbols).await {
|
|
Ok(samples) => {
|
|
println!("{:<10} regime: {} sample bars found", regime_type, samples.len());
|
|
|
|
if !samples.is_empty() {
|
|
let sample = &samples[0];
|
|
let range = sample.high - sample.low;
|
|
let avg_price = (sample.high + sample.low) / rust_decimal::Decimal::from(2);
|
|
let range_pct = (range / avg_price * rust_decimal::Decimal::from(10000))
|
|
.to_string()
|
|
.parse::<f64>()
|
|
.unwrap()
|
|
/ 100.0;
|
|
|
|
println!(" Example: {} @ {} (range: {:.2}%)", sample.symbol, sample.timestamp, range_pct);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("{:<10} regime: Error - {}", regime_type, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 7. Price Return Analysis
|
|
println!("\n=== Price Return Analysis ===");
|
|
|
|
let mut returns: Vec<f64> = Vec::new();
|
|
for i in 1..bars.len() {
|
|
let prev_close = bars[i - 1].close.to_string().parse::<f64>().unwrap();
|
|
let curr_close = bars[i].close.to_string().parse::<f64>().unwrap();
|
|
let ret = (curr_close - prev_close) / prev_close;
|
|
returns.push(ret);
|
|
}
|
|
|
|
let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;
|
|
let variance = returns
|
|
.iter()
|
|
.map(|r| (r - mean_return).powi(2))
|
|
.sum::<f64>()
|
|
/ returns.len() as f64;
|
|
let std_dev = variance.sqrt();
|
|
|
|
println!("Mean return: {:.6} ({:.4}%)", mean_return, mean_return * 100.0);
|
|
println!("Std dev: {:.6} ({:.4}%)", std_dev, std_dev * 100.0);
|
|
println!(
|
|
"Min return: {:.6} ({:.4}%)",
|
|
returns.iter().cloned().fold(f64::INFINITY, f64::min),
|
|
returns.iter().cloned().fold(f64::INFINITY, f64::min) * 100.0
|
|
);
|
|
println!(
|
|
"Max return: {:.6} ({:.4}%)",
|
|
returns.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
|
|
returns.iter().cloned().fold(f64::NEG_INFINITY, f64::max) * 100.0
|
|
);
|
|
|
|
// Sharpe ratio (annualized, assuming 252 trading days)
|
|
let sharpe = (mean_return / std_dev) * (252.0 * 390.0_f64).sqrt(); // 390 bars per day
|
|
println!("Sharpe ratio: {:.2}", sharpe);
|
|
|
|
// 8. Volume Analysis
|
|
println!("\n=== Volume Analysis ===");
|
|
|
|
let volumes: Vec<f64> = bars
|
|
.iter()
|
|
.map(|b| b.volume.to_string().parse().unwrap())
|
|
.collect();
|
|
|
|
let mean_volume = volumes.iter().sum::<f64>() / volumes.len() as f64;
|
|
let max_volume = volumes.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
|
let min_volume = volumes.iter().cloned().fold(f64::INFINITY, f64::min);
|
|
|
|
println!("Mean volume: {:.0}", mean_volume);
|
|
println!("Max volume: {:.0}", max_volume);
|
|
println!("Min volume: {:.0}", min_volume);
|
|
|
|
// Find high-volume bars
|
|
let high_volume_threshold = mean_volume * 2.0;
|
|
let high_volume_bars: Vec<_> = bars
|
|
.iter()
|
|
.filter(|b| b.volume.to_string().parse::<f64>().unwrap() > high_volume_threshold)
|
|
.collect();
|
|
|
|
println!("\nHigh-volume bars (>2x mean): {}", high_volume_bars.len());
|
|
for bar in high_volume_bars.iter().take(3) {
|
|
println!(
|
|
" {} @ {} | Volume: {}",
|
|
bar.symbol, bar.timestamp, bar.volume
|
|
);
|
|
}
|
|
|
|
println!("\n✅ Example completed successfully!");
|
|
|
|
Ok(())
|
|
}
|