use crate::DEFAULT_SYMBOL;
use clap::Args as ClapArgs;
use ibapi::{
Client,
accounts::PositionUpdate,
contracts::Contract,
market_data::{MarketDataType, TradingHours},
orders::Orders,
prelude::{StreamExt, SubscriptionItemStreamExt},
};
use std::error::Error;
const RUN_DELAY: tokio::time::Duration = tokio::time::Duration::from_secs(1);
const RETRY_DELAY: tokio::time::Duration = tokio::time::Duration::from_secs(10);
const SMART_EXCHANGE: &str = "SMART";
const OVERNIGHT_EXCHANGE: &str = "OVERNIGHT";
#[derive(ClapArgs)]
pub struct Args {
#[arg(long, default_value = DEFAULT_SYMBOL)]
symbol: String,
}
impl Default for Args {
fn default() -> Self {
Self {
symbol: DEFAULT_SYMBOL.to_string(),
}
}
}
pub async fn run(address: &str, client_id: i32, args: &Args) -> Result<(), Box<dyn Error>> {
loop {
match Client::connect(address, client_id).await {
Ok(client) => {
if let Err(error) = run_with_connection(&client, &args.symbol).await {
eprintln!("Error: {error}");
}
}
Err(error) => eprintln!("Connection to Interactive Brokers Gateway failed: {error}"),
}
tokio::time::sleep(RETRY_DELAY).await;
}
}
async fn run_with_connection(client: &Client, symbol: &str) -> Result<(), Box<dyn Error>> {
client
.switch_market_data_type(MarketDataType::Realtime)
.await?;
tokio::try_join!(
run_steps(client),
stream_live_data(client, symbol),
stream_realtime_bars(client, symbol, SMART_EXCHANGE),
stream_realtime_bars(client, symbol, OVERNIGHT_EXCHANGE),
)?;
Ok(())
}
async fn run_steps(client: &Client) -> Result<(), Box<dyn Error>> {
loop {
run_step(client).await?;
tokio::time::sleep(RUN_DELAY).await;
}
}
async fn stream_live_data(client: &Client, symbol: &str) -> Result<(), Box<dyn Error>> {
let contract = Contract::stock(symbol).build();
let mut subscription = client
.market_data(&contract)
.streaming()
.subscribe()
.await?;
println!("[market data] Streaming {symbol} market data…");
while let Some(tick) = subscription.next().await {
println!("[market data] {symbol}: {:?}", tick?);
}
Err(ibapi::Error::UnexpectedEndOfStream.into())
}
async fn stream_realtime_bars(
client: &Client,
symbol: &str,
exchange: &str,
) -> Result<(), Box<dyn Error>> {
let contract = Contract::stock(symbol).on_exchange(exchange).build();
let subscription = client
.realtime_bars(&contract)
.trading_hours(TradingHours::Extended)
.subscribe()
.await?;
let mut bars = subscription.filter_data();
println!("[bars] Streaming {symbol} five-second bars from {exchange}…");
while let Some(bar) = bars.next().await {
println!("[bars] {symbol} ({exchange}): {:?}", bar?);
}
Err(ibapi::Error::UnexpectedEndOfStream.into())
}
async fn run_step(client: &Client) -> Result<(), Box<dyn Error>> {
list_orders(client).await?;
list_positions(client).await?;
Ok(())
}
async fn list_orders(client: &Client) -> Result<(), Box<dyn Error>> {
println!("[orders] Requesting all open orders…");
let subscription = client.all_open_orders().await?;
let mut orders = subscription.filter_data();
let mut order_count: usize = 0;
while let Some(order) = orders.next().await {
match order? {
Orders::OrderData(data) => {
order_count += 1;
println!("[orders] - {data:?}");
}
Orders::OrderStatus(status) => println!("{status:?}"),
}
}
if order_count == 0 {
println!("[orders] No open orders found.");
} else if order_count == 1 {
println!("[orders] Finished listing 1 open order.");
} else {
println!("[orders] Finished listing {order_count} open orders.");
}
Ok(())
}
async fn list_positions(client: &Client) -> Result<(), Box<dyn Error>> {
println!("[positions] Requesting all positions…");
let subscription = client.positions().await?;
let mut positions = subscription.filter_data();
let mut position_count: usize = 0;
while let Some(update) = positions.next().await {
match update? {
PositionUpdate::Position(position) => {
position_count += 1;
println!("[positions] - {position:?}");
}
PositionUpdate::PositionEnd => break,
}
}
if position_count == 0 {
println!("[positions] No positions found.");
} else if position_count == 1 {
println!("[positions] Finished listing 1 position.");
} else {
println!("[positions] Finished listing {position_count} positions.");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::Args;
use clap::Parser;
#[derive(Parser)]
struct TestCli {
#[command(flatten)]
args: Args,
}
#[test]
fn default_symbol() {
let cli = TestCli::try_parse_from(["run"]).unwrap();
assert_eq!(cli.args.symbol, "SOXL");
}
#[test]
fn explicit_symbol() {
let cli = TestCli::try_parse_from(["run", "--symbol", "AAPL"]).unwrap();
assert_eq!(cli.args.symbol, "AAPL");
}
}