#![allow(clippy::uninlined_format_args)]
use std::sync::Arc;
use ibapi::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let client = Arc::new(Client::connect("127.0.0.1:4002", 100).await?);
println!("Connected to IB Gateway");
let test_cases = vec![
(
"AAPL",
Contract::stock("AAPL").build(),
HistoricalBarSize::Week,
TradingHours::Regular,
"1 week RTH",
),
(
"SPY",
Contract::stock("SPY").build(),
HistoricalBarSize::Day,
TradingHours::Regular,
"1 day RTH",
),
(
"TSLA",
Contract::stock("TSLA").build(),
HistoricalBarSize::Week,
TradingHours::Extended,
"1 week all hours",
),
];
for (symbol, contract, period, trading_hours, description) in test_cases {
println!("\n{symbol} Histogram ({description}):");
println!("Period: {period:?}, Trading hours: {trading_hours:?}");
match client.histogram_data(&contract, trading_hours, period).await {
Ok(histogram) => {
if histogram.is_empty() {
println!("No histogram data available");
continue;
}
let total_count: i64 = histogram.iter().map(|e| e.size as i64).sum();
let min_price = histogram.iter().map(|e| e.price).fold(f64::INFINITY, f64::min);
let max_price = histogram.iter().map(|e| e.price).fold(f64::NEG_INFINITY, f64::max);
let weighted_sum: f64 = histogram.iter().map(|e| e.price * e.size as f64).sum();
let weighted_avg = weighted_sum / total_count as f64;
println!("\nPrice Distribution:");
println!("Price | Count | Percentage | Bar");
println!("----------|----------|------------|{}", "-".repeat(50));
let max_count = histogram.iter().map(|e| e.size as i64).max().unwrap_or(1);
let mut sorted_histogram = histogram.clone();
sorted_histogram.sort_by(|a, b| a.price.partial_cmp(&b.price).unwrap());
let display_count = 10;
let total_entries = sorted_histogram.len();
if total_entries <= display_count * 2 {
for entry in &sorted_histogram {
print_histogram_entry(entry, total_count, max_count);
}
} else {
println!("Top {display_count} price levels:");
for entry in sorted_histogram.iter().rev().take(display_count).rev() {
print_histogram_entry(entry, total_count, max_count);
}
println!("... ({} entries omitted) ...", total_entries - display_count * 2);
println!("Bottom {display_count} price levels:");
for entry in sorted_histogram.iter().take(display_count) {
print_histogram_entry(entry, total_count, max_count);
}
}
println!("\nStatistics:");
println!(" Total observations: {total_count}");
println!(" Price range: ${min_price:.2} - ${max_price:.2}");
println!(" Price levels: {}", histogram.len());
println!(" Weighted average: ${weighted_avg:.2}");
if let Some(mode_entry) = histogram.iter().max_by_key(|e| e.size) {
let mode_pct = (mode_entry.size as f64 / total_count as f64) * 100.0;
println!(" Mode: ${:.2} ({} occurrences, {:.1}%)", mode_entry.price, mode_entry.size, mode_pct);
}
}
Err(e) => {
println!("Error: {e}");
}
}
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
println!("\nExample completed!");
Ok(())
}
fn print_histogram_entry(entry: &ibapi::market_data::historical::HistogramEntry, total_count: i64, max_count: i64) {
let percentage = (entry.size as f64 / total_count as f64) * 100.0;
let bar_length = ((entry.size as f64 / max_count as f64) * 50.0) as usize;
let bar = "█".repeat(bar_length);
println!("${:8.2} | {:8} | {:9.2}% | {}", entry.price, entry.size, percentage, bar);
}