Skip to main content

ibapi/client/
async.rs

1//! Asynchronous client implementation
2
3use std::sync::Arc;
4use std::time::Duration;
5
6use log::debug;
7use time::OffsetDateTime;
8use time_tz::Tz;
9
10use crate::connection::common::{ConnectionOptions, StartupMessageCallback};
11use crate::connection::{r#async::AsyncConnection, ConnectionMetadata};
12use crate::messages::{OutgoingMessages, RequestMessage};
13use crate::transport::{
14    r#async::{AsyncInternalSubscription, AsyncTcpMessageBus},
15    AsyncMessageBus,
16};
17use crate::Error;
18
19use super::id_generator::ClientIdManager;
20use crate::accounts;
21use crate::accounts::types::{AccountGroup, AccountId, ContractId, ModelCode};
22use crate::accounts::{AccountSummaryResult, AccountUpdate, AccountUpdateMulti, FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti};
23use crate::contracts::Contract;
24use crate::display_groups;
25use crate::market_data::builder::MarketDataBuilder;
26use crate::market_data::TradingHours;
27use crate::orders::OrderBuilder;
28use crate::subscriptions::Subscription;
29
30/// Asynchronous TWS API Client
31pub struct Client {
32    /// IB server version
33    pub(crate) server_version: i32,
34    pub(crate) connection_time: Option<OffsetDateTime>,
35    pub(crate) time_zone: Option<&'static Tz>,
36    pub(crate) message_bus: Arc<dyn AsyncMessageBus>,
37
38    client_id: i32,                   // ID of client.
39    id_manager: Arc<ClientIdManager>, // Manages request and order ID generation
40}
41
42impl Drop for Client {
43    fn drop(&mut self) {
44        debug!("dropping async client");
45        // Request shutdown of the message bus synchronously
46        self.message_bus.request_shutdown_sync();
47    }
48}
49
50impl Client {
51    /// Establishes async connection to TWS or Gateway
52    ///
53    /// Connects to server using the given connection string
54    ///
55    /// # Arguments
56    /// * `address`   - address of server. e.g. 127.0.0.1:4002
57    /// * `client_id` - id of client. e.g. 100
58    ///
59    /// # Examples
60    ///
61    /// ```no_run
62    /// use ibapi::Client;
63    ///
64    /// #[tokio::main]
65    /// async fn main() {
66    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
67    ///
68    ///     println!("server_version: {}", client.server_version());
69    ///     println!("connection_time: {:?}", client.connection_time());
70    ///     println!("next_order_id: {}", client.next_order_id());
71    /// }
72    /// ```
73    pub async fn connect(address: &str, client_id: i32) -> Result<Client, Error> {
74        Self::connect_with_callback(address, client_id, None).await
75    }
76
77    /// Establishes async connection to TWS or Gateway with a callback for startup messages
78    ///
79    /// This is similar to [`connect`](Self::connect), but allows you to provide a callback
80    /// that will be invoked for any unsolicited messages received during the connection
81    /// handshake (e.g., OpenOrder, OrderStatus).
82    ///
83    /// Note: The callback is only invoked during the initial connection, not during
84    /// automatic reconnections.
85    ///
86    /// # Arguments
87    /// * `address`          - address of server. e.g. 127.0.0.1:4002
88    /// * `client_id`        - id of client. e.g. 100
89    /// * `startup_callback` - optional callback for unsolicited messages during connection
90    ///
91    /// # Examples
92    ///
93    /// ```no_run
94    /// use ibapi::{Client, StartupMessageCallback};
95    /// use ibapi::messages::IncomingMessages;
96    /// use std::sync::{Arc, Mutex};
97    ///
98    /// #[tokio::main]
99    /// async fn main() {
100    ///     let orders = Arc::new(Mutex::new(Vec::new()));
101    ///     let orders_clone = orders.clone();
102    ///
103    ///     let callback: StartupMessageCallback = Box::new(move |msg| {
104    ///         match msg.message_type() {
105    ///             IncomingMessages::OpenOrder | IncomingMessages::OrderStatus => {
106    ///                 orders_clone.lock().unwrap().push(msg);
107    ///             }
108    ///             _ => {}
109    ///         }
110    ///     });
111    ///
112    ///     let client = Client::connect_with_callback("127.0.0.1:4002", 100, Some(callback))
113    ///         .await
114    ///         .expect("connection failed");
115    ///
116    ///     println!("Received {} startup orders", orders.lock().unwrap().len());
117    /// }
118    /// ```
119    pub async fn connect_with_callback(address: &str, client_id: i32, startup_callback: Option<StartupMessageCallback>) -> Result<Client, Error> {
120        Self::connect_with_options(address, client_id, startup_callback.into()).await
121    }
122
123    /// Establishes async connection to TWS or Gateway with custom options
124    ///
125    /// This is similar to [`connect`](Self::connect), but allows you to configure
126    /// connection options like `TCP_NODELAY` and startup callbacks via
127    /// [`ConnectionOptions`].
128    ///
129    /// # Arguments
130    /// * `address`   - address of server. e.g. 127.0.0.1:4002
131    /// * `client_id` - id of client. e.g. 100
132    /// * `options`   - connection options
133    ///
134    /// # Examples
135    ///
136    /// ```no_run
137    /// use ibapi::{Client, ConnectionOptions};
138    ///
139    /// #[tokio::main]
140    /// async fn main() {
141    ///     let options = ConnectionOptions::default()
142    ///         .tcp_no_delay(true);
143    ///
144    ///     let client = Client::connect_with_options("127.0.0.1:4002", 100, options)
145    ///         .await
146    ///         .expect("connection failed");
147    /// }
148    /// ```
149    pub async fn connect_with_options(address: &str, client_id: i32, options: ConnectionOptions) -> Result<Client, Error> {
150        let connection = AsyncConnection::connect_with_options(address, client_id, options).await?;
151        let connection_metadata = connection.connection_metadata().await;
152
153        let message_bus = Arc::new(AsyncTcpMessageBus::new(connection)?);
154
155        // Start background task to read messages from TWS
156        message_bus
157            .clone()
158            .process_messages(connection_metadata.server_version, Duration::from_secs(1))?;
159
160        Client::new(connection_metadata, message_bus)
161    }
162
163    fn new(connection_metadata: ConnectionMetadata, message_bus: Arc<dyn AsyncMessageBus>) -> Result<Client, Error> {
164        let client = Client {
165            server_version: connection_metadata.server_version,
166            connection_time: connection_metadata.connection_time,
167            time_zone: connection_metadata.time_zone,
168            message_bus,
169            client_id: connection_metadata.client_id,
170            id_manager: Arc::new(ClientIdManager::new(connection_metadata.next_order_id)),
171        };
172
173        Ok(client)
174    }
175
176    /// Returns the server version
177    pub fn server_version(&self) -> i32 {
178        self.server_version
179    }
180
181    /// Returns the connection time
182    pub fn connection_time(&self) -> Option<OffsetDateTime> {
183        self.connection_time
184    }
185
186    /// Returns the server's time zone
187    pub fn time_zone(&self) -> Option<&'static Tz> {
188        self.time_zone
189    }
190
191    /// Returns a decoder context for this client
192    pub(crate) fn decoder_context(&self) -> crate::subscriptions::DecoderContext {
193        crate::subscriptions::DecoderContext::new(self.server_version, self.time_zone)
194    }
195
196    /// Returns true if the client is currently connected to TWS/IB Gateway.
197    ///
198    /// This method checks if the underlying connection to TWS or IB Gateway is active.
199    /// Returns false if the connection has been lost, shut down, or reset.
200    ///
201    /// # Examples
202    ///
203    /// ```no_run
204    /// use ibapi::Client;
205    ///
206    /// #[tokio::main]
207    /// async fn main() {
208    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
209    ///     
210    ///     if client.is_connected() {
211    ///         println!("Client is connected to TWS/Gateway");
212    ///     } else {
213    ///         println!("Client is not connected");
214    ///     }
215    /// }
216    /// ```
217    pub fn is_connected(&self) -> bool {
218        self.message_bus.is_connected()
219    }
220
221    /// Cleanly shuts down the message bus.
222    ///
223    /// All outstanding [`Subscription`]s see their channels close and their
224    /// `next()` calls return `None`. The background dispatch task is awaited
225    /// to completion before this returns.
226    ///
227    /// **Call this before dropping the final `Arc<Client>` if any spawned
228    /// tasks hold that `Arc`.** Otherwise the tokio runtime will hang on
229    /// shutdown — `Drop` cannot perform the full async shutdown because it
230    /// is not async.
231    ///
232    /// Safe to call multiple times.
233    ///
234    /// # Examples
235    ///
236    /// ```no_run
237    /// use ibapi::Client;
238    ///
239    /// #[tokio::main]
240    /// async fn main() {
241    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
242    ///     // ... use client, spawn tasks holding Arc<Client> ...
243    ///     client.disconnect().await;
244    /// }
245    /// ```
246    pub async fn disconnect(&self) {
247        self.message_bus.ensure_shutdown().await;
248    }
249
250    /// Returns the ID assigned to the [Client].
251    pub fn client_id(&self) -> i32 {
252        self.client_id
253    }
254
255    /// Returns the next order ID
256    pub fn next_order_id(&self) -> i32 {
257        self.id_manager.next_order_id()
258    }
259
260    /// Returns the next request ID
261    pub fn next_request_id(&self) -> i32 {
262        self.id_manager.next_request_id()
263    }
264
265    /// Sets the current value of order ID.
266    pub(crate) fn set_next_order_id(&self, order_id: i32) {
267        self.id_manager.set_order_id(order_id);
268    }
269
270    /// Start building an order for the given contract
271    ///
272    /// This is the primary API for creating orders, providing a fluent interface
273    /// that guides you through the order creation process.
274    ///
275    /// # Example
276    /// ```no_run
277    /// use ibapi::Client;
278    /// use ibapi::contracts::Contract;
279    ///
280    /// #[tokio::main]
281    /// async fn main() {
282    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
283    ///     let contract = Contract::stock("AAPL").build();
284    ///     
285    ///     let order_id = client.order(&contract)
286    ///         .buy(100)
287    ///         .limit(50.0)
288    ///         .submit().await.expect("order submission failed");
289    /// }
290    /// ```
291    pub fn order<'a>(&'a self, contract: &'a Contract) -> OrderBuilder<'a, Self> {
292        OrderBuilder::new(self, contract)
293    }
294
295    /// Check server version requirement
296    pub fn check_server_version(&self, required_version: i32, feature: &str) -> Result<(), Error> {
297        if self.server_version < required_version {
298            return Err(Error::Simple(format!(
299                "Server version {} is too old. {} requires version {}",
300                self.server_version, feature, required_version
301            )));
302        }
303        Ok(())
304    }
305
306    /// Send a request with a specific request ID
307    pub(crate) async fn send_request(&self, request_id: i32, message: RequestMessage) -> Result<AsyncInternalSubscription, Error> {
308        // Use atomic subscribe + send
309        self.message_bus.send_request(request_id, message).await
310    }
311
312    /// Send a shared request (no ID)
313    pub(crate) async fn send_shared_request(
314        &self,
315        message_type: OutgoingMessages,
316        message: RequestMessage,
317    ) -> Result<AsyncInternalSubscription, Error> {
318        // Use atomic subscribe + send
319        self.message_bus.send_shared_request(message_type, message).await
320    }
321
322    /// Send an order request
323    pub(crate) async fn send_order(&self, order_id: i32, message: RequestMessage) -> Result<AsyncInternalSubscription, Error> {
324        // Use atomic subscribe + send
325        self.message_bus.send_order_request(order_id, message).await
326    }
327
328    /// Create order update subscription
329    pub(crate) async fn create_order_update_subscription(&self) -> Result<AsyncInternalSubscription, Error> {
330        self.message_bus.create_order_update_subscription().await
331    }
332
333    /// Send a message without expecting a response
334    pub(crate) async fn send_message(&self, message: RequestMessage) -> Result<(), Error> {
335        self.message_bus.send_message(message).await
336    }
337
338    // === Account Management ===
339
340    /// Requests the current server time.
341    ///
342    /// # Examples
343    ///
344    /// ```no_run
345    /// use ibapi::Client;
346    ///
347    /// #[tokio::main]
348    /// async fn main() {
349    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
350    ///     let server_time = client.server_time().await.expect("error requesting server time");
351    ///     println!("server time: {server_time:?}");
352    /// }
353    /// ```
354    pub async fn server_time(&self) -> Result<OffsetDateTime, Error> {
355        accounts::server_time(self).await
356    }
357
358    /// Requests the current server time with millisecond precision.
359    pub async fn server_time_millis(&self) -> Result<OffsetDateTime, Error> {
360        accounts::server_time_millis(self).await
361    }
362
363    /// Subscribes to position updates for all accessible accounts.
364    /// All positions sent initially, and then only updates as positions change.
365    ///
366    /// # Examples
367    ///
368    /// ```no_run
369    /// use ibapi::Client;
370    /// use ibapi::accounts::PositionUpdate;
371    /// use futures::StreamExt;
372    ///
373    /// #[tokio::main]
374    /// async fn main() {
375    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
376    ///     let mut subscription = client.positions().await.expect("error requesting positions");
377    ///     
378    ///     while let Some(position_response) = subscription.next().await {
379    ///         match position_response {
380    ///             Ok(PositionUpdate::Position(position)) => println!("{position:?}"),
381    ///             Ok(PositionUpdate::PositionEnd) => println!("initial set of positions received"),
382    ///             Err(e) => eprintln!("Error: {e}"),
383    ///         }
384    ///     }
385    /// }
386    /// ```
387    pub async fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
388        accounts::positions(self).await
389    }
390
391    /// Subscribes to position updates for account and/or model.
392    /// Initially all positions are returned, and then updates are returned for any position changes in real time.
393    ///
394    /// # Arguments
395    /// * `account`    - If an account Id is provided, only the account's positions belonging to the specified model will be delivered.
396    /// * `model_code` - The code of the model's positions we are interested in.
397    ///
398    /// # Examples
399    ///
400    /// ```no_run
401    /// use ibapi::Client;
402    /// use ibapi::accounts::types::AccountId;
403    /// use futures::StreamExt;
404    ///
405    /// #[tokio::main]
406    /// async fn main() {
407    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
408    ///
409    ///     let account = AccountId("U1234567".to_string());
410    ///     let mut subscription = client.positions_multi(Some(&account), None).await.expect("error requesting positions by model");
411    ///     
412    ///     while let Some(position) = subscription.next().await {
413    ///         println!("{position:?}")
414    ///     }
415    /// }
416    /// ```
417    pub async fn positions_multi(
418        &self,
419        account: Option<&AccountId>,
420        model_code: Option<&ModelCode>,
421    ) -> Result<Subscription<PositionUpdateMulti>, Error> {
422        accounts::positions_multi(self, account, model_code).await
423    }
424
425    /// Creates subscription for real time daily PnL and unrealized PnL updates.
426    ///
427    /// # Arguments
428    /// * `account`    - account for which to receive PnL updates
429    /// * `model_code` - specify to request PnL updates for a specific model
430    ///
431    /// # Examples
432    ///
433    /// ```no_run
434    /// use ibapi::Client;
435    /// use ibapi::accounts::types::AccountId;
436    /// use futures::StreamExt;
437    ///
438    /// #[tokio::main]
439    /// async fn main() {
440    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
441    ///     let account = AccountId("account id".to_string());
442    ///     let mut subscription = client.pnl(&account, None).await.expect("error requesting pnl");
443    ///     
444    ///     while let Some(pnl) = subscription.next().await {
445    ///         println!("{pnl:?}")
446    ///     }
447    /// }
448    /// ```
449    pub async fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
450        accounts::pnl(self, account, model_code).await
451    }
452
453    /// Requests real time updates for daily PnL of individual positions.
454    ///
455    /// # Arguments
456    /// * `account`     - Account in which position exists
457    /// * `contract_id` - Contract ID of contract to receive daily PnL updates for. Note: does not return response if invalid conId is entered.
458    /// * `model_code`  - Model in which position exists
459    ///
460    /// # Examples
461    ///
462    /// ```no_run
463    /// use ibapi::Client;
464    /// use ibapi::accounts::types::{AccountId, ContractId};
465    /// use futures::StreamExt;
466    ///
467    /// #[tokio::main]
468    /// async fn main() {
469    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
470    ///
471    ///     let account = AccountId("<account id>".to_string());
472    ///     let contract_id = ContractId(1001);
473    ///
474    ///     let mut subscription = client.pnl_single(&account, contract_id, None).await.expect("error requesting pnl");
475    ///     
476    ///     while let Some(pnl) = subscription.next().await {
477    ///         println!("{pnl:?}")
478    ///     }
479    /// }
480    /// ```
481    pub async fn pnl_single(
482        &self,
483        account: &AccountId,
484        contract_id: ContractId,
485        model_code: Option<&ModelCode>,
486    ) -> Result<Subscription<PnLSingle>, Error> {
487        accounts::pnl_single(self, account, contract_id, model_code).await
488    }
489
490    /// Requests a specific account's summary. Subscribes to the account summary as presented in the TWS' Account Summary tab.
491    /// Data received is specified by using a specific tags value.
492    ///
493    /// # Arguments
494    /// * `group` - Set to "All" to return account summary data for all accounts, or set to a specific Advisor Account Group name that has already been created in TWS Global Configuration.
495    /// * `tags`  - List of the desired tags.
496    ///
497    /// # Examples
498    ///
499    /// ```no_run
500    /// use ibapi::Client;
501    /// use ibapi::accounts::AccountSummaryTags;
502    /// use ibapi::accounts::types::AccountGroup;
503    /// use futures::StreamExt;
504    ///
505    /// #[tokio::main]
506    /// async fn main() {
507    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
508    ///
509    ///     let group = AccountGroup("All".to_string());
510    ///
511    ///     let mut subscription = client.account_summary(&group, AccountSummaryTags::ALL).await.expect("error requesting account summary");
512    ///     
513    ///     while let Some(summary) = subscription.next().await {
514    ///         println!("{summary:?}")
515    ///     }
516    /// }
517    /// ```
518    pub async fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
519        accounts::account_summary(self, group, tags).await
520    }
521
522    /// Subscribes to a specific account's information and portfolio.
523    ///
524    /// All account values and positions will be returned initially, and then there will only be updates when there is a change
525    /// in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
526    ///
527    /// # Arguments
528    /// * `account` - The account id (i.e. U1234567) for which the information is requested.
529    ///
530    /// # Examples
531    ///
532    /// ```no_run
533    /// use ibapi::Client;
534    /// use ibapi::accounts::AccountUpdate;
535    /// use ibapi::accounts::types::AccountId;
536    /// use futures::StreamExt;
537    ///
538    /// #[tokio::main]
539    /// async fn main() {
540    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
541    ///
542    ///     let account = AccountId("U1234567".to_string());
543    ///
544    ///     let mut subscription = client.account_updates(&account).await.expect("error requesting account updates");
545    ///     
546    ///     while let Some(update_result) = subscription.next().await {
547    ///         match update_result {
548    ///             Ok(update) => {
549    ///                 println!("{update:?}");
550    ///
551    ///                 // stop after full initial update
552    ///                 if let AccountUpdate::End = update {
553    ///                     break;
554    ///                 }
555    ///             }
556    ///             Err(e) => eprintln!("Error: {e}"),
557    ///         }
558    ///     }
559    /// }
560    /// ```
561    pub async fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
562        accounts::account_updates(self, account).await
563    }
564
565    /// Requests account updates for account and/or model.
566    ///
567    /// All account values and positions will be returned initially, and then there will only be updates when there is a change
568    /// in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
569    ///
570    /// # Arguments
571    /// * `account`        - Account values can be requested for a particular account.
572    /// * `model_code`     - Account values can also be requested for a model.
573    ///
574    /// # Examples
575    ///
576    /// ```no_run
577    /// use ibapi::Client;
578    /// use ibapi::accounts::AccountUpdateMulti;
579    /// use ibapi::accounts::types::AccountId;
580    /// use futures::StreamExt;
581    ///
582    /// #[tokio::main]
583    /// async fn main() {
584    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
585    ///
586    ///     let account = AccountId("U1234567".to_string());
587    ///
588    ///     let mut subscription = client.account_updates_multi(Some(&account), None).await.expect("error requesting account updates multi");
589    ///     
590    ///     while let Some(update_result) = subscription.next().await {
591    ///         match update_result {
592    ///             Ok(update) => {
593    ///                 println!("{update:?}");
594    ///
595    ///                 // stop after full initial update
596    ///                 if let AccountUpdateMulti::End = update {
597    ///                     break;
598    ///                 }
599    ///             }
600    ///             Err(e) => eprintln!("Error: {e}"),
601    ///         }
602    ///     }
603    /// }
604    /// ```
605    pub async fn account_updates_multi(
606        &self,
607        account: Option<&AccountId>,
608        model_code: Option<&ModelCode>,
609    ) -> Result<Subscription<AccountUpdateMulti>, Error> {
610        accounts::account_updates_multi(self, account, model_code).await
611    }
612
613    /// Requests the accounts to which the logged user has access to.
614    ///
615    /// # Examples
616    ///
617    /// ```no_run
618    /// use ibapi::Client;
619    ///
620    /// #[tokio::main]
621    /// async fn main() {
622    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
623    ///
624    ///     let accounts = client.managed_accounts().await.expect("error requesting managed accounts");
625    ///     println!("managed accounts: {accounts:?}")
626    /// }
627    /// ```
628    pub async fn managed_accounts(&self) -> Result<Vec<String>, Error> {
629        accounts::managed_accounts(self).await
630    }
631
632    /// Get current family codes for all accessible accounts.
633    ///
634    /// # Examples
635    ///
636    /// ```no_run
637    /// use ibapi::Client;
638    ///
639    /// #[tokio::main]
640    /// async fn main() {
641    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
642    ///     let codes = client.family_codes().await.expect("error requesting family codes");
643    ///     println!("family codes: {codes:?}")
644    /// }
645    /// ```
646    pub async fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
647        accounts::family_codes(self).await
648    }
649
650    /// Subscribes to TWS's Display Groups.
651    ///
652    /// Display Groups are a TWS-only feature (not available in IB Gateway).
653    /// They allow organizing contracts into color-coded groups in the TWS UI.
654    /// When subscribed, you receive updates whenever the user changes the contract
655    /// displayed in that group within TWS.
656    ///
657    /// # Arguments
658    /// * `group_id` - The ID of the group to subscribe to (1-9)
659    ///
660    /// # Examples
661    ///
662    /// ```no_run
663    /// use ibapi::Client;
664    ///
665    /// #[tokio::main]
666    /// async fn main() {
667    ///     let client = Client::connect("127.0.0.1:7497", 100).await.expect("connection failed");
668    ///
669    ///     let mut subscription = client.subscribe_to_group_events(1).await.expect("subscription failed");
670    ///
671    ///     // Update the displayed contract
672    ///     subscription.update("265598@SMART").await.expect("update failed");
673    ///
674    ///     while let Some(event) = subscription.next().await {
675    ///         println!("Received group event: {:?}", event);
676    ///     }
677    /// }
678    /// ```
679    pub async fn subscribe_to_group_events(&self, group_id: i32) -> Result<display_groups::DisplayGroupSubscription, Error> {
680        display_groups::r#async::subscribe_to_group_events(self, group_id).await
681    }
682
683    // === Market Data ===
684
685    /// Creates a market data subscription builder with a fluent interface.
686    ///
687    /// This is the preferred way to subscribe to market data, providing a more
688    /// intuitive and discoverable API than the raw method.
689    ///
690    /// # Arguments
691    /// * `contract` - The contract to receive market data for
692    ///
693    /// # Examples
694    ///
695    /// ```no_run
696    /// use ibapi::prelude::*;
697    /// use ibapi::client::r#async::Client;
698    ///
699    /// #[tokio::main]
700    /// async fn main() {
701    ///     let client = Client::connect("127.0.0.1:4002", 100).await
702    ///         .expect("connection failed");
703    ///     let contract = Contract::stock("AAPL").build();
704    ///
705    ///     // Subscribe to real-time streaming data with specific tick types
706    ///     let mut subscription = client.market_data(&contract)
707    ///         .generic_ticks(&["233", "236"])  // RTVolume and Shortable
708    ///         .subscribe()
709    ///         .await
710    ///         .expect("subscription failed");
711    ///
712    ///     while let Some(tick) = subscription.next().await {
713    ///         match tick {
714    ///             Ok(TickTypes::Price(price)) => println!("Price: {price:?}"),
715    ///             Ok(TickTypes::Size(size)) => println!("Size: {size:?}"),
716    ///             Ok(TickTypes::SnapshotEnd) => break,
717    ///             Err(e) => eprintln!("Error: {e:?}"),
718    ///             _ => {}
719    ///         }
720    ///     }
721    /// }
722    /// ```
723    pub fn market_data<'a>(&'a self, contract: &'a Contract) -> MarketDataBuilder<'a, Self> {
724        MarketDataBuilder::new(self, contract)
725    }
726
727    /// Requests real time bars
728    /// Currently, only 5 seconds bars are provided.
729    ///
730    /// # Arguments
731    /// * `contract` - The Contract for which the depth is being requested
732    /// * `bar_size` - Currently being ignored
733    /// * `what_to_show` - The nature of the data being retrieved (TRADES, MIDPOINT, BID, ASK)
734    /// * `trading_hours` - Use TradingHours::Regular for data generated only during regular trading hours, or TradingHours::Extended to include data from outside regular trading hours
735    ///
736    /// # Examples
737    ///
738    /// ```no_run
739    /// use ibapi::Client;
740    /// use ibapi::contracts::Contract;
741    /// use ibapi::market_data::realtime::{BarSize, WhatToShow};
742    /// use ibapi::market_data::TradingHours;
743    /// use futures::StreamExt;
744    ///
745    /// #[tokio::main]
746    /// async fn main() {
747    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
748    ///
749    ///     let contract = Contract::stock("TSLA").build();
750    ///     let mut subscription = client
751    ///         .realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, TradingHours::Extended)
752    ///         .await
753    ///         .expect("request failed");
754    ///
755    ///     while let Some(bar_result) = subscription.next().await {
756    ///         match bar_result {
757    ///             Ok(bar) => println!("{bar:?}"),
758    ///             Err(e) => eprintln!("Error: {e:?}"),
759    ///         }
760    ///     }
761    /// }
762    /// ```
763    pub async fn realtime_bars(
764        &self,
765        contract: &crate::contracts::Contract,
766        bar_size: crate::market_data::realtime::BarSize,
767        what_to_show: crate::market_data::realtime::WhatToShow,
768        trading_hours: TradingHours,
769    ) -> Result<Subscription<crate::market_data::realtime::Bar>, Error> {
770        crate::market_data::realtime::realtime_bars(self, contract, &bar_size, &what_to_show, trading_hours, vec![]).await
771    }
772
773    /// Requests tick by tick AllLast ticks.
774    ///
775    /// # Arguments
776    /// * `contract` - The Contract for which tick-by-tick data is requested.
777    /// * `number_of_ticks` - Number of historical ticks to return from the TWS's historical database. Max value is 1000.
778    /// * `ignore_size` - Ignore size flag.
779    ///
780    /// # Examples
781    ///
782    /// ```no_run
783    /// use ibapi::Client;
784    /// use ibapi::contracts::Contract;
785    /// use futures::StreamExt;
786    ///
787    /// #[tokio::main]
788    /// async fn main() {
789    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
790    ///
791    ///     let contract = Contract::stock("AAPL").build();
792    ///     let mut subscription = client
793    ///         .tick_by_tick_all_last(&contract, 0, false)
794    ///         .await
795    ///         .expect("request failed");
796    ///
797    ///     while let Some(trade_result) = subscription.next().await {
798    ///         match trade_result {
799    ///             Ok(trade) => println!("Trade: {} - ${} x {} on {}",
800    ///                 trade.time, trade.price, trade.size, trade.exchange),
801    ///             Err(e) => eprintln!("Error: {e:?}"),
802    ///         }
803    ///     }
804    /// }
805    /// ```
806    pub async fn tick_by_tick_all_last(
807        &self,
808        contract: &crate::contracts::Contract,
809        number_of_ticks: i32,
810        ignore_size: bool,
811    ) -> Result<Subscription<crate::market_data::realtime::Trade>, Error> {
812        crate::market_data::realtime::tick_by_tick_all_last(self, contract, number_of_ticks, ignore_size).await
813    }
814
815    /// Requests tick by tick Last ticks.
816    ///
817    /// # Arguments
818    /// * `contract` - The Contract for which tick-by-tick data is requested.
819    /// * `number_of_ticks` - Number of historical ticks to return from the TWS's historical database. Max value is 1000.
820    /// * `ignore_size` - Ignore size flag.
821    pub async fn tick_by_tick_last(
822        &self,
823        contract: &crate::contracts::Contract,
824        number_of_ticks: i32,
825        ignore_size: bool,
826    ) -> Result<Subscription<crate::market_data::realtime::Trade>, Error> {
827        crate::market_data::realtime::tick_by_tick_last(self, contract, number_of_ticks, ignore_size).await
828    }
829
830    /// Requests tick by tick BidAsk ticks.
831    ///
832    /// # Arguments
833    /// * `contract` - The Contract for which tick-by-tick data is requested.
834    /// * `number_of_ticks` - Number of historical ticks to return from the TWS's historical database. Max value is 1000.
835    /// * `ignore_size` - Ignore size flag.
836    ///
837    /// # Examples
838    ///
839    /// ```no_run
840    /// use ibapi::Client;
841    /// use ibapi::contracts::Contract;
842    /// use futures::StreamExt;
843    ///
844    /// #[tokio::main]
845    /// async fn main() {
846    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
847    ///
848    ///     let contract = Contract::stock("AAPL").build();
849    ///     let mut subscription = client
850    ///         .tick_by_tick_bid_ask(&contract, 0, false)
851    ///         .await
852    ///         .expect("request failed");
853    ///
854    ///     while let Some(quote_result) = subscription.next().await {
855    ///         match quote_result {
856    ///             Ok(quote) => println!("Quote: {} - Bid: ${} x {} | Ask: ${} x {}",
857    ///                 quote.time, quote.bid_price, quote.bid_size,
858    ///                 quote.ask_price, quote.ask_size),
859    ///             Err(e) => eprintln!("Error: {e:?}"),
860    ///         }
861    ///     }
862    /// }
863    /// ```
864    pub async fn tick_by_tick_bid_ask(
865        &self,
866        contract: &crate::contracts::Contract,
867        number_of_ticks: i32,
868        ignore_size: bool,
869    ) -> Result<Subscription<crate::market_data::realtime::BidAsk>, Error> {
870        crate::market_data::realtime::tick_by_tick_bid_ask(self, contract, number_of_ticks, ignore_size).await
871    }
872
873    /// Requests tick by tick MidPoint ticks.
874    ///
875    /// # Arguments
876    /// * `contract` - The Contract for which tick-by-tick data is requested.
877    /// * `number_of_ticks` - Number of historical ticks to return from the TWS's historical database. Max value is 1000.
878    /// * `ignore_size` - Ignore size flag.
879    pub async fn tick_by_tick_midpoint(
880        &self,
881        contract: &crate::contracts::Contract,
882        number_of_ticks: i32,
883        ignore_size: bool,
884    ) -> Result<Subscription<crate::market_data::realtime::MidPoint>, Error> {
885        crate::market_data::realtime::tick_by_tick_midpoint(self, contract, number_of_ticks, ignore_size).await
886    }
887
888    /// Requests the contract's market depth (order book).
889    ///
890    /// This request returns the full available market depth and updates whenever there's a change in the order book.
891    /// Market depth data is not available for all instruments. Check the TWS Contract Details under "Market Data Availability" - "Deep Book" field
892    /// before requesting market depth.
893    ///
894    /// # Arguments
895    /// * `contract` - The Contract for which the depth is being requested
896    /// * `number_of_rows` - The number of rows on each side of the order book (max 50)
897    /// * `is_smart_depth` - Flag indicates that this is smart depth request
898    ///
899    /// # Examples
900    ///
901    /// ```no_run
902    /// use ibapi::Client;
903    /// use ibapi::contracts::Contract;
904    /// use ibapi::market_data::realtime::MarketDepths;
905    /// use futures::StreamExt;
906    ///
907    /// #[tokio::main]
908    /// async fn main() {
909    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
910    ///
911    ///     let contract = Contract::stock("AAPL").build();
912    ///     let mut subscription = client
913    ///         .market_depth(&contract, 5, false)
914    ///         .await
915    ///         .expect("request failed");
916    ///
917    ///     while let Some(depth_result) = subscription.next().await {
918    ///         match depth_result {
919    ///             Ok(MarketDepths::MarketDepth(depth)) => {
920    ///                 let side = if depth.side == 1 { "Bid" } else { "Ask" };
921    ///                 let operation = match depth.operation {
922    ///                     0 => "Insert",
923    ///                     1 => "Update",
924    ///                     2 => "Delete",
925    ///                     _ => "Unknown",
926    ///                 };
927    ///                 println!("{} {} at position {} - Price: ${}, Size: {}",
928    ///                     operation, side, depth.position, depth.price, depth.size);
929    ///             }
930    ///             Ok(MarketDepths::Notice(notice)) => println!("Notice: {}", notice.message),
931    ///             _ => {}
932    ///         }
933    ///     }
934    /// }
935    /// ```
936    pub async fn market_depth(
937        &self,
938        contract: &crate::contracts::Contract,
939        number_of_rows: i32,
940        is_smart_depth: bool,
941    ) -> Result<Subscription<crate::market_data::realtime::MarketDepths>, Error> {
942        crate::market_data::realtime::market_depth(self, contract, number_of_rows, is_smart_depth).await
943    }
944
945    /// Requests venues for which market data is returned to market_depth (those with market makers)
946    ///
947    /// # Examples
948    ///
949    /// ```no_run
950    /// use ibapi::Client;
951    ///
952    /// #[tokio::main]
953    /// async fn main() {
954    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
955    ///     
956    ///     let exchanges = client.market_depth_exchanges().await.expect("request failed");
957    ///     for exchange in exchanges {
958    ///         println!("{} - {} ({})",
959    ///             exchange.exchange_name, exchange.security_type, exchange.service_data_type);
960    ///     }
961    /// }
962    /// ```
963    pub async fn market_depth_exchanges(&self) -> Result<Vec<crate::market_data::realtime::DepthMarketDataDescription>, Error> {
964        crate::market_data::realtime::market_depth_exchanges(self).await
965    }
966
967    /// Switches market data type returned from market data request.
968    ///
969    /// # Arguments
970    /// * `market_data_type` - Type of market data to retrieve.
971    ///
972    /// # Examples
973    ///
974    /// ```no_run
975    /// use ibapi::Client;
976    /// use ibapi::market_data::{MarketDataType};
977    ///
978    /// #[tokio::main]
979    /// async fn main() {
980    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
981    ///
982    ///     let market_data_type = MarketDataType::Realtime;
983    ///     client.switch_market_data_type(market_data_type).await.expect("request failed");
984    ///     println!("market data switched: {market_data_type:?}");
985    /// }
986    /// ```
987    pub async fn switch_market_data_type(&self, market_data_type: crate::market_data::MarketDataType) -> Result<(), Error> {
988        crate::market_data::switch_market_data_type(self, market_data_type).await
989    }
990
991    /// Returns the timestamp of earliest available historical data for a contract and data type.
992    ///
993    /// # Examples
994    ///
995    /// ```no_run
996    /// use ibapi::Client;
997    /// use ibapi::contracts::Contract;
998    /// use ibapi::market_data::historical::WhatToShow;
999    /// use ibapi::market_data::TradingHours;
1000    ///
1001    /// #[tokio::main]
1002    /// async fn main() {
1003    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1004    ///
1005    ///     let contract = Contract::stock("MSFT").build();
1006    ///     let what_to_show = WhatToShow::Trades;
1007    ///     let trading_hours = TradingHours::Regular;
1008    ///
1009    ///     let timestamp = client
1010    ///         .head_timestamp(&contract, what_to_show, trading_hours)
1011    ///         .await
1012    ///         .expect("error requesting head timestamp");
1013    ///     println!("Earliest data available: {timestamp:?}");
1014    /// }
1015    /// ```
1016    pub async fn head_timestamp(
1017        &self,
1018        contract: &crate::contracts::Contract,
1019        what_to_show: crate::market_data::historical::WhatToShow,
1020        trading_hours: TradingHours,
1021    ) -> Result<OffsetDateTime, Error> {
1022        crate::market_data::historical::head_timestamp(self, contract, what_to_show, trading_hours).await
1023    }
1024
1025    /// Requests historical bars data.
1026    ///
1027    /// When requesting historical data, a finishing time and date is required along with a duration string.
1028    /// For example, having: end_date = 20130701 23:59:59 GMT and duration = 3 D
1029    /// will return three days of data counting backwards from July 1st 2013 at 23:59:59 GMT resulting in all the
1030    /// available bars of the last three days until the date and time specified.
1031    ///
1032    /// # Arguments
1033    /// * `contract` - The contract for which we want to retrieve the data.
1034    /// * `end_date` - Request's ending time. If None, current time is used.
1035    /// * `duration` - The amount of time for which the data needs to be retrieved.
1036    /// * `bar_size` - The bar size.
1037    /// * `what_to_show` - The kind of information being retrieved.
1038    /// * `trading_hours` - Use TradingHours::Regular for data generated only during regular trading hours, or TradingHours::Extended to include data from outside regular trading hours.
1039    ///
1040    /// # Examples
1041    ///
1042    /// ```no_run
1043    /// use time::macros::datetime;
1044    /// use ibapi::contracts::Contract;
1045    /// use ibapi::Client;
1046    /// use ibapi::market_data::historical::{BarSize, ToDuration, WhatToShow};
1047    /// use ibapi::market_data::TradingHours;
1048    ///
1049    /// #[tokio::main]
1050    /// async fn main() {
1051    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1052    ///
1053    ///     let contract = Contract::stock("TSLA").build();
1054    ///
1055    ///     let interval_end = Some(datetime!(2023-04-11 20:00 UTC));
1056    ///     let duration = 5.days();
1057    ///     let bar_size = BarSize::Hour;
1058    ///     let what_to_show = Some(WhatToShow::Trades);
1059    ///     let trading_hours = TradingHours::Regular;
1060    ///
1061    ///     let historical_data = client
1062    ///         .historical_data(&contract, interval_end, duration, bar_size, what_to_show, trading_hours)
1063    ///         .await
1064    ///         .expect("historical bars request failed");
1065    ///
1066    ///     println!("start: {}, end: {}", historical_data.start, historical_data.end);
1067    ///     for bar in &historical_data.bars {
1068    ///         println!("{bar:?}")
1069    ///     }
1070    /// }
1071    /// ```
1072    pub async fn historical_data(
1073        &self,
1074        contract: &crate::contracts::Contract,
1075        end_date: Option<OffsetDateTime>,
1076        duration: crate::market_data::historical::Duration,
1077        bar_size: crate::market_data::historical::BarSize,
1078        what_to_show: Option<crate::market_data::historical::WhatToShow>,
1079        trading_hours: TradingHours,
1080    ) -> Result<crate::market_data::historical::HistoricalData, Error> {
1081        crate::market_data::historical::historical_data(self, contract, end_date, duration, bar_size, what_to_show, trading_hours).await
1082    }
1083
1084    /// Requests historical data with optional streaming updates.
1085    ///
1086    /// This method returns a subscription that first yields the initial historical bars.
1087    /// When `keep_up_to_date` is `true`, it continues to yield streaming updates for
1088    /// the current bar as it builds. IBKR sends updated bars every ~4-6 seconds until
1089    /// the bar completes.
1090    ///
1091    /// # Arguments
1092    /// * `contract` - Contract object that is subject of query
1093    /// * `duration` - The amount of time for which the data needs to be retrieved
1094    /// * `bar_size` - The bar size (resolution)
1095    /// * `what_to_show` - The type of data to retrieve (Trades, MidPoint, etc.)
1096    /// * `trading_hours` - Regular trading hours only, or include extended hours
1097    /// * `keep_up_to_date` - If true, continue receiving streaming updates after initial data
1098    ///
1099    /// # Examples
1100    ///
1101    /// ```no_run
1102    /// use ibapi::contracts::Contract;
1103    /// use ibapi::Client;
1104    /// use ibapi::market_data::historical::{ToDuration, HistoricalBarUpdate};
1105    /// use ibapi::prelude::{HistoricalBarSize, HistoricalWhatToShow, TradingHours};
1106    ///
1107    /// #[tokio::main]
1108    /// async fn main() {
1109    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1110    ///     let contract = Contract::stock("SPY").build();
1111    ///
1112    ///     let mut subscription = client
1113    ///         .historical_data_streaming(
1114    ///             &contract, 3.days(), HistoricalBarSize::Min15,
1115    ///             Some(HistoricalWhatToShow::Trades), TradingHours::Extended, true
1116    ///         )
1117    ///         .await
1118    ///         .expect("streaming request failed");
1119    ///
1120    ///     while let Some(update) = subscription.next().await {
1121    ///         match update {
1122    ///             HistoricalBarUpdate::Historical(data) => println!("Initial bars: {}", data.bars.len()),
1123    ///             HistoricalBarUpdate::Update(bar) => println!("Streaming update: {:?}", bar),
1124    ///             HistoricalBarUpdate::End { start, end } => println!("Stream ended: {} - {}", start, end),
1125    ///         }
1126    ///     }
1127    /// }
1128    /// ```
1129    pub async fn historical_data_streaming(
1130        &self,
1131        contract: &crate::contracts::Contract,
1132        duration: crate::market_data::historical::Duration,
1133        bar_size: crate::market_data::historical::BarSize,
1134        what_to_show: Option<crate::market_data::historical::WhatToShow>,
1135        trading_hours: TradingHours,
1136        keep_up_to_date: bool,
1137    ) -> Result<crate::market_data::historical::HistoricalDataStreamingSubscription, Error> {
1138        crate::market_data::historical::historical_data_streaming(self, contract, duration, bar_size, what_to_show, trading_hours, keep_up_to_date)
1139            .await
1140    }
1141
1142    /// Requests historical schedule.
1143    ///
1144    /// # Arguments
1145    /// * `contract` - Contract object for which trading schedule is requested.
1146    /// * `end_date` - Request's ending date. If None, current time is used.
1147    /// * `duration` - The amount of time for which the data needs to be retrieved.
1148    ///
1149    /// # Examples
1150    ///
1151    /// ```no_run
1152    /// use time::macros::datetime;
1153    /// use ibapi::contracts::Contract;
1154    /// use ibapi::Client;
1155    /// use ibapi::market_data::historical::ToDuration;
1156    ///
1157    /// #[tokio::main]
1158    /// async fn main() {
1159    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1160    ///
1161    ///     let contract = Contract::stock("GM").build();
1162    ///
1163    ///     let end_date = Some(datetime!(2022-11-21 00:00 UTC));
1164    ///     let duration = 30.days();
1165    ///
1166    ///     let schedule = client
1167    ///         .historical_schedule(&contract, end_date, duration)
1168    ///         .await
1169    ///         .expect("error requesting historical schedule");
1170    ///     
1171    ///     println!("Trading schedule from {} to {}", schedule.start, schedule.end);
1172    ///     for session in &schedule.sessions {
1173    ///         println!("  {} - Trading: {} to {}",
1174    ///             session.reference, session.start, session.end);
1175    ///     }
1176    /// }
1177    /// ```
1178    pub async fn historical_schedule(
1179        &self,
1180        contract: &crate::contracts::Contract,
1181        end_date: Option<OffsetDateTime>,
1182        duration: crate::market_data::historical::Duration,
1183    ) -> Result<crate::market_data::historical::Schedule, Error> {
1184        crate::market_data::historical::historical_schedule(self, contract, end_date, duration).await
1185    }
1186
1187    /// Requests historical bid/ask tick data.
1188    ///
1189    /// # Arguments
1190    /// * `contract` - Contract object that is subject of query
1191    /// * `start` - Start timestamp. Either start or end must be specified.
1192    /// * `end` - End timestamp. Either start or end must be specified.
1193    /// * `number_of_ticks` - Number of ticks to retrieve
1194    /// * `trading_hours` - Regular trading hours only, or include extended hours
1195    /// * `ignore_size` - Ignore size flag
1196    ///
1197    /// # Examples
1198    ///
1199    /// ```no_run
1200    /// use time::macros::datetime;
1201    /// use ibapi::contracts::Contract;
1202    /// use ibapi::Client;
1203    /// use ibapi::market_data::TradingHours;
1204    ///
1205    /// #[tokio::main]
1206    /// async fn main() {
1207    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1208    ///
1209    ///     let contract = Contract::stock("GM").build();
1210    ///
1211    ///     let start = Some(datetime!(2022-11-07 16:00 UTC));
1212    ///     let end = Some(datetime!(2022-11-07 17:00 UTC));
1213    ///     let number_of_ticks = 1000;
1214    ///     let trading_hours = TradingHours::Regular;
1215    ///     let ignore_size = false;
1216    ///
1217    ///     let mut subscription = client
1218    ///         .historical_ticks_bid_ask(&contract, start, end, number_of_ticks, trading_hours, ignore_size)
1219    ///         .await
1220    ///         .expect("error requesting historical ticks");
1221    ///
1222    ///     while let Some(tick) = subscription.next().await {
1223    ///         println!("Bid/Ask tick: {} - Bid: ${} x {} | Ask: ${} x {}",
1224    ///             tick.timestamp, tick.price_bid, tick.size_bid,
1225    ///             tick.price_ask, tick.size_ask);
1226    ///     }
1227    /// }
1228    /// ```
1229    pub async fn historical_ticks_bid_ask(
1230        &self,
1231        contract: &crate::contracts::Contract,
1232        start: Option<OffsetDateTime>,
1233        end: Option<OffsetDateTime>,
1234        number_of_ticks: i32,
1235        trading_hours: TradingHours,
1236        ignore_size: bool,
1237    ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickBidAsk>, Error> {
1238        crate::market_data::historical::historical_ticks_bid_ask(self, contract, start, end, number_of_ticks, trading_hours, ignore_size).await
1239    }
1240
1241    /// Requests historical midpoint tick data.
1242    ///
1243    /// # Arguments
1244    /// * `contract` - Contract object that is subject of query
1245    /// * `start` - Start timestamp. Either start or end must be specified.
1246    /// * `end` - End timestamp. Either start or end must be specified.
1247    /// * `number_of_ticks` - Number of ticks to retrieve
1248    /// * `trading_hours` - Regular trading hours only, or include extended hours
1249    pub async fn historical_ticks_mid_point(
1250        &self,
1251        contract: &crate::contracts::Contract,
1252        start: Option<OffsetDateTime>,
1253        end: Option<OffsetDateTime>,
1254        number_of_ticks: i32,
1255        trading_hours: TradingHours,
1256    ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickMidpoint>, Error> {
1257        crate::market_data::historical::historical_ticks_mid_point(self, contract, start, end, number_of_ticks, trading_hours).await
1258    }
1259
1260    /// Requests historical trade tick data.
1261    ///
1262    /// # Arguments
1263    /// * `contract` - Contract object that is subject of query
1264    /// * `start` - Start timestamp. Either start or end must be specified.
1265    /// * `end` - End timestamp. Either start or end must be specified.
1266    /// * `number_of_ticks` - Number of ticks to retrieve
1267    /// * `trading_hours` - Regular trading hours only, or include extended hours
1268    pub async fn historical_ticks_trade(
1269        &self,
1270        contract: &crate::contracts::Contract,
1271        start: Option<OffsetDateTime>,
1272        end: Option<OffsetDateTime>,
1273        number_of_ticks: i32,
1274        trading_hours: TradingHours,
1275    ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickLast>, Error> {
1276        crate::market_data::historical::historical_ticks_trade(self, contract, start, end, number_of_ticks, trading_hours).await
1277    }
1278
1279    /// Cancels an in-flight historical ticks request.
1280    ///
1281    /// # Arguments
1282    /// * `request_id` - The request ID of the historical ticks subscription to cancel.
1283    pub async fn cancel_historical_ticks(&self, request_id: i32) -> Result<(), Error> {
1284        crate::market_data::historical::cancel_historical_ticks(self, request_id).await
1285    }
1286
1287    /// Returns histogram of market data for a contract.
1288    ///
1289    /// # Arguments
1290    /// * `contract` - Contract object for which histogram is being requested
1291    /// * `trading_hours` - Regular trading hours only, or include extended hours
1292    /// * `period` - Period of which data is being requested
1293    ///
1294    /// # Examples
1295    ///
1296    /// ```no_run
1297    /// use time::macros::datetime;
1298    /// use ibapi::contracts::Contract;
1299    /// use ibapi::Client;
1300    /// use ibapi::market_data::historical::BarSize;
1301    /// use ibapi::market_data::TradingHours;
1302    ///
1303    /// #[tokio::main]
1304    /// async fn main() {
1305    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1306    ///
1307    ///     let contract = Contract::stock("GM").build();
1308    ///
1309    ///     let trading_hours = TradingHours::Regular;
1310    ///     let period = BarSize::Week;
1311    ///
1312    ///     let histogram = client
1313    ///         .histogram_data(&contract, trading_hours, period)
1314    ///         .await
1315    ///         .expect("error requesting histogram");
1316    ///
1317    ///     for entry in &histogram {
1318    ///         println!("Price: ${} - Count: {}", entry.price, entry.size);
1319    ///     }
1320    /// }
1321    /// ```
1322    pub async fn histogram_data(
1323        &self,
1324        contract: &crate::contracts::Contract,
1325        trading_hours: TradingHours,
1326        period: crate::market_data::historical::BarSize,
1327    ) -> Result<Vec<crate::market_data::historical::HistogramEntry>, Error> {
1328        crate::market_data::historical::histogram_data(self, contract, trading_hours, period).await
1329    }
1330
1331    // === Wall Street Horizon (WSH) Data ===
1332
1333    /// Requests Wall Street Horizon metadata information.
1334    ///
1335    /// # Examples
1336    ///
1337    /// ```no_run
1338    /// use ibapi::Client;
1339    ///
1340    /// #[tokio::main]
1341    /// async fn main() {
1342    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1343    ///
1344    ///     let metadata = client.wsh_metadata().await.expect("error requesting wsh metadata");
1345    ///     println!("wsh metadata: {metadata:?}")
1346    /// }
1347    /// ```
1348    pub async fn wsh_metadata(&self) -> Result<crate::wsh::WshMetadata, Error> {
1349        crate::wsh::wsh_metadata(self).await
1350    }
1351
1352    /// Requests event data for a specified contract from the Wall Street Horizons (WSH) calendar.
1353    ///
1354    /// # Arguments
1355    ///
1356    /// * `contract_id` - Contract identifier for the event request.
1357    /// * `start_date`  - Start date of the event request.
1358    /// * `end_date`    - End date of the event request.
1359    /// * `limit`       - Number of events to return.
1360    /// * `auto_fill`   - Autofill configuration for watchlist, portfolio, and position.
1361    ///
1362    /// # Examples
1363    ///
1364    /// ```no_run
1365    /// use ibapi::Client;
1366    /// use time::macros::date;
1367    /// use ibapi::wsh::AutoFill;
1368    ///
1369    /// #[tokio::main]
1370    /// async fn main() {
1371    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1372    ///
1373    ///     let contract_id = 12345;
1374    ///     let start_date = Some(date!(2024-01-01));
1375    ///     let end_date = Some(date!(2024-12-31));
1376    ///     let limit = Some(100);
1377    ///     let auto_fill = Some(AutoFill {
1378    ///         competitors: true,
1379    ///         portfolio: false,
1380    ///         watchlist: false,
1381    ///     });
1382    ///
1383    ///     let event_data = client
1384    ///         .wsh_event_data_by_contract(contract_id, start_date, end_date, limit, auto_fill)
1385    ///         .await
1386    ///         .expect("error requesting wsh event data");
1387    ///     println!("wsh event data: {event_data:?}")
1388    /// }
1389    /// ```
1390    pub async fn wsh_event_data_by_contract(
1391        &self,
1392        contract_id: i32,
1393        start_date: Option<time::Date>,
1394        end_date: Option<time::Date>,
1395        limit: Option<i32>,
1396        auto_fill: Option<crate::wsh::AutoFill>,
1397    ) -> Result<crate::wsh::WshEventData, Error> {
1398        crate::wsh::wsh_event_data_by_contract(self, contract_id, start_date, end_date, limit, auto_fill).await
1399    }
1400
1401    /// Requests event data using a filter from the Wall Street Horizons (WSH) calendar.
1402    ///
1403    /// # Arguments
1404    ///
1405    /// * `filter`    - Filter for the event request (e.g. JSON-encoded string).
1406    /// * `limit`     - Number of events to return.
1407    /// * `auto_fill` - Autofill configuration for watchlist, portfolio, and position.
1408    ///
1409    /// # Examples
1410    ///
1411    /// ```no_run
1412    /// use ibapi::Client;
1413    /// use ibapi::wsh::AutoFill;
1414    /// use futures::StreamExt;
1415    ///
1416    /// #[tokio::main]
1417    /// async fn main() {
1418    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1419    ///
1420    ///     let filter = r#"{"country": "US"}"#;
1421    ///     let limit = Some(100);
1422    ///     let auto_fill = Some(AutoFill {
1423    ///         competitors: true,
1424    ///         portfolio: false,
1425    ///         watchlist: false,
1426    ///     });
1427    ///
1428    ///     let mut event_data_subscription = client
1429    ///         .wsh_event_data_by_filter(filter, limit, auto_fill)
1430    ///         .await
1431    ///         .expect("error requesting wsh event data");
1432    ///     
1433    ///     while let Some(event_data) = event_data_subscription.next().await {
1434    ///         println!("{event_data:?}")
1435    ///     }
1436    /// }
1437    /// ```
1438    pub async fn wsh_event_data_by_filter(
1439        &self,
1440        filter: &str,
1441        limit: Option<i32>,
1442        auto_fill: Option<crate::wsh::AutoFill>,
1443    ) -> Result<Subscription<crate::wsh::WshEventData>, Error> {
1444        crate::wsh::wsh_event_data_by_filter(self, filter, limit, auto_fill).await
1445    }
1446
1447    // === Contract Management ===
1448
1449    /// Requests detailed contract information for matching contracts.
1450    ///
1451    /// This function returns all contracts that match the provided contract sample.
1452    /// It can be used to retrieve complete options and futures chains.
1453    ///
1454    /// # Arguments
1455    /// * `contract` - The Contract used as a sample to query available contracts
1456    ///
1457    /// # Examples
1458    ///
1459    /// ```no_run
1460    /// use ibapi::Client;
1461    /// use ibapi::contracts::Contract;
1462    ///
1463    /// #[tokio::main]
1464    /// async fn main() {
1465    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1466    ///     
1467    ///     let contract = Contract::stock("AAPL").build();
1468    ///     let details = client.contract_details(&contract).await.expect("request failed");
1469    ///     
1470    ///     for detail in details {
1471    ///         println!("Contract: {} - Exchange: {}", detail.contract.symbol, detail.contract.exchange);
1472    ///     }
1473    /// }
1474    /// ```
1475    pub async fn contract_details(&self, contract: &crate::contracts::Contract) -> Result<Vec<crate::contracts::ContractDetails>, Error> {
1476        crate::contracts::contract_details(self, contract).await
1477    }
1478
1479    /// Cancels an in-flight contract details request.
1480    ///
1481    /// # Arguments
1482    /// * `request_id` - The request ID returned by a prior `contract_details` call.
1483    pub async fn cancel_contract_details(&self, request_id: i32) -> Result<(), Error> {
1484        crate::contracts::cancel_contract_details(self, request_id).await
1485    }
1486
1487    /// Searches for stock contracts matching the provided pattern.
1488    ///
1489    /// # Arguments
1490    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name
1491    ///
1492    /// # Examples
1493    ///
1494    /// ```no_run
1495    /// use ibapi::Client;
1496    ///
1497    /// #[tokio::main]
1498    /// async fn main() {
1499    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1500    ///     
1501    ///     let symbols = client.matching_symbols("AAP").await.expect("request failed");
1502    ///     for symbol in symbols {
1503    ///         println!("{} - {} ({})", symbol.contract.symbol,
1504    ///                  symbol.contract.primary_exchange, symbol.contract.currency);
1505    ///     }
1506    /// }
1507    /// ```
1508    pub async fn matching_symbols(&self, pattern: &str) -> Result<Vec<crate::contracts::ContractDescription>, Error> {
1509        crate::contracts::matching_symbols(self, pattern).await
1510    }
1511
1512    /// Retrieves market rule details for a specific market rule ID.
1513    ///
1514    /// Market rules define how minimum price increments change with price.
1515    /// Rule IDs can be obtained from contract details.
1516    ///
1517    /// # Arguments
1518    /// * `market_rule_id` - The market rule ID to query
1519    ///
1520    /// # Examples
1521    ///
1522    /// ```no_run
1523    /// use ibapi::Client;
1524    ///
1525    /// #[tokio::main]
1526    /// async fn main() {
1527    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1528    ///     
1529    ///     let rule = client.market_rule(26).await.expect("request failed");
1530    ///     for increment in rule.price_increments {
1531    ///         println!("Above ${}: increment ${}", increment.low_edge, increment.increment);
1532    ///     }
1533    /// }
1534    /// ```
1535    pub async fn market_rule(&self, market_rule_id: i32) -> Result<crate::contracts::MarketRule, Error> {
1536        crate::contracts::market_rule(self, market_rule_id).await
1537    }
1538
1539    /// Calculates option price based on volatility and underlying price.
1540    ///
1541    /// # Arguments
1542    /// * `contract` - The option contract
1543    /// * `volatility` - Hypothetical volatility
1544    /// * `underlying_price` - Hypothetical underlying price
1545    ///
1546    /// # Examples
1547    ///
1548    /// ```no_run
1549    /// use ibapi::Client;
1550    /// use ibapi::contracts::Contract;
1551    ///
1552    /// #[tokio::main]
1553    /// async fn main() {
1554    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1555    ///     
1556    ///     let option = Contract::option("AAPL", "20240119", 150.0, "C");
1557    ///     let computation = client.calculate_option_price(&option, 0.3, 145.0).await
1558    ///         .expect("calculation failed");
1559    ///         
1560    ///     if let Some(price) = computation.option_price {
1561    ///         println!("Option price: ${:.2}", price);
1562    ///     }
1563    /// }
1564    /// ```
1565    pub async fn calculate_option_price(
1566        &self,
1567        contract: &crate::contracts::Contract,
1568        volatility: f64,
1569        underlying_price: f64,
1570    ) -> Result<crate::contracts::OptionComputation, Error> {
1571        crate::contracts::calculate_option_price(self, contract, volatility, underlying_price).await
1572    }
1573
1574    /// Calculates implied volatility based on option and underlying prices.
1575    ///
1576    /// # Arguments
1577    /// * `contract` - The option contract
1578    /// * `option_price` - Hypothetical option price
1579    /// * `underlying_price` - Hypothetical underlying price
1580    ///
1581    /// # Examples
1582    ///
1583    /// ```no_run
1584    /// use ibapi::Client;
1585    /// use ibapi::contracts::Contract;
1586    ///
1587    /// #[tokio::main]
1588    /// async fn main() {
1589    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1590    ///     
1591    ///     let option = Contract::option("AAPL", "20240119", 150.0, "C");
1592    ///     let computation = client.calculate_implied_volatility(&option, 7.5, 148.0).await
1593    ///         .expect("calculation failed");
1594    ///         
1595    ///     if let Some(iv) = computation.implied_volatility {
1596    ///         println!("Implied volatility: {:.2}%", iv * 100.0);
1597    ///     }
1598    /// }
1599    /// ```
1600    pub async fn calculate_implied_volatility(
1601        &self,
1602        contract: &crate::contracts::Contract,
1603        option_price: f64,
1604        underlying_price: f64,
1605    ) -> Result<crate::contracts::OptionComputation, Error> {
1606        crate::contracts::calculate_implied_volatility(self, contract, option_price, underlying_price).await
1607    }
1608
1609    /// Requests option chain data for an underlying instrument.
1610    ///
1611    /// Returns option expiration dates and strikes available for the specified underlying.
1612    ///
1613    /// # Arguments
1614    /// * `symbol` - The underlying symbol
1615    /// * `exchange` - The exchange
1616    /// * `security_type` - The underlying security type
1617    /// * `contract_id` - The underlying contract ID
1618    ///
1619    /// # Examples
1620    ///
1621    /// ```no_run
1622    /// use ibapi::Client;
1623    /// use ibapi::contracts::SecurityType;
1624    /// use futures::StreamExt;
1625    ///
1626    /// #[tokio::main]
1627    /// async fn main() {
1628    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1629    ///     
1630    ///     let mut chain = client.option_chain("AAPL", "SMART", SecurityType::Stock, 265598).await
1631    ///         .expect("request failed");
1632    ///         
1633    ///     while let Some(result) = chain.next().await {
1634    ///         match result {
1635    ///             Ok(data) => {
1636    ///                 println!("Expirations: {:?}", data.expirations);
1637    ///                 println!("Strikes: {:?}", data.strikes);
1638    ///             }
1639    ///             Err(e) => eprintln!("Error: {e:?}"),
1640    ///         }
1641    ///     }
1642    /// }
1643    /// ```
1644    pub async fn option_chain(
1645        &self,
1646        symbol: &str,
1647        exchange: &str,
1648        security_type: crate::contracts::SecurityType,
1649        contract_id: i32,
1650    ) -> Result<Subscription<crate::contracts::OptionChain>, Error> {
1651        crate::contracts::option_chain(self, symbol, exchange, security_type, contract_id).await
1652    }
1653
1654    // === Order Management ===
1655
1656    /// Subscribes to order update events. Only one subscription can be active at a time.
1657    ///
1658    /// This function returns a subscription that will receive updates of activity for all orders placed by the client.
1659    /// Use this when you need a global view of all order activity, especially with submit_order().
1660    ///
1661    /// # Examples
1662    ///
1663    /// ```no_run
1664    /// use futures::StreamExt;
1665    /// use ibapi::Client;
1666    /// use ibapi::orders::OrderUpdate;
1667    ///
1668    /// #[tokio::main]
1669    /// async fn main() {
1670    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1671    ///     
1672    ///     let mut stream = client.order_update_stream().await.expect("failed to create stream");
1673    ///     
1674    ///     while let Some(update) = stream.next().await {
1675    ///         match update {
1676    ///             Ok(OrderUpdate::OrderStatus(status)) => {
1677    ///                 println!("Order {} status: {}", status.order_id, status.status);
1678    ///             }
1679    ///             Ok(OrderUpdate::ExecutionData(exec)) => {
1680    ///                 println!("Execution: {} shares @ {}", exec.execution.shares, exec.execution.price);
1681    ///             }
1682    ///             _ => {}
1683    ///         }
1684    ///     }
1685    /// }
1686    /// ```
1687    pub async fn order_update_stream(&self) -> Result<Subscription<crate::orders::OrderUpdate>, Error> {
1688        crate::orders::order_update_stream(self).await
1689    }
1690
1691    /// Submits an Order (fire-and-forget).
1692    ///
1693    /// After the order is submitted correctly, events will be returned through the order_update_stream().
1694    /// This is a fire-and-forget method that does not wait for confirmation or return a subscription.
1695    ///
1696    /// # Arguments
1697    /// * `order_id` - Unique order identifier
1698    /// * `contract` - Contract to submit order for
1699    /// * `order` - Order details
1700    ///
1701    /// # Examples
1702    ///
1703    /// ```no_run
1704    /// use ibapi::Client;
1705    /// use ibapi::contracts::Contract;
1706    ///
1707    /// #[tokio::main]
1708    /// async fn main() {
1709    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1710    ///     
1711    ///     let contract = Contract::stock("AAPL").build();
1712    ///     
1713    ///     let order = client.order(&contract)
1714    ///         .buy(100)
1715    ///         .limit(150.0)
1716    ///         .build_order()
1717    ///         .expect("failed to build order");
1718    ///     
1719    ///     let order_id = client.next_order_id();
1720    ///     client.submit_order(order_id, &contract, &order).await.expect("failed to submit order");
1721    /// }
1722    /// ```
1723    pub async fn submit_order(&self, order_id: i32, contract: &crate::contracts::Contract, order: &crate::orders::Order) -> Result<(), Error> {
1724        crate::orders::submit_order(self, order_id, contract, order).await
1725    }
1726
1727    /// Submits an Order with a subscription for updates.
1728    ///
1729    /// After the order is submitted correctly, events will be returned concerning the order's activity
1730    /// through the returned subscription.
1731    ///
1732    /// # Arguments
1733    /// * `order_id` - Unique order identifier
1734    /// * `contract` - Contract to submit order for
1735    /// * `order` - Order details
1736    ///
1737    /// # Examples
1738    ///
1739    /// ```no_run
1740    /// use futures::StreamExt;
1741    /// use ibapi::Client;
1742    /// use ibapi::contracts::Contract;
1743    /// use ibapi::orders::PlaceOrder;
1744    ///
1745    /// #[tokio::main]
1746    /// async fn main() {
1747    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1748    ///     
1749    ///     let contract = Contract::stock("AAPL").build();
1750    ///     
1751    ///     let order = client.order(&contract)
1752    ///         .buy(100)
1753    ///         .limit(150.0)
1754    ///         .build_order()
1755    ///         .expect("failed to build order");
1756    ///     
1757    ///     let order_id = client.next_order_id();
1758    ///     let mut subscription = client.place_order(order_id, &contract, &order).await
1759    ///         .expect("failed to place order");
1760    ///         
1761    ///     while let Some(update) = subscription.next().await {
1762    ///         match update {
1763    ///             Ok(PlaceOrder::OrderStatus(status)) => {
1764    ///                 println!("Status: {}", status.status);
1765    ///                 if status.status == "Filled" { break; }
1766    ///             }
1767    ///             _ => {}
1768    ///         }
1769    ///     }
1770    /// }
1771    /// ```
1772    pub async fn place_order(
1773        &self,
1774        order_id: i32,
1775        contract: &crate::contracts::Contract,
1776        order: &crate::orders::Order,
1777    ) -> Result<Subscription<crate::orders::PlaceOrder>, Error> {
1778        crate::orders::place_order(self, order_id, contract, order).await
1779    }
1780
1781    /// Cancels an open Order.
1782    ///
1783    /// # Arguments
1784    /// * `order_id` - Order ID to cancel
1785    /// * `manual_order_cancel_time` - Time of manual order cancellation (empty string for API cancellations)
1786    pub async fn cancel_order(&self, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<crate::orders::CancelOrder>, Error> {
1787        crate::orders::cancel_order(self, order_id, manual_order_cancel_time).await
1788    }
1789
1790    /// Cancels all open Orders.
1791    pub async fn global_cancel(&self) -> Result<(), Error> {
1792        crate::orders::global_cancel(self).await
1793    }
1794
1795    /// Gets next valid order id.
1796    pub async fn next_valid_order_id(&self) -> Result<i32, Error> {
1797        crate::orders::next_valid_order_id(self).await
1798    }
1799
1800    /// Requests completed Orders.
1801    ///
1802    /// # Arguments
1803    /// * `api_only` - If true, only orders placed through the API are returned
1804    pub async fn completed_orders(&self, api_only: bool) -> Result<Subscription<crate::orders::Orders>, Error> {
1805        crate::orders::completed_orders(self, api_only).await
1806    }
1807
1808    /// Requests all open orders placed by this specific API client (identified by the API client id).
1809    /// For client ID 0, this will bind previous manual TWS orders.
1810    pub async fn open_orders(&self) -> Result<Subscription<crate::orders::Orders>, Error> {
1811        crate::orders::open_orders(self).await
1812    }
1813
1814    /// Requests all *current* open orders in associated accounts at the current moment.
1815    /// Open orders are returned once; this function does not initiate a subscription.
1816    pub async fn all_open_orders(&self) -> Result<Subscription<crate::orders::Orders>, Error> {
1817        crate::orders::all_open_orders(self).await
1818    }
1819
1820    /// Requests status updates about future orders placed from TWS. Can only be used with client ID 0.
1821    ///
1822    /// # Arguments
1823    /// * `auto_bind` - If true, newly submitted orders will be implicitly associated with this client
1824    pub async fn auto_open_orders(&self, auto_bind: bool) -> Result<Subscription<crate::orders::Orders>, Error> {
1825        crate::orders::auto_open_orders(self, auto_bind).await
1826    }
1827
1828    /// Requests current day's (since midnight) executions matching the filter.
1829    ///
1830    /// Only the current day's executions can be retrieved.
1831    /// Along with the ExecutionData, the CommissionReport will also be returned.
1832    ///
1833    /// # Arguments
1834    /// * `filter` - Filter criteria used to determine which execution reports are returned
1835    pub async fn executions(&self, filter: crate::orders::ExecutionFilter) -> Result<Subscription<crate::orders::Executions>, Error> {
1836        crate::orders::executions(self, filter).await
1837    }
1838
1839    /// Exercises an options contract.
1840    ///
1841    /// # Arguments
1842    /// * `contract` - Option contract to exercise
1843    /// * `exercise_action` - Whether to exercise (1) or lapse (2)
1844    /// * `exercise_quantity` - Number of contracts to exercise
1845    /// * `account` - Account for which to exercise
1846    /// * `ovrd` - Override default handling action
1847    /// * `manual_order_time` - Time of manual order entry
1848    pub async fn exercise_options(
1849        &self,
1850        contract: &crate::contracts::Contract,
1851        exercise_action: crate::orders::ExerciseAction,
1852        exercise_quantity: i32,
1853        account: &str,
1854        ovrd: bool,
1855        manual_order_time: Option<OffsetDateTime>,
1856    ) -> Result<Subscription<crate::orders::ExerciseOptions>, Error> {
1857        crate::orders::exercise_options(self, contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time).await
1858    }
1859
1860    // === News Management ===
1861
1862    /// Requests available news providers.
1863    ///
1864    /// Returns a list of news providers that the user has subscribed to.
1865    ///
1866    /// # Examples
1867    ///
1868    /// ```no_run
1869    /// use ibapi::Client;
1870    ///
1871    /// #[tokio::main]
1872    /// async fn main() {
1873    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1874    ///     
1875    ///     let providers = client.news_providers().await.expect("request failed");
1876    ///     for provider in providers {
1877    ///         println!("{} - {}", provider.code, provider.name);
1878    ///     }
1879    /// }
1880    /// ```
1881    pub async fn news_providers(&self) -> Result<Vec<crate::news::NewsProvider>, Error> {
1882        crate::news::news_providers(self).await
1883    }
1884
1885    /// Subscribes to IB News Bulletins.
1886    ///
1887    /// # Arguments
1888    /// * `all_messages` - If true, returns all messages including exchange availability
1889    ///
1890    /// # Examples
1891    ///
1892    /// ```no_run
1893    /// use ibapi::Client;
1894    /// use futures::StreamExt;
1895    ///
1896    /// #[tokio::main]
1897    /// async fn main() {
1898    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1899    ///     
1900    ///     let mut bulletins = client.news_bulletins(true).await.expect("request failed");
1901    ///     while let Some(result) = bulletins.next().await {
1902    ///         match result {
1903    ///             Ok(bulletin) => println!("{}: {}", bulletin.exchange, bulletin.message),
1904    ///             Err(e) => eprintln!("Error: {e:?}"),
1905    ///         }
1906    ///     }
1907    /// }
1908    /// ```
1909    pub async fn news_bulletins(&self, all_messages: bool) -> Result<Subscription<crate::news::NewsBulletin>, Error> {
1910        crate::news::news_bulletins(self, all_messages).await
1911    }
1912
1913    /// Requests historical news headlines.
1914    ///
1915    /// # Arguments
1916    /// * `contract_id` - Contract ID to get news for
1917    /// * `provider_codes` - List of provider codes to filter by
1918    /// * `start_time` - Start of the time period
1919    /// * `end_time` - End of the time period
1920    /// * `total_results` - Maximum number of headlines to return
1921    ///
1922    /// # Examples
1923    ///
1924    /// ```no_run
1925    /// use ibapi::Client;
1926    /// use futures::StreamExt;
1927    ///
1928    /// #[tokio::main]
1929    /// async fn main() {
1930    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1931    ///     
1932    ///     let contract_id = 265598; // AAPL
1933    ///     let providers = &["BRFG", "DJNL"];
1934    ///     let end_time = time::OffsetDateTime::now_utc();
1935    ///     let start_time = end_time - time::Duration::days(7);
1936    ///     
1937    ///     let mut news = client
1938    ///         .historical_news(contract_id, providers, start_time, end_time, 100)
1939    ///         .await
1940    ///         .expect("request failed");
1941    ///         
1942    ///     while let Some(result) = news.next().await {
1943    ///         match result {
1944    ///             Ok(article) => println!("{}: {}", article.time, article.headline),
1945    ///             Err(e) => eprintln!("Error: {e:?}"),
1946    ///         }
1947    ///     }
1948    /// }
1949    /// ```
1950    pub async fn historical_news(
1951        &self,
1952        contract_id: i32,
1953        provider_codes: &[&str],
1954        start_time: OffsetDateTime,
1955        end_time: OffsetDateTime,
1956        total_results: u8,
1957    ) -> Result<Subscription<crate::news::NewsArticle>, Error> {
1958        crate::news::historical_news(self, contract_id, provider_codes, start_time, end_time, total_results).await
1959    }
1960
1961    /// Requests the body of a news article.
1962    ///
1963    /// # Arguments
1964    /// * `provider_code` - The news provider code
1965    /// * `article_id` - The article ID
1966    ///
1967    /// # Examples
1968    ///
1969    /// ```no_run
1970    /// use ibapi::Client;
1971    ///
1972    /// #[tokio::main]
1973    /// async fn main() {
1974    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
1975    ///     
1976    ///     let article = client.news_article("BRFG", "BRFG$12345").await.expect("request failed");
1977    ///     println!("Article type: {:?}", article.article_type);
1978    ///     println!("Content: {}", article.article_text);
1979    /// }
1980    /// ```
1981    pub async fn news_article(&self, provider_code: &str, article_id: &str) -> Result<crate::news::NewsArticleBody, Error> {
1982        crate::news::news_article(self, provider_code, article_id).await
1983    }
1984
1985    /// Subscribes to real-time news for a specific contract.
1986    ///
1987    /// # Arguments
1988    /// * `contract` - The contract to monitor
1989    /// * `provider_codes` - List of provider codes to subscribe to
1990    ///
1991    /// # Examples
1992    ///
1993    /// ```no_run
1994    /// use ibapi::Client;
1995    /// use ibapi::contracts::Contract;
1996    /// use futures::StreamExt;
1997    ///
1998    /// #[tokio::main]
1999    /// async fn main() {
2000    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
2001    ///     
2002    ///     let contract = Contract::stock("AAPL").build();
2003    ///     let providers = &["BRFG", "DJNL"];
2004    ///     
2005    ///     let mut news = client.contract_news(&contract, providers).await.expect("request failed");
2006    ///     while let Some(result) = news.next().await {
2007    ///         match result {
2008    ///             Ok(article) => println!("{}: {}", article.time, article.headline),
2009    ///             Err(e) => eprintln!("Error: {e:?}"),
2010    ///         }
2011    ///     }
2012    /// }
2013    /// ```
2014    pub async fn contract_news(
2015        &self,
2016        contract: &crate::contracts::Contract,
2017        provider_codes: &[&str],
2018    ) -> Result<Subscription<crate::news::NewsArticle>, Error> {
2019        crate::news::contract_news(self, contract, provider_codes).await
2020    }
2021
2022    /// Subscribes to broad tape news from a specific provider.
2023    ///
2024    /// # Arguments
2025    /// * `provider_code` - The news provider code
2026    ///
2027    /// # Examples
2028    ///
2029    /// ```no_run
2030    /// use ibapi::Client;
2031    /// use futures::StreamExt;
2032    ///
2033    /// #[tokio::main]
2034    /// async fn main() {
2035    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
2036    ///     
2037    ///     let mut news = client.broad_tape_news("BRFG").await.expect("request failed");
2038    ///     while let Some(result) = news.next().await {
2039    ///         match result {
2040    ///             Ok(article) => println!("{}: {}", article.time, article.headline),
2041    ///             Err(e) => eprintln!("Error: {e:?}"),
2042    ///         }
2043    ///     }
2044    /// }
2045    /// ```
2046    pub async fn broad_tape_news(&self, provider_code: &str) -> Result<Subscription<crate::news::NewsArticle>, Error> {
2047        crate::news::broad_tape_news(self, provider_code).await
2048    }
2049
2050    // === Scanner ===
2051
2052    /// Requests scanner parameters available in TWS.
2053    ///
2054    /// Returns an XML string containing all available scanner parameters including
2055    /// scan types, locations, instruments, and filters.
2056    ///
2057    /// # Examples
2058    ///
2059    /// ```no_run
2060    /// use ibapi::Client;
2061    ///
2062    /// #[tokio::main]
2063    /// async fn main() {
2064    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
2065    ///     
2066    ///     let xml = client.scanner_parameters().await.expect("request failed");
2067    ///     println!("Scanner parameters XML: {} bytes", xml.len());
2068    /// }
2069    /// ```
2070    pub async fn scanner_parameters(&self) -> Result<String, Error> {
2071        crate::scanner::scanner_parameters(self).await
2072    }
2073
2074    /// Starts a subscription to market scanner results.
2075    ///
2076    /// Scans the market based on the specified criteria and returns matching contracts.
2077    ///
2078    /// # Arguments
2079    /// * `subscription` - Scanner subscription parameters defining the scan criteria
2080    /// * `filter` - Additional filters to apply to the scan
2081    ///
2082    /// # Examples
2083    ///
2084    /// ```no_run
2085    /// use ibapi::Client;
2086    /// use ibapi::scanner::ScannerSubscription;
2087    /// use futures::StreamExt;
2088    ///
2089    /// #[tokio::main]
2090    /// async fn main() {
2091    ///     let client = Client::connect("127.0.0.1:4002", 100).await.expect("connection failed");
2092    ///     
2093    ///     let subscription = ScannerSubscription {
2094    ///         number_of_rows: 10,
2095    ///         instrument: Some("STK".to_string()),
2096    ///         location_code: Some("STK.US.MAJOR".to_string()),
2097    ///         scan_code: Some("TOP_PERC_GAIN".to_string()),
2098    ///         above_price: Some(5.0),
2099    ///         ..Default::default()
2100    ///     };
2101    ///     
2102    ///     let mut scanner = client.scanner_subscription(&subscription, &vec![]).await
2103    ///         .expect("request failed");
2104    ///         
2105    ///     while let Some(result) = scanner.next().await {
2106    ///         match result {
2107    ///             Ok(data_list) => {
2108    ///                 for data in data_list {
2109    ///                     println!("Rank {}: {}", data.rank,
2110    ///                              data.contract_details.contract.symbol);
2111    ///                 }
2112    ///             }
2113    ///             Err(e) => eprintln!("Error: {e:?}"),
2114    ///         }
2115    ///     }
2116    /// }
2117    /// ```
2118    pub async fn scanner_subscription(
2119        &self,
2120        subscription: &crate::scanner::ScannerSubscription,
2121        filter: &Vec<crate::orders::TagValue>,
2122    ) -> Result<Subscription<Vec<crate::scanner::ScannerData>>, Error> {
2123        crate::scanner::scanner_subscription(self, subscription, filter).await
2124    }
2125
2126    /// Creates a stubbed client for testing
2127    #[cfg(test)]
2128    pub fn stubbed(message_bus: Arc<dyn AsyncMessageBus>, server_version: i32) -> Self {
2129        use crate::connection::ConnectionMetadata;
2130
2131        let connection_metadata = ConnectionMetadata {
2132            client_id: 100,
2133            next_order_id: 9000,
2134            server_version,
2135            managed_accounts: String::new(),
2136            connection_time: None,
2137            time_zone: None,
2138        };
2139
2140        Client::new(connection_metadata, message_bus).expect("Failed to create stubbed client")
2141    }
2142
2143    /// Get a reference to the message bus for testing
2144    #[cfg(test)]
2145    pub fn message_bus(&self) -> &Arc<dyn AsyncMessageBus> {
2146        &self.message_bus
2147    }
2148}
2149
2150#[cfg(test)]
2151mod tests {
2152    use super::Client;
2153    use crate::client::common::tests::*;
2154    use crate::contracts::{Currency, Exchange, Symbol};
2155    use crate::market_data::TradingHours;
2156
2157    const CLIENT_ID: i32 = 100;
2158
2159    #[tokio::test]
2160    async fn test_connect() {
2161        let gateway = setup_connect();
2162
2163        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2164
2165        assert_eq!(client.client_id(), CLIENT_ID);
2166        assert_eq!(client.server_version(), gateway.server_version());
2167        assert_eq!(client.time_zone, gateway.time_zone());
2168
2169        assert_eq!(gateway.requests().len(), 0, "No requests should be sent on connect");
2170    }
2171
2172    #[tokio::test]
2173    async fn test_server_time() {
2174        let (gateway, expectations) = setup_server_time();
2175
2176        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2177
2178        let server_time = client.server_time().await.unwrap();
2179        assert_eq!(server_time, expectations.server_time);
2180
2181        let requests = gateway.requests();
2182        assert_eq!(requests[0], "49\01\0");
2183    }
2184
2185    #[tokio::test]
2186    async fn test_next_valid_order_id() {
2187        let (gateway, expectations) = setup_next_valid_order_id();
2188
2189        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2190
2191        let next_valid_order_id = client.next_valid_order_id().await.unwrap();
2192        assert_eq!(next_valid_order_id, expectations.next_valid_order_id);
2193
2194        let requests = gateway.requests();
2195        assert_eq!(requests[0], "8\01\00\0");
2196    }
2197
2198    #[tokio::test]
2199    async fn test_managed_accounts() {
2200        let (gateway, expectations) = setup_managed_accounts();
2201
2202        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2203
2204        let accounts = client.managed_accounts().await.unwrap();
2205        assert_eq!(accounts, expectations.accounts);
2206
2207        let requests = gateway.requests();
2208        assert_eq!(requests[0], "17\01\0");
2209    }
2210
2211    #[tokio::test]
2212    async fn test_positions() {
2213        let gateway = setup_positions();
2214
2215        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2216
2217        let mut positions = client.positions().await.unwrap();
2218        let mut position_count = 0;
2219
2220        while let Some(position_update) = positions.next().await {
2221            match position_update.unwrap() {
2222                crate::accounts::PositionUpdate::Position(position) => {
2223                    assert_eq!(position.account, "DU1234567");
2224                    assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2225                    assert_eq!(position.position, 500.0);
2226                    assert_eq!(position.average_cost, 150.25);
2227                    position_count += 1;
2228                }
2229                crate::accounts::PositionUpdate::PositionEnd => {
2230                    break;
2231                }
2232            }
2233        }
2234
2235        assert_eq!(position_count, 1);
2236        let requests = gateway.requests();
2237        assert_eq!(requests[0], "61\01\0");
2238    }
2239
2240    #[tokio::test]
2241    async fn test_positions_multi() {
2242        use crate::accounts::types::AccountId;
2243
2244        let gateway = setup_positions_multi();
2245
2246        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2247
2248        let account = AccountId("DU1234567".to_string());
2249        let mut positions = client.positions_multi(Some(&account), None).await.unwrap();
2250        let mut position_count = 0;
2251
2252        while let Some(position_update) = positions.next().await {
2253            match position_update.unwrap() {
2254                crate::accounts::PositionUpdateMulti::Position(position) => {
2255                    position_count += 1;
2256                    if position_count == 1 {
2257                        assert_eq!(position.account, "DU1234567");
2258                        assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2259                        assert_eq!(position.position, 500.0);
2260                        assert_eq!(position.average_cost, 150.25);
2261                        assert_eq!(position.model_code, "MODEL1");
2262                    } else if position_count == 2 {
2263                        assert_eq!(position.account, "DU1234568");
2264                        assert_eq!(position.contract.symbol, Symbol::from("GOOGL"));
2265                        assert_eq!(position.position, 200.0);
2266                        assert_eq!(position.average_cost, 2500.00);
2267                        assert_eq!(position.model_code, "MODEL1");
2268                    }
2269                }
2270                crate::accounts::PositionUpdateMulti::PositionEnd => {
2271                    break;
2272                }
2273            }
2274        }
2275
2276        assert_eq!(position_count, 2);
2277        let requests = gateway.requests();
2278        assert_eq!(requests[0], "74\01\09000\0DU1234567\0\0");
2279    }
2280
2281    #[tokio::test]
2282    async fn test_account_summary() {
2283        use crate::accounts::types::AccountGroup;
2284
2285        let gateway = setup_account_summary();
2286
2287        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2288
2289        let group = AccountGroup("All".to_string());
2290        let tags = vec!["NetLiquidation", "TotalCashValue"];
2291
2292        let mut summaries = client.account_summary(&group, &tags).await.unwrap();
2293        let mut summary_count = 0;
2294
2295        while let Some(summary_result) = summaries.next().await {
2296            match summary_result.unwrap() {
2297                crate::accounts::AccountSummaryResult::Summary(summary) => {
2298                    assert_eq!(summary.account, "DU1234567");
2299                    assert_eq!(summary.currency, "USD");
2300
2301                    if summary.tag == "NetLiquidation" {
2302                        assert_eq!(summary.value, "25000.00");
2303                    } else if summary.tag == "TotalCashValue" {
2304                        assert_eq!(summary.value, "15000.00");
2305                    }
2306                    summary_count += 1;
2307                }
2308                crate::accounts::AccountSummaryResult::End => {
2309                    break;
2310                }
2311            }
2312        }
2313
2314        assert_eq!(summary_count, 2);
2315        let requests = gateway.requests();
2316        assert_eq!(requests[0], "62\01\09000\0All\0NetLiquidation,TotalCashValue\0");
2317    }
2318
2319    #[tokio::test]
2320    async fn test_pnl() {
2321        use crate::accounts::types::AccountId;
2322
2323        let gateway = setup_pnl();
2324
2325        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2326
2327        let account = AccountId("DU1234567".to_string());
2328        let mut pnl = client.pnl(&account, None).await.unwrap();
2329
2330        let first_pnl = pnl.next().await.unwrap().unwrap();
2331        assert_eq!(first_pnl.daily_pnl, 250.50);
2332        assert_eq!(first_pnl.unrealized_pnl, Some(1500.00));
2333        assert_eq!(first_pnl.realized_pnl, Some(750.00));
2334
2335        let requests = gateway.requests();
2336        assert_eq!(requests[0], "92\09000\0DU1234567\0\0");
2337    }
2338
2339    #[tokio::test]
2340    async fn test_pnl_single() {
2341        use crate::accounts::types::{AccountId, ContractId};
2342
2343        let gateway = setup_pnl_single();
2344
2345        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2346
2347        let account = AccountId("DU1234567".to_string());
2348        let contract_id = ContractId(12345);
2349        let mut pnl_single = client.pnl_single(&account, contract_id, None).await.unwrap();
2350
2351        let first_pnl = pnl_single.next().await.unwrap().unwrap();
2352        assert_eq!(first_pnl.position, 100.0);
2353        assert_eq!(first_pnl.daily_pnl, 150.25);
2354        assert_eq!(first_pnl.unrealized_pnl, 500.00);
2355        assert_eq!(first_pnl.realized_pnl, 250.00);
2356        assert_eq!(first_pnl.value, 1000.00);
2357
2358        let requests = gateway.requests();
2359        assert_eq!(requests[0], "94\09000\0DU1234567\0\012345\0");
2360    }
2361
2362    #[tokio::test]
2363    async fn test_account_updates() {
2364        use crate::accounts::types::AccountId;
2365
2366        let gateway = setup_account_updates();
2367
2368        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2369
2370        let account = AccountId("DU1234567".to_string());
2371        let mut updates = client.account_updates(&account).await.unwrap();
2372
2373        let mut value_count = 0;
2374        let mut portfolio_count = 0;
2375        let mut has_time_update = false;
2376        let mut has_end = false;
2377
2378        while let Some(update) = updates.next().await {
2379            match update.unwrap() {
2380                crate::accounts::AccountUpdate::AccountValue(value) => {
2381                    assert_eq!(value.key, "NetLiquidation");
2382                    assert_eq!(value.value, "25000.00");
2383                    assert_eq!(value.currency, "USD");
2384                    assert_eq!(value.account, Some("DU1234567".to_string()));
2385                    value_count += 1;
2386                }
2387                crate::accounts::AccountUpdate::PortfolioValue(portfolio) => {
2388                    assert_eq!(portfolio.contract.symbol, Symbol::from("AAPL"));
2389                    assert_eq!(portfolio.position, 500.0);
2390                    assert_eq!(portfolio.market_price, 151.50);
2391                    assert_eq!(portfolio.market_value, 75750.00);
2392                    assert_eq!(portfolio.average_cost, 150.25);
2393                    assert_eq!(portfolio.unrealized_pnl, 375.00);
2394                    assert_eq!(portfolio.realized_pnl, 125.00);
2395                    assert_eq!(portfolio.account, Some("DU1234567".to_string()));
2396                    portfolio_count += 1;
2397                }
2398                crate::accounts::AccountUpdate::UpdateTime(time) => {
2399                    assert_eq!(time.timestamp, "20240122 15:30:00");
2400                    has_time_update = true;
2401                }
2402                crate::accounts::AccountUpdate::End => {
2403                    has_end = true;
2404                    break;
2405                }
2406            }
2407        }
2408
2409        assert!(has_end, "Expected End message");
2410        assert_eq!(value_count, 1);
2411        assert_eq!(portfolio_count, 1);
2412        assert!(has_time_update);
2413
2414        let requests = gateway.requests();
2415        assert_eq!(requests[0], "6\02\01\0DU1234567\0");
2416    }
2417
2418    #[tokio::test]
2419    async fn test_family_codes() {
2420        let gateway = setup_family_codes();
2421
2422        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2423
2424        let family_codes = client.family_codes().await.unwrap();
2425
2426        assert_eq!(family_codes.len(), 2);
2427        assert_eq!(family_codes[0].account_id, "DU1234567");
2428        assert_eq!(family_codes[0].family_code, "FAM001");
2429        assert_eq!(family_codes[1].account_id, "DU1234568");
2430        assert_eq!(family_codes[1].family_code, "FAM002");
2431
2432        let requests = gateway.requests();
2433        assert_eq!(requests[0], "80\01\0");
2434    }
2435
2436    #[tokio::test]
2437    async fn test_account_updates_multi() {
2438        use crate::accounts::types::{AccountId, ModelCode};
2439
2440        let gateway = setup_account_updates_multi();
2441
2442        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2443
2444        let account = AccountId("DU1234567".to_string());
2445        let model_code: Option<ModelCode> = None;
2446        let mut updates = client.account_updates_multi(Some(&account), model_code.as_ref()).await.unwrap();
2447
2448        let mut cash_balance_found = false;
2449        let mut currency_found = false;
2450        let mut stock_market_value_found = false;
2451        let mut has_end = false;
2452
2453        while let Some(update) = updates.next().await {
2454            match update.unwrap() {
2455                crate::accounts::AccountUpdateMulti::AccountMultiValue(value) => {
2456                    assert_eq!(value.account, "DU1234567");
2457                    assert_eq!(value.model_code, "");
2458
2459                    match value.key.as_str() {
2460                        "CashBalance" => {
2461                            assert_eq!(value.value, "94629.71");
2462                            assert_eq!(value.currency, "USD");
2463                            cash_balance_found = true;
2464                        }
2465                        "Currency" => {
2466                            assert_eq!(value.value, "USD");
2467                            assert_eq!(value.currency, "USD");
2468                            currency_found = true;
2469                        }
2470                        "StockMarketValue" => {
2471                            assert_eq!(value.value, "0.00");
2472                            assert_eq!(value.currency, "BASE");
2473                            stock_market_value_found = true;
2474                        }
2475                        _ => panic!("Unexpected key: {}", value.key),
2476                    }
2477                }
2478                crate::accounts::AccountUpdateMulti::End => {
2479                    has_end = true;
2480                    break;
2481                }
2482            }
2483        }
2484
2485        assert!(cash_balance_found, "Expected CashBalance update");
2486        assert!(currency_found, "Expected Currency update");
2487        assert!(stock_market_value_found, "Expected StockMarketValue update");
2488        assert!(has_end, "Expected End message");
2489
2490        let requests = gateway.requests();
2491        assert_eq!(requests[0], "76\01\09000\0DU1234567\0\01\0");
2492    }
2493
2494    #[tokio::test]
2495    async fn test_contract_details() {
2496        let gateway = setup_contract_details();
2497
2498        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2499
2500        let contract = crate::contracts::Contract::stock("AAPL").build();
2501        let details = client.contract_details(&contract).await.expect("Failed to get contract details");
2502
2503        assert_eq!(details.len(), 1);
2504        let detail = &details[0];
2505
2506        // Verify contract fields
2507        assert_eq!(detail.contract.symbol, Symbol::from("AAPL"));
2508        assert_eq!(detail.contract.security_type, crate::contracts::SecurityType::Stock);
2509        assert_eq!(detail.contract.currency, Currency::from("USD"));
2510        assert_eq!(detail.contract.exchange, Exchange::from("NASDAQ"));
2511        assert_eq!(detail.contract.local_symbol, "AAPL");
2512        assert_eq!(detail.contract.trading_class, "AAPL");
2513        assert_eq!(detail.contract.contract_id, 265598);
2514        assert_eq!(detail.contract.primary_exchange, Exchange::from("NASDAQ"));
2515
2516        // Verify contract details fields
2517        assert_eq!(detail.market_name, "NMS");
2518        assert_eq!(detail.min_tick, 0.01);
2519        assert!(detail.order_types.contains(&"LMT".to_string()));
2520        assert!(detail.order_types.contains(&"MKT".to_string()));
2521        assert!(detail.valid_exchanges.contains(&"SMART".to_string()));
2522        assert_eq!(detail.long_name, "Apple Inc");
2523        assert_eq!(detail.industry, "Technology");
2524        assert_eq!(detail.category, "Computers");
2525        assert_eq!(detail.subcategory, "Computers");
2526        assert_eq!(detail.time_zone_id, "US/Eastern");
2527        assert_eq!(detail.stock_type, "NMS");
2528        assert_eq!(detail.min_size, 1.0);
2529        assert_eq!(detail.size_increment, 1.0);
2530        assert_eq!(detail.suggested_size_increment, 1.0);
2531
2532        let requests = gateway.requests();
2533        // Request format: OutgoingMessages::RequestContractData(9), version(8), request_id, contract_id(0),
2534        // symbol, security_type, last_trade_date, strike, right, multiplier, exchange, primary_exchange,
2535        // currency, local_symbol, trading_class, include_expired, security_id_type, security_id, issuer_id
2536        assert_eq!(requests[0], "9\08\09000\00\0AAPL\0STK\0\00\0\0\0SMART\0\0USD\0\0\00\0\0\0");
2537    }
2538
2539    #[tokio::test]
2540    async fn test_matching_symbols() {
2541        let gateway = setup_matching_symbols();
2542
2543        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2544
2545        let contract_descriptions = client.matching_symbols("AAP").await.expect("Failed to get matching symbols");
2546
2547        assert_eq!(contract_descriptions.len(), 2, "Should have 2 matching symbols");
2548
2549        // First contract description
2550        assert_eq!(contract_descriptions[0].contract.contract_id, 265598);
2551        assert_eq!(contract_descriptions[0].contract.symbol, Symbol::from("AAPL"));
2552        assert_eq!(contract_descriptions[0].contract.security_type, crate::contracts::SecurityType::Stock);
2553        assert_eq!(contract_descriptions[0].contract.primary_exchange, Exchange::from("NASDAQ"));
2554        assert_eq!(contract_descriptions[0].contract.currency, Currency::from("USD"));
2555        assert_eq!(contract_descriptions[0].derivative_security_types.len(), 2);
2556        assert_eq!(contract_descriptions[0].derivative_security_types[0], "OPT");
2557        assert_eq!(contract_descriptions[0].derivative_security_types[1], "WAR");
2558        assert_eq!(contract_descriptions[0].contract.description, "Apple Inc.");
2559        assert_eq!(contract_descriptions[0].contract.issuer_id, "AAPL123");
2560
2561        // Second contract description
2562        assert_eq!(contract_descriptions[1].contract.contract_id, 276821);
2563        assert_eq!(contract_descriptions[1].contract.symbol, Symbol::from("MSFT"));
2564        assert_eq!(contract_descriptions[1].contract.security_type, crate::contracts::SecurityType::Stock);
2565        assert_eq!(contract_descriptions[1].contract.primary_exchange, Exchange::from("NASDAQ"));
2566        assert_eq!(contract_descriptions[1].contract.currency, Currency::from("USD"));
2567        assert_eq!(contract_descriptions[1].derivative_security_types.len(), 1);
2568        assert_eq!(contract_descriptions[1].derivative_security_types[0], "OPT");
2569        assert_eq!(contract_descriptions[1].contract.description, "Microsoft Corporation");
2570        assert_eq!(contract_descriptions[1].contract.issuer_id, "MSFT456");
2571
2572        // Verify request format
2573        let requests = gateway.requests();
2574        assert_eq!(requests.len(), 1, "Should have 1 request");
2575        // Request format: RequestMatchingSymbols(81), request_id, pattern
2576        assert!(requests[0].starts_with("81\0"), "Request should start with message type 81");
2577        assert!(requests[0].contains("\0AAP\0"), "Request should contain the pattern AAP");
2578    }
2579
2580    #[tokio::test]
2581    async fn test_market_rule() {
2582        let gateway = setup_market_rule();
2583
2584        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2585
2586        let market_rule = client.market_rule(26).await.expect("Failed to get market rule");
2587
2588        // Verify market rule ID
2589        assert_eq!(market_rule.market_rule_id, 26, "Market rule ID should be 26");
2590
2591        // Verify price increments
2592        assert_eq!(market_rule.price_increments.len(), 3, "Should have 3 price increments");
2593
2594        // First increment: 0-100, increment 0.01
2595        assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "First increment low edge");
2596        assert_eq!(market_rule.price_increments[0].increment, 0.01, "First increment value");
2597
2598        // Second increment: 100-1000, increment 0.05
2599        assert_eq!(market_rule.price_increments[1].low_edge, 100.0, "Second increment low edge");
2600        assert_eq!(market_rule.price_increments[1].increment, 0.05, "Second increment value");
2601
2602        // Third increment: 1000+, increment 0.10
2603        assert_eq!(market_rule.price_increments[2].low_edge, 1000.0, "Third increment low edge");
2604        assert_eq!(market_rule.price_increments[2].increment, 0.10, "Third increment value");
2605
2606        // Verify request format
2607        let requests = gateway.requests();
2608        assert_eq!(requests.len(), 1, "Should have 1 request");
2609        // Request format: RequestMarketRule(91), market_rule_id
2610        assert_eq!(requests[0], "91\026\0", "Request should be message type 91 with market rule ID 26");
2611    }
2612
2613    #[tokio::test]
2614    async fn test_calculate_option_price() {
2615        let gateway = setup_calculate_option_price();
2616
2617        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2618
2619        // Create an option contract
2620        let contract = crate::contracts::Contract {
2621            symbol: Symbol::from("AAPL"),
2622            security_type: crate::contracts::SecurityType::Option,
2623            exchange: Exchange::from("SMART"),
2624            currency: Currency::from("USD"),
2625            last_trade_date_or_contract_month: "20250120".to_string(),
2626            strike: 100.0,
2627            right: "C".to_string(),
2628            ..Default::default()
2629        };
2630
2631        let volatility = 0.25;
2632        let underlying_price = 100.0;
2633
2634        let computation = client
2635            .calculate_option_price(&contract, volatility, underlying_price)
2636            .await
2637            .expect("Failed to calculate option price");
2638
2639        // Verify computation results
2640        assert_eq!(
2641            computation.field,
2642            crate::contracts::tick_types::TickType::ModelOption,
2643            "Should be ModelOption tick type"
2644        );
2645        assert_eq!(computation.tick_attribute, Some(0), "Tick attribute should be 0");
2646        assert_eq!(computation.implied_volatility, Some(0.25), "Implied volatility should match");
2647        assert_eq!(computation.delta, Some(0.5), "Delta should be 0.5");
2648        assert_eq!(computation.option_price, Some(12.75), "Option price should be 12.75");
2649        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2650        assert_eq!(computation.gamma, Some(0.05), "Gamma should be 0.05");
2651        assert_eq!(computation.vega, Some(0.02), "Vega should be 0.02");
2652        assert_eq!(computation.theta, Some(-0.01), "Theta should be -0.01");
2653        assert_eq!(computation.underlying_price, Some(100.0), "Underlying price should be 100");
2654
2655        // Verify request format
2656        let requests = gateway.requests();
2657        assert_eq!(requests.len(), 1, "Should have 1 request");
2658        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, volatility, underlying_price
2659        assert!(
2660            requests[0].starts_with("54\03\0"),
2661            "Request should start with message type 54 and version 3"
2662        );
2663        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2664        assert!(requests[0].contains("\00.25\0"), "Request should contain volatility 0.25");
2665        assert!(requests[0].contains("\0100\0"), "Request should contain underlying price 100");
2666    }
2667
2668    #[tokio::test]
2669    async fn test_calculate_implied_volatility() {
2670        let gateway = setup_calculate_implied_volatility();
2671
2672        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2673
2674        // Create an option contract
2675        let contract = crate::contracts::Contract {
2676            symbol: Symbol::from("MSFT"),
2677            security_type: crate::contracts::SecurityType::Option,
2678            exchange: Exchange::from("SMART"),
2679            currency: Currency::from("USD"),
2680            last_trade_date_or_contract_month: "20250220".to_string(),
2681            strike: 105.0,
2682            right: "P".to_string(), // Put option
2683            ..Default::default()
2684        };
2685
2686        let option_price = 15.50;
2687        let underlying_price = 105.0;
2688
2689        let computation = client
2690            .calculate_implied_volatility(&contract, option_price, underlying_price)
2691            .await
2692            .expect("Failed to calculate implied volatility");
2693
2694        // Verify computation results
2695        assert_eq!(
2696            computation.field,
2697            crate::contracts::tick_types::TickType::ModelOption,
2698            "Should be ModelOption tick type"
2699        );
2700        assert_eq!(computation.tick_attribute, Some(1), "Tick attribute should be 1 (price-based)");
2701        assert_eq!(computation.implied_volatility, Some(0.35), "Implied volatility should be 0.35");
2702        assert_eq!(computation.delta, Some(0.45), "Delta should be 0.45");
2703        assert_eq!(computation.option_price, Some(15.50), "Option price should be 15.50");
2704        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2705        assert_eq!(computation.gamma, Some(0.04), "Gamma should be 0.04");
2706        assert_eq!(computation.vega, Some(0.03), "Vega should be 0.03");
2707        assert_eq!(computation.theta, Some(-0.02), "Theta should be -0.02");
2708        assert_eq!(computation.underlying_price, Some(105.0), "Underlying price should be 105");
2709
2710        // Verify request format
2711        let requests = gateway.requests();
2712        assert_eq!(requests.len(), 1, "Should have 1 request");
2713        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, option_price, underlying_price
2714        assert!(
2715            requests[0].starts_with("54\03\0"),
2716            "Request should start with message type 54 and version 3"
2717        );
2718        assert!(requests[0].contains("\0MSFT\0"), "Request should contain symbol MSFT");
2719        assert!(requests[0].contains("\015.5\0"), "Request should contain option price 15.5");
2720        assert!(requests[0].contains("\0105\0"), "Request should contain underlying price 105");
2721    }
2722
2723    #[tokio::test]
2724    async fn test_option_chain() {
2725        let gateway = setup_option_chain();
2726
2727        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2728
2729        let subscription = client
2730            .option_chain("AAPL", "", crate::contracts::SecurityType::Stock, 0)
2731            .await
2732            .expect("Failed to get option chain");
2733
2734        let mut chains = Vec::new();
2735        let mut subscription = subscription;
2736        while let Some(chain_result) = subscription.next().await {
2737            match chain_result {
2738                Ok(chain) => chains.push(chain),
2739                Err(crate::Error::EndOfStream) => break,
2740                Err(e) => panic!("Unexpected error: {:?}", e),
2741            }
2742        }
2743
2744        // Verify we received the expected chains
2745        assert_eq!(chains.len(), 2, "Should have 2 option chains");
2746
2747        // Verify first chain (SMART)
2748        assert_eq!(chains[0].exchange, "SMART", "First chain should be SMART");
2749        assert_eq!(chains[0].underlying_contract_id, 265598, "Should have correct contract ID");
2750        assert_eq!(chains[0].trading_class, "AAPL", "Should have correct trading class");
2751        assert_eq!(chains[0].multiplier, "100", "Should have correct multiplier");
2752        assert_eq!(chains[0].expirations.len(), 3, "SMART should have 3 expirations");
2753        assert_eq!(chains[0].expirations[0], "20250117", "First expiration should be 20250117");
2754        assert_eq!(chains[0].expirations[1], "20250221", "Second expiration should be 20250221");
2755        assert_eq!(chains[0].expirations[2], "20250321", "Third expiration should be 20250321");
2756        assert_eq!(chains[0].strikes.len(), 5, "SMART should have 5 strikes");
2757        assert_eq!(chains[0].strikes[0], 90.0, "First strike should be 90.0");
2758        assert_eq!(chains[0].strikes[4], 110.0, "Last strike should be 110.0");
2759
2760        // Verify second chain (CBOE)
2761        assert_eq!(chains[1].exchange, "CBOE", "Second chain should be CBOE");
2762        assert_eq!(chains[1].underlying_contract_id, 265598, "Should have correct contract ID");
2763        assert_eq!(chains[1].trading_class, "AAPL", "Should have correct trading class");
2764        assert_eq!(chains[1].multiplier, "100", "Should have correct multiplier");
2765        assert_eq!(chains[1].expirations.len(), 2, "CBOE should have 2 expirations");
2766        assert_eq!(chains[1].strikes.len(), 4, "CBOE should have 4 strikes");
2767
2768        // Verify request format
2769        let requests = gateway.requests();
2770        assert_eq!(requests.len(), 1, "Should have 1 request");
2771        // Request format: RequestSecurityDefinitionOptionalParameters(78), request_id, symbol, exchange, security_type, contract_id
2772        assert!(requests[0].starts_with("78\0"), "Request should start with message type 78");
2773        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2774        assert!(requests[0].contains("\0STK\0"), "Request should contain security type STK");
2775    }
2776
2777    #[tokio::test]
2778    async fn test_place_order() {
2779        use crate::client::common::tests::setup_place_order;
2780        use crate::contracts::Contract;
2781        use crate::orders::{order_builder, Action, PlaceOrder};
2782
2783        // Initialize env_logger for debug output
2784        let _ = env_logger::try_init();
2785
2786        let gateway = setup_place_order();
2787        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2788
2789        // Create a stock contract
2790        let contract = Contract::stock("AAPL").build();
2791
2792        // Create a market order
2793        let order = order_builder::market_order(Action::Buy, 100.0);
2794
2795        // Use order ID 1001 to match the mock responses
2796        let order_id = 1001;
2797
2798        // Place the order
2799        let mut subscription = client.place_order(order_id, &contract, &order).await.expect("Failed to place order");
2800
2801        // Collect all events from the subscription
2802        let mut order_status_count = 0;
2803        let mut _open_order_count = 0;
2804        let mut execution_count = 0;
2805        let mut commission_count = 0;
2806
2807        // We expect 6 messages total (3 order statuses, 1 open order, 1 execution, 1 commission)
2808        // Take only the expected number of events to avoid reading the shutdown message
2809        for _ in 0..6 {
2810            let event = match subscription.next().await {
2811                Some(Ok(event)) => event,
2812                Some(Err(crate::Error::EndOfStream)) => break,
2813                Some(Err(e)) => panic!("Unexpected error: {:?}", e),
2814                None => break,
2815            };
2816
2817            match event {
2818                PlaceOrder::OrderStatus(status) => {
2819                    order_status_count += 1;
2820                    assert_eq!(status.order_id, order_id);
2821
2822                    if order_status_count == 1 {
2823                        // First status: PreSubmitted
2824                        assert_eq!(status.status, "PreSubmitted");
2825                        assert_eq!(status.filled, 0.0);
2826                        assert_eq!(status.remaining, 100.0);
2827                    } else if order_status_count == 2 {
2828                        // Second status: Submitted
2829                        assert_eq!(status.status, "Submitted");
2830                        assert_eq!(status.filled, 0.0);
2831                        assert_eq!(status.remaining, 100.0);
2832                    } else if order_status_count == 3 {
2833                        // Third status: Filled
2834                        assert_eq!(status.status, "Filled");
2835                        assert_eq!(status.filled, 100.0);
2836                        assert_eq!(status.remaining, 0.0);
2837                        assert_eq!(status.average_fill_price, 150.25);
2838                    }
2839                }
2840                PlaceOrder::OpenOrder(order_data) => {
2841                    _open_order_count += 1;
2842                    assert_eq!(order_data.order_id, order_id);
2843                    assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2844                    assert_eq!(order_data.contract.contract_id, 265598);
2845                    assert_eq!(order_data.order.action, Action::Buy);
2846                    assert_eq!(order_data.order.total_quantity, 100.0);
2847                    assert_eq!(order_data.order.order_type, "LMT");
2848                    assert_eq!(order_data.order.limit_price, Some(1.0));
2849                }
2850                PlaceOrder::ExecutionData(exec_data) => {
2851                    execution_count += 1;
2852                    assert_eq!(exec_data.execution.order_id, order_id);
2853                    assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2854                    assert_eq!(exec_data.execution.shares, 100.0);
2855                    assert_eq!(exec_data.execution.price, 150.25);
2856                }
2857                PlaceOrder::CommissionReport(report) => {
2858                    commission_count += 1;
2859                    assert_eq!(report.commission, 1.25);
2860                    assert_eq!(report.currency, "USD");
2861                }
2862                PlaceOrder::Message(_) => {
2863                    // Skip any messages
2864                }
2865            }
2866        }
2867
2868        // Verify we received all expected events
2869        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2870        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2871        assert_eq!(execution_count, 1, "Should receive 1 execution");
2872        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2873
2874        // Verify the request was sent
2875        let requests = gateway.requests();
2876        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2877        // PlaceOrder message type is 3
2878        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2879        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2880    }
2881
2882    #[tokio::test]
2883    async fn test_submit_order_with_order_update_stream() {
2884        use crate::client::common::tests::setup_place_order;
2885        use crate::contracts::Contract;
2886        use crate::orders::{order_builder, Action, OrderUpdate};
2887
2888        // Initialize env_logger for debug output
2889        let _ = env_logger::try_init();
2890
2891        let gateway = setup_place_order();
2892        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2893
2894        // Create a stock contract
2895        let contract = Contract::stock("AAPL").build();
2896
2897        // Create a market order
2898        let order = order_builder::market_order(Action::Buy, 100.0);
2899
2900        // Use order ID 1001 to match the mock responses
2901        let order_id = 1001;
2902
2903        // First, start the order update stream
2904        let mut update_stream = client.order_update_stream().await.expect("Failed to create order update stream");
2905
2906        // Submit the order (fire and forget)
2907        client.submit_order(order_id, &contract, &order).await.expect("Failed to submit order");
2908
2909        // Collect events from the update stream
2910        let mut order_status_count = 0;
2911        let mut _open_order_count = 0;
2912        let mut execution_count = 0;
2913        let mut commission_count = 0;
2914
2915        // Read events from the update stream with timeout
2916        println!("Starting to read from update stream...");
2917        let timeout_duration = std::time::Duration::from_millis(500);
2918        let mut events_received = 0;
2919
2920        while events_received < 6 {
2921            let update = match tokio::time::timeout(timeout_duration, update_stream.next()).await {
2922                Ok(Some(Ok(update))) => {
2923                    events_received += 1;
2924                    println!("Event {}: {:?}", events_received, &update);
2925                    update
2926                }
2927                Ok(Some(Err(e))) => panic!("Error receiving update: {}", e),
2928                Ok(None) => break, // Stream ended
2929                Err(_) => break,   // Timeout reached
2930            };
2931
2932            match update {
2933                OrderUpdate::OrderStatus(status) => {
2934                    order_status_count += 1;
2935                    assert_eq!(status.order_id, order_id);
2936
2937                    if order_status_count == 1 {
2938                        // First status: PreSubmitted
2939                        assert_eq!(status.status, "PreSubmitted");
2940                        assert_eq!(status.filled, 0.0);
2941                        assert_eq!(status.remaining, 100.0);
2942                    } else if order_status_count == 2 {
2943                        // Second status: Submitted
2944                        assert_eq!(status.status, "Submitted");
2945                        assert_eq!(status.filled, 0.0);
2946                        assert_eq!(status.remaining, 100.0);
2947                    } else if order_status_count == 3 {
2948                        // Third status: Filled
2949                        assert_eq!(status.status, "Filled");
2950                        assert_eq!(status.filled, 100.0);
2951                        assert_eq!(status.remaining, 0.0);
2952                        assert_eq!(status.average_fill_price, 150.25);
2953                    }
2954                }
2955                OrderUpdate::OpenOrder(order_data) => {
2956                    _open_order_count += 1;
2957                    assert_eq!(order_data.order_id, order_id);
2958                    assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2959                    assert_eq!(order_data.contract.contract_id, 265598);
2960                    assert_eq!(order_data.order.action, Action::Buy);
2961                    assert_eq!(order_data.order.total_quantity, 100.0);
2962                    assert_eq!(order_data.order.order_type, "LMT");
2963                    assert_eq!(order_data.order.limit_price, Some(1.0));
2964                }
2965                OrderUpdate::ExecutionData(exec_data) => {
2966                    execution_count += 1;
2967                    assert_eq!(exec_data.execution.order_id, order_id);
2968                    assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2969                    assert_eq!(exec_data.execution.shares, 100.0);
2970                    assert_eq!(exec_data.execution.price, 150.25);
2971                }
2972                OrderUpdate::CommissionReport(report) => {
2973                    commission_count += 1;
2974                    assert_eq!(report.commission, 1.25);
2975                    assert_eq!(report.currency, "USD");
2976                }
2977                OrderUpdate::Message(_) => {
2978                    // Skip any messages
2979                }
2980            }
2981        }
2982
2983        // Verify we received all expected events
2984        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2985        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2986        assert_eq!(execution_count, 1, "Should receive 1 execution");
2987        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2988
2989        // Verify the request was sent
2990        let requests = gateway.requests();
2991        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2992        // PlaceOrder message type is 3
2993        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2994        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2995    }
2996
2997    #[tokio::test]
2998    async fn test_open_orders() {
2999        use crate::client::common::tests::setup_open_orders;
3000        use crate::orders::{Action, Orders};
3001
3002        // Initialize env_logger for debug output
3003        let _ = env_logger::try_init();
3004
3005        let gateway = setup_open_orders();
3006        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3007
3008        // Request open orders
3009        let mut subscription = client.open_orders().await.expect("Failed to request open orders");
3010
3011        // Collect orders from the subscription
3012        let mut orders = Vec::new();
3013        while let Some(result) = subscription.next().await {
3014            match result {
3015                Ok(Orders::OrderData(order_data)) => {
3016                    orders.push(order_data);
3017                }
3018                Ok(Orders::OrderStatus(_)) => {
3019                    // Skip order status messages for this test
3020                }
3021                Ok(Orders::Notice(_)) => {
3022                    // Skip notices
3023                }
3024                Err(crate::Error::EndOfStream) => break,
3025                Err(e) => panic!("Unexpected error: {:?}", e),
3026            }
3027        }
3028
3029        // Verify we received 2 orders
3030        assert_eq!(orders.len(), 2, "Should receive 2 open orders");
3031
3032        // Verify first order (AAPL)
3033        let order1 = &orders[0];
3034        assert_eq!(order1.order_id, 1001);
3035        assert_eq!(order1.contract.symbol, Symbol::from("AAPL"));
3036        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
3037        assert_eq!(order1.order.action, Action::Buy);
3038        assert_eq!(order1.order.total_quantity, 100.0);
3039        assert_eq!(order1.order.order_type, "MKT");
3040        assert_eq!(order1.order_state.status, "PreSubmitted");
3041
3042        // Verify second order (MSFT)
3043        let order2 = &orders[1];
3044        assert_eq!(order2.order_id, 1002);
3045        assert_eq!(order2.contract.symbol, Symbol::from("MSFT"));
3046        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3047        assert_eq!(order2.order.action, Action::Sell);
3048        assert_eq!(order2.order.total_quantity, 50.0);
3049        assert_eq!(order2.order.order_type, "LMT");
3050        assert_eq!(order2.order.limit_price, Some(350.0));
3051        assert_eq!(order2.order_state.status, "Submitted");
3052
3053        // Verify the request was sent correctly
3054        let requests = gateway.requests();
3055        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3056        assert_eq!(requests[0], "5\01\0", "Request should be RequestOpenOrders with version 1");
3057    }
3058
3059    #[tokio::test]
3060    async fn test_all_open_orders() {
3061        use crate::client::common::tests::setup_all_open_orders;
3062        use crate::orders::{Action, Orders};
3063
3064        // Initialize env_logger for debug output
3065        let _ = env_logger::try_init();
3066
3067        let gateway = setup_all_open_orders();
3068        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3069
3070        // Request all open orders
3071        let mut subscription = client.all_open_orders().await.expect("Failed to request all open orders");
3072
3073        // Collect orders from the subscription
3074        let mut orders = Vec::new();
3075        while let Some(result) = subscription.next().await {
3076            match result {
3077                Ok(Orders::OrderData(order_data)) => {
3078                    orders.push(order_data);
3079                }
3080                Ok(Orders::OrderStatus(_)) => {
3081                    // Skip order status messages for this test
3082                }
3083                Ok(Orders::Notice(_)) => {
3084                    // Skip notices
3085                }
3086                Err(crate::Error::EndOfStream) => break,
3087                Err(e) => panic!("Unexpected error: {:?}", e),
3088            }
3089        }
3090
3091        // Verify we received 3 orders (from different clients)
3092        assert_eq!(orders.len(), 3, "Should receive 3 open orders from all accounts");
3093
3094        // Verify first order (TSLA from client 101)
3095        let order1 = &orders[0];
3096        assert_eq!(order1.order_id, 2001);
3097        assert_eq!(order1.contract.symbol, Symbol::from("TSLA"));
3098        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
3099        assert_eq!(order1.order.action, Action::Buy);
3100        assert_eq!(order1.order.total_quantity, 10.0);
3101        assert_eq!(order1.order.order_type, "LMT");
3102        assert_eq!(order1.order.limit_price, Some(420.0));
3103        assert_eq!(order1.order.account, "DU1236110");
3104
3105        // Verify second order (AMZN from client 102)
3106        let order2 = &orders[1];
3107        assert_eq!(order2.order_id, 2002);
3108        assert_eq!(order2.contract.symbol, Symbol::from("AMZN"));
3109        assert_eq!(order2.order.action, Action::Sell);
3110        assert_eq!(order2.order.total_quantity, 5.0);
3111        assert_eq!(order2.order.order_type, "MKT");
3112        assert_eq!(order2.order.account, "DU1236111");
3113
3114        // Verify third order (GOOGL from current client 100)
3115        let order3 = &orders[2];
3116        assert_eq!(order3.order_id, 1003);
3117        assert_eq!(order3.contract.symbol, Symbol::from("GOOGL"));
3118        assert_eq!(order3.order.action, Action::Buy);
3119        assert_eq!(order3.order.total_quantity, 20.0);
3120        assert_eq!(order3.order.order_type, "LMT");
3121        assert_eq!(order3.order.limit_price, Some(2800.0));
3122        assert_eq!(order3.order.account, "DU1236109");
3123
3124        // Verify the request was sent correctly
3125        let requests = gateway.requests();
3126        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3127        assert_eq!(requests[0], "16\01\0", "Request should be RequestAllOpenOrders with version 1");
3128    }
3129
3130    #[tokio::test]
3131    async fn test_auto_open_orders() {
3132        use crate::client::common::tests::setup_auto_open_orders;
3133        use crate::orders::Orders;
3134
3135        // Initialize env_logger for debug output
3136        let _ = env_logger::try_init();
3137
3138        let gateway = setup_auto_open_orders();
3139        // Note: auto_open_orders usually requires client_id 0 for real TWS connections,
3140        // but for testing we use CLIENT_ID (100) to match the mock gateway expectation
3141        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3142
3143        // Request auto open orders with auto_bind=true
3144        let mut subscription = client.auto_open_orders(true).await.expect("Failed to request auto open orders");
3145
3146        // Collect messages from the subscription
3147        let mut order_statuses = Vec::new();
3148        let mut orders = Vec::new();
3149        while let Some(result) = subscription.next().await {
3150            match result {
3151                Ok(Orders::OrderData(order_data)) => {
3152                    orders.push(order_data);
3153                }
3154                Ok(Orders::OrderStatus(status)) => {
3155                    order_statuses.push(status);
3156                }
3157                Ok(Orders::Notice(_)) => {
3158                    // Skip notices
3159                }
3160                Err(crate::Error::EndOfStream) => break,
3161                Err(e) => panic!("Unexpected error: {:?}", e),
3162            }
3163        }
3164
3165        // Verify we received order status updates
3166        assert_eq!(order_statuses.len(), 2, "Should receive 2 order status updates");
3167
3168        // Verify first status (PreSubmitted)
3169        let status1 = &order_statuses[0];
3170        assert_eq!(status1.order_id, 3001);
3171        assert_eq!(status1.status, "PreSubmitted");
3172
3173        // Verify second status (Submitted)
3174        let status2 = &order_statuses[1];
3175        assert_eq!(status2.order_id, 3001);
3176        assert_eq!(status2.status, "Submitted");
3177
3178        // Verify we received 1 order
3179        assert_eq!(orders.len(), 1, "Should receive 1 order");
3180
3181        // Verify the order (FB from TWS)
3182        let order = &orders[0];
3183        assert_eq!(order.order_id, 3001);
3184        assert_eq!(order.contract.symbol, Symbol::from("FB"));
3185        assert_eq!(order.contract.security_type, crate::contracts::SecurityType::Stock);
3186        assert_eq!(order.order.action, crate::orders::Action::Buy);
3187        assert_eq!(order.order.total_quantity, 50.0);
3188        assert_eq!(order.order.order_type, "MKT");
3189        assert_eq!(order.order.account, "TWS");
3190
3191        // Verify the request was sent correctly
3192        let requests = gateway.requests();
3193        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3194        assert_eq!(
3195            requests[0], "15\01\01\0",
3196            "Request should be RequestAutoOpenOrders with version 1 and auto_bind=true"
3197        );
3198    }
3199
3200    #[tokio::test]
3201    async fn test_completed_orders() {
3202        use crate::client::common::tests::setup_completed_orders;
3203        use crate::orders::{Action, Orders};
3204
3205        // Initialize env_logger for debug output
3206        let _ = env_logger::try_init();
3207
3208        let gateway = setup_completed_orders();
3209        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3210
3211        // Request completed orders (api_only=false to get all completed orders)
3212        let mut subscription = client.completed_orders(false).await.expect("Failed to request completed orders");
3213
3214        // Collect orders from the subscription
3215        let mut orders = Vec::new();
3216        while let Some(result) = subscription.next().await {
3217            match result {
3218                Ok(Orders::OrderData(order_data)) => {
3219                    orders.push(order_data);
3220                }
3221                Ok(Orders::OrderStatus(_)) => {
3222                    // Skip order status messages
3223                }
3224                Ok(Orders::Notice(_)) => {
3225                    // Skip notices
3226                }
3227                Err(crate::Error::EndOfStream) => break,
3228                Err(e) => panic!("Unexpected error: {:?}", e),
3229            }
3230        }
3231
3232        // Verify we received 2 completed orders
3233        assert_eq!(orders.len(), 2, "Should receive 2 completed orders");
3234
3235        // Verify first completed order (ES futures - based on captured data)
3236        let order1 = &orders[0];
3237        // CompletedOrder messages don't have order_id in the message, defaults to -1
3238        assert_eq!(order1.order_id, -1);
3239        assert_eq!(order1.contract.symbol, Symbol::from("ES"));
3240        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Future);
3241        assert_eq!(order1.order.action, Action::Buy);
3242        assert_eq!(order1.order.total_quantity, 1.0);
3243        assert_eq!(order1.order.order_type, "LMT");
3244        assert_eq!(order1.order_state.status, "Cancelled");
3245        assert_eq!(order1.order.perm_id, 616088517);
3246
3247        // Verify second completed order (AAPL)
3248        let order2 = &orders[1];
3249        assert_eq!(order2.order_id, -1); // CompletedOrder messages don't have order_id
3250        assert_eq!(order2.contract.symbol, Symbol::from("AAPL"));
3251        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3252        assert_eq!(order2.order.action, Action::Buy);
3253        assert_eq!(order2.order.total_quantity, 100.0);
3254        assert_eq!(order2.order.order_type, "MKT");
3255        assert_eq!(order2.order_state.status, "Filled");
3256        assert_eq!(order2.order.perm_id, 1377295418);
3257
3258        // Verify the request was sent correctly
3259        let requests = gateway.requests();
3260        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3261        assert_eq!(requests[0], "99\00\0", "Request should be RequestCompletedOrders with api_only=false");
3262    }
3263
3264    #[tokio::test]
3265    async fn test_cancel_order() {
3266        use crate::client::common::tests::setup_cancel_order;
3267        use crate::messages::Notice;
3268        use crate::orders::CancelOrder;
3269
3270        // Initialize env_logger for debug output
3271        let _ = env_logger::try_init();
3272
3273        let gateway = setup_cancel_order();
3274        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3275
3276        // Cancel order with ID 1001
3277        let order_id = 1001;
3278        let manual_order_cancel_time = "";
3279
3280        // Call cancel_order and get the result
3281        let result = client.cancel_order(order_id, manual_order_cancel_time).await;
3282
3283        // Verify the result
3284        match result {
3285            Ok(mut cancel_stream) => {
3286                // Collect results from the stream
3287                let mut order_status_received = false;
3288                let mut notice_received = false;
3289
3290                while let Some(result) = cancel_stream.next().await {
3291                    match result {
3292                        Ok(CancelOrder::OrderStatus(status)) => {
3293                            assert_eq!(status.order_id, order_id);
3294                            assert_eq!(status.status, "Cancelled");
3295                            assert_eq!(status.filled, 0.0);
3296                            assert_eq!(status.remaining, 100.0);
3297                            order_status_received = true;
3298                            println!("Received OrderStatus: {:?}", status);
3299                        }
3300                        Ok(CancelOrder::Notice(Notice { code, message, .. })) => {
3301                            // Notice messages with code 202 are order cancellation confirmations
3302                            // The message should contain the order ID in the format
3303                            assert_eq!(code, 202);
3304                            assert!(message.contains("Order Cancelled"));
3305                            notice_received = true;
3306                            println!("Received Notice: code={}, message={}", code, message);
3307                        }
3308                        Err(e) => panic!("Error in cancel stream: {}", e),
3309                    }
3310                }
3311
3312                assert!(order_status_received, "Should have received OrderStatus");
3313                assert!(notice_received, "Should have received Notice confirmation");
3314            }
3315            Err(e) => panic!("Failed to cancel order: {}", e),
3316        }
3317
3318        // Verify the request was sent correctly
3319        let requests = gateway.requests();
3320        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3321        assert!(requests[0].starts_with("4\0"), "Request should be a CancelOrder message");
3322        assert!(requests[0].contains(&format!("{}\0", order_id)), "Request should contain order ID");
3323    }
3324
3325    #[tokio::test]
3326    async fn test_global_cancel() {
3327        use crate::client::common::tests::setup_global_cancel;
3328
3329        // Initialize env_logger for debug output
3330        let _ = env_logger::try_init();
3331
3332        let gateway = setup_global_cancel();
3333        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3334
3335        // Call global_cancel
3336        let result = client.global_cancel().await;
3337
3338        // Verify the result
3339        match result {
3340            Ok(()) => {
3341                println!("Global cancel request sent successfully");
3342            }
3343            Err(e) => panic!("Failed to send global cancel: {}", e),
3344        }
3345
3346        // Give the gateway time to process the request
3347        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3348
3349        // Verify the request was sent correctly
3350        let requests = gateway.requests();
3351        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3352        assert_eq!(requests[0], "58\01\0", "Request should be a RequestGlobalCancel message with version 1");
3353    }
3354
3355    #[tokio::test]
3356    async fn test_executions() {
3357        use crate::client::common::tests::setup_executions;
3358        use crate::contracts::SecurityType;
3359        use crate::orders::{ExecutionFilter, Executions};
3360
3361        // Initialize env_logger for debug output
3362        let _ = env_logger::try_init();
3363
3364        let gateway = setup_executions();
3365        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3366
3367        // Create an execution filter
3368        let filter = ExecutionFilter {
3369            client_id: Some(CLIENT_ID),
3370            account_code: "DU1234567".to_string(),
3371            time: "".to_string(),          // Empty means all time
3372            symbol: "".to_string(),        // Empty means all symbols
3373            security_type: "".to_string(), // Empty means all types
3374            exchange: "".to_string(),      // Empty means all exchanges
3375            side: "".to_string(),          // Empty means all sides
3376            ..Default::default()
3377        };
3378
3379        // Request executions
3380        let mut subscription = client.executions(filter).await.expect("Failed to request executions");
3381
3382        // Collect executions from the subscription
3383        let mut execution_data = Vec::new();
3384        let mut commission_reports = Vec::new();
3385
3386        while let Some(result) = subscription.next().await {
3387            match result {
3388                Ok(Executions::ExecutionData(data)) => {
3389                    execution_data.push(data);
3390                }
3391                Ok(Executions::CommissionReport(report)) => {
3392                    commission_reports.push(report);
3393                }
3394                Ok(Executions::Notice(_)) => {
3395                    // Skip notices
3396                }
3397                Err(crate::Error::EndOfStream) => break,
3398                Err(e) => panic!("Unexpected error: {:?}", e),
3399            }
3400        }
3401
3402        // Verify we received 3 executions and 3 commission reports
3403        assert_eq!(execution_data.len(), 3, "Should receive 3 execution data messages");
3404        assert_eq!(commission_reports.len(), 3, "Should receive 3 commission reports");
3405
3406        // Verify first execution (AAPL stock)
3407        let exec1 = &execution_data[0];
3408        assert_eq!(exec1.request_id, 9000);
3409        assert_eq!(exec1.execution.order_id, 1001);
3410        assert_eq!(exec1.contract.symbol, Symbol::from("AAPL"));
3411        assert_eq!(exec1.contract.security_type, SecurityType::Stock);
3412        assert_eq!(exec1.execution.execution_id, "000e1a2b.67890abc.01.01");
3413        assert_eq!(exec1.execution.side, "BOT");
3414        assert_eq!(exec1.execution.shares, 100.0);
3415        assert_eq!(exec1.execution.price, 150.25);
3416
3417        // Verify first commission report
3418        let comm1 = &commission_reports[0];
3419        assert_eq!(comm1.execution_id, "000e1a2b.67890abc.01.01");
3420        assert_eq!(comm1.commission, 1.25);
3421        assert_eq!(comm1.currency, "USD");
3422
3423        // Verify second execution (ES futures)
3424        let exec2 = &execution_data[1];
3425        assert_eq!(exec2.request_id, 9000);
3426        assert_eq!(exec2.execution.order_id, 1002);
3427        assert_eq!(exec2.contract.symbol, Symbol::from("ES"));
3428        assert_eq!(exec2.contract.security_type, SecurityType::Future);
3429        assert_eq!(exec2.execution.execution_id, "000e1a2b.67890def.02.01");
3430        assert_eq!(exec2.execution.side, "SLD");
3431        assert_eq!(exec2.execution.shares, 5.0);
3432        assert_eq!(exec2.execution.price, 5050.25);
3433
3434        // Verify second commission report
3435        let comm2 = &commission_reports[1];
3436        assert_eq!(comm2.execution_id, "000e1a2b.67890def.02.01");
3437        assert_eq!(comm2.commission, 2.50);
3438        assert_eq!(comm2.realized_pnl, Some(125.50));
3439
3440        // Verify third execution (SPY options)
3441        let exec3 = &execution_data[2];
3442        assert_eq!(exec3.request_id, 9000);
3443        assert_eq!(exec3.execution.order_id, 1003);
3444        assert_eq!(exec3.contract.symbol, Symbol::from("SPY"));
3445        assert_eq!(exec3.contract.security_type, SecurityType::Option);
3446        assert_eq!(exec3.execution.execution_id, "000e1a2b.67890ghi.03.01");
3447        assert_eq!(exec3.execution.side, "BOT");
3448        assert_eq!(exec3.execution.shares, 10.0);
3449        assert_eq!(exec3.execution.price, 2.50);
3450
3451        // Verify third commission report
3452        let comm3 = &commission_reports[2];
3453        assert_eq!(comm3.execution_id, "000e1a2b.67890ghi.03.01");
3454        assert_eq!(comm3.commission, 0.65);
3455        assert_eq!(comm3.realized_pnl, Some(250.00));
3456
3457        // Verify the request was sent correctly
3458        let requests = gateway.requests();
3459        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3460        // Request format: RequestExecutions(7), version(3), request_id(9000), client_id, account_code, time, symbol, security_type, exchange, side
3461        assert_eq!(
3462            requests[0], "7\03\09000\0100\0DU1234567\0\0\0\0\0\0",
3463            "Request should be RequestExecutions with correct filter parameters"
3464        );
3465    }
3466
3467    #[tokio::test]
3468    async fn test_exercise_options() {
3469        use crate::client::common::tests::setup_exercise_options;
3470        use crate::contracts::{Contract, Currency, Exchange, SecurityType, Symbol};
3471        use crate::orders::{ExerciseAction, ExerciseOptions};
3472        use time::macros::datetime;
3473
3474        // Initialize env_logger for debug output
3475        let _ = env_logger::try_init();
3476
3477        let gateway = setup_exercise_options();
3478        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3479
3480        // Create option contract for SPY
3481        let contract = Contract {
3482            contract_id: 123456789,
3483            symbol: Symbol::from("SPY"),
3484            security_type: SecurityType::Option,
3485            last_trade_date_or_contract_month: "20240126".to_string(),
3486            strike: 450.0,
3487            right: "C".to_string(), // Call option
3488            multiplier: "100".to_string(),
3489            exchange: Exchange::from("CBOE"),
3490            currency: Currency::from("USD"),
3491            local_symbol: "SPY240126C00450000".to_string(),
3492            trading_class: "SPY".to_string(),
3493            ..Default::default()
3494        };
3495
3496        // Exercise the option
3497        let exercise_action = ExerciseAction::Exercise;
3498        let exercise_quantity = 10;
3499        let account = "DU1234567";
3500        let ovrd = false;
3501        let manual_order_time = Some(datetime!(2024-01-25 10:30:00 UTC));
3502
3503        let mut subscription = client
3504            .exercise_options(&contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
3505            .await
3506            .expect("Failed to exercise options");
3507
3508        // Collect results
3509        let mut order_statuses = Vec::new();
3510        let mut open_orders = Vec::new();
3511
3512        while let Some(result) = subscription.next().await {
3513            match result {
3514                Ok(ExerciseOptions::OrderStatus(status)) => order_statuses.push(status),
3515                Ok(ExerciseOptions::OpenOrder(order)) => open_orders.push(order),
3516                Ok(ExerciseOptions::Notice(_notice)) => {
3517                    // Note: Warning messages (2100-2200) are currently not routed to subscriptions
3518                    // They are only logged. See TODO.md for future improvements.
3519                }
3520                Err(crate::Error::EndOfStream) => break,
3521                Err(e) => panic!("Unexpected error: {:?}", e),
3522            }
3523        }
3524
3525        // Verify we got the expected responses
3526        assert_eq!(order_statuses.len(), 3, "Should have 3 order status updates");
3527        assert_eq!(open_orders.len(), 1, "Should have 1 open order");
3528
3529        // Verify order statuses
3530        assert_eq!(order_statuses[0].status, "PreSubmitted");
3531        assert_eq!(order_statuses[0].filled, 0.0);
3532        assert_eq!(order_statuses[0].remaining, 10.0);
3533
3534        assert_eq!(order_statuses[1].status, "Submitted");
3535        assert_eq!(order_statuses[2].status, "Filled");
3536        assert_eq!(order_statuses[2].filled, 10.0);
3537        assert_eq!(order_statuses[2].remaining, 0.0);
3538
3539        // Verify open order
3540        let open_order = &open_orders[0];
3541        assert_eq!(open_order.order.order_id, 90);
3542        assert_eq!(open_order.contract.symbol, Symbol::from("SPY"));
3543        assert_eq!(open_order.contract.security_type, SecurityType::Option);
3544        assert_eq!(open_order.order.order_type, "EXERCISE");
3545
3546        // Verify the request was sent correctly
3547        let requests = gateway.requests();
3548        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3549
3550        // Request format: ExerciseOptions(21), version(2), order_id, contract fields, exercise_action, exercise_quantity, account, ovrd, manual_order_time
3551        let expected_request = format!(
3552            "21\02\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0",
3553            90, // order_id (using next_order_id from client)
3554            contract.contract_id,
3555            contract.symbol,
3556            contract.security_type,
3557            contract.last_trade_date_or_contract_month,
3558            contract.strike,
3559            contract.right,
3560            contract.multiplier,
3561            contract.exchange,
3562            contract.currency,
3563            contract.local_symbol,
3564            contract.trading_class,
3565            exercise_action as i32,
3566            exercise_quantity,
3567            account,
3568            if ovrd { 1 } else { 0 },
3569            "20240125 10:30:00 UTC" // manual_order_time formatted
3570        );
3571
3572        assert_eq!(requests[0], expected_request, "Request should be ExerciseOptions with correct parameters");
3573    }
3574
3575    // === Real-time Market Data Tests ===
3576
3577    #[tokio::test]
3578    async fn test_market_data() {
3579        use crate::client::common::tests::setup_market_data;
3580        use crate::contracts::tick_types::TickType;
3581        use crate::contracts::Contract;
3582        use crate::market_data::realtime::TickTypes;
3583
3584        let gateway = setup_market_data();
3585        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3586
3587        let contract = Contract::stock("AAPL").build();
3588        let generic_ticks = vec!["100", "101", "104"]; // Option volume, option open interest, historical volatility
3589
3590        let mut subscription = client
3591            .market_data(&contract)
3592            .generic_ticks(&generic_ticks)
3593            .snapshot()
3594            .subscribe()
3595            .await
3596            .expect("Failed to request market data");
3597
3598        let mut tick_count = 0;
3599        let mut has_bid_price = false;
3600        let mut has_bid_size = false;
3601        let mut has_ask_price = false;
3602        let mut has_ask_size = false;
3603        let mut has_last_price = false;
3604        let mut has_last_size = false;
3605        let mut has_volume = false;
3606        let mut has_snapshot_end = false;
3607
3608        while let Some(tick_result) = subscription.next().await {
3609            let tick = tick_result.expect("Failed to get tick");
3610            tick_count += 1;
3611            match tick {
3612                TickTypes::PriceSize(price_size) => {
3613                    match price_size.price_tick_type {
3614                        TickType::Bid => {
3615                            assert_eq!(price_size.price, 150.50);
3616                            has_bid_price = true;
3617                        }
3618                        TickType::Ask => {
3619                            assert_eq!(price_size.price, 151.00);
3620                            has_ask_price = true;
3621                        }
3622                        TickType::Last => {
3623                            assert_eq!(price_size.price, 150.75);
3624                            has_last_price = true;
3625                        }
3626                        _ => {}
3627                    }
3628                    // Note: size_tick_type might be present but size value is 0 in PriceSize
3629                }
3630                TickTypes::Size(size_tick) => match size_tick.tick_type {
3631                    TickType::BidSize => {
3632                        assert_eq!(size_tick.size, 100.0);
3633                        has_bid_size = true;
3634                    }
3635                    TickType::AskSize => {
3636                        assert_eq!(size_tick.size, 200.0);
3637                        has_ask_size = true;
3638                    }
3639                    TickType::LastSize => {
3640                        assert_eq!(size_tick.size, 50.0);
3641                        has_last_size = true;
3642                    }
3643                    _ => {}
3644                },
3645                TickTypes::Generic(generic_tick) => {
3646                    if generic_tick.tick_type == TickType::Volume {
3647                        assert_eq!(generic_tick.value, 1500000.0);
3648                        has_volume = true;
3649                    }
3650                }
3651                TickTypes::String(_) => {
3652                    // Ignore string ticks like LastTimestamp
3653                }
3654                TickTypes::SnapshotEnd => {
3655                    has_snapshot_end = true;
3656                    break; // Snapshot complete
3657                }
3658                _ => {}
3659            }
3660
3661            if tick_count > 20 {
3662                break; // Safety limit
3663            }
3664        }
3665
3666        assert!(has_bid_price, "Should receive bid price");
3667        assert!(has_bid_size, "Should receive bid size");
3668        assert!(has_ask_price, "Should receive ask price");
3669        assert!(has_ask_size, "Should receive ask size");
3670        assert!(has_last_price, "Should receive last price");
3671        assert!(has_last_size, "Should receive last size");
3672        assert!(has_volume, "Should receive volume");
3673        assert!(has_snapshot_end, "Should receive snapshot end");
3674
3675        let requests = gateway.requests();
3676        assert!(requests[0].starts_with("1\011\09000\0"), "Request should be RequestMarketData");
3677        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3678        assert!(requests[0].contains("100,101,104\0"), "Request should contain generic ticks");
3679        assert!(requests[0].contains("\01\0"), "Request should have snapshot=true");
3680    }
3681
3682    #[tokio::test]
3683    async fn test_realtime_bars() {
3684        use crate::client::common::tests::setup_realtime_bars;
3685        use crate::contracts::Contract;
3686        use crate::market_data::realtime::{BarSize, WhatToShow};
3687
3688        let gateway = setup_realtime_bars();
3689        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3690
3691        let contract = Contract::stock("AAPL").build();
3692        let bar_size = BarSize::Sec5;
3693        let what_to_show = WhatToShow::Trades;
3694        let trading_hours = TradingHours::Extended;
3695
3696        let mut subscription = client
3697            .realtime_bars(&contract, bar_size, what_to_show, trading_hours)
3698            .await
3699            .expect("Failed to request realtime bars");
3700
3701        let mut bars = Vec::new();
3702        for _ in 0..3 {
3703            if let Some(bar_result) = subscription.next().await {
3704                bars.push(bar_result.expect("Failed to get bar"));
3705            }
3706        }
3707
3708        assert_eq!(bars.len(), 3, "Should receive 3 bars");
3709
3710        // Verify first bar
3711        let bar1 = &bars[0];
3712        assert_eq!(bar1.open, 150.25);
3713        assert_eq!(bar1.high, 150.75);
3714        assert_eq!(bar1.low, 150.00);
3715        assert_eq!(bar1.close, 150.50);
3716        assert_eq!(bar1.volume, 1000.0);
3717        assert_eq!(bar1.wap, 150.40);
3718        assert_eq!(bar1.count, 25);
3719
3720        // Verify second bar
3721        let bar2 = &bars[1];
3722        assert_eq!(bar2.open, 150.50);
3723        assert_eq!(bar2.high, 151.00);
3724        assert_eq!(bar2.low, 150.40);
3725        assert_eq!(bar2.close, 150.90);
3726        assert_eq!(bar2.volume, 1200.0);
3727
3728        // Verify third bar
3729        let bar3 = &bars[2];
3730        assert_eq!(bar3.open, 150.90);
3731        assert_eq!(bar3.high, 151.25);
3732        assert_eq!(bar3.low, 150.85);
3733        assert_eq!(bar3.close, 151.20);
3734
3735        let requests = gateway.requests();
3736        assert!(requests[0].starts_with("50\08\09000\0"), "Request should be RequestRealTimeBars");
3737        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3738        assert!(
3739            requests[0].contains("\00\0TRADES\00\0"),
3740            "Request should have bar_size=0 (5 sec) and TRADES"
3741        );
3742    }
3743
3744    #[tokio::test]
3745    async fn test_tick_by_tick_last() {
3746        use crate::client::common::tests::setup_tick_by_tick_last;
3747        use crate::contracts::Contract;
3748
3749        let gateway = setup_tick_by_tick_last();
3750        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3751
3752        let contract = Contract::stock("AAPL").build();
3753        let number_of_ticks = 0;
3754        let ignore_size = false;
3755
3756        let mut subscription = client
3757            .tick_by_tick_last(&contract, number_of_ticks, ignore_size)
3758            .await
3759            .expect("Failed to request tick by tick last");
3760
3761        let mut trades = Vec::new();
3762        for _ in 0..3 {
3763            if let Some(trade_result) = subscription.next().await {
3764                trades.push(trade_result.expect("Failed to get trade"));
3765            }
3766        }
3767
3768        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3769
3770        // Verify first trade
3771        let trade1 = &trades[0];
3772        assert_eq!(trade1.tick_type, "1"); // 1 = Last
3773        assert_eq!(trade1.price, 150.75);
3774        assert_eq!(trade1.size, 100.0);
3775        assert_eq!(trade1.exchange, "NASDAQ");
3776        assert!(!trade1.trade_attribute.past_limit);
3777        assert!(!trade1.trade_attribute.unreported);
3778
3779        // Verify second trade (unreported)
3780        let trade2 = &trades[1];
3781        assert_eq!(trade2.price, 150.80);
3782        assert_eq!(trade2.size, 50.0);
3783        assert_eq!(trade2.exchange, "NYSE");
3784        assert!(trade2.trade_attribute.unreported);
3785
3786        // Verify third trade
3787        let trade3 = &trades[2];
3788        assert_eq!(trade3.price, 150.70);
3789        assert_eq!(trade3.size, 150.0);
3790
3791        let requests = gateway.requests();
3792        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3793        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3794        assert!(requests[0].contains("Last\0"), "Request should have Last tick type");
3795    }
3796
3797    #[tokio::test]
3798    async fn test_tick_by_tick_all_last() {
3799        use crate::client::common::tests::setup_tick_by_tick_all_last;
3800        use crate::contracts::Contract;
3801
3802        let gateway = setup_tick_by_tick_all_last();
3803        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3804
3805        let contract = Contract::stock("AAPL").build();
3806        let number_of_ticks = 0;
3807        let ignore_size = false;
3808
3809        let mut subscription = client
3810            .tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
3811            .await
3812            .expect("Failed to request tick by tick all last");
3813
3814        let mut trades = Vec::new();
3815        for _ in 0..3 {
3816            if let Some(trade_result) = subscription.next().await {
3817                trades.push(trade_result.expect("Failed to get trade"));
3818            }
3819        }
3820
3821        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3822
3823        // Verify first trade
3824        let trade1 = &trades[0];
3825        assert_eq!(trade1.tick_type, "2"); // 2 = AllLast
3826        assert_eq!(trade1.price, 150.75);
3827        assert_eq!(trade1.exchange, "NASDAQ");
3828
3829        // Verify second trade (unreported dark pool trade)
3830        let trade2 = &trades[1];
3831        assert_eq!(trade2.price, 150.80);
3832        assert_eq!(trade2.exchange, "DARK");
3833        assert_eq!(trade2.special_conditions, "ISO");
3834        assert!(trade2.trade_attribute.unreported);
3835
3836        // Verify third trade
3837        let trade3 = &trades[2];
3838        assert_eq!(trade3.price, 150.70);
3839        assert_eq!(trade3.exchange, "NYSE");
3840
3841        let requests = gateway.requests();
3842        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3843        assert!(requests[0].contains("AllLast\0"), "Request should have AllLast tick type");
3844    }
3845
3846    #[tokio::test]
3847    async fn test_tick_by_tick_bid_ask() {
3848        use crate::client::common::tests::setup_tick_by_tick_bid_ask;
3849        use crate::contracts::Contract;
3850
3851        let gateway = setup_tick_by_tick_bid_ask();
3852        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3853
3854        let contract = Contract::stock("AAPL").build();
3855        let number_of_ticks = 0;
3856        let ignore_size = false;
3857
3858        let mut subscription = client
3859            .tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
3860            .await
3861            .expect("Failed to request tick by tick bid ask");
3862
3863        let mut bid_asks = Vec::new();
3864        for _ in 0..3 {
3865            if let Some(bid_ask_result) = subscription.next().await {
3866                bid_asks.push(bid_ask_result.expect("Failed to get bid/ask"));
3867            }
3868        }
3869
3870        assert_eq!(bid_asks.len(), 3, "Should receive 3 bid/ask updates");
3871
3872        // Verify first bid/ask
3873        let ba1 = &bid_asks[0];
3874        assert_eq!(ba1.bid_price, 150.50);
3875        assert_eq!(ba1.ask_price, 150.55);
3876        assert_eq!(ba1.bid_size, 100.0);
3877        assert_eq!(ba1.ask_size, 200.0);
3878        assert!(!ba1.bid_ask_attribute.bid_past_low);
3879        assert!(!ba1.bid_ask_attribute.ask_past_high);
3880
3881        // Verify second bid/ask (bid past low)
3882        let ba2 = &bid_asks[1];
3883        assert_eq!(ba2.bid_price, 150.45);
3884        assert_eq!(ba2.ask_price, 150.55);
3885        assert!(ba2.bid_ask_attribute.bid_past_low);
3886
3887        // Verify third bid/ask (ask past high)
3888        let ba3 = &bid_asks[2];
3889        assert_eq!(ba3.ask_price, 150.60);
3890        assert!(ba3.bid_ask_attribute.ask_past_high);
3891
3892        let requests = gateway.requests();
3893        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3894        assert!(requests[0].contains("BidAsk\0"), "Request should have BidAsk tick type");
3895    }
3896
3897    #[tokio::test]
3898    async fn test_tick_by_tick_midpoint() {
3899        use crate::client::common::tests::setup_tick_by_tick_midpoint;
3900        use crate::contracts::Contract;
3901
3902        let gateway = setup_tick_by_tick_midpoint();
3903        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3904
3905        let contract = Contract::stock("AAPL").build();
3906        let number_of_ticks = 0;
3907        let ignore_size = false;
3908
3909        let mut subscription = client
3910            .tick_by_tick_midpoint(&contract, number_of_ticks, ignore_size)
3911            .await
3912            .expect("Failed to request tick by tick midpoint");
3913
3914        let mut midpoints = Vec::new();
3915        for _ in 0..3 {
3916            if let Some(midpoint_result) = subscription.next().await {
3917                midpoints.push(midpoint_result.expect("Failed to get midpoint"));
3918            }
3919        }
3920
3921        assert_eq!(midpoints.len(), 3, "Should receive 3 midpoint updates");
3922
3923        assert_eq!(midpoints[0].mid_point, 150.525);
3924        assert_eq!(midpoints[1].mid_point, 150.50);
3925        assert_eq!(midpoints[2].mid_point, 150.525);
3926
3927        let requests = gateway.requests();
3928        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3929        assert!(requests[0].contains("MidPoint\0"), "Request should have MidPoint tick type");
3930    }
3931
3932    #[tokio::test]
3933    async fn test_market_depth() {
3934        use crate::client::common::tests::setup_market_depth;
3935        use crate::contracts::Contract;
3936        use crate::market_data::realtime::MarketDepths;
3937
3938        let gateway = setup_market_depth();
3939        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3940
3941        let contract = Contract::stock("AAPL").build();
3942        let num_rows = 5;
3943        let is_smart_depth = false;
3944
3945        let mut subscription = client
3946            .market_depth(&contract, num_rows, is_smart_depth)
3947            .await
3948            .expect("Failed to request market depth");
3949
3950        let mut updates = Vec::new();
3951        for _ in 0..4 {
3952            if let Some(update_result) = subscription.next().await {
3953                let update = update_result.expect("Failed to get depth update");
3954                if let MarketDepths::MarketDepth(depth) = update {
3955                    updates.push(depth);
3956                }
3957            }
3958        }
3959
3960        assert_eq!(updates.len(), 4, "Should receive 4 depth updates");
3961
3962        // Verify bid insert
3963        let update1 = &updates[0];
3964        assert_eq!(update1.position, 0);
3965        // MarketDepth (L1) doesn't have market_maker field
3966        assert_eq!(update1.operation, 0); // Insert
3967        assert_eq!(update1.side, 1); // Bid
3968        assert_eq!(update1.price, 150.50);
3969        assert_eq!(update1.size, 100.0);
3970
3971        // Verify ask insert
3972        let update2 = &updates[1];
3973        assert_eq!(update2.operation, 0); // Insert
3974        assert_eq!(update2.side, 0); // Ask
3975        assert_eq!(update2.price, 150.55);
3976        assert_eq!(update2.size, 200.0);
3977
3978        // Verify bid update
3979        let update3 = &updates[2];
3980        assert_eq!(update3.operation, 1); // Update
3981        assert_eq!(update3.price, 150.49);
3982
3983        // Verify ask delete
3984        let update4 = &updates[3];
3985        assert_eq!(update4.operation, 2); // Delete
3986
3987        let requests = gateway.requests();
3988        assert!(requests[0].starts_with("10\05\09000\0"), "Request should be RequestMarketDepth");
3989        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3990        assert!(requests[0].contains("5\00\0"), "Request should have 5 rows and smart_depth=false");
3991    }
3992
3993    #[tokio::test]
3994    async fn test_market_depth_exchanges() {
3995        use crate::client::common::tests::setup_market_depth_exchanges;
3996
3997        let gateway = setup_market_depth_exchanges();
3998        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3999
4000        let exchanges = client.market_depth_exchanges().await.expect("Failed to get market depth exchanges");
4001
4002        assert_eq!(exchanges.len(), 3, "Should receive 3 exchange descriptions");
4003
4004        // Verify first exchange
4005        let ex1 = &exchanges[0];
4006        assert_eq!(ex1.exchange_name, "ISLAND");
4007        assert_eq!(ex1.security_type, "STK");
4008        assert_eq!(ex1.listing_exchange, "NASDAQ");
4009        assert_eq!(ex1.service_data_type, "Deep2");
4010        assert_eq!(ex1.aggregated_group, Some("1".to_string()));
4011
4012        // Verify second exchange
4013        let ex2 = &exchanges[1];
4014        assert_eq!(ex2.exchange_name, "NYSE");
4015        assert_eq!(ex2.security_type, "STK");
4016        assert_eq!(ex2.service_data_type, "Deep");
4017        assert_eq!(ex2.aggregated_group, Some("2".to_string()));
4018
4019        // Verify third exchange
4020        let ex3 = &exchanges[2];
4021        assert_eq!(ex3.exchange_name, "ARCA");
4022        assert_eq!(ex3.aggregated_group, Some("2".to_string()));
4023
4024        let requests = gateway.requests();
4025        assert_eq!(requests[0], "82\0", "Request should be RequestMktDepthExchanges");
4026    }
4027
4028    #[tokio::test]
4029    async fn test_switch_market_data_type() {
4030        use crate::client::common::tests::setup_switch_market_data_type;
4031        use crate::market_data::MarketDataType;
4032
4033        let gateway = setup_switch_market_data_type();
4034        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4035
4036        // Test switching to delayed market data
4037        client
4038            .switch_market_data_type(MarketDataType::Delayed)
4039            .await
4040            .expect("Failed to switch market data type");
4041
4042        // Give the mock gateway time to receive the request
4043        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
4044
4045        let requests = gateway.requests();
4046        assert_eq!(requests.len(), 1, "Should have sent 1 request");
4047        // Verify request format: RequestMarketDataType(59), version(1), market_data_type(3=Delayed)
4048        assert_eq!(requests[0], "59\01\03\0", "Request should be RequestMarketDataType with Delayed(3)");
4049    }
4050
4051    // === Historical Data Tests ===
4052
4053    #[tokio::test]
4054    async fn test_head_timestamp() {
4055        use crate::client::common::tests::setup_head_timestamp;
4056        use crate::contracts::Contract;
4057        use crate::market_data::historical::WhatToShow;
4058
4059        let gateway = setup_head_timestamp();
4060        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4061
4062        let contract = Contract::stock("AAPL").build();
4063        let what_to_show = WhatToShow::Trades;
4064        let trading_hours = TradingHours::Regular;
4065
4066        let timestamp = client
4067            .head_timestamp(&contract, what_to_show, trading_hours)
4068            .await
4069            .expect("Failed to get head timestamp");
4070
4071        // Verify the timestamp is as expected (2024-01-15 09:30:00)
4072        assert_eq!(timestamp.year(), 2024);
4073        assert_eq!(timestamp.month() as u8, 1);
4074        assert_eq!(timestamp.day(), 15);
4075        assert_eq!(timestamp.hour(), 9);
4076        assert_eq!(timestamp.minute(), 30);
4077
4078        let requests = gateway.requests();
4079        assert!(requests[0].starts_with("87\0"), "Request should be RequestHeadTimestamp");
4080        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4081        assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4082    }
4083
4084    #[tokio::test]
4085    async fn test_historical_data() {
4086        use crate::client::common::tests::setup_historical_data;
4087        use crate::contracts::Contract;
4088        use crate::market_data::historical::{BarSize, Duration, WhatToShow};
4089        use time::macros::datetime;
4090
4091        let gateway = setup_historical_data();
4092        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4093
4094        let contract = Contract::stock("AAPL").build();
4095        let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4096        let duration = Duration::days(1);
4097        let bar_size = BarSize::Min5;
4098        let what_to_show = WhatToShow::Trades;
4099        let trading_hours = TradingHours::Regular;
4100
4101        let historical_data = client
4102            .historical_data(&contract, Some(end_date_time), duration, bar_size, Some(what_to_show), trading_hours)
4103            .await
4104            .expect("Failed to get historical data");
4105
4106        // Get bars from HistoricalData struct
4107        let bars = &historical_data.bars;
4108
4109        assert_eq!(bars.len(), 3, "Should receive 3 bars");
4110
4111        // Verify first bar
4112        assert_eq!(bars[0].open, 150.25);
4113        assert_eq!(bars[0].high, 150.75);
4114        assert_eq!(bars[0].low, 150.00);
4115        assert_eq!(bars[0].close, 150.50);
4116        assert_eq!(bars[0].volume, 1000.0);
4117        assert_eq!(bars[0].wap, 150.40);
4118        assert_eq!(bars[0].count, 25);
4119
4120        // Verify second bar
4121        assert_eq!(bars[1].open, 150.50);
4122        assert_eq!(bars[1].high, 151.00);
4123        assert_eq!(bars[1].low, 150.40);
4124        assert_eq!(bars[1].close, 150.90);
4125        assert_eq!(bars[1].volume, 1200.0);
4126
4127        // Verify third bar
4128        assert_eq!(bars[2].open, 150.90);
4129        assert_eq!(bars[2].high, 151.25);
4130        assert_eq!(bars[2].low, 150.85);
4131        assert_eq!(bars[2].close, 151.20);
4132
4133        let requests = gateway.requests();
4134        assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4135        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4136    }
4137
4138    #[tokio::test]
4139    async fn test_historical_schedule() {
4140        use crate::client::common::tests::setup_historical_schedules;
4141        use crate::contracts::Contract;
4142        use crate::market_data::historical::Duration;
4143        use time::macros::datetime;
4144
4145        let gateway = setup_historical_schedules();
4146        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4147
4148        let contract = Contract::stock("AAPL").build();
4149        let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4150        let duration = Duration::days(1);
4151
4152        let schedule = client
4153            .historical_schedule(&contract, Some(end_date_time), duration)
4154            .await
4155            .expect("Failed to get historical schedule");
4156
4157        // Schedule has start and end as OffsetDateTime
4158        assert_eq!(schedule.time_zone, "US/Eastern");
4159        assert!(!schedule.sessions.is_empty(), "Should have at least one session");
4160
4161        let requests = gateway.requests();
4162        assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4163        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4164        assert!(requests[0].contains("2\0"), "Request should contain formatDate=2 for schedule");
4165    }
4166
4167    #[tokio::test]
4168    async fn test_historical_ticks_bid_ask() {
4169        use crate::client::common::tests::setup_historical_ticks_bid_ask;
4170        use crate::contracts::Contract;
4171        use time::macros::datetime;
4172
4173        let gateway = setup_historical_ticks_bid_ask();
4174        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4175
4176        let contract = Contract::stock("AAPL").build();
4177        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4178        let number_of_ticks = 100;
4179        let trading_hours = TradingHours::Regular;
4180
4181        let mut tick_subscription = client
4182            .historical_ticks_bid_ask(&contract, Some(start_date_time), None, number_of_ticks, trading_hours, false)
4183            .await
4184            .expect("Failed to get historical ticks bid/ask");
4185
4186        // Collect ticks from the subscription
4187        let mut ticks = Vec::new();
4188        while let Some(tick) = tick_subscription.next().await {
4189            ticks.push(tick);
4190        }
4191
4192        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4193
4194        // Verify first tick
4195        assert_eq!(ticks[0].price_bid, 150.25);
4196        assert_eq!(ticks[0].price_ask, 150.50);
4197        assert_eq!(ticks[0].size_bid, 100);
4198        assert_eq!(ticks[0].size_ask, 200);
4199
4200        // Verify second tick
4201        assert_eq!(ticks[1].price_bid, 150.30);
4202        assert_eq!(ticks[1].price_ask, 150.55);
4203        assert_eq!(ticks[1].size_bid, 150);
4204        assert_eq!(ticks[1].size_ask, 250);
4205
4206        // Verify third tick
4207        assert_eq!(ticks[2].price_bid, 150.35);
4208        assert_eq!(ticks[2].price_ask, 150.60);
4209
4210        let requests = gateway.requests();
4211        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4212        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4213        assert!(requests[0].contains("BID_ASK\0"), "Request should contain BID_ASK");
4214    }
4215
4216    #[tokio::test]
4217    async fn test_historical_ticks_mid_point() {
4218        use crate::client::common::tests::setup_historical_ticks_mid_point;
4219        use crate::contracts::Contract;
4220        use time::macros::datetime;
4221
4222        let gateway = setup_historical_ticks_mid_point();
4223        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4224
4225        let contract = Contract::stock("AAPL").build();
4226        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4227        let number_of_ticks = 100;
4228        let trading_hours = TradingHours::Regular;
4229
4230        let mut tick_subscription = client
4231            .historical_ticks_mid_point(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4232            .await
4233            .expect("Failed to get historical ticks midpoint");
4234
4235        // Collect ticks from the subscription
4236        let mut ticks = Vec::new();
4237        while let Some(tick) = tick_subscription.next().await {
4238            ticks.push(tick);
4239        }
4240
4241        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4242
4243        // Verify ticks
4244        assert_eq!(ticks[0].price, 150.375);
4245        assert_eq!(ticks[0].size, 0);
4246        assert_eq!(ticks[1].price, 150.425);
4247        assert_eq!(ticks[1].size, 0);
4248        assert_eq!(ticks[2].price, 150.475);
4249        assert_eq!(ticks[2].size, 0);
4250
4251        let requests = gateway.requests();
4252        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4253        assert!(requests[0].contains("MIDPOINT\0"), "Request should contain MIDPOINT");
4254    }
4255
4256    #[tokio::test]
4257    async fn test_historical_ticks_trade() {
4258        use crate::client::common::tests::setup_historical_ticks_trade;
4259        use crate::contracts::Contract;
4260        use time::macros::datetime;
4261
4262        let gateway = setup_historical_ticks_trade();
4263        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4264
4265        let contract = Contract::stock("AAPL").build();
4266        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4267        let number_of_ticks = 100;
4268        let trading_hours = TradingHours::Regular;
4269
4270        let mut tick_subscription = client
4271            .historical_ticks_trade(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4272            .await
4273            .expect("Failed to get historical ticks trade");
4274
4275        // Collect ticks from the subscription
4276        let mut ticks = Vec::new();
4277        while let Some(tick) = tick_subscription.next().await {
4278            ticks.push(tick);
4279        }
4280
4281        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4282
4283        // Verify ticks
4284        assert_eq!(ticks[0].price, 150.50);
4285        assert_eq!(ticks[0].size, 100);
4286        assert_eq!(ticks[0].exchange, "NASDAQ");
4287        assert_eq!(ticks[0].special_conditions, "T");
4288
4289        assert_eq!(ticks[1].price, 150.55);
4290        assert_eq!(ticks[1].size, 200);
4291        assert_eq!(ticks[1].exchange, "NYSE");
4292
4293        assert_eq!(ticks[2].price, 150.60);
4294        assert_eq!(ticks[2].size, 150);
4295
4296        let requests = gateway.requests();
4297        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4298        assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4299    }
4300
4301    #[tokio::test]
4302    async fn test_histogram_data() {
4303        use crate::client::common::tests::setup_histogram_data;
4304        use crate::contracts::Contract;
4305        use crate::market_data::historical::BarSize;
4306
4307        let gateway = setup_histogram_data();
4308        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4309
4310        let contract = Contract::stock("AAPL").build();
4311        let trading_hours = TradingHours::Regular;
4312        let period = BarSize::Day;
4313
4314        let entries = client
4315            .histogram_data(&contract, trading_hours, period)
4316            .await
4317            .expect("Failed to get histogram data");
4318
4319        assert_eq!(entries.len(), 3, "Should receive 3 entries");
4320
4321        // Verify entries
4322        assert_eq!(entries[0].price, 150.00);
4323        assert_eq!(entries[0].size, 1000);
4324
4325        assert_eq!(entries[1].price, 150.50);
4326        assert_eq!(entries[1].size, 1500);
4327
4328        assert_eq!(entries[2].price, 151.00);
4329        assert_eq!(entries[2].size, 800);
4330
4331        let requests = gateway.requests();
4332        assert!(requests[0].starts_with("88\0"), "Request should be RequestHistogramData");
4333        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4334    }
4335
4336    // === News Tests ===
4337
4338    #[tokio::test]
4339    async fn test_news_providers() {
4340        use crate::client::common::tests::setup_news_providers;
4341
4342        let gateway = setup_news_providers();
4343        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4344
4345        // Request news providers
4346        let providers = client.news_providers().await.expect("Failed to get news providers");
4347
4348        // Verify we received 3 providers
4349        assert_eq!(providers.len(), 3, "Should receive 3 news providers");
4350
4351        // Verify provider details
4352        assert_eq!(providers[0].code, "BRFG");
4353        assert_eq!(providers[0].name, "Briefing.com General Market Columns");
4354
4355        assert_eq!(providers[1].code, "BRFUPDN");
4356        assert_eq!(providers[1].name, "Briefing.com Analyst Actions");
4357
4358        assert_eq!(providers[2].code, "DJ-RT");
4359        assert_eq!(providers[2].name, "Dow Jones Real-Time News");
4360
4361        // Verify the request was sent correctly
4362        let requests = gateway.requests();
4363        assert_eq!(requests.len(), 1, "Should have sent 1 request");
4364        assert_eq!(requests[0], "85\0", "Request should be RequestNewsProviders");
4365    }
4366
4367    #[tokio::test]
4368    async fn test_news_bulletins() {
4369        use crate::client::common::tests::setup_news_bulletins;
4370
4371        let gateway = setup_news_bulletins();
4372        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4373
4374        // Request news bulletins with all_messages=true
4375        let mut subscription = client.news_bulletins(true).await.expect("Failed to get news bulletins");
4376
4377        // Collect news bulletins
4378        let mut bulletins = Vec::new();
4379        while let Some(result) = subscription.next().await {
4380            match result {
4381                Ok(b) => bulletins.push(b),
4382                Err(_) => break,
4383            }
4384            if bulletins.len() >= 2 {
4385                break; // We expect 2 bulletins
4386            }
4387        }
4388
4389        // Verify we received 2 bulletins
4390        assert_eq!(bulletins.len(), 2, "Should receive 2 news bulletins");
4391
4392        // Verify bulletin details
4393        assert_eq!(bulletins[0].message_id, 123);
4394        assert_eq!(bulletins[0].message_type, 1);
4395        assert_eq!(bulletins[0].message, "Important market announcement");
4396        assert_eq!(bulletins[0].exchange, "NYSE");
4397
4398        assert_eq!(bulletins[1].message_id, 124);
4399        assert_eq!(bulletins[1].message_type, 2);
4400        assert_eq!(bulletins[1].message, "Trading halt on symbol XYZ");
4401        assert_eq!(bulletins[1].exchange, "NASDAQ");
4402
4403        // Verify the request was sent correctly
4404        let requests = gateway.requests();
4405        assert!(
4406            requests[0].starts_with("12\01\01\0"),
4407            "Request should be RequestNewsBulletins with version 1 and all_messages=true"
4408        );
4409    }
4410
4411    #[tokio::test]
4412    async fn test_historical_news() {
4413        use crate::client::common::tests::setup_historical_news;
4414        use time::macros::datetime;
4415
4416        let gateway = setup_historical_news();
4417        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4418
4419        // Request historical news
4420        let start_time = datetime!(2024-01-15 14:00:00 UTC);
4421        let end_time = datetime!(2024-01-15 15:00:00 UTC);
4422        let mut subscription = client
4423            .historical_news(
4424                1234,               // contract_id
4425                &["DJ-RT", "BRFG"], // provider_codes
4426                start_time,
4427                end_time,
4428                10, // total_results
4429            )
4430            .await
4431            .expect("Failed to get historical news");
4432
4433        // Collect news articles
4434        let mut articles = Vec::new();
4435        while let Some(result) = subscription.next().await {
4436            match result {
4437                Ok(a) => articles.push(a),
4438                Err(_) => break,
4439            }
4440            if articles.len() >= 2 {
4441                break; // We expect 2 articles
4442            }
4443        }
4444
4445        // Verify we received 2 articles
4446        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4447
4448        // Verify article details
4449        assert_eq!(articles[0].provider_code, "DJ-RT");
4450        assert_eq!(articles[0].article_id, "DJ001234");
4451        assert_eq!(articles[0].headline, "Market hits new highs amid positive earnings");
4452
4453        assert_eq!(articles[1].provider_code, "BRFG");
4454        assert_eq!(articles[1].article_id, "BRF5678");
4455        assert_eq!(articles[1].headline, "Federal Reserve announces policy decision");
4456
4457        // Verify the request was sent correctly
4458        let requests = gateway.requests();
4459        assert!(requests[0].starts_with("86\0"), "Request should be RequestHistoricalNews");
4460        assert!(requests[0].contains("1234\0"), "Request should contain contract_id 1234");
4461        assert!(requests[0].contains("DJ-RT+BRFG\0"), "Request should contain provider codes");
4462    }
4463
4464    #[tokio::test]
4465    async fn test_news_article() {
4466        use crate::client::common::tests::setup_news_article;
4467
4468        let gateway = setup_news_article();
4469        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4470
4471        // Request news article
4472        let article = client
4473            .news_article(
4474                "DJ-RT",    // provider_code
4475                "DJ001234", // article_id
4476            )
4477            .await
4478            .expect("Failed to get news article");
4479
4480        // Verify article details
4481        assert_eq!(article.article_type, crate::news::ArticleType::Text);
4482        assert_eq!(
4483            article.article_text,
4484            "This is the full text of the news article. It contains detailed information about the market event described in the headline."
4485        );
4486
4487        // Verify the request was sent correctly
4488        let requests = gateway.requests();
4489        assert!(requests[0].starts_with("84\0"), "Request should be RequestNewsArticle");
4490        assert!(requests[0].contains("DJ-RT\0"), "Request should contain provider code");
4491        assert!(requests[0].contains("DJ001234\0"), "Request should contain article ID");
4492    }
4493
4494    #[tokio::test]
4495    async fn test_scanner_parameters() {
4496        use crate::client::common::tests::setup_scanner_parameters;
4497
4498        let gateway = setup_scanner_parameters();
4499        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4500
4501        // Request scanner parameters
4502        let xml = client.scanner_parameters().await.expect("Failed to get scanner parameters");
4503
4504        // Verify we received XML content
4505        assert!(xml.contains("<ScanParameterResponse>"), "Should contain ScanParameterResponse");
4506        assert!(xml.contains("<Instrument>STK</Instrument>"), "Should contain STK instrument");
4507        assert!(xml.contains("<Instrument>OPT</Instrument>"), "Should contain OPT instrument");
4508        assert!(xml.contains("<Location>US</Location>"), "Should contain US location");
4509        assert!(
4510            xml.contains("<ScanType>TOP_PERC_GAIN</ScanType>"),
4511            "Should contain TOP_PERC_GAIN scan type"
4512        );
4513
4514        // Verify the request was sent correctly
4515        let requests = gateway.requests();
4516        assert_eq!(requests.len(), 1, "Should have sent 1 request");
4517        assert_eq!(requests[0], "24\01\0", "Request should be RequestScannerParameters with version 1");
4518    }
4519
4520    #[tokio::test]
4521    async fn test_scanner_subscription() {
4522        use crate::client::common::tests::setup_scanner_subscription;
4523        use crate::scanner::ScannerSubscription;
4524
4525        let gateway = setup_scanner_subscription();
4526        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4527
4528        // Create scanner subscription parameters
4529        let scanner_subscription = ScannerSubscription {
4530            instrument: Some("STK".to_string()),
4531            location_code: Some("STK.US.MAJOR".to_string()),
4532            scan_code: Some("TOP_PERC_GAIN".to_string()),
4533            number_of_rows: 10,
4534            ..Default::default()
4535        };
4536
4537        // Request scanner subscription
4538        let mut subscription = client
4539            .scanner_subscription(&scanner_subscription, &vec![])
4540            .await
4541            .expect("Failed to get scanner subscription");
4542
4543        // Collect scanner data - subscription yields Vec<ScannerData>, not individual items
4544        let mut scan_data_vecs = Vec::new();
4545        while let Some(result) = subscription.next().await {
4546            match result {
4547                Ok(d) => scan_data_vecs.push(d),
4548                Err(_) => break,
4549            }
4550            if !scan_data_vecs.is_empty() {
4551                break; // We expect 1 batch
4552            }
4553        }
4554
4555        assert_eq!(scan_data_vecs.len(), 1, "Should receive 1 batch of scan data");
4556        let scan_data = &scan_data_vecs[0];
4557
4558        // Verify we received 2 scan items
4559        assert_eq!(scan_data.len(), 2, "Should receive 2 scan data items");
4560
4561        // Verify scan data details
4562        assert_eq!(scan_data[0].rank, 1);
4563        assert_eq!(scan_data[0].contract_details.contract.contract_id, 1234);
4564        assert_eq!(scan_data[0].contract_details.contract.symbol, Symbol::from("AAPL"));
4565
4566        assert_eq!(scan_data[1].rank, 2);
4567        assert_eq!(scan_data[1].contract_details.contract.contract_id, 5678);
4568        assert_eq!(scan_data[1].contract_details.contract.symbol, Symbol::from("GOOGL"));
4569
4570        // Verify the request was sent correctly
4571        let requests = gateway.requests();
4572        assert!(requests[0].starts_with("22\0"), "Request should be RequestScannerSubscription");
4573    }
4574
4575    #[tokio::test]
4576    async fn test_wsh_metadata() {
4577        use crate::client::common::tests::setup_wsh_metadata;
4578
4579        let gateway = setup_wsh_metadata();
4580        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4581
4582        // Request WSH metadata
4583        let metadata = client.wsh_metadata().await.expect("Failed to get WSH metadata");
4584
4585        // Verify metadata
4586        assert_eq!(metadata.data_json, "{\"dataJson\":\"sample_metadata\"}");
4587
4588        // Verify the request was sent correctly
4589        let requests = gateway.requests();
4590        assert!(requests[0].starts_with("100\0"), "Request should be RequestWshMetaData");
4591    }
4592
4593    #[tokio::test]
4594    async fn test_wsh_event_data() {
4595        use crate::client::common::tests::setup_wsh_event_data;
4596
4597        let gateway = setup_wsh_event_data();
4598        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4599
4600        // Request WSH event data by contract_id - returns a single WshEventData
4601        let event_data = client
4602            .wsh_event_data_by_contract(1234, None, None, None, None)
4603            .await
4604            .expect("Failed to get WSH event data");
4605
4606        // Verify we received the event data (only the first message is processed)
4607        assert_eq!(event_data.data_json, "{\"dataJson\":\"event1\"}");
4608
4609        // Verify the request was sent correctly
4610        let requests = gateway.requests();
4611        assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4612    }
4613
4614    #[tokio::test]
4615    async fn test_contract_news() {
4616        use crate::client::common::tests::setup_contract_news;
4617        use crate::contracts::Contract;
4618
4619        let gateway = setup_contract_news();
4620        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4621
4622        // Create a contract for the request
4623        let contract = Contract::stock("AAPL").build();
4624        let provider_codes = &["DJ-RT", "BRFG"];
4625
4626        // Request contract news
4627        let mut subscription = client
4628            .contract_news(&contract, provider_codes)
4629            .await
4630            .expect("Failed to get contract news");
4631
4632        // Collect news articles
4633        let mut articles = Vec::new();
4634        while let Some(result) = subscription.next().await {
4635            match result {
4636                Ok(a) => articles.push(a),
4637                Err(_) => break,
4638            }
4639            if articles.len() >= 2 {
4640                break; // We expect 2 articles
4641            }
4642        }
4643
4644        // Verify we received 2 articles
4645        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4646
4647        // Verify article details
4648        assert_eq!(articles[0].provider_code, "DJ-RT");
4649        assert_eq!(articles[0].article_id, "DJ001234");
4650        assert_eq!(articles[0].headline, "Stock rises on earnings beat");
4651        assert_eq!(articles[0].extra_data, "extraData1");
4652
4653        assert_eq!(articles[1].provider_code, "BRFG");
4654        assert_eq!(articles[1].article_id, "BRF5678");
4655        assert_eq!(articles[1].headline, "Company announces expansion");
4656        assert_eq!(articles[1].extra_data, "extraData2");
4657
4658        // Verify the request was sent correctly
4659        let requests = gateway.requests();
4660        assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4661        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4662        assert!(
4663            requests[0].contains("mdoff,292:DJ-RT,292:BRFG\0"),
4664            "Request should contain news generic ticks"
4665        );
4666    }
4667
4668    #[tokio::test]
4669    async fn test_broad_tape_news() {
4670        use crate::client::common::tests::setup_broad_tape_news;
4671
4672        let gateway = setup_broad_tape_news();
4673        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4674
4675        // Request broad tape news
4676        let mut subscription = client.broad_tape_news("BRFG").await.expect("Failed to get broad tape news");
4677
4678        // Collect news articles
4679        let mut articles = Vec::new();
4680        while let Some(result) = subscription.next().await {
4681            match result {
4682                Ok(a) => articles.push(a),
4683                Err(_) => break,
4684            }
4685            if articles.len() >= 2 {
4686                break; // We expect 2 articles
4687            }
4688        }
4689
4690        // Verify we received 2 articles
4691        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4692
4693        // Verify article details
4694        assert_eq!(articles[0].provider_code, "BRFG");
4695        assert_eq!(articles[0].article_id, "BRF001");
4696        assert_eq!(articles[0].headline, "Market update: Tech sector rallies");
4697        assert_eq!(articles[0].extra_data, "extraData1");
4698
4699        assert_eq!(articles[1].provider_code, "BRFG");
4700        assert_eq!(articles[1].article_id, "BRF002");
4701        assert_eq!(articles[1].headline, "Fed minutes released");
4702        assert_eq!(articles[1].extra_data, "extraData2");
4703
4704        // Verify the request was sent correctly
4705        let requests = gateway.requests();
4706        assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4707
4708        // Debug: print the actual request to understand the format
4709        if !requests[0].contains("BRFG") || !requests[0].contains("NEWS") {
4710            eprintln!("Actual request: {:?}", requests[0]);
4711        }
4712
4713        // Check for the contract components (symbol, sec_type, exchange)
4714        assert!(requests[0].contains("BRFG:BRFG_ALL"), "Request should contain BRFG:BRFG_ALL symbol");
4715        assert!(requests[0].contains("NEWS"), "Request should contain NEWS security type");
4716        assert!(requests[0].contains("mdoff,292\0"), "Request should contain news generic ticks");
4717    }
4718
4719    #[tokio::test]
4720    async fn test_wsh_event_data_by_filter() {
4721        use crate::client::common::tests::setup_wsh_event_data_by_filter;
4722
4723        let gateway = setup_wsh_event_data_by_filter();
4724        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4725
4726        // Request WSH event data by filter (no limit param to avoid version check)
4727        let filter = "{\"watchlist\":[\"AAPL\"],\"country\":\"ALL\"}";
4728        let mut subscription = client
4729            .wsh_event_data_by_filter(filter, None, None)
4730            .await
4731            .expect("Failed to get WSH event data by filter");
4732
4733        // Collect events
4734        let mut events = Vec::new();
4735        while let Some(result) = subscription.next().await {
4736            match result {
4737                Ok(e) => events.push(e),
4738                Err(_) => break,
4739            }
4740            if events.len() >= 2 {
4741                break; // We expect 2 events
4742            }
4743        }
4744
4745        // Verify we received 2 events
4746        assert_eq!(events.len(), 2, "Should receive 2 WSH events");
4747        assert_eq!(events[0].data_json, "{\"dataJson\":\"filtered_event1\"}");
4748        assert_eq!(events[1].data_json, "{\"dataJson\":\"filtered_event2\"}");
4749
4750        // Verify the request was sent correctly
4751        let requests = gateway.requests();
4752        assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4753        assert!(requests[0].contains(filter), "Request should contain the filter");
4754    }
4755
4756    #[tokio::test]
4757    async fn test_disconnect_completes() {
4758        let gateway = setup_connect();
4759        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4760
4761        tokio::time::timeout(std::time::Duration::from_secs(2), client.disconnect())
4762            .await
4763            .expect("disconnect did not complete in time");
4764
4765        assert!(!client.is_connected());
4766    }
4767
4768    #[tokio::test]
4769    async fn test_disconnect_is_idempotent() {
4770        let gateway = setup_connect();
4771        let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4772
4773        tokio::time::timeout(std::time::Duration::from_secs(2), async {
4774            client.disconnect().await;
4775            client.disconnect().await;
4776        })
4777        .await
4778        .expect("repeated disconnect did not complete in time");
4779    }
4780}