Skip to main content

ibapi/market_data/realtime/builder/
market_data.rs

1use std::time::Duration;
2
3use crate::contracts::Contract;
4use crate::market_data::realtime::TickTypes;
5use crate::Error;
6
7#[cfg(test)]
8#[path = "market_data_tests.rs"]
9mod tests;
10
11/// Builder for creating market data subscriptions with a fluent interface.
12///
13/// Defaults: no generic ticks, streaming (not snapshot), no regulatory
14/// snapshot. Terminals: `.subscribe()`, or `.snapshot_once(timeout)` for a
15/// collected one-shot snapshot.
16#[must_use = "MarketDataBuilder does nothing until you call .subscribe()"]
17pub struct MarketDataBuilder<'a, C> {
18    client: &'a C,
19    contract: &'a Contract,
20    generic_ticks: Vec<String>,
21    snapshot: bool,
22    regulatory_snapshot: bool,
23}
24
25impl<'a, C> MarketDataBuilder<'a, C> {
26    pub(crate) fn new(client: &'a C, contract: &'a Contract) -> Self {
27        Self {
28            client,
29            contract,
30            generic_ticks: Vec::new(),
31            snapshot: false,
32            regulatory_snapshot: false,
33        }
34    }
35
36    /// Replace the generic tick list to subscribe to
37    ///
38    /// Each value is a numeric IB *generic tick request ID* (the
39    /// `genericTickList` parameter on `reqMktData`). To add ticks one at a
40    /// time, use [`Self::add_generic_tick`] instead.
41    ///
42    /// # Arguments
43    /// * `ticks` - Slice of generic tick request IDs. Prefer the named
44    ///   constants in
45    ///   [`crate::market_data::realtime::generic_tick`] over raw numeric
46    ///   strings.
47    ///
48    /// # Examples
49    ///
50    /// ```no_run
51    /// # #[cfg(feature = "sync")]
52    /// # {
53    /// use ibapi::client::blocking::Client;
54    /// use ibapi::contracts::Contract;
55    /// use ibapi::market_data::realtime::generic_tick;
56    ///
57    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
58    /// let contract = Contract::stock("AAPL").build();
59    ///
60    /// let subscription = client
61    ///     .market_data(&contract)
62    ///     .generic_ticks(&[generic_tick::RT_VOLUME, generic_tick::SHORTABLE])
63    ///     .subscribe()
64    ///     .expect("subscription failed");
65    /// # let _ = subscription;
66    /// # }
67    /// ```
68    ///
69    /// See: <https://interactivebrokers.github.io/tws-api/tick_types.html>
70    pub fn generic_ticks(mut self, ticks: &[&str]) -> Self {
71        self.generic_ticks = ticks.iter().map(|s| s.to_string()).collect();
72        self
73    }
74
75    /// Append a single generic tick ID to the subscription
76    ///
77    /// Multiple calls accumulate; use [`Self::generic_ticks`] to replace the
78    /// list in one shot. Pairs naturally with conditional composition (e.g.
79    /// only add [`generic_tick::SHORTABLE`] for stocks). Prefer the named
80    /// constants over raw numeric strings.
81    ///
82    /// See [`Self::subscribe`] for a runnable end-to-end example.
83    ///
84    /// [`generic_tick::SHORTABLE`]: crate::market_data::realtime::generic_tick::SHORTABLE
85    pub fn add_generic_tick(mut self, tick: impl AsRef<str>) -> Self {
86        self.generic_ticks.push(tick.as_ref().to_string());
87        self
88    }
89
90    /// Request a one-time snapshot of market data
91    ///
92    /// When enabled, the subscription will receive current market data once
93    /// and then automatically end with a SnapshotEnd tick type.
94    pub fn snapshot(mut self) -> Self {
95        self.snapshot = true;
96        self
97    }
98
99    /// Request regulatory snapshot
100    ///
101    /// For U.S. stocks, a regulatory snapshot request requires the
102    /// subscription of Market Data for US Securities and Futures Snapshot Bundle.
103    pub fn regulatory_snapshot(mut self) -> Self {
104        self.regulatory_snapshot = true;
105        self
106    }
107
108    /// Enable real-time streaming data (default)
109    ///
110    /// This is the default behavior - data will stream continuously
111    /// until the subscription is cancelled.
112    pub fn streaming(mut self) -> Self {
113        self.snapshot = false;
114        self
115    }
116
117    fn generic_tick_refs(&self) -> Vec<&str> {
118        self.generic_ticks.iter().map(|s| s.as_str()).collect()
119    }
120}
121
122#[cfg(feature = "sync")]
123impl<'a> MarketDataBuilder<'a, crate::client::sync::Client> {
124    /// Subscribe to market data
125    ///
126    /// Returns a subscription that yields TickTypes as market data arrives.
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// use ibapi::client::blocking::Client;
132    /// use ibapi::contracts::Contract;
133    /// use ibapi::market_data::realtime::{generic_tick, TickTypes};
134    ///
135    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
136    /// let contract = Contract::stock("AAPL").build();
137    ///
138    /// let subscription = client.market_data(&contract)
139    ///     .add_generic_tick(generic_tick::RT_VOLUME)
140    ///     .add_generic_tick(generic_tick::SHORTABLE)
141    ///     .subscribe()
142    ///     .expect("subscription failed");
143    ///
144    /// for tick in &subscription {
145    ///     println!("{tick:?}");
146    /// }
147    /// ```
148    pub fn subscribe(self) -> Result<crate::subscriptions::sync::Subscription<TickTypes>, Error> {
149        let generic_ticks = self.generic_tick_refs();
150
151        crate::market_data::realtime::sync::market_data(self.client, self.contract, &generic_ticks, self.snapshot, self.regulatory_snapshot)
152    }
153
154    /// Request a one-shot snapshot and collect the resulting ticks.
155    ///
156    /// Forces [`snapshot`](Self::snapshot) mode, subscribes, and collects every
157    /// tick until the snapshot completes (or `timeout` elapses), returning the
158    /// accumulated [`TickTypes`] for the caller to map into a domain struct.
159    /// This replaces the hand-written collect-with-timeout loop. See
160    /// [`Subscription::collect_for`](crate::subscriptions::sync::Subscription::collect_for)
161    /// for the underlying terminal and its stop conditions.
162    ///
163    /// # Examples
164    ///
165    /// ```no_run
166    /// use ibapi::client::blocking::Client;
167    /// use ibapi::contracts::Contract;
168    /// use std::time::Duration;
169    ///
170    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
171    /// let contract = Contract::stock("AAPL").build();
172    ///
173    /// let ticks = client.market_data(&contract).snapshot_once(Duration::from_secs(5)).expect("snapshot failed");
174    /// println!("collected {} ticks", ticks.len());
175    /// ```
176    pub fn snapshot_once(mut self, timeout: Duration) -> Result<Vec<TickTypes>, Error> {
177        self.snapshot = true;
178        let subscription = self.subscribe()?;
179        Ok(subscription.collect_for(timeout))
180    }
181}
182
183#[cfg(feature = "async")]
184impl<'a> MarketDataBuilder<'a, crate::client::r#async::Client> {
185    /// Subscribe to market data
186    ///
187    /// Returns a subscription that yields TickTypes as market data arrives.
188    ///
189    /// # Examples
190    ///
191    /// ```no_run
192    /// use ibapi::market_data::realtime::generic_tick;
193    /// use ibapi::prelude::*;
194    ///
195    /// #[tokio::main]
196    /// async fn main() {
197    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
198    ///     let contract = Contract::stock("AAPL").build();
199    ///
200    ///     let mut subscription = client.market_data(&contract)
201    ///         .add_generic_tick(generic_tick::RT_VOLUME)
202    ///         .add_generic_tick(generic_tick::SHORTABLE)
203    ///         .subscribe()
204    ///         .await
205    ///         .expect("subscription failed");
206    ///
207    ///     while let Some(tick) = subscription.next().await {
208    ///         println!("{tick:?}");
209    ///     }
210    /// }
211    /// ```
212    pub async fn subscribe(self) -> Result<crate::subscriptions::Subscription<TickTypes>, Error> {
213        let generic_ticks = self.generic_tick_refs();
214
215        crate::market_data::realtime::r#async::market_data(self.client, self.contract, &generic_ticks, self.snapshot, self.regulatory_snapshot).await
216    }
217
218    /// Request a one-shot snapshot and collect the resulting ticks.
219    ///
220    /// Forces [`snapshot`](Self::snapshot) mode, subscribes, and collects every
221    /// tick until the snapshot completes (or `timeout` elapses), returning the
222    /// accumulated [`TickTypes`] for the caller to map into a domain struct.
223    /// This replaces the hand-written collect-with-timeout loop. See
224    /// [`Subscription::collect_for`](crate::subscriptions::Subscription::collect_for)
225    /// for the underlying terminal and its stop conditions.
226    ///
227    /// # Examples
228    ///
229    /// ```no_run
230    /// use ibapi::prelude::*;
231    /// use std::time::Duration;
232    ///
233    /// #[tokio::main]
234    /// async fn main() {
235    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
236    ///     let contract = Contract::stock("AAPL").build();
237    ///
238    ///     let ticks = client.market_data(&contract).snapshot_once(Duration::from_secs(5)).await.expect("snapshot failed");
239    ///     println!("collected {} ticks", ticks.len());
240    /// }
241    /// ```
242    pub async fn snapshot_once(mut self, timeout: Duration) -> Result<Vec<TickTypes>, Error> {
243        self.snapshot = true;
244        let mut subscription = self.subscribe().await?;
245        Ok(subscription.collect_for(timeout).await)
246    }
247}