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;