//! Validate CL.FUT DBN data file //! //! Inspects the downloaded CL.FUT data and reports statistics. use std::fs::File; use dbn::decode::DbnDecoder; #[tokio::main] async fn main() -> Result<(), Box> { println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!(" CL.FUT Data Validation"); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!(); let file_path = "test_data/real/databento/CL.FUT_ohlcv-1m_2024-01-02.dbn"; println!("📂 File: {}", file_path); // Check file exists and get size let file_metadata = std::fs::metadata(file_path)?; let size = file_metadata.len(); println!("📊 Size: {} bytes ({:.2} KB, {:.2} MB)", size, size as f64 / 1024.0, size as f64 / 1_048_576.0); println!(); // Open file and create DBN decoder let file = File::open(file_path)?; let mut decoder = DbnDecoder::new(file)?; // Read DBN metadata let metadata = decoder.metadata(); println!("📋 Metadata:"); println!(" Dataset: {}", metadata.dataset); println!(" Schema: {}", metadata.schema); println!(" Start: {}", metadata.start); println!(" End: {}", metadata.end); println!(" Symbols: {}", metadata.symbols.join(", ")); println!(" Stype In: {}", metadata.stype_in); println!(); let mut bar_count = 0; let mut min_price = f64::MAX; let mut max_price = f64::MIN; let mut total_volume = 0.0; // Read all records for record in decoder.decode_records::() { let record = record?; bar_count += 1; // Track price range let open = record.open as f64 / 1_000_000_000.0; // Convert from fixed point let high = record.high as f64 / 1_000_000_000.0; let low = record.low as f64 / 1_000_000_000.0; let close = record.close as f64 / 1_000_000_000.0; let volume = record.volume as f64; if low < min_price { min_price = low; } if high > max_price { max_price = high; } total_volume += volume; // Print first few bars for inspection if bar_count <= 3 { println!("📊 Bar {}: O={:.2} H={:.2} L={:.2} C={:.2} V={:.0}", bar_count, open, high, low, close, volume); } } println!(); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!(" Statistics:"); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!(" Total Bars: {}", bar_count); println!(" Price Range: ${:.2} - ${:.2}", min_price, max_price); println!(" Total Volume: {:.0}", total_volume); println!(" Avg Volume: {:.0}", total_volume / bar_count as f64); println!(); // Estimate cost let size_gb = size as f64 / 1_073_741_824.0; let cost_low = size_gb * 0.50; let cost_high = size_gb * 2.00; println!("💰 Cost Estimate:"); println!(" Size (GB): {:.10}", size_gb); println!(" Estimated: ${:.6} - ${:.6}", cost_low, cost_high); println!(); // Validate expectations println!("✅ Validation:"); // CL.FUT typically has 390-400 bars per trading day (6.5 hours * 60 min) let expected_bars = 390; if bar_count >= expected_bars - 50 && bar_count <= expected_bars + 50 { println!(" ✓ Bar count reasonable ({} bars, expected ~{})", bar_count, expected_bars); } else { println!(" ⚠ Bar count unexpected ({} bars, expected ~{})", bar_count, expected_bars); } // CL.FUT (Crude Oil) typically trades in $70-$85 range in Jan 2024 if min_price >= 60.0 && max_price <= 100.0 { println!(" ✓ Price range reasonable (${:.2} - ${:.2})", min_price, max_price); } else { println!(" ⚠ Price range unexpected (${:.2} - ${:.2})", min_price, max_price); } if total_volume > 0.0 { println!(" ✓ Volume data present"); } else { println!(" ⚠ No volume data"); } println!(); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); println!(" Validation Complete!"); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Ok(()) }