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}