//! DBN Data Visualization Example //! //! Creates ASCII chart visualization of ES.FUT price data. use anyhow::Result; use backtesting_service::dbn_repository::DbnMarketDataRepository; use backtesting_service::repositories::MarketDataRepository; use chrono::Timelike; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use std::collections::HashMap; #[tokio::main] async fn main() -> Result<()> { // Load data 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?; let symbols = vec!["ES.FUT".to_string()]; let start_time = 1704153600_000_000_000i64; // 2024-01-02 00:00:00 UTC let end_time = 1704240000_000_000_000i64; // 2024-01-03 00:00:00 UTC let data = repo .load_historical_data(&symbols, start_time, end_time) .await?; if data.is_empty() { println!("❌ ERROR: No data loaded!"); return Ok(()); } println!("ES.FUT Price Chart (ASCII)"); println!("==========================\n"); println!("Date: 2024-01-02 (E-mini S&P 500 Futures)"); println!( "Total bars: {} (sampling every {}th bar for visualization)\n", data.len(), if data.len() > 400 { 10 } else { 5 } ); // Sample data for visualization let step_size = if data.len() > 400 { 10 } else { 5 }; let sample_data: Vec<_> = data.iter().step_by(step_size).take(50).collect(); // Normalize prices to 0-25 range for ASCII chart let min_price = sample_data .iter() .map(|b| b.low) .min() .unwrap_or(Decimal::ZERO); let max_price = sample_data .iter() .map(|b| b.high) .max() .unwrap_or(Decimal::ZERO); let price_range = max_price - min_price; let price_range_f64 = price_range.to_f64().unwrap_or(1.0); println!(" Price Range: ${:.2} - ${:.2}\n", min_price, max_price); // Print chart header println!( " Time Price Chart (Low to High) Volume" ); println!(" -------- ------- ----------------------------------------- -------"); for bar in sample_data { let low_f64 = bar.low.to_f64().unwrap_or(0.0); let high_f64 = bar.high.to_f64().unwrap_or(0.0); let close_f64 = bar.close.to_f64().unwrap_or(0.0); let min_price_f64 = min_price.to_f64().unwrap_or(0.0); let norm_low = (((low_f64 - min_price_f64) / price_range_f64 * 40.0) as usize).min(39); let norm_high = (((high_f64 - min_price_f64) / price_range_f64 * 40.0) as usize).min(39); let norm_close = (((close_f64 - min_price_f64) / price_range_f64 * 40.0) as usize).min(39); let mut line = String::from("|"); for i in 0..40 { if i >= norm_low && i <= norm_high { if i == norm_close { line.push('●'); // Close price } else if i == norm_low || i == norm_high { line.push('┼'); // High/Low markers } else { line.push('│'); // Range bar } } else { line.push(' '); } } line.push('|'); println!( " {:02}:{:02}:{:02} ${:>7.2} {} {:>7.0}", bar.timestamp.hour(), bar.timestamp.minute(), bar.timestamp.second(), bar.close, line, bar.volume ); } println!("\n Legend:"); println!(" ● = Close price"); println!(" │ = High-Low range"); println!(" ┼ = High/Low markers"); // Print some key statistics println!("\n📊 Key Statistics:"); let total_volume: Decimal = data.iter().map(|b| b.volume).sum(); let sum_prices: Decimal = data.iter().map(|b| b.close).sum(); let avg_price = sum_prices / Decimal::from(data.len()); println!(" Average Price: ${:.2}", avg_price); println!(" Total Volume: {:.0}", total_volume); println!(" Price Range: ${:.2}", price_range); Ok(()) }