ibapi 4.0.0

A Rust implementation of the Interactive Brokers TWS API, providing a reliable and user friendly interface for TWS and IB Gateway. Designed with a focus on simplicity and performance.
Documentation
use crate::client::blocking::{ClientRequestBuilders, Subscription};
use crate::common::request_helpers::{self, empty_on_end_of_stream, expect_proto};
use crate::contracts::{Contract, TagValue};
use crate::messages::OutgoingMessages;
use crate::protocol::{check_version, Features};
use crate::{client::sync::Client, server_versions, Error};

use super::common::{decoders, encoders};
use super::{
    Bar, DepthMarketDataDescription, MarketDataBuilder, MarketDepthBuilder, MarketDepths, RealtimeBarsBuilder, TickByTickBuilder, TickTypes,
    WhatToShow,
};
use crate::market_data::{SmartDepth, TradingHours};
use crate::subscriptions::StreamDecoder;

/// Validates that server supports the given request.
pub(super) fn validate_tick_by_tick_request(client: &Client, _contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<(), Error> {
    check_version(client.server_version(), Features::TICK_BY_TICK)?;

    if number_of_ticks != 0 || ignore_size {
        check_version(client.server_version(), Features::TICK_BY_TICK_IGNORE_SIZE)?;
    }

    Ok(())
}

impl Client {
    /// Returns a builder for a streaming level-1 market data subscription.
    ///
    /// Streams by default; `.snapshot()` switches to a one-shot request. See
    /// [`MarketDataBuilder`] for the chained methods.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    /// use ibapi::market_data::realtime::TickTypes;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    /// let contract = Contract::stock("AAPL").build();
    ///
    /// // Subscribe to real-time streaming data with specific tick types
    /// let subscription = client.market_data(&contract)
    ///     .generic_ticks(&["233", "236"])  // RTVolume and Shortable
    ///     .subscribe()
    ///     .expect("subscription failed");
    ///
    /// for tick in subscription.iter_data() {
    ///     match tick? {
    ///         TickTypes::Price(price) => println!("Price: {price:?}"),
    ///         TickTypes::Size(size) => println!("Size: {size:?}"),
    ///         _ => {}
    ///     }
    /// }
    /// # Ok::<(), ibapi::Error>(())
    /// ```
    ///
    /// A one-shot snapshot, driven manually. To just collect the ticks and stop, prefer
    /// [`MarketDataBuilder::snapshot_once`], which wraps this loop with a timeout.
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    /// use ibapi::market_data::realtime::TickTypes;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    /// let contract = Contract::stock("AAPL").build();
    ///
    /// let subscription = client.market_data(&contract)
    ///     .snapshot()
    ///     .subscribe()
    ///     .expect("subscription failed");
    ///
    /// for tick in subscription.iter_data() {
    ///     if let TickTypes::SnapshotEnd = tick? {
    ///         println!("Snapshot complete");
    ///         break;
    ///     }
    /// }
    /// # Ok::<(), ibapi::Error>(())
    /// ```
    pub fn market_data<'a>(&'a self, contract: &'a Contract) -> MarketDataBuilder<'a, Self> {
        MarketDataBuilder::new(self, contract)
    }

    /// Returns a builder for a real-time 5-second bar subscription.
    ///
    /// Defaults to `WhatToShow::Trades` and `TradingHours::Regular`. See
    /// [`RealtimeBarsBuilder`] for the chained methods.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    /// use ibapi::market_data::realtime::WhatToShow;
    /// use ibapi::market_data::TradingHours;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contract = Contract::stock("TSLA").build();
    /// let subscription = client
    ///     .realtime_bars(&contract)
    ///     .what_to_show(WhatToShow::Trades)
    ///     .trading_hours(TradingHours::Extended)
    ///     .subscribe()
    ///     .expect("realtime bars request failed");
    ///
    /// for (i, bar) in subscription.iter_data().enumerate().take(60) {
    ///     match bar {
    ///         Ok(bar) => println!("bar[{i}]: {bar:?}"),
    ///         Err(e) => { eprintln!("error: {e:?}"); break; }
    ///     }
    /// }
    /// ```
    pub fn realtime_bars<'a>(&'a self, contract: &'a Contract) -> RealtimeBarsBuilder<'a, Self> {
        RealtimeBarsBuilder::new(self, contract)
    }

    /// Returns a builder for a tick-by-tick real-time subscription.
    ///
    /// Pick the tick stream with the terminal — `.last()` / `.all_last()` /
    /// `.bid_ask(IgnoreSize)` / `.mid_point()`. See [`TickByTickBuilder`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    /// use ibapi::market_data::IgnoreSize;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    /// let contract = Contract::stock("AAPL").build();
    ///
    /// let quotes = client
    ///     .tick_by_tick(&contract, 10)
    ///     .bid_ask(IgnoreSize::No)
    ///     .expect("tick-by-tick bid/ask request failed");
    ///
    /// for quote in quotes.iter_data().take(10) {
    ///     match quote {
    ///         Ok(quote) => println!("{quote:?}"),
    ///         Err(e) => { eprintln!("error: {e:?}"); break; }
    ///     }
    /// }
    /// ```
    pub fn tick_by_tick<'a>(&'a self, contract: &'a Contract, number_of_ticks: i32) -> TickByTickBuilder<'a, Self> {
        TickByTickBuilder::new(self, contract, number_of_ticks)
    }

    /// Returns a builder for a level-2 market-depth (order book) subscription.
    ///
    /// Defaults to `SmartDepth::No`. See [`MarketDepthBuilder`] for the chained
    /// methods.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    /// use ibapi::market_data::SmartDepth;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contract = Contract::stock("AAPL").build();
    /// let subscription = client
    ///     .market_depth(&contract, 5)
    ///     .smart_depth(SmartDepth::Yes)
    ///     .subscribe()
    ///     .expect("error requesting market depth");
    ///
    /// for row in subscription.iter_data() {
    ///     match row {
    ///         Ok(row) => println!("row: {row:?}"),
    ///         Err(e) => {
    ///             eprintln!("error: {e:?}");
    ///             break;
    ///         }
    ///     }
    /// }
    /// ```
    pub fn market_depth<'a>(&'a self, contract: &'a Contract, number_of_rows: i32) -> MarketDepthBuilder<'a, Self> {
        MarketDepthBuilder::new(self, contract, number_of_rows)
    }

    /// Requests venues for which market data is returned to market_depth (those with market makers)
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    /// let exchanges = client.market_depth_exchanges().expect("error requesting market depth exchanges");
    /// for exchange in &exchanges {
    ///     println!("{exchange:?}");
    /// }
    /// ```
    pub fn market_depth_exchanges(&self) -> Result<Vec<DepthMarketDataDescription>, Error> {
        check_version(self.server_version(), Features::REQ_MKT_DEPTH_EXCHANGES)?;

        request_helpers::blocking::one_shot_shared(
            self,
            OutgoingMessages::RequestMktDepthExchanges,
            encoders::encode_request_market_depth_exchanges,
            expect_proto(decoders::decode_market_depth_exchanges_proto),
        )
        .or_else(empty_on_end_of_stream)
    }

    /// Switches market data type returned from request_market_data requests to Live, Frozen, Delayed, or FrozenDelayed.
    ///
    /// # Arguments
    /// * `market_data_type` - Type of market data to retrieve.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::market_data::{MarketDataType};
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let market_data_type = MarketDataType::Realtime;
    /// client.switch_market_data_type(market_data_type).expect("request failed");
    /// println!("market data switched: {market_data_type:?}");
    /// ```
    pub fn switch_market_data_type(&self, market_data_type: crate::market_data::MarketDataType) -> Result<(), Error> {
        self.check_server_version(server_versions::REQ_MARKET_DATA_TYPE, "It does not support market data type requests.")?;

        let message = crate::market_data::encoders::encode_request_market_data_type(market_data_type)?;
        self.send_message(message)
    }
}

pub(crate) fn market_data(
    client: &Client,
    contract: &Contract,
    generic_ticks: &[&str],
    snapshot: bool,
    regulatory_snapshot: bool,
) -> Result<Subscription<TickTypes>, Error> {
    let builder = client.request();
    let request = encoders::encode_request_market_data(builder.request_id(), contract, generic_ticks, snapshot, regulatory_snapshot)?;

    builder.send(request)
}

pub(crate) fn realtime_bars(
    client: &Client,
    contract: &Contract,
    what_to_show: &WhatToShow,
    trading_hours: TradingHours,
    options: &[TagValue],
) -> Result<Subscription<Bar>, Error> {
    let builder = client.request();
    let request = encoders::encode_request_realtime_bars(builder.request_id(), contract, what_to_show, trading_hours.use_rth(), options)?;

    builder.send(request)
}

pub(crate) fn market_depth(
    client: &Client,
    contract: &Contract,
    number_of_rows: i32,
    smart_depth: SmartDepth,
) -> Result<Subscription<MarketDepths>, Error> {
    let is_smart_depth = matches!(smart_depth, SmartDepth::Yes);
    if is_smart_depth {
        check_version(client.server_version(), Features::SMART_DEPTH)?;
    }
    if !contract.primary_exchange.is_empty() {
        check_version(client.server_version(), Features::MKT_DEPTH_PRIM_EXCHANGE)?;
    }

    let builder = client.request();
    let request = encoders::encode_request_market_depth(builder.request_id(), contract, number_of_rows, is_smart_depth)?;
    builder.send_with_context(request, client.decoder_context().with_smart_depth(is_smart_depth))
}

pub(crate) fn tick_by_tick<T: StreamDecoder<T>>(
    client: &Client,
    contract: &Contract,
    tick_type: &str,
    number_of_ticks: i32,
    ignore_size: bool,
) -> Result<Subscription<T>, Error> {
    validate_tick_by_tick_request(client, contract, number_of_ticks, ignore_size)?;

    let builder = client.request();
    let request = encoders::encode_tick_by_tick(builder.request_id(), contract, tick_type, number_of_ticks, ignore_size)?;
    builder.send(request)
}

#[cfg(test)]
#[path = "sync_tests.rs"]
mod tests;