ibapi/market_data/historical/sync.rs
1use std::collections::VecDeque;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::{Arc, Mutex};
4
5use log::error;
6use time::OffsetDateTime;
7
8use crate::client::blocking::ClientRequestBuilders;
9use crate::common::request_helpers::{self, empty_on_end_of_stream};
10use crate::common::retry::blocking::retry_on_connection_reset;
11use crate::contracts::Contract;
12use crate::protocol::{check_version, Features};
13use crate::subscriptions::common::{RoutedItem, SubscriptionItem};
14use crate::subscriptions::log_cancel_error;
15use crate::subscriptions::sync::{FilterData, Subscription, SubscriptionItemIterExt};
16use crate::transport::{InternalSubscription, MessageBus};
17use crate::{client::sync::Client, Error};
18
19use super::common::tick::{classify, TickAction};
20use super::common::{self, encoders};
21use super::{
22 BarSize, Duration, HeadTimestampRequest, HistogramEntry, HistogramRequest, HistoricalBarUpdate, HistoricalData, HistoricalDataFrame, Schedule,
23 ScheduleRequest, TickDecoder, WhatToShow,
24};
25use crate::market_data::TradingHours;
26
27impl Client {
28 /// Returns the timestamp of earliest available historical data for a contract and data type.
29 ///
30 /// # Examples
31 ///
32 /// ```no_run
33 /// use ibapi::client::blocking::Client;
34 /// use ibapi::contracts::Contract;
35 /// use ibapi::market_data::historical::{self, WhatToShow};
36 /// use ibapi::market_data::TradingHours;
37 ///
38 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
39 ///
40 /// let contract = Contract::stock("MSFT").build();
41 /// let what_to_show = WhatToShow::Trades;
42 /// let trading_hours = TradingHours::Regular;
43 ///
44 /// let result = client.head_timestamp(&contract, what_to_show, trading_hours).expect("head timestamp failed");
45 ///
46 /// print!("head_timestamp: {result:?}");
47 /// ```
48 pub fn head_timestamp(&self, contract: &Contract, what_to_show: WhatToShow, trading_hours: TradingHours) -> Result<OffsetDateTime, Error> {
49 request_helpers::blocking::one_shot_cancelling::<HeadTimestampRequest>(self, |request_id| {
50 encoders::encode_request_head_timestamp(request_id, contract, what_to_show, trading_hours.use_rth())
51 })
52 }
53
54 /// Build a request for historical bar data.
55 ///
56 /// Required: a date spec via either [`HistoricalDataBuilder::duration`](super::HistoricalDataBuilder::duration)
57 /// (with optional [`HistoricalDataBuilder::ending`](super::HistoricalDataBuilder::ending)) or
58 /// [`HistoricalDataBuilder::between`](super::HistoricalDataBuilder::between). Terminals:
59 /// [`HistoricalDataBuilder::fetch`](super::HistoricalDataBuilder::fetch) for a one-shot
60 /// [`HistoricalData`] result; [`HistoricalDataBuilder::stream`](super::HistoricalDataBuilder::stream)
61 /// for a `Subscription<HistoricalBarUpdate>` that yields bars as they arrive.
62 ///
63 /// # Arguments
64 /// * `contract` - Contract object that is subject of query
65 /// * `bar_size` - Bar size (resolution)
66 ///
67 /// # Examples
68 ///
69 /// ```no_run
70 /// use ibapi::client::blocking::Client;
71 /// use ibapi::contracts::Contract;
72 /// use ibapi::market_data::historical::{BarSize, ToDuration, WhatToShow};
73 /// use time::macros::datetime;
74 ///
75 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
76 /// let contract = Contract::stock("AAPL").build();
77 ///
78 /// // IBKR-native: amount of data ending at a specific time (or now if `.ending` is unset)
79 /// let bars = client
80 /// .historical_data(&contract, BarSize::Hour)
81 /// .what_to_show(WhatToShow::Trades)
82 /// .duration(7.days())
83 /// .fetch()
84 /// .expect("historical data request failed");
85 ///
86 /// // Convenience: explicit date range; bars outside it are dropped
87 /// let bars = client
88 /// .historical_data(&contract, BarSize::Hour)
89 /// .between(datetime!(2023-04-08 0:00 UTC), datetime!(2023-04-15 0:00 UTC))
90 /// .fetch()
91 /// .expect("historical data request failed");
92 /// # let _ = bars;
93 /// ```
94 pub fn historical_data<'a>(&'a self, contract: &'a Contract, bar_size: BarSize) -> super::HistoricalDataBuilder<'a, Self> {
95 super::HistoricalDataBuilder::new(self, contract, bar_size)
96 }
97
98 /// Build a request for [`Schedule`] data over the given duration.
99 ///
100 /// Defaults to anchoring at the current time. Use [`HistoricalScheduleBuilder::ending`](super::HistoricalScheduleBuilder::ending)
101 /// to anchor at a specific end date.
102 ///
103 /// # Arguments
104 /// * `contract` - [Contract] to retrieve [Schedule] for.
105 /// * `duration` - [Duration] of the interval to retrieve.
106 ///
107 /// # Examples
108 ///
109 /// ```no_run
110 /// use time::macros::datetime;
111 /// use ibapi::client::blocking::Client;
112 /// use ibapi::contracts::Contract;
113 /// use ibapi::market_data::historical::ToDuration;
114 ///
115 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
116 /// let contract = Contract::stock("GM").build();
117 ///
118 /// // Ending now:
119 /// let schedule = client
120 /// .historical_schedules(&contract, 30.days())
121 /// .fetch()
122 /// .expect("historical schedule request failed");
123 ///
124 /// // Anchored to a specific end date:
125 /// let schedule = client
126 /// .historical_schedules(&contract, 30.days())
127 /// .ending(datetime!(2023-04-15 0:00 UTC))
128 /// .fetch()
129 /// .expect("historical schedule request failed");
130 ///
131 /// for session in &schedule.sessions {
132 /// println!("{session:?}");
133 /// }
134 /// ```
135 pub fn historical_schedules<'a>(&'a self, contract: &'a Contract, duration: Duration) -> super::HistoricalScheduleBuilder<'a, Self> {
136 super::HistoricalScheduleBuilder::new(self, contract, duration)
137 }
138
139 /// Build a request for historical time & sales data (tick-by-tick).
140 ///
141 /// The terminal method selects the tick type:
142 /// [`HistoricalTicksBuilder::trade`](super::HistoricalTicksBuilder::trade) /
143 /// `.mid_point()` / `.bid_ask(IgnoreSize)`. Use
144 /// [`HistoricalTicksBuilder::starting`](super::HistoricalTicksBuilder::starting) /
145 /// `.ending()` to anchor the query (at least one is required per IBKR).
146 ///
147 /// # Arguments
148 /// * `contract` - [Contract] object that is subject of query
149 /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
150 ///
151 /// # Examples
152 ///
153 /// ```no_run
154 /// use ibapi::client::blocking::Client;
155 /// use ibapi::contracts::Contract;
156 /// use ibapi::market_data::IgnoreSize;
157 /// use ibapi::market_data::TradingHours;
158 /// use time::macros::datetime;
159 ///
160 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
161 /// let contract = Contract::stock("TSLA").build();
162 ///
163 /// // Trade ticks anchored at a start date:
164 /// let trades = client
165 /// .historical_ticks(&contract, 100)
166 /// .starting(datetime!(2023-04-15 0:00 UTC))
167 /// .trading_hours(TradingHours::Regular)
168 /// .trade()
169 /// .expect("historical ticks request failed");
170 ///
171 /// // Bid/ask ticks anchored at an end date, ignoring tick sizes:
172 /// let quotes = client
173 /// .historical_ticks(&contract, 100)
174 /// .ending(datetime!(2023-04-15 0:00 UTC))
175 /// .bid_ask(IgnoreSize::Yes)
176 /// .expect("historical ticks request failed");
177 ///
178 /// for tick in trades {
179 /// println!("{tick:?}");
180 /// }
181 /// # let _ = quotes;
182 /// ```
183 pub fn historical_ticks<'a>(&'a self, contract: &'a Contract, number_of_ticks: i32) -> super::HistoricalTicksBuilder<'a, Self> {
184 super::HistoricalTicksBuilder::new(self, contract, number_of_ticks)
185 }
186
187 /// Cancels an in-flight historical ticks request.
188 ///
189 /// # Arguments
190 /// * `request_id` - The request ID of the historical ticks subscription to cancel.
191 /// # Examples
192 ///
193 /// ```no_run
194 /// use ibapi::client::blocking::Client;
195 /// use ibapi::contracts::Contract;
196 ///
197 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
198 ///
199 /// let contract = Contract::stock("MSFT").build();
200 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
201 ///
202 /// // Holding the subscription, `subscription.cancel()` (or dropping it) does the same.
203 /// client.cancel_historical_ticks(subscription.request_id()).expect("cancel failed");
204 /// ```
205 pub fn cancel_historical_ticks(&self, request_id: i32) -> Result<(), Error> {
206 check_version(self.server_version(), Features::CANCEL_CONTRACT_DATA)?;
207
208 let message = encoders::encode_cancel_historical_ticks(request_id)?;
209 self.send_message(message)?;
210 Ok(())
211 }
212
213 /// Requests data histogram of specified contract.
214 ///
215 /// # Arguments
216 /// * `contract` - [Contract] to retrieve [Histogram Entries](HistogramEntry) for.
217 /// * `trading_hours` - Regular trading hours only, or include extended hours.
218 /// * `period` - The time period of each histogram bar (e.g., `BarSize::Day`, `BarSize::Week`, `BarSize::Month`).
219 ///
220 /// # Examples
221 ///
222 /// ```no_run
223 /// use time::macros::datetime;
224 ///
225 /// use ibapi::contracts::Contract;
226 /// use ibapi::client::blocking::Client;
227 /// use ibapi::market_data::historical::BarSize;
228 /// use ibapi::market_data::TradingHours;
229 ///
230 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
231 ///
232 /// let contract = Contract::stock("GM").build();
233 ///
234 /// let histogram = client
235 /// .histogram_data(&contract, TradingHours::Regular, BarSize::Week)
236 /// .expect("histogram request failed");
237 ///
238 /// for item in &histogram {
239 /// println!("{item:?}");
240 /// }
241 /// ```
242 pub fn histogram_data(&self, contract: &Contract, trading_hours: TradingHours, period: BarSize) -> Result<Vec<HistogramEntry>, Error> {
243 request_helpers::blocking::one_shot_cancelling::<HistogramRequest>(self, |request_id| {
244 encoders::encode_request_histogram_data(request_id, contract, trading_hours.use_rth(), period)
245 })
246 .or_else(empty_on_end_of_stream)
247 }
248}
249
250pub(crate) fn historical_data(
251 client: &Client,
252 contract: &Contract,
253 end_date: Option<OffsetDateTime>,
254 duration: Duration,
255 bar_size: BarSize,
256 what_to_show: WhatToShow,
257 trading_hours: TradingHours,
258) -> Result<HistoricalData, Error> {
259 common::validate_historical_data(end_date, what_to_show)?;
260 // One deadline for both reads and every resend.
261 let deadline = request_helpers::blocking::one_shot_deadline(client);
262
263 retry_on_connection_reset(client, || {
264 request_helpers::blocking::check_deadline(deadline)?;
265 let builder = client.request();
266 let request = encoders::encode_request_historical_data(
267 builder.request_id(),
268 contract,
269 end_date,
270 duration,
271 bar_size,
272 what_to_show,
273 trading_hours.use_rth(),
274 false,
275 )?;
276
277 // The window frame is a second read, which is why this is not a
278 // one-shot and cannot use the helpers in `common::request_helpers`.
279 // Dropping the subscription before it, on any error below, sends
280 // TWS's cancel; after it, nothing is sent.
281 let subscription = builder.send::<HistoricalDataFrame>(request)?;
282
283 let mut data = request_helpers::blocking::next_frame(&subscription, deadline)?.into_bars()?;
284 // Only the window frame proves the download is complete, so a close
285 // before it is an error.
286 (data.start, data.end) = request_helpers::blocking::next_frame(&subscription, deadline)?.into_window()?;
287
288 Ok(data)
289 })
290}
291
292pub(crate) fn historical_data_stream(
293 client: &Client,
294 contract: &Contract,
295 duration: Duration,
296 bar_size: BarSize,
297 what_to_show: WhatToShow,
298 trading_hours: TradingHours,
299 buffer_limit: Option<usize>,
300) -> Result<Subscription<HistoricalBarUpdate>, Error> {
301 let builder = client.request();
302 let request = encoders::encode_request_historical_data(
303 builder.request_id(),
304 contract,
305 None, // IBKR requires end_date=None when keep_up_to_date=true
306 duration,
307 bar_size,
308 what_to_show,
309 trading_hours.use_rth(),
310 true, // keep_up_to_date — the whole point of .stream()
311 )?;
312
313 builder.send_capped::<HistoricalBarUpdate>(request, buffer_limit)
314}
315
316// pub(crate) internal plumbing called from `HistoricalTicksBuilder`; the
317// public API is already a builder, so flat args here are the deliberate
318// seam between the typed builder and the wire encoder. Internal, so the param
319// budget doesn't apply; the allow is for clippy's 8-arg lint.
320#[allow(clippy::too_many_arguments)]
321pub(crate) fn historical_ticks<T: TickDecoder<T>>(
322 client: &Client,
323 contract: &Contract,
324 start: Option<OffsetDateTime>,
325 end: Option<OffsetDateTime>,
326 number_of_ticks: i32,
327 what_to_show: WhatToShow,
328 trading_hours: TradingHours,
329 ignore_size: bool,
330) -> Result<TickSubscription<T>, Error> {
331 let builder = client.request();
332 let request = encoders::encode_request_historical_ticks(
333 builder.request_id(),
334 contract,
335 start,
336 end,
337 number_of_ticks,
338 what_to_show,
339 trading_hours.use_rth(),
340 ignore_size,
341 )?;
342 let request_id = builder.request_id();
343 let subscription = builder.send_raw(request)?;
344
345 Ok(TickSubscription::new(subscription, request_id, Arc::clone(&client.message_bus)))
346}
347
348pub(crate) fn historical_schedule(
349 client: &Client,
350 contract: &Contract,
351 end_date: Option<OffsetDateTime>,
352 duration: Duration,
353) -> Result<Schedule, Error> {
354 request_helpers::blocking::one_shot_cancelling::<ScheduleRequest>(client, |request_id| {
355 encoders::encode_request_historical_data(request_id, contract, end_date, duration, BarSize::Day, WhatToShow::Schedule, true, false)
356 })
357}
358
359// TickSubscription and related types
360
361/// Shared subscription handle that decodes historical tick batches as they arrive.
362///
363/// Each [`next`](Self::next), [`try_next`](Self::try_next), or
364/// [`next_timeout`](Self::next_timeout) returns
365/// `Option<Result<SubscriptionItem<T>, Error>>`, the same shape as
366/// [`Subscription`](crate::subscriptions::Subscription):
367///
368/// * `None` — the stream has ended.
369/// * `Some(Ok(SubscriptionItem::Data(tick)))` — a decoded tick.
370/// * `Some(Ok(SubscriptionItem::Notice(n)))` — a non-fatal IB notice bound to
371/// this request; the stream stays open.
372/// * `Some(Err(e))` — terminal error (decode or transport); subsequent calls
373/// return `None`.
374///
375/// When you only care about ticks, use [`iter_data`](Self::iter_data) (or
376/// [`next_data`](Self::next_data)), which filter notices and yield
377/// `Result<T, Error>`.
378#[must_use = "TickSubscription must be polled (.next() or .iter_data()) to receive ticks; dropping it cancels the request"]
379pub struct TickSubscription<T: TickDecoder<T>> {
380 done: AtomicBool,
381 stream_ended: AtomicBool,
382 messages: InternalSubscription,
383 buffer: Mutex<VecDeque<T>>,
384 request_id: i32,
385 message_bus: Arc<dyn MessageBus>,
386 cancelled: AtomicBool,
387}
388
389impl<T: TickDecoder<T>> TickSubscription<T> {
390 fn new(messages: InternalSubscription, request_id: i32, message_bus: Arc<dyn MessageBus>) -> Self {
391 Self {
392 done: false.into(),
393 stream_ended: AtomicBool::new(false),
394 messages,
395 buffer: Mutex::new(VecDeque::new()),
396 request_id,
397 message_bus,
398 cancelled: AtomicBool::new(false),
399 }
400 }
401
402 /// The request ID TWS knows this request by, for [`Client::cancel_historical_ticks`](crate::client::blocking::Client::cancel_historical_ticks)
403 /// from code that does not hold the subscription, or to match it in logs.
404 pub fn request_id(&self) -> i32 {
405 self.request_id
406 }
407
408 /// Cancel the historical-ticks request. After completion nothing is written.
409 /// Also fired automatically on `Drop` for unfinished subscriptions; explicit calls are idempotent.
410 ///
411 /// # Examples
412 ///
413 /// ```no_run
414 /// use ibapi::client::blocking::Client;
415 /// use ibapi::contracts::Contract;
416 ///
417 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
418 /// let contract = Contract::stock("MSFT").build();
419 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
420 /// subscription.cancel();
421 /// ```
422 pub fn cancel(&self) {
423 if self.cancelled.swap(true, Ordering::Relaxed) {
424 return;
425 }
426
427 // After the last batch TWS has finished the request: nothing to
428 // write, but the registration still goes.
429 if !self.done.load(Ordering::Relaxed) {
430 match encoders::encode_cancel_historical_ticks(self.request_id) {
431 Ok(message) => {
432 if let Err(e) = self.message_bus.send_message(&message) {
433 log_cancel_error("historical ticks subscription", &e);
434 }
435 }
436 Err(e) => error!("error encoding cancel historical ticks: {e}"),
437 }
438 }
439 self.messages.cancel();
440 }
441
442 /// Blocking iterator yielding `Result<SubscriptionItem<T>, Error>` — both
443 /// `Data` and `Notice` arms surface to the caller. Use
444 /// [`iter_data`](Self::iter_data) when you only want ticks.
445 ///
446 /// # Examples
447 ///
448 /// ```no_run
449 /// use ibapi::client::blocking::Client;
450 /// use ibapi::contracts::Contract;
451 /// use ibapi::subscriptions::SubscriptionItem;
452 ///
453 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
454 /// let contract = Contract::stock("MSFT").build();
455 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
456 /// for item in subscription.iter() {
457 /// match item {
458 /// Ok(SubscriptionItem::Data(tick)) => println!("tick: {tick:?}"),
459 /// Ok(SubscriptionItem::Notice(note)) => eprintln!("notice: {note}"),
460 /// Err(e) => { eprintln!("error: {e}"); break; }
461 /// }
462 /// }
463 /// ```
464 pub fn iter(&self) -> TickSubscriptionIter<'_, T> {
465 TickSubscriptionIter { subscription: self }
466 }
467
468 /// Non-blocking iterator. Same `SubscriptionItem<T>` shape as [`iter`](Self::iter);
469 /// see [`try_iter_data`](Self::try_iter_data) for the data-only variant.
470 ///
471 /// # Examples
472 ///
473 /// ```no_run
474 /// use ibapi::client::blocking::Client;
475 /// use ibapi::contracts::Contract;
476 ///
477 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
478 /// let contract = Contract::stock("MSFT").build();
479 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
480 /// for tick in subscription.try_iter_data() {
481 /// println!("tick: {:?}", tick.expect("decode error"));
482 /// }
483 /// ```
484 pub fn try_iter(&self) -> TickSubscriptionTryIter<'_, T> {
485 TickSubscriptionTryIter { subscription: self }
486 }
487
488 /// Iterator that waits up to `duration` for each item. Same `SubscriptionItem<T>`
489 /// shape as [`iter`](Self::iter); see [`timeout_iter_data`](Self::timeout_iter_data)
490 /// for the data-only variant.
491 ///
492 /// # Examples
493 ///
494 /// ```no_run
495 /// use std::time::Duration;
496 /// use ibapi::client::blocking::Client;
497 /// use ibapi::contracts::Contract;
498 ///
499 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
500 /// let contract = Contract::stock("MSFT").build();
501 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
502 /// for tick in subscription.timeout_iter_data(Duration::from_secs(5)) {
503 /// println!("tick: {:?}", tick.expect("decode error"));
504 /// }
505 /// ```
506 pub fn timeout_iter(&self, duration: std::time::Duration) -> TickSubscriptionTimeoutIter<'_, T> {
507 TickSubscriptionTimeoutIter {
508 subscription: self,
509 timeout: duration,
510 }
511 }
512
513 /// Blocking data iterator that filters notices and yields `Result<T, Error>`.
514 /// Notices are logged at `warn!` level.
515 ///
516 /// # Examples
517 ///
518 /// ```no_run
519 /// use ibapi::client::blocking::Client;
520 /// use ibapi::contracts::Contract;
521 ///
522 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
523 /// let contract = Contract::stock("MSFT").build();
524 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
525 /// for tick in subscription.iter_data() {
526 /// println!("tick: {:?}", tick.expect("decode error"));
527 /// }
528 /// ```
529 pub fn iter_data(&self) -> FilterData<TickSubscriptionIter<'_, T>> {
530 self.iter().filter_data()
531 }
532
533 /// Non-blocking data iterator (notices filtered).
534 pub fn try_iter_data(&self) -> FilterData<TickSubscriptionTryIter<'_, T>> {
535 self.try_iter().filter_data()
536 }
537
538 /// Timeout-bounded data iterator (notices filtered).
539 pub fn timeout_iter_data(&self, duration: std::time::Duration) -> FilterData<TickSubscriptionTimeoutIter<'_, T>> {
540 self.timeout_iter(duration).filter_data()
541 }
542
543 /// Block until the next item is available.
544 ///
545 /// Returns the `SubscriptionItem<T>` envelope; use [`next_data`](Self::next_data)
546 /// when you only want ticks.
547 ///
548 /// # Examples
549 ///
550 /// ```no_run
551 /// use ibapi::client::blocking::Client;
552 /// use ibapi::contracts::Contract;
553 /// use ibapi::subscriptions::SubscriptionItem;
554 ///
555 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
556 /// let contract = Contract::stock("MSFT").build();
557 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
558 /// while let Some(item) = subscription.next() {
559 /// match item {
560 /// Ok(SubscriptionItem::Data(tick)) => println!("tick: {tick:?}"),
561 /// Ok(SubscriptionItem::Notice(note)) => eprintln!("notice: {note}"),
562 /// Err(e) => { eprintln!("error: {e}"); break; }
563 /// }
564 /// }
565 /// ```
566 pub fn next(&self) -> Option<Result<SubscriptionItem<T>, Error>> {
567 self.next_helper(|| self.messages.next_routed())
568 }
569
570 /// Attempt to fetch the next item without blocking.
571 ///
572 /// Same `SubscriptionItem<T>` shape as [`next`](Self::next); returns `None`
573 /// if nothing is queued right now.
574 ///
575 /// # Examples
576 ///
577 /// ```no_run
578 /// use ibapi::client::blocking::Client;
579 /// use ibapi::contracts::Contract;
580 ///
581 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
582 /// let contract = Contract::stock("MSFT").build();
583 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
584 /// if let Some(Ok(item)) = subscription.try_next() {
585 /// println!("item: {item:?}");
586 /// }
587 /// ```
588 pub fn try_next(&self) -> Option<Result<SubscriptionItem<T>, Error>> {
589 self.next_helper(|| self.messages.try_next_routed())
590 }
591
592 /// Wait up to `duration` for the next item to arrive.
593 ///
594 /// Same `SubscriptionItem<T>` shape as [`next`](Self::next). `None` means
595 /// either that `duration` passed or that the stream ended; terminal errors
596 /// come as `Some(Err(_))`.
597 ///
598 /// # Examples
599 ///
600 /// ```no_run
601 /// use std::time::Duration;
602 /// use ibapi::client::blocking::Client;
603 /// use ibapi::contracts::Contract;
604 ///
605 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
606 /// let contract = Contract::stock("MSFT").build();
607 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
608 /// if let Some(Ok(item)) = subscription.next_timeout(Duration::from_secs(5)) {
609 /// println!("item: {item:?}");
610 /// }
611 /// ```
612 pub fn next_timeout(&self, duration: std::time::Duration) -> Option<Result<SubscriptionItem<T>, Error>> {
613 self.next_helper(|| self.messages.next_timeout_routed(duration))
614 }
615
616 /// Convenience: blocking `next` that filters out notices and yields just a tick.
617 /// Equivalent to `iter_data().next()`. Filtered notices are logged at `warn!`.
618 /// Use [`next`](Self::next) instead if you want to observe `Notice` items.
619 ///
620 /// # Examples
621 ///
622 /// ```no_run
623 /// use ibapi::client::blocking::Client;
624 /// use ibapi::contracts::Contract;
625 ///
626 /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
627 /// let contract = Contract::stock("MSFT").build();
628 /// let subscription = client.historical_ticks(&contract, 100).trade().expect("request failed");
629 /// while let Some(tick) = subscription.next_data() {
630 /// println!("tick: {:?}", tick.expect("decode error"));
631 /// }
632 /// ```
633 pub fn next_data(&self) -> Option<Result<T, Error>> {
634 self.iter_data().next()
635 }
636
637 fn next_helper<F>(&self, next_routed: F) -> Option<Result<SubscriptionItem<T>, Error>>
638 where
639 F: Fn() -> Option<RoutedItem>,
640 {
641 loop {
642 if let Some(tick) = self.next_buffered() {
643 return Some(Ok(SubscriptionItem::Data(tick)));
644 }
645
646 // `done` (decoder said this was the last batch) and `stream_ended`
647 // (terminal error/EndOfStream) are distinct: the former is graceful
648 // completion, the latter is termination. Either ends the stream once
649 // the buffer is drained.
650 if self.done.load(Ordering::Relaxed) || self.stream_ended.load(Ordering::Relaxed) {
651 return None;
652 }
653
654 let item = next_routed()?;
655
656 match classify::<T>(item) {
657 TickAction::Batch(ticks, done) => {
658 self.buffer.lock().unwrap().extend(ticks);
659 self.done.store(done, Ordering::Relaxed);
660 }
661 TickAction::Skip => {}
662 TickAction::Notice(notice) => return Some(Ok(SubscriptionItem::Notice(notice))),
663 TickAction::EndOfStream => {
664 self.stream_ended.store(true, Ordering::Relaxed);
665 return None;
666 }
667 TickAction::Error(e) => {
668 self.stream_ended.store(true, Ordering::Relaxed);
669 return Some(Err(e));
670 }
671 }
672 }
673 }
674
675 fn next_buffered(&self) -> Option<T> {
676 let mut buffer = self.buffer.lock().unwrap();
677 buffer.pop_front()
678 }
679}
680
681impl<T: TickDecoder<T>> Drop for TickSubscription<T> {
682 fn drop(&mut self) {
683 self.cancel();
684 }
685}
686
687/// A blocking iterator over `Result<SubscriptionItem<T>, Error>`.
688pub struct TickSubscriptionIter<'a, T: TickDecoder<T>> {
689 subscription: &'a TickSubscription<T>,
690}
691
692impl<T: TickDecoder<T>> Iterator for TickSubscriptionIter<'_, T> {
693 type Item = Result<SubscriptionItem<T>, Error>;
694
695 fn next(&mut self) -> Option<Self::Item> {
696 self.subscription.next()
697 }
698}
699
700impl<'a, T: TickDecoder<T>> IntoIterator for &'a TickSubscription<T> {
701 type Item = Result<SubscriptionItem<T>, Error>;
702 type IntoIter = TickSubscriptionIter<'a, T>;
703
704 fn into_iter(self) -> Self::IntoIter {
705 self.iter()
706 }
707}
708
709/// An owned blocking iterator over `Result<SubscriptionItem<T>, Error>`.
710pub struct TickSubscriptionOwnedIter<T: TickDecoder<T>> {
711 subscription: TickSubscription<T>,
712}
713
714impl<T: TickDecoder<T>> Iterator for TickSubscriptionOwnedIter<T> {
715 type Item = Result<SubscriptionItem<T>, Error>;
716
717 fn next(&mut self) -> Option<Self::Item> {
718 self.subscription.next()
719 }
720}
721
722impl<T: TickDecoder<T>> IntoIterator for TickSubscription<T> {
723 type Item = Result<SubscriptionItem<T>, Error>;
724 type IntoIter = TickSubscriptionOwnedIter<T>;
725
726 fn into_iter(self) -> Self::IntoIter {
727 TickSubscriptionOwnedIter { subscription: self }
728 }
729}
730
731/// A non-blocking iterator over `Result<SubscriptionItem<T>, Error>`.
732pub struct TickSubscriptionTryIter<'a, T: TickDecoder<T>> {
733 subscription: &'a TickSubscription<T>,
734}
735
736impl<T: TickDecoder<T>> Iterator for TickSubscriptionTryIter<'_, T> {
737 type Item = Result<SubscriptionItem<T>, Error>;
738
739 fn next(&mut self) -> Option<Self::Item> {
740 self.subscription.try_next()
741 }
742}
743
744/// An iterator that waits for the specified timeout duration for each item.
745pub struct TickSubscriptionTimeoutIter<'a, T: TickDecoder<T>> {
746 subscription: &'a TickSubscription<T>,
747 timeout: std::time::Duration,
748}
749
750impl<T: TickDecoder<T>> Iterator for TickSubscriptionTimeoutIter<'_, T> {
751 type Item = Result<SubscriptionItem<T>, Error>;
752
753 fn next(&mut self) -> Option<Self::Item> {
754 self.subscription.next_timeout(self.timeout)
755 }
756}
757
758#[cfg(test)]
759#[path = "sync_tests.rs"]
760mod tests;