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 super::common::{decoders, encoders, verify};
use super::*;
use crate::client::blocking::{ClientRequestBuilders, Subscription};
use crate::common::request_helpers::{self, empty_on_end_of_stream, expect_proto};
use crate::messages::{IncomingMessages, OutgoingMessages};
use crate::protocol::{check_version, Features};
use crate::subscriptions::StreamDecoder;
use crate::{client::sync::Client, Error};

impl Client {
    /// Requests contract information.
    ///
    /// Provides all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains. Though it is now (in API version > 9.72.12) advised to use [Client::option_chain] for that purpose.
    ///
    /// # Arguments
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contract = Contract::stock("TSLA").build();
    /// let results = client.contract_details(&contract).expect("request failed");
    /// for contract_detail in results {
    ///     println!("contract: {contract_detail:?}");
    /// }
    /// ```
    pub fn contract_details(&self, contract: &Contract) -> Result<Vec<ContractDetails>, Error> {
        verify::verify_contract(self.server_version, contract)?;

        let builder = self.request();
        let request_id = builder.request_id();
        let packet = encoders::encode_request_contract_data(request_id, contract)?;

        let responses = builder.send_raw(packet)?;

        let mut contract_details: Vec<ContractDetails> = Vec::default();

        while let Some(response) = responses.next() {
            log::debug!("response: {response:#?}");
            match response {
                Ok(message) if message.message_type() == IncomingMessages::ContractData => {
                    let decoded = decoders::decode_contract_details(&message)?;
                    contract_details.push(decoded);
                }
                Ok(message) if message.message_type() == IncomingMessages::ContractDataEnd => return Ok(contract_details),
                Ok(message) => return Err(Error::unexpected_response(&message)),
                Err(e) => return Err(e),
            }
        }

        Err(Error::UnexpectedEndOfStream)
    }

    /// Cancels an in-flight contract details request.
    ///
    /// # Arguments
    /// * `request_id` - The request ID returned by a prior `contract_details` call.
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// // `request_id` is the id the in-flight `contract_details` call was issued with;
    /// // cancelling one that has already completed is harmless.
    /// let request_id = client.next_request_id();
    /// client.cancel_contract_details(request_id).expect("cancel failed");
    /// ```
    pub fn cancel_contract_details(&self, request_id: i32) -> Result<(), Error> {
        check_version(self.server_version, Features::CANCEL_CONTRACT_DATA)?;

        let message = encoders::encode_cancel_contract_data(request_id)?;
        self.send_message(message)?;
        Ok(())
    }

    /// Requests details about a given market rule
    ///
    /// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
    /// A list of market rule ids can be obtained by invoking [Self::contract_details()] for a particular contract.
    /// The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [`crate::contracts::ContractDetails`].
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::Contract;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// // Market rule ids come from a contract's details.
    /// let details = client.contract_details(&Contract::stock("AAPL").build()).expect("request failed");
    /// let rule_id: i32 = details[0]
    ///     .market_rule_ids
    ///     .first()
    ///     .and_then(|id| id.parse().ok())
    ///     .expect("contract has no market rule ids");
    ///
    /// let rule = client.market_rule(rule_id).expect("market rule request failed");
    /// for increment in &rule.price_increments {
    ///     println!("above {}: increment {}", increment.low_edge, increment.increment);
    /// }
    /// ```
    pub fn market_rule(&self, market_rule_id: i32) -> Result<MarketRule, Error> {
        check_version(self.server_version, Features::MARKET_RULES)?;

        request_helpers::blocking::one_shot_shared(
            self,
            OutgoingMessages::RequestMarketRule,
            || encoders::encode_request_market_rule(market_rule_id),
            expect_proto(decoders::decode_market_rule_proto),
        )
    }

    /// Requests the underlying exchanges that contribute to a consolidated (BBO) feed.
    ///
    /// Given a BBO exchange code (an opaque per-session token, e.g. `"a6"`),
    /// returns the list of underlying exchanges with each entry's bit
    /// position, full exchange name, and single-letter abbreviation. Useful
    /// for decoding the `mdSize` / `mdMask` bitmaps on tick-by-tick and
    /// market-depth streams. The token is typically obtained from the
    /// `LAST_EXCHANGE` market-data tick (tick type 84).
    ///
    /// # Arguments
    /// * `bbo_exchange` - The BBO exchange token (e.g. `"a6"`).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let components = client.smart_components("a6").expect("request failed");
    /// for component in &components {
    ///     println!("bit {}: {} ({})", component.bit_number, component.exchange, component.exchange_letter);
    /// }
    /// ```
    pub fn smart_components(&self, bbo_exchange: &str) -> Result<Vec<SmartComponent>, Error> {
        check_version(self.server_version, Features::SMART_COMPONENTS)?;

        request_helpers::blocking::one_shot_by_request_id(
            self,
            |request_id| encoders::encode_request_smart_components(request_id, bbo_exchange),
            expect_proto(decoders::decode_smart_components_proto),
        )
    }

    /// Requests matching stock symbols.
    ///
    /// # Arguments
    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contracts = client.matching_symbols("IB").expect("request failed");
    /// for contract in contracts {
    ///     println!("contract: {contract:?}");
    /// }
    /// ```
    pub fn matching_symbols(&self, pattern: &str) -> Result<Vec<ContractDescription>, Error> {
        check_version(self.server_version, Features::REQ_MATCHING_SYMBOLS)?;

        request_helpers::blocking::one_shot_by_request_id(
            self,
            |request_id| encoders::encode_request_matching_symbols(request_id, pattern),
            expect_proto(decoders::decode_symbol_samples_proto),
        )
        .or_else(empty_on_end_of_stream)
    }

    /// Calculates an option's price based on the provided volatility and its underlying's price.
    ///
    /// # Arguments
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
    /// * `volatility`      - Hypothetical volatility as a percentage (e.g., 20.0 for 20%).
    /// * `underlying_price` - Hypothetical price of the underlying asset.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::{Contract, OptionRight};
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contract = Contract::option("AAPL", "20251219", 150.0, OptionRight::Call);
    /// let calculation = client.calculate_option_price(&contract, 100.0, 235.0).expect("request failed");
    /// println!("calculation: {calculation:?}");
    /// ```
    pub fn calculate_option_price(&self, contract: &Contract, volatility: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
        check_version(self.server_version, Features::REQ_CALC_OPTION_PRICE)?;

        request_helpers::blocking::one_shot_by_request_id(
            self,
            |request_id| encoders::encode_calculate_option_price(request_id, contract, volatility, underlying_price),
            |message| OptionComputation::decode(&self.decoder_context(), message),
        )
    }

    /// Calculates the implied volatility based on the hypothetical option price and underlying price.
    ///
    /// # Arguments
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
    /// * `option_price`    - Hypothetical option price.
    /// * `underlying_price` - Hypothetical price of the underlying asset.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::{Contract, OptionRight};
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let contract = Contract::option("AAPL", "20230519", 150.0, OptionRight::Call);
    /// let calculation = client.calculate_implied_volatility(&contract, 25.0, 235.0).expect("request failed");
    /// println!("calculation: {calculation:?}");
    /// ```
    pub fn calculate_implied_volatility(&self, contract: &Contract, option_price: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
        check_version(self.server_version, Features::REQ_CALC_IMPLIED_VOLAT)?;

        request_helpers::blocking::one_shot_by_request_id(
            self,
            |request_id| encoders::encode_calculate_implied_volatility(request_id, contract, option_price, underlying_price),
            |message| OptionComputation::decode(&self.decoder_context(), message),
        )
    }

    /// Build a request for an underlying's option chain: one [`OptionChain`] per
    /// exchange the options trade on.
    ///
    /// Terminal: [`OptionChainBuilder::subscribe`]. Optional narrowing via [`OptionChainBuilder::exchange`].
    ///
    /// # Arguments
    /// * `symbol` - Symbol of the underlying.
    /// * `security_type` - Security type of the underlying, e.g. `SecurityType::Stock`.
    /// * `contract_id` - Contract id of the underlying. Required; TWS rejects `0` with
    ///   code 321 "Invalid contract id".
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::client::blocking::Client;
    /// use ibapi::contracts::SecurityType;
    ///
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
    ///
    /// let subscription = client
    ///     .option_chain("AAPL", SecurityType::Stock, 265598)
    ///     .subscribe()
    ///     .expect("request option chain failed");
    ///
    /// for chain in subscription.iter_data() {
    ///     let chain = chain.expect("decode error");
    ///     println!("{}: {} expirations, {} strikes", chain.exchange, chain.expirations.len(), chain.strikes.len());
    /// }
    /// ```
    pub fn option_chain<'a>(&'a self, symbol: &'a str, security_type: SecurityType, contract_id: i32) -> OptionChainBuilder<'a, Self> {
        OptionChainBuilder::new(self, symbol, security_type, contract_id)
    }
}

/// Request an underlying's option chain. Reached through
/// [`OptionChainBuilder::subscribe`]; the flat arguments are the builder-fed
/// param-budget exception.
pub(in crate::contracts) fn option_chain(
    client: &Client,
    symbol: &str,
    exchange: Option<&str>,
    security_type: SecurityType,
    contract_id: i32,
) -> Result<Subscription<OptionChain>, Error> {
    request_helpers::blocking::request_with_id(client, Features::SEC_DEF_OPT_PARAMS_REQ, |request_id| {
        encoders::encode_request_option_chain(request_id, symbol, exchange, security_type, contract_id)
    })
}

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