Skip to main content

ibapi/contracts/
async.rs

1//! Asynchronous implementation of contract management functionality
2
3use super::common::{decoders, encoders, verify};
4use super::*;
5use crate::client::ClientRequestBuilders;
6use crate::common::request_helpers::{self, empty_on_end_of_stream, expect_proto};
7use crate::messages::{IncomingMessages, OutgoingMessages};
8use crate::protocol::{check_version, Features};
9use crate::subscriptions::{StreamDecoder, Subscription};
10use crate::{Client, Error};
11
12impl Client {
13    /// Requests contract information.
14    ///
15    /// Provides all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains.
16    ///
17    /// # Arguments
18    /// * `contract` - The [Contract] used as sample to query the available contracts.
19    ///
20    /// # Examples
21    ///
22    /// ```no_run
23    /// use ibapi::Client;
24    /// use ibapi::contracts::Contract;
25    ///
26    /// #[tokio::main]
27    /// async fn main() {
28    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
29    ///
30    ///     let contract = Contract::stock("AAPL").build();
31    ///     let details = client.contract_details(&contract).await.expect("request failed");
32    ///
33    ///     for detail in details {
34    ///         println!("Contract: {} - Exchange: {}", detail.contract.symbol, detail.contract.exchange);
35    ///     }
36    /// }
37    /// ```
38    pub async fn contract_details(&self, contract: &Contract) -> Result<Vec<ContractDetails>, Error> {
39        verify::verify_contract(self.server_version(), contract)?;
40
41        let builder = self.request();
42        let request_id = builder.request_id();
43        let packet = encoders::encode_request_contract_data(request_id, contract)?;
44
45        let mut responses = builder.send_raw(packet).await?;
46
47        let mut contract_details: Vec<ContractDetails> = Vec::default();
48
49        while let Some(response_result) = responses.next().await {
50            match response_result {
51                Ok(response) => {
52                    log::debug!("response: {response:#?}");
53                    match response.message_type() {
54                        IncomingMessages::ContractData => {
55                            let decoded = decoders::decode_contract_details(&response)?;
56                            contract_details.push(decoded);
57                        }
58                        IncomingMessages::ContractDataEnd => return Ok(contract_details),
59                        _ => return Err(Error::unexpected_response(&response)),
60                    }
61                }
62                Err(e) => return Err(e),
63            }
64        }
65
66        Err(Error::UnexpectedEndOfStream)
67    }
68
69    /// Requests matching stock symbols.
70    ///
71    /// # Arguments
72    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
73    ///
74    /// # Examples
75    ///
76    /// ```no_run
77    /// use ibapi::Client;
78    ///
79    /// #[tokio::main]
80    /// async fn main() {
81    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
82    ///
83    ///     let symbols = client.matching_symbols("AAP").await.expect("request failed");
84    ///     for symbol in symbols {
85    ///         println!("{} - {} ({})", symbol.contract.symbol,
86    ///                  symbol.contract.primary_exchange, symbol.contract.currency);
87    ///     }
88    /// }
89    /// ```
90    pub async fn matching_symbols(&self, pattern: &str) -> Result<Vec<ContractDescription>, Error> {
91        check_version(self.server_version(), Features::REQ_MATCHING_SYMBOLS)?;
92
93        request_helpers::one_shot_by_request_id(
94            self,
95            |request_id| encoders::encode_request_matching_symbols(request_id, pattern),
96            expect_proto(decoders::decode_symbol_samples_proto),
97        )
98        .await
99        .or_else(empty_on_end_of_stream)
100    }
101
102    /// Requests details about a given market rule.
103    ///
104    /// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
105    ///
106    /// # Arguments
107    /// * `market_rule_id` - The market rule ID to query
108    ///
109    /// # Examples
110    ///
111    /// ```no_run
112    /// use ibapi::Client;
113    ///
114    /// #[tokio::main]
115    /// async fn main() {
116    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
117    ///
118    ///     let rule = client.market_rule(26).await.expect("request failed");
119    ///     for increment in rule.price_increments {
120    ///         println!("Above ${}: increment ${}", increment.low_edge, increment.increment);
121    ///     }
122    /// }
123    /// ```
124    pub async fn market_rule(&self, market_rule_id: i32) -> Result<MarketRule, Error> {
125        check_version(self.server_version(), Features::MARKET_RULES)?;
126
127        request_helpers::one_shot_shared(
128            self,
129            OutgoingMessages::RequestMarketRule,
130            || encoders::encode_request_market_rule(market_rule_id),
131            expect_proto(decoders::decode_market_rule_proto),
132        )
133        .await
134    }
135
136    /// Requests the underlying exchanges that contribute to a consolidated (BBO) feed.
137    ///
138    /// Given a BBO exchange code (an opaque per-session token, e.g. `"a6"`),
139    /// returns the list of underlying exchanges with each entry's bit
140    /// position, full exchange name, and single-letter abbreviation. Useful
141    /// for decoding the `mdSize` / `mdMask` bitmaps on tick-by-tick and
142    /// market-depth streams. The token is typically obtained from the
143    /// `LAST_EXCHANGE` market-data tick (tick type 84).
144    ///
145    /// # Arguments
146    /// * `bbo_exchange` - The BBO exchange token (e.g. `"a6"`).
147    ///
148    /// # Examples
149    ///
150    /// ```no_run
151    /// use ibapi::Client;
152    ///
153    /// #[tokio::main]
154    /// async fn main() {
155    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
156    ///
157    ///     let components = client.smart_components("a6").await.expect("request failed");
158    ///     for component in &components {
159    ///         println!("bit {}: {} ({})", component.bit_number, component.exchange, component.exchange_letter);
160    ///     }
161    /// }
162    /// ```
163    pub async fn smart_components(&self, bbo_exchange: &str) -> Result<Vec<SmartComponent>, Error> {
164        check_version(self.server_version(), Features::SMART_COMPONENTS)?;
165
166        request_helpers::one_shot_by_request_id(
167            self,
168            |request_id| encoders::encode_request_smart_components(request_id, bbo_exchange),
169            expect_proto(decoders::decode_smart_components_proto),
170        )
171        .await
172    }
173
174    /// Calculates an option's price based on the provided volatility and its underlying's price.
175    ///
176    /// # Arguments
177    /// * `contract`   - The [Contract] object for which the depth is being requested.
178    /// * `volatility` - Hypothetical volatility.
179    /// * `underlying_price` - Hypothetical option's underlying price.
180    ///
181    /// # Examples
182    ///
183    /// ```no_run
184    /// use ibapi::prelude::*;
185    ///
186    /// #[tokio::main]
187    /// async fn main() {
188    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
189    ///     let contract = Contract::option("AAPL", "20251219", 150.0, OptionRight::Call);
190    ///     let calculation = client
191    ///         .calculate_option_price(&contract, 100.0, 235.0)
192    ///         .await
193    ///         .expect("request failed");
194    ///     println!("calculation: {calculation:?}");
195    /// }
196    /// ```
197    pub async fn calculate_option_price(&self, contract: &Contract, volatility: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
198        check_version(self.server_version(), Features::REQ_CALC_OPTION_PRICE)?;
199
200        request_helpers::one_shot_by_request_id(
201            self,
202            |request_id| encoders::encode_calculate_option_price(request_id, contract, volatility, underlying_price),
203            |message| OptionComputation::decode(&self.decoder_context(), message),
204        )
205        .await
206    }
207
208    /// Calculates the implied volatility based on hypothetical option and its underlying prices.
209    ///
210    /// # Arguments
211    /// * `contract`   - The [Contract] object for which the depth is being requested.
212    /// * `option_price` - Hypothetical option price.
213    /// * `underlying_price` - Hypothetical option's underlying price.
214    ///
215    /// # Examples
216    ///
217    /// ```no_run
218    /// use ibapi::prelude::*;
219    ///
220    /// #[tokio::main]
221    /// async fn main() {
222    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
223    ///     let contract = Contract::option("AAPL", "20230519", 150.0, OptionRight::Call);
224    ///     let calculation = client
225    ///         .calculate_implied_volatility(&contract, 25.0, 235.0)
226    ///         .await
227    ///         .expect("request failed");
228    ///     println!("calculation: {calculation:?}");
229    /// }
230    /// ```
231    pub async fn calculate_implied_volatility(
232        &self,
233        contract: &Contract,
234        option_price: f64,
235        underlying_price: f64,
236    ) -> Result<OptionComputation, Error> {
237        check_version(self.server_version(), Features::REQ_CALC_IMPLIED_VOLAT)?;
238
239        request_helpers::one_shot_by_request_id(
240            self,
241            |request_id| encoders::encode_calculate_implied_volatility(request_id, contract, option_price, underlying_price),
242            |message| OptionComputation::decode(&self.decoder_context(), message),
243        )
244        .await
245    }
246
247    /// Cancels an in-flight contract details request.
248    ///
249    /// # Examples
250    ///
251    /// ```no_run
252    /// use ibapi::prelude::*;
253    ///
254    /// #[tokio::main]
255    /// async fn main() {
256    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
257    ///     // `request_id` is the id used to launch the original contract_details request.
258    ///     client.cancel_contract_details(42).await.expect("cancel failed");
259    /// }
260    /// ```
261    pub async fn cancel_contract_details(&self, request_id: i32) -> Result<(), Error> {
262        check_version(self.server_version(), Features::CANCEL_CONTRACT_DATA)?;
263
264        let message = encoders::encode_cancel_contract_data(request_id)?;
265        self.send_message(message).await?;
266        Ok(())
267    }
268
269    /// Build a request for an underlying's option chain: one [`OptionChain`] per
270    /// exchange the options trade on.
271    ///
272    /// Terminal: [`OptionChainBuilder::subscribe`]. Optional narrowing via [`OptionChainBuilder::exchange`].
273    ///
274    /// # Arguments
275    /// * `symbol` - Symbol of the underlying.
276    /// * `security_type` - Security type of the underlying, e.g. `SecurityType::Stock`.
277    /// * `contract_id` - Contract id of the underlying. Required; TWS rejects `0` with
278    ///   code 321 "Invalid contract id".
279    ///
280    /// # Examples
281    ///
282    /// ```no_run
283    /// use ibapi::prelude::*;
284    ///
285    /// #[tokio::main]
286    /// async fn main() {
287    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
288    ///
289    ///     let subscription = client
290    ///         .option_chain("AAPL", SecurityType::Stock, 265598)
291    ///         .subscribe()
292    ///         .await
293    ///         .expect("request option chain failed");
294    ///
295    ///     let mut chains = subscription.filter_data();
296    ///     while let Some(chain) = chains.next().await {
297    ///         let chain = chain.expect("decode error");
298    ///         println!("{}: {} expirations, {} strikes", chain.exchange, chain.expirations.len(), chain.strikes.len());
299    ///     }
300    /// }
301    /// ```
302    pub fn option_chain<'a>(&'a self, symbol: &'a str, security_type: SecurityType, contract_id: i32) -> OptionChainBuilder<'a, Self> {
303        OptionChainBuilder::new(self, symbol, security_type, contract_id)
304    }
305}
306
307/// Request an underlying's option chain. Reached through
308/// [`OptionChainBuilder::subscribe`]; the flat arguments are the builder-fed
309/// param-budget exception.
310pub(in crate::contracts) async fn option_chain(
311    client: &Client,
312    symbol: &str,
313    exchange: Option<&str>,
314    security_type: SecurityType,
315    contract_id: i32,
316) -> Result<Subscription<OptionChain>, Error> {
317    request_helpers::request_with_id(client, Features::SEC_DEF_OPT_PARAMS_REQ, |request_id| {
318        encoders::encode_request_option_chain(request_id, symbol, exchange, security_type, contract_id)
319    })
320    .await
321}
322
323#[cfg(test)]
324#[path = "async_tests.rs"]
325mod tests;