• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wboayue / rust-ibapi / 18546784772

16 Oct 2025 12:48AM UTC coverage: 80.487% (+0.002%) from 80.485%
18546784772

push

github

web-flow
feat: allow blocking client alongside async (#321)

* feat: allow blocking client alongside async

* fix: align sync docs and news cancellation

* fix: isolate order update stream tracking

* docs: enforce all-feature documentation coverage

* docs: enable prelude doctests

91 of 136 new or added lines in 13 files covered. (66.91%)

4 existing lines in 1 file now uncovered.

6579 of 8174 relevant lines covered (80.49%)

101.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.72
/src/client/sync.rs
1
//! Client implementation for connecting to and communicating with TWS and IB Gateway.
2
//!
3
//! The Client provides the main interface for establishing connections, sending requests,
4
//! and receiving responses from the Interactive Brokers API. It manages message routing,
5
//! subscriptions, and maintains the connection state.
6

7
use std::fmt::Debug;
8
use std::net::TcpStream;
9
use std::sync::Arc;
10
use std::time::Duration;
11

12
use log::debug;
13
use time::{Date, OffsetDateTime};
14
use time_tz::Tz;
15

16
use crate::accounts::types::{AccountGroup, AccountId, ContractId, ModelCode};
17
use crate::accounts::{AccountSummaryResult, AccountUpdate, AccountUpdateMulti, FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti};
18
use crate::connection::{sync::Connection, ConnectionMetadata};
19
use crate::contracts::{Contract, OptionComputation, SecurityType};
20
use crate::errors::Error;
21
use crate::market_data::builder::MarketDataBuilder;
22
use crate::market_data::historical::{self, HistogramEntry};
23
use crate::market_data::realtime::{self, Bar, BarSize, DepthMarketDataDescription, MarketDepths, MidPoint, WhatToShow};
24
use crate::market_data::{MarketDataType, TradingHours};
25
use crate::messages::{OutgoingMessages, RequestMessage};
26
use crate::news::NewsArticle;
27
use crate::orders::{CancelOrder, Executions, ExerciseOptions, Order, OrderBuilder, OrderUpdate, Orders, PlaceOrder};
28
use crate::scanner::ScannerData;
29
use crate::subscriptions::sync::Subscription;
30
use crate::transport::{InternalSubscription, MessageBus, TcpMessageBus, TcpSocket};
31
use crate::wsh::AutoFill;
32
use crate::{accounts, contracts, market_data, news, orders, scanner, wsh};
33

34
use super::id_generator::ClientIdManager;
35

36
// Client
37

38
/// TWS API Client. Manages the connection to TWS or Gateway.
39
/// Tracks some global information such as server version and server time.
40
/// Supports generation of order ids.
41
pub struct Client {
42
    /// IB server version
43
    pub(crate) server_version: i32,
44
    pub(crate) connection_time: Option<OffsetDateTime>,
45
    pub(crate) time_zone: Option<&'static Tz>,
46
    pub(crate) message_bus: Arc<dyn MessageBus>,
47

48
    client_id: i32,              // ID of client.
49
    id_manager: ClientIdManager, // Manages request and order ID generation
50
}
51

52
impl Client {
53
    /// Establishes connection to TWS or Gateway
54
    ///
55
    /// Connects to server using the given connection string
56
    ///
57
    /// # Arguments
58
    /// * `address`   - address of server. e.g. 127.0.0.1:4002
59
    /// * `client_id` - id of client. e.g. 100
60
    ///
61
    /// # Examples
62
    ///
63
    /// ```no_run
64
    /// use ibapi::client::blocking::Client;
65
    ///
66
    /// let client = Client::connect("127.0.0.1:4002", 100).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
    pub fn connect(address: &str, client_id: i32) -> Result<Client, Error> {
78✔
73
        let stream = TcpStream::connect(address)?;
234✔
74
        let socket = TcpSocket::new(stream, address)?;
78✔
75

76
        let connection = Connection::connect(socket, client_id)?;
78✔
77
        let connection_metadata = connection.connection_metadata();
78

79
        let message_bus = Arc::new(TcpMessageBus::new(connection)?);
78✔
80

81
        // Starts thread to read messages from TWS
82
        message_bus.process_messages(connection_metadata.server_version, Duration::from_secs(1))?;
×
83

84
        Client::new(connection_metadata, message_bus)
78✔
85
    }
86

87
    fn new(connection_metadata: ConnectionMetadata, message_bus: Arc<dyn MessageBus>) -> Result<Client, Error> {
79✔
88
        let client = Client {
89
            server_version: connection_metadata.server_version,
158✔
90
            connection_time: connection_metadata.connection_time,
158✔
91
            time_zone: connection_metadata.time_zone,
158✔
92
            message_bus,
93
            client_id: connection_metadata.client_id,
158✔
94
            id_manager: ClientIdManager::new(connection_metadata.next_order_id),
79✔
95
        };
96

97
        Ok(client)
79✔
98
    }
99

100
    /// Returns the ID assigned to the [Client].
101
    pub fn client_id(&self) -> i32 {
2✔
102
        self.client_id
2✔
103
    }
104

105
    /// Returns the next request ID.
106
    pub fn next_request_id(&self) -> i32 {
132✔
107
        self.id_manager.next_request_id()
264✔
108
    }
109

110
    /// Returns and increments the order ID.
111
    ///
112
    /// The client maintains a sequence of order IDs. This function returns the next order ID in the sequence.
113
    pub fn next_order_id(&self) -> i32 {
3✔
114
        self.id_manager.next_order_id()
6✔
115
    }
116

117
    /// Gets the next valid order ID from the TWS server.
118
    ///
119
    /// Unlike [Self::next_order_id], this function requests the next valid order ID from the TWS server and updates the client's internal order ID sequence.
120
    /// This can be for ensuring that order IDs are unique across multiple clients.
121
    ///
122
    /// Use this method when coordinating order IDs across multiple client instances or when you need to synchronize with the server's order ID sequence at the start of a session.
123
    ///
124
    /// # Examples
125
    ///
126
    /// ```no_run
127
    /// use ibapi::client::blocking::Client;
128
    ///
129
    /// // Connect to the TWS server at the given address with client ID.
130
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
131
    ///
132
    /// // Request the next valid order ID from the server.
133
    /// let next_valid_order_id = client.next_valid_order_id().expect("request failed");
134
    /// println!("next_valid_order_id: {next_valid_order_id}");
135
    /// ```
136
    pub fn next_valid_order_id(&self) -> Result<i32, Error> {
1✔
137
        orders::blocking::next_valid_order_id(self)
2✔
138
    }
139

140
    /// Sets the current value of order ID.
141
    pub(crate) fn set_next_order_id(&self, order_id: i32) {
2✔
142
        self.id_manager.set_order_id(order_id);
6✔
143
    }
144

145
    /// Start building an order for the given contract
146
    ///
147
    /// This is the primary API for creating orders, providing a fluent interface
148
    /// that guides you through the order creation process.
149
    ///
150
    /// # Example
151
    /// ```no_run
152
    /// use ibapi::client::blocking::Client;
153
    /// use ibapi::contracts::Contract;
154
    ///
155
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
156
    /// let contract = Contract::stock("AAPL").build();
157
    ///
158
    /// let order_id = client.order(&contract)
159
    ///     .buy(100)
160
    ///     .limit(50.0)
161
    ///     .submit().expect("order submission failed");
162
    /// ```
163
    pub fn order<'a>(&'a self, contract: &'a Contract) -> OrderBuilder<'a, Self> {
×
164
        OrderBuilder::new(self, contract)
×
165
    }
166

167
    /// Returns the version of the TWS API server to which the client is connected.
168
    /// This version is determined during the initial connection handshake.
169
    ///
170
    /// # Examples
171
    ///
172
    /// ```no_run
173
    /// use ibapi::client::blocking::Client;
174
    ///
175
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
176
    /// let server_version = client.server_version();
177
    /// println!("Connected to TWS server version: {server_version:?}");
178
    /// ```
179
    pub fn server_version(&self) -> i32 {
138✔
180
        self.server_version
138✔
181
    }
182

183
    /// The time of the server when the client connected
184
    pub fn connection_time(&self) -> Option<OffsetDateTime> {
×
185
        self.connection_time
×
186
    }
187

188
    /// Returns true if the client is currently connected to TWS/IB Gateway.
189
    ///
190
    /// This method checks if the underlying connection to TWS or IB Gateway is active.
191
    /// Returns false if the connection has been lost, shut down, or reset.
192
    ///
193
    /// # Examples
194
    ///
195
    /// ```no_run
196
    /// use ibapi::client::blocking::Client;
197
    ///
198
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
199
    ///
200
    /// if client.is_connected() {
201
    ///     println!("Client is connected to TWS/Gateway");
202
    /// } else {
203
    ///     println!("Client is not connected");
204
    /// }
205
    /// ```
206
    pub fn is_connected(&self) -> bool {
×
207
        self.message_bus.is_connected()
×
208
    }
209

210
    // === Accounts ===
211

212
    /// TWS's current time. TWS is synchronized with the server (not local computer) using NTP and this function will receive the current time in TWS.
213
    ///
214
    /// # Examples
215
    ///
216
    /// ```no_run
217
    /// use ibapi::client::blocking::Client;
218
    ///
219
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
220
    /// let server_time = client.server_time().expect("error requesting server time");
221
    /// println!("server time: {server_time:?}");
222
    /// ```
223
    pub fn server_time(&self) -> Result<OffsetDateTime, Error> {
10✔
224
        accounts::blocking::server_time(self)
20✔
225
    }
226

227
    /// Subscribes to [PositionUpdate]s for all accessible accounts.
228
    /// All positions sent initially, and then only updates as positions change.
229
    ///
230
    /// # Examples
231
    ///
232
    /// ```no_run
233
    /// use ibapi::client::blocking::Client;
234
    /// use ibapi::accounts::PositionUpdate;
235
    ///
236
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
237
    /// let subscription = client.positions().expect("error requesting positions");
238
    /// for position_response in subscription.iter() {
239
    ///     match position_response {
240
    ///         PositionUpdate::Position(position) => println!("{position:?}"),
241
    ///         PositionUpdate::PositionEnd => println!("initial set of positions received"),
242
    ///     }
243
    /// }
244
    /// ```
245
    pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
5✔
246
        accounts::blocking::positions(self)
10✔
247
    }
248

249
    /// Subscribes to [PositionUpdateMulti] updates for account and/or model.
250
    /// Initially all positions are returned, and then updates are returned for any position changes in real time.
251
    ///
252
    /// # Arguments
253
    /// * `account`    - If an account Id is provided, only the account’s positions belonging to the specified model will be delivered.
254
    /// * `model_code` - The code of the model’s positions we are interested in.
255
    ///
256
    /// # Examples
257
    ///
258
    /// ```no_run
259
    /// use ibapi::client::blocking::Client;
260
    ///
261
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
262
    ///
263
    /// use ibapi::accounts::types::AccountId;
264
    ///
265
    /// let account = AccountId("U1234567".to_string());
266
    /// let subscription = client.positions_multi(Some(&account), None).expect("error requesting positions by model");
267
    /// for position in subscription.iter() {
268
    ///     println!("{position:?}")
269
    /// }
270
    /// ```
271
    pub fn positions_multi(&self, account: Option<&AccountId>, model_code: Option<&ModelCode>) -> Result<Subscription<PositionUpdateMulti>, Error> {
9✔
272
        accounts::blocking::positions_multi(self, account, model_code)
36✔
273
    }
274

275
    /// Creates subscription for real time daily PnL and unrealized PnL updates.
276
    ///
277
    /// # Arguments
278
    /// * `account`    - account for which to receive PnL updates
279
    /// * `model_code` - specify to request PnL updates for a specific model
280
    ///
281
    /// # Examples
282
    ///
283
    /// ```no_run
284
    /// use ibapi::client::blocking::Client;
285
    ///
286
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
287
    /// use ibapi::accounts::types::AccountId;
288
    ///
289
    /// let account = AccountId("account id".to_string());
290
    /// let subscription = client.pnl(&account, None).expect("error requesting pnl");
291
    /// for pnl in subscription.iter() {
292
    ///     println!("{pnl:?}")
293
    /// }
294
    /// ```
295
    pub fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
12✔
296
        accounts::blocking::pnl(self, account, model_code)
48✔
297
    }
298

299
    /// Requests real time updates for daily PnL of individual positions.
300
    ///
301
    /// # Arguments
302
    /// * `account`     - Account in which position exists
303
    /// * `contract_id` - Contract ID of contract to receive daily PnL updates for. Note: does not return response if invalid conId is entered.
304
    /// * `model_code`  - Model in which position exists
305
    ///
306
    /// # Examples
307
    ///
308
    /// ```no_run
309
    /// use ibapi::client::blocking::Client;
310
    ///
311
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
312
    ///
313
    /// use ibapi::accounts::types::{AccountId, ContractId};
314
    ///
315
    /// let account = AccountId("<account id>".to_string());
316
    /// let contract_id = ContractId(1001);
317
    ///
318
    /// let subscription = client.pnl_single(&account, contract_id, None).expect("error requesting pnl");
319
    /// for pnl in &subscription {
320
    ///     println!("{pnl:?}")
321
    /// }
322
    /// ```
323
    pub fn pnl_single(&self, account: &AccountId, contract_id: ContractId, model_code: Option<&ModelCode>) -> Result<Subscription<PnLSingle>, Error> {
10✔
324
        accounts::blocking::pnl_single(self, account, contract_id, model_code)
50✔
325
    }
326

327
    /// Requests a specific account’s summary. Subscribes to the account summary as presented in the TWS’ Account Summary tab. Data received is specified by using a specific tags value.
328
    ///
329
    /// # Arguments
330
    /// * `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.
331
    /// * `tags`  - List of the desired tags.
332
    ///
333
    /// # Examples
334
    ///
335
    /// ```no_run
336
    /// use ibapi::client::blocking::Client;
337
    /// use ibapi::accounts::AccountSummaryTags;
338
    /// use ibapi::accounts::types::AccountGroup;
339
    ///
340
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
341
    ///
342
    /// let group = AccountGroup("All".to_string());
343
    ///
344
    /// let subscription = client.account_summary(&group, &[AccountSummaryTags::ACCOUNT_TYPE]).expect("error requesting account summary");
345
    /// for summary in &subscription {
346
    ///     println!("{summary:?}")
347
    /// }
348
    /// ```
349
    pub fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
11✔
350
        accounts::blocking::account_summary(self, group, tags)
44✔
351
    }
352

353
    /// Subscribes to a specific account’s information and portfolio.
354
    ///
355
    /// All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
356
    ///
357
    /// # Arguments
358
    /// * `account` - The account id (i.e. U1234567) for which the information is requested.
359
    ///
360
    /// # Examples
361
    ///
362
    /// ```no_run
363
    /// use ibapi::client::blocking::Client;
364
    /// use ibapi::accounts::AccountUpdate;
365
    ///
366
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
367
    ///
368
    /// use ibapi::accounts::types::AccountId;
369
    ///
370
    /// let account = AccountId("U1234567".to_string());
371
    ///
372
    /// let subscription = client.account_updates(&account).expect("error requesting account updates");
373
    /// for update in &subscription {
374
    ///     println!("{update:?}");
375
    ///
376
    ///     // stop after full initial update
377
    ///     if let AccountUpdate::End = update {
378
    ///         subscription.cancel();
379
    ///     }
380
    /// }
381
    /// ```
382
    pub fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
3✔
383
        accounts::blocking::account_updates(self, account)
9✔
384
    }
385

386
    /// Requests account updates for account and/or model.
387
    ///
388
    /// All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
389
    ///
390
    /// # Arguments
391
    /// * `account`        - Account values can be requested for a particular account.
392
    /// * `model_code`     - Account values can also be requested for a model.
393
    ///
394
    /// # Examples
395
    ///
396
    /// ```no_run
397
    /// use ibapi::client::blocking::Client;
398
    /// use ibapi::accounts::AccountUpdateMulti;
399
    ///
400
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
401
    ///
402
    /// use ibapi::accounts::types::AccountId;
403
    ///
404
    /// let account = AccountId("U1234567".to_string());
405
    ///
406
    /// let subscription = client.account_updates_multi(Some(&account), None).expect("error requesting account updates multi");
407
    /// for update in &subscription {
408
    ///     println!("{update:?}");
409
    ///
410
    ///     // stop after full initial update
411
    ///     if let AccountUpdateMulti::End = update {
412
    ///         subscription.cancel();
413
    ///     }
414
    /// }
415
    /// ```
416
    pub fn account_updates_multi(
3✔
417
        &self,
418
        account: Option<&AccountId>,
419
        model_code: Option<&ModelCode>,
420
    ) -> Result<Subscription<AccountUpdateMulti>, Error> {
421
        accounts::blocking::account_updates_multi(self, account, model_code)
12✔
422
    }
423

424
    /// Requests the accounts to which the logged user has access to.
425
    ///
426
    /// # Examples
427
    ///
428
    /// ```no_run
429
    /// use ibapi::client::blocking::Client;
430
    ///
431
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
432
    ///
433
    /// let accounts = client.managed_accounts().expect("error requesting managed accounts");
434
    /// println!("managed accounts: {accounts:?}")
435
    /// ```
436
    pub fn managed_accounts(&self) -> Result<Vec<String>, Error> {
11✔
437
        accounts::blocking::managed_accounts(self)
22✔
438
    }
439

440
    // === Contracts ===
441

442
    /// Requests contract information.
443
    ///
444
    /// Provides all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains. Though it is now (in API version > 9.72.12) advised to use [Client::option_chain] for that purpose.
445
    ///
446
    /// # Arguments
447
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
448
    ///
449
    /// # Examples
450
    ///
451
    /// ```no_run
452
    /// use ibapi::client::blocking::Client;
453
    /// use ibapi::contracts::Contract;
454
    ///
455
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
456
    ///
457
    /// let contract = Contract::stock("TSLA").build();
458
    /// let results = client.contract_details(&contract).expect("request failed");
459
    /// for contract_detail in results {
460
    ///     println!("contract: {contract_detail:?}");
461
    /// }
462
    /// ```
463
    pub fn contract_details(&self, contract: &Contract) -> Result<Vec<contracts::ContractDetails>, Error> {
2✔
464
        contracts::blocking::contract_details(self, contract)
6✔
465
    }
466

467
    /// Get current [FamilyCode]s for all accessible accounts.
468
    pub fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
5✔
469
        accounts::blocking::family_codes(self)
10✔
470
    }
471

472
    /// Requests details about a given market rule
473
    ///
474
    /// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
475
    /// A list of market rule ids can be obtained by invoking [Self::contract_details()] for a particular contract.
476
    /// The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [contracts::ContractDetails].
477
    pub fn market_rule(&self, market_rule_id: i32) -> Result<contracts::MarketRule, Error> {
1✔
478
        contracts::blocking::market_rule(self, market_rule_id)
3✔
479
    }
480

481
    /// Requests matching stock symbols.
482
    ///
483
    /// # Arguments
484
    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
485
    ///
486
    /// # Examples
487
    ///
488
    /// ```no_run
489
    /// use ibapi::client::blocking::Client;
490
    ///
491
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
492
    ///
493
    /// let contracts = client.matching_symbols("IB").expect("request failed");
494
    /// for contract in contracts {
495
    ///     println!("contract: {contract:?}");
496
    /// }
497
    /// ```
498
    pub fn matching_symbols(&self, pattern: &str) -> Result<impl Iterator<Item = contracts::ContractDescription>, Error> {
1✔
499
        Ok(contracts::blocking::matching_symbols(self, pattern)?.into_iter())
4✔
500
    }
501

502
    /// Calculates an option’s price based on the provided volatility and its underlying’s price.
503
    ///
504
    /// # Arguments
505
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
506
    /// * `volatility`      - Hypothetical volatility as a percentage (e.g., 20.0 for 20%).
507
    /// * `underlying_price` - Hypothetical price of the underlying asset.
508
    ///
509
    /// # Examples
510
    ///
511
    /// ```no_run
512
    /// use ibapi::client::blocking::Client;
513
    /// use ibapi::contracts::Contract;
514
    ///
515
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
516
    ///
517
    /// let contract = Contract::option("AAPL", "20251219", 150.0, "C");
518
    /// let calculation = client.calculate_option_price(&contract, 100.0, 235.0).expect("request failed");
519
    /// println!("calculation: {calculation:?}");
520
    /// ```
521
    pub fn calculate_option_price(&self, contract: &Contract, volatility: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
2✔
522
        contracts::blocking::calculate_option_price(self, contract, volatility, underlying_price)
10✔
523
    }
524

525
    /// Calculates the implied volatility based on the hypothetical option price and underlying price.
526
    ///
527
    /// # Arguments
528
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
529
    /// * `option_price`    - Hypothetical option price.
530
    /// * `underlying_price` - Hypothetical price of the underlying asset.
531
    ///
532
    /// # Examples
533
    ///
534
    /// ```no_run
535
    /// use ibapi::client::blocking::Client;
536
    /// use ibapi::contracts::Contract;
537
    ///
538
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
539
    ///
540
    /// let contract = Contract::option("AAPL", "20230519", 150.0, "C");
541
    /// let calculation = client.calculate_implied_volatility(&contract, 25.0, 235.0).expect("request failed");
542
    /// println!("calculation: {calculation:?}");
543
    /// ```
544
    pub fn calculate_implied_volatility(&self, contract: &Contract, option_price: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
2✔
545
        contracts::blocking::calculate_implied_volatility(self, contract, option_price, underlying_price)
10✔
546
    }
547

548
    /// Requests security definition option parameters for viewing a contract’s option chain.
549
    ///
550
    /// # Arguments
551
    /// `symbol`   - Contract symbol of the underlying.
552
    /// `exchange` - The exchange on which the returned options are trading. Can be set to the empty string for all exchanges.
553
    /// `security_type` - The type of the underlying security, i.e. STK
554
    /// `contract_id`   - The contract ID of the underlying security.
555
    ///
556
    /// # Examples
557
    ///
558
    /// ```no_run
559
    /// use ibapi::client::blocking::Client;
560
    /// use ibapi::contracts::SecurityType;
561
    ///
562
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
563
    ///
564
    /// let symbol = "AAPL";
565
    /// let exchange = ""; // all exchanges
566
    /// let security_type = SecurityType::Stock;
567
    /// let contract_id = 265598;
568
    ///
569
    /// let subscription = client
570
    ///     .option_chain(symbol, exchange, security_type, contract_id)
571
    ///     .expect("request option chain failed!");
572
    ///
573
    /// for option_chain in &subscription {
574
    ///     println!("{option_chain:?}")
575
    /// }
576
    /// ```
577
    pub fn option_chain(
1✔
578
        &self,
579
        symbol: &str,
580
        exchange: &str,
581
        security_type: SecurityType,
582
        contract_id: i32,
583
    ) -> Result<Subscription<contracts::OptionChain>, Error> {
584
        contracts::blocking::option_chain(self, symbol, exchange, security_type, contract_id)
6✔
585
    }
586

587
    // === Orders ===
588

589
    /// Requests all *current* open orders in associated accounts at the current moment.
590
    /// Open orders are returned once; this function does not initiate a subscription.
591
    ///
592
    /// # Examples
593
    ///
594
    /// ```no_run
595
    /// use ibapi::client::blocking::Client;
596
    ///
597
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
598
    ///
599
    /// let subscription = client.all_open_orders().expect("request failed");
600
    /// for order_data in &subscription {
601
    ///    println!("{order_data:?}")
602
    /// }
603
    /// ```
604
    pub fn all_open_orders(&self) -> Result<Subscription<Orders>, Error> {
2✔
605
        orders::blocking::all_open_orders(self)
4✔
606
    }
607

608
    /// Requests status updates about future orders placed from TWS. Can only be used with client ID 0.
609
    ///
610
    /// # Arguments
611
    /// * `auto_bind` - if set to true, the newly created orders will be assigned an API order ID and implicitly associated with this client. If set to false, future orders will not be.
612
    ///
613
    /// # Examples
614
    ///
615
    /// ```no_run
616
    /// use ibapi::client::blocking::Client;
617
    ///
618
    /// let client = Client::connect("127.0.0.1:4002", 0).expect("connection failed");
619
    ///
620
    /// let subscription = client.auto_open_orders(false).expect("request failed");
621
    /// for order_data in &subscription {
622
    ///    println!("{order_data:?}")
623
    /// }
624
    /// ```
625
    pub fn auto_open_orders(&self, auto_bind: bool) -> Result<Subscription<Orders>, Error> {
2✔
626
        orders::blocking::auto_open_orders(self, auto_bind)
6✔
627
    }
628

629
    /// Cancels an active [Order] placed by the same API client ID.
630
    ///
631
    /// # Arguments
632
    /// * `order_id` - ID of the [Order] to cancel.
633
    /// * `manual_order_cancel_time` - Optional timestamp to specify the cancellation time. Use an empty string to use the current time.
634
    ///
635
    /// # Examples
636
    ///
637
    /// ```no_run
638
    /// use ibapi::client::blocking::Client;
639
    ///
640
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
641
    ///
642
    /// let order_id = 15;
643
    /// let subscription = client.cancel_order(order_id, "").expect("request failed");
644
    /// for result in subscription {
645
    ///    println!("{result:?}");
646
    /// }
647
    /// ```
648
    pub fn cancel_order(&self, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<CancelOrder>, Error> {
2✔
649
        orders::blocking::cancel_order(self, order_id, manual_order_cancel_time)
8✔
650
    }
651

652
    /// Requests completed [Order]s.
653
    ///
654
    /// # Arguments
655
    /// * `api_only` - request only orders placed by the API.
656
    ///
657
    /// # Examples
658
    ///
659
    /// ```no_run
660
    /// use ibapi::client::blocking::Client;
661
    ///
662
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
663
    ///
664
    /// let subscription = client.completed_orders(false).expect("request failed");
665
    /// for order_data in &subscription {
666
    ///    println!("{order_data:?}")
667
    /// }
668
    /// ```
669
    pub fn completed_orders(&self, api_only: bool) -> Result<Subscription<Orders>, Error> {
1✔
670
        orders::blocking::completed_orders(self, api_only)
3✔
671
    }
672

673
    /// Requests current day's (since midnight) executions matching the filter.
674
    ///
675
    /// Only the current day's executions can be retrieved.
676
    /// Along with the [orders::ExecutionData], the [orders::CommissionReport] will also be returned.
677
    /// When requesting executions, a filter can be specified to receive only a subset of them
678
    ///
679
    /// # Arguments
680
    /// * `filter` - filter criteria used to determine which execution reports are returned
681
    ///
682
    /// # Examples
683
    ///
684
    /// ```no_run
685
    /// use ibapi::client::blocking::Client;
686
    /// use ibapi::orders::ExecutionFilter;
687
    ///
688
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
689
    ///
690
    /// let filter = ExecutionFilter{
691
    ///    side: "BUY".to_owned(),
692
    ///    ..ExecutionFilter::default()
693
    /// };
694
    ///
695
    /// let subscription = client.executions(filter).expect("request failed");
696
    /// for execution_data in &subscription {
697
    ///    println!("{execution_data:?}")
698
    /// }
699
    /// ```
700
    pub fn executions(&self, filter: orders::ExecutionFilter) -> Result<Subscription<Executions>, Error> {
2✔
701
        orders::blocking::executions(self, filter)
6✔
702
    }
703

704
    /// Cancels all open [Order]s.
705
    ///
706
    /// # Examples
707
    ///
708
    /// ```no_run
709
    /// use ibapi::client::blocking::Client;
710
    ///
711
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
712
    ///
713
    /// client.global_cancel().expect("request failed");
714
    /// ```
715
    pub fn global_cancel(&self) -> Result<(), Error> {
1✔
716
        orders::blocking::global_cancel(self)
2✔
717
    }
718

719
    /// Requests all open orders places by this specific API client (identified by the API client id).
720
    /// For client ID 0, this will bind previous manual TWS orders.
721
    ///
722
    /// # Examples
723
    ///
724
    /// ```no_run
725
    /// use ibapi::client::blocking::Client;
726
    ///
727
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
728
    ///
729
    /// let subscription = client.open_orders().expect("request failed");
730
    /// for order_data in &subscription {
731
    ///    println!("{order_data:?}")
732
    /// }
733
    /// ```
734
    pub fn open_orders(&self) -> Result<Subscription<Orders>, Error> {
1✔
735
        orders::blocking::open_orders(self)
2✔
736
    }
737

738
    /// Places or modifies an [Order].
739
    ///
740
    /// Submits an [Order] using [Client] for the given [Contract].
741
    /// Upon successful submission, the client will start receiving events related to the order's activity via the subscription, including order status updates and execution reports.
742
    ///
743
    /// # Arguments
744
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
745
    /// * `contract` - [Contract] to submit order for.
746
    /// * `order` - [Order] to submit.
747
    ///
748
    /// # Examples
749
    ///
750
    /// ```no_run
751
    /// use ibapi::client::blocking::Client;
752
    /// use ibapi::contracts::Contract;
753
    /// use ibapi::orders::PlaceOrder;
754
    ///
755
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
756
    ///
757
    /// let contract = Contract::stock("MSFT").build();
758
    /// let order = client.order(&contract)
759
    ///     .buy(100)
760
    ///     .market()
761
    ///     .build_order()
762
    ///     .expect("failed to build order");
763
    /// let order_id = client.next_order_id();
764
    ///
765
    /// let events = client.place_order(order_id, &contract, &order).expect("request failed");
766
    ///
767
    /// for event in &events {
768
    ///     match event {
769
    ///         PlaceOrder::OrderStatus(order_status) => {
770
    ///             println!("order status: {order_status:?}")
771
    ///         }
772
    ///         PlaceOrder::OpenOrder(open_order) => println!("open order: {open_order:?}"),
773
    ///         PlaceOrder::ExecutionData(execution) => println!("execution: {execution:?}"),
774
    ///         PlaceOrder::CommissionReport(report) => println!("commission report: {report:?}"),
775
    ///         PlaceOrder::Message(message) => println!("message: {message:?}"),
776
    ///    }
777
    /// }
778
    /// ```
779
    pub fn place_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<Subscription<PlaceOrder>, Error> {
4✔
780
        orders::blocking::place_order(self, order_id, contract, order)
20✔
781
    }
782

783
    /// Submits or modifies an [Order] without returning a subscription.
784
    ///
785
    /// This is a fire-and-forget method that submits an [Order] for the given [Contract]
786
    /// but does not return a subscription for order updates. To receive order status updates,
787
    /// fills, and commission reports, use the [`order_update_stream`](Client::order_update_stream) method
788
    /// or use [`place_order`](Client::place_order) instead which returns a subscription.
789
    ///
790
    /// # Arguments
791
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
792
    /// * `contract` - [Contract] to submit order for.
793
    /// * `order` - [Order] to submit.
794
    ///
795
    /// # Returns
796
    /// * `Ok(())` if the order was successfully sent
797
    /// * `Err(Error)` if validation failed or sending failed
798
    ///
799
    /// # Examples
800
    ///
801
    /// ```no_run
802
    /// use ibapi::client::blocking::Client;
803
    /// use ibapi::contracts::Contract;
804
    ///
805
    /// # fn main() -> Result<(), ibapi::Error> {
806
    ///
807
    /// let client = Client::connect("127.0.0.1:4002", 100)?;
808
    ///
809
    /// let contract = Contract::stock("MSFT").build();
810
    /// let order = client.order(&contract)
811
    ///     .buy(100)
812
    ///     .market()
813
    ///     .build_order()?;
814
    /// let order_id = client.next_order_id();
815
    ///
816
    /// // Submit order without waiting for confirmation
817
    /// client.submit_order(order_id, &contract, &order)?;
818
    ///
819
    /// // Monitor all order updates via the order update stream
820
    /// // This will receive updates for ALL orders, not just this one
821
    /// use ibapi::orders::OrderUpdate;
822
    /// for event in client.order_update_stream()? {
823
    ///     match event {
824
    ///         OrderUpdate::OrderStatus(status) => println!("Order Status: {status:?}"),
825
    ///         OrderUpdate::ExecutionData(exec) => println!("Execution: {exec:?}"),
826
    ///         OrderUpdate::CommissionReport(report) => println!("Commission: {report:?}"),
827
    ///         _ => {}
828
    ///     }
829
    /// }
830
    ///
831
    /// # Ok(())
832
    /// # }
833
    /// ```
834
    pub fn submit_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<(), Error> {
2✔
835
        orders::blocking::submit_order(self, order_id, contract, order)
10✔
836
    }
837

838
    /// Creates a subscription stream for receiving real-time order updates.
839
    ///
840
    /// This method establishes a stream that receives all order-related events including:
841
    /// - Order status updates (e.g., submitted, filled, cancelled)
842
    /// - Open order information
843
    /// - Execution data for trades
844
    /// - Commission reports
845
    /// - Order-related messages and notices
846
    ///
847
    /// The stream will receive updates for all orders placed through this client connection,
848
    /// including both new orders submitted after creating the stream and existing orders.
849
    ///
850
    /// # Returns
851
    ///
852
    /// Returns a `Subscription<OrderUpdate>` that yields `OrderUpdate` enum variants containing:
853
    /// - `OrderStatus`: Current status of an order (filled amount, average price, etc.)
854
    /// - `OpenOrder`: Complete order details including contract and order parameters
855
    /// - `ExecutionData`: Details about individual trade executions
856
    /// - `CommissionReport`: Commission information for executed trades
857
    /// - `Message`: Notices or error messages related to orders
858
    ///
859
    /// # Errors
860
    ///
861
    /// Returns an error if the subscription cannot be created, typically due to
862
    /// connection issues or internal errors.
863
    ///
864
    /// # Examples
865
    ///
866
    /// ```no_run
867
    /// use ibapi::client::blocking::Client;
868
    /// use ibapi::orders::OrderUpdate;
869
    ///
870
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
871
    ///
872
    /// // Create order update stream
873
    /// let updates = client.order_update_stream().expect("failed to create stream");
874
    ///
875
    /// // Process order updates
876
    /// for update in updates {
877
    ///     match update {
878
    ///         OrderUpdate::OrderStatus(status) => {
879
    ///             println!("Order {} status: {} - filled: {}/{}",
880
    ///                 status.order_id, status.status, status.filled, status.remaining);
881
    ///         },
882
    ///         OrderUpdate::OpenOrder(order_data) => {
883
    ///             println!("Open order {}: {} {} @ {}",
884
    ///                 order_data.order.order_id,
885
    ///                 order_data.order.action,
886
    ///                 order_data.order.total_quantity,
887
    ///                 order_data.order.limit_price.unwrap_or(0.0));
888
    ///         },
889
    ///         OrderUpdate::ExecutionData(exec) => {
890
    ///             println!("Execution: {} {} @ {} on {}",
891
    ///                 exec.execution.side,
892
    ///                 exec.execution.shares,
893
    ///                 exec.execution.price,
894
    ///                 exec.execution.exchange);
895
    ///         },
896
    ///         OrderUpdate::CommissionReport(report) => {
897
    ///             println!("Commission: ${} for execution {}",
898
    ///                 report.commission, report.execution_id);
899
    ///         },
900
    ///         OrderUpdate::Message(notice) => {
901
    ///             println!("Order message: {}", notice.message);
902
    ///         }
903
    ///     }
904
    /// }
905
    /// ```
906
    ///
907
    /// # Note
908
    ///
909
    /// This stream provides updates for all orders, not just a specific order.
910
    /// To track a specific order, filter the updates by order ID.
911
    pub fn order_update_stream(&self) -> Result<Subscription<OrderUpdate>, Error> {
4✔
912
        orders::blocking::order_update_stream(self)
8✔
913
    }
914

915
    /// Exercises an options contract.
916
    ///
917
    /// Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized.
918
    ///
919
    /// # Arguments
920
    /// * `contract`          - The option [Contract] to be exercised.
921
    /// * `exercise_action`   - Exercise option. ExerciseAction::Exercise or ExerciseAction::Lapse.
922
    /// * `exercise_quantity` - Number of contracts to be exercised.
923
    /// * `account`           - Destination account.
924
    /// * `ovrd`              - Specifies whether your setting will override the system’s natural action. For example, if your action is "exercise" and the option is not in-the-money, by natural action the option would not exercise. If you have override set to true the natural action would be overridden and the out-of-the money option would be exercised.
925
    /// * `manual_order_time` - Specify the time at which the options should be exercised. If `None`, the current time will be used. Requires TWS API 10.26 or higher.
926
    pub fn exercise_options(
1✔
927
        &self,
928
        contract: &Contract,
929
        exercise_action: orders::ExerciseAction,
930
        exercise_quantity: i32,
931
        account: &str,
932
        ovrd: bool,
933
        manual_order_time: Option<OffsetDateTime>,
934
    ) -> Result<Subscription<ExerciseOptions>, Error> {
935
        orders::blocking::exercise_options(self, contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
8✔
936
    }
937

938
    // === Historical Market Data ===
939

940
    /// Returns the timestamp of earliest available historical data for a contract and data type.
941
    ///
942
    /// ```no_run
943
    /// use ibapi::client::blocking::Client;
944
    /// use ibapi::contracts::Contract;
945
    /// use ibapi::market_data::historical::{self, WhatToShow};
946
    /// use ibapi::market_data::TradingHours;
947
    ///
948
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
949
    ///
950
    /// let contract = Contract::stock("MSFT").build();
951
    /// let what_to_show = WhatToShow::Trades;
952
    /// let trading_hours = TradingHours::Regular;
953
    ///
954
    /// let result = client.head_timestamp(&contract, what_to_show, trading_hours).expect("head timestamp failed");
955
    ///
956
    /// print!("head_timestamp: {result:?}");
957
    /// ```
958
    pub fn head_timestamp(
2✔
959
        &self,
960
        contract: &Contract,
961
        what_to_show: historical::WhatToShow,
962
        trading_hours: TradingHours,
963
    ) -> Result<OffsetDateTime, Error> {
964
        historical::blocking::head_timestamp(self, contract, what_to_show, trading_hours)
10✔
965
    }
966

967
    /// Requests interval of historical data ending at specified time for [Contract].
968
    ///
969
    /// # Arguments
970
    /// * `contract`     - [Contract] to retrieve [historical::HistoricalData] for.
971
    /// * `interval_end` - optional end date of interval to retrieve [historical::HistoricalData] for. If `None` current time or last trading of contract is implied.
972
    /// * `duration`     - duration of interval to retrieve [historical::HistoricalData] for.
973
    /// * `bar_size`     - [historical::BarSize] to return.
974
    /// * `what_to_show` - requested bar type: [historical::WhatToShow].
975
    /// * `use_rth`      - use regular trading hours.
976
    ///
977
    /// # Examples
978
    ///
979
    /// ```no_run
980
    /// use time::macros::datetime;
981
    ///
982
    /// use ibapi::contracts::Contract;
983
    /// use ibapi::client::blocking::Client;
984
    /// use ibapi::market_data::historical::{BarSize, ToDuration, WhatToShow};
985
    /// use ibapi::market_data::TradingHours;
986
    ///
987
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
988
    ///
989
    /// let contract = Contract::stock("TSLA").build();
990
    ///
991
    /// let historical_data = client
992
    ///     .historical_data(&contract, Some(datetime!(2023-04-15 0:00 UTC)), 7.days(), BarSize::Day, WhatToShow::Trades, TradingHours::Regular)
993
    ///     .expect("historical data request failed");
994
    ///
995
    /// println!("start_date: {}, end_date: {}", historical_data.start, historical_data.end);
996
    ///
997
    /// for bar in &historical_data.bars {
998
    ///     println!("{bar:?}");
999
    /// }
1000
    /// ```
1001
    pub fn historical_data(
7✔
1002
        &self,
1003
        contract: &Contract,
1004
        interval_end: Option<OffsetDateTime>,
1005
        duration: historical::Duration,
1006
        bar_size: historical::BarSize,
1007
        what_to_show: historical::WhatToShow,
1008
        trading_hours: TradingHours,
1009
    ) -> Result<historical::HistoricalData, Error> {
1010
        historical::blocking::historical_data(self, contract, interval_end, duration, bar_size, Some(what_to_show), trading_hours)
56✔
1011
    }
1012

1013
    /// Requests [Schedule](historical::Schedule) for an interval of given duration
1014
    /// ending at specified date.
1015
    ///
1016
    /// # Arguments
1017
    /// * `contract`     - [Contract] to retrieve [Schedule](historical::Schedule) for.
1018
    /// * `interval_end` - end date of interval to retrieve [Schedule](historical::Schedule) for.
1019
    /// * `duration`     - duration of interval to retrieve [Schedule](historical::Schedule) for.
1020
    ///
1021
    /// # Examples
1022
    ///
1023
    /// ```no_run
1024
    /// use time::macros::datetime;
1025
    /// use ibapi::contracts::Contract;
1026
    /// use ibapi::client::blocking::Client;
1027
    /// use ibapi::market_data::historical::ToDuration;
1028
    ///
1029
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1030
    ///
1031
    /// let contract = Contract::stock("GM").build();
1032
    ///
1033
    /// let historical_data = client
1034
    ///     .historical_schedules(&contract, datetime!(2023-04-15 0:00 UTC), 30.days())
1035
    ///     .expect("historical schedule request failed");
1036
    ///
1037
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
1038
    ///
1039
    /// for session in &historical_data.sessions {
1040
    ///     println!("{session:?}");
1041
    /// }
1042
    /// ```
1043
    pub fn historical_schedules(
2✔
1044
        &self,
1045
        contract: &Contract,
1046
        interval_end: OffsetDateTime,
1047
        duration: historical::Duration,
1048
    ) -> Result<historical::Schedule, Error> {
1049
        historical::blocking::historical_schedule(self, contract, Some(interval_end), duration)
10✔
1050
    }
1051

1052
    /// Requests [historical::Schedule] for interval ending at current time.
1053
    ///
1054
    /// # Arguments
1055
    /// * `contract` - [Contract] to retrieve [historical::Schedule] for.
1056
    /// * `duration` - [historical::Duration] for interval to retrieve [historical::Schedule] for.
1057
    ///
1058
    /// # Examples
1059
    ///
1060
    /// ```no_run
1061
    /// use ibapi::contracts::Contract;
1062
    /// use ibapi::client::blocking::Client;
1063
    /// use ibapi::market_data::historical::ToDuration;
1064
    ///
1065
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1066
    ///
1067
    /// let contract = Contract::stock("GM").build();
1068
    ///
1069
    /// let historical_data = client
1070
    ///     .historical_schedules_ending_now(&contract, 30.days())
1071
    ///     .expect("historical schedule request failed");
1072
    ///
1073
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
1074
    ///
1075
    /// for session in &historical_data.sessions {
1076
    ///     println!("{session:?}");
1077
    /// }
1078
    /// ```
1079
    pub fn historical_schedules_ending_now(&self, contract: &Contract, duration: historical::Duration) -> Result<historical::Schedule, Error> {
×
NEW
1080
        historical::blocking::historical_schedule(self, contract, None, duration)
×
1081
    }
1082

1083
    /// Requests historical time & sales data (Bid/Ask) for an instrument.
1084
    ///
1085
    /// # Arguments
1086
    /// * `contract` - [Contract] object that is subject of query
1087
    /// * `start`    - Start time. Either start time or end time is specified.
1088
    /// * `end`      - End time. Either start time or end time is specified.
1089
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1090
    /// * `trading_hours`   - Regular trading hours only, or include extended hours
1091
    /// * `ignore_size`     - A filter only used when the source price is Bid_Ask
1092
    ///
1093
    /// # Examples
1094
    ///
1095
    /// ```no_run
1096
    /// use time::macros::datetime;
1097
    ///
1098
    /// use ibapi::contracts::Contract;
1099
    /// use ibapi::client::blocking::Client;
1100
    /// use ibapi::market_data::TradingHours;
1101
    ///
1102
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1103
    ///
1104
    /// let contract = Contract::stock("TSLA").build();
1105
    ///
1106
    /// let ticks = client
1107
    ///     .historical_ticks_bid_ask(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, TradingHours::Regular, false)
1108
    ///     .expect("historical ticks request failed");
1109
    ///
1110
    /// for tick in ticks {
1111
    ///     println!("{tick:?}");
1112
    /// }
1113
    /// ```
1114
    pub fn historical_ticks_bid_ask(
3✔
1115
        &self,
1116
        contract: &Contract,
1117
        start: Option<OffsetDateTime>,
1118
        end: Option<OffsetDateTime>,
1119
        number_of_ticks: i32,
1120
        trading_hours: TradingHours,
1121
        ignore_size: bool,
1122
    ) -> Result<historical::blocking::TickSubscription<historical::TickBidAsk>, Error> {
1123
        historical::blocking::historical_ticks_bid_ask(self, contract, start, end, number_of_ticks, trading_hours, ignore_size)
24✔
1124
    }
1125

1126
    /// Requests historical time & sales data (Midpoint) for an instrument.
1127
    ///
1128
    /// # Arguments
1129
    /// * `contract` - [Contract] object that is subject of query
1130
    /// * `start`    - Start time. Either start time or end time is specified.
1131
    /// * `end`      - End time. Either start time or end time is specified.
1132
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1133
    /// * `trading_hours`   - Regular trading hours only, or include extended hours
1134
    ///
1135
    /// # Examples
1136
    ///
1137
    /// ```no_run
1138
    /// use time::macros::datetime;
1139
    ///
1140
    /// use ibapi::contracts::Contract;
1141
    /// use ibapi::client::blocking::Client;
1142
    /// use ibapi::market_data::TradingHours;
1143
    ///
1144
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1145
    ///
1146
    /// let contract = Contract::stock("TSLA").build();
1147
    ///
1148
    /// let ticks = client
1149
    ///     .historical_ticks_mid_point(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, TradingHours::Regular)
1150
    ///     .expect("historical ticks request failed");
1151
    ///
1152
    /// for tick in ticks {
1153
    ///     println!("{tick:?}");
1154
    /// }
1155
    /// ```
1156
    pub fn historical_ticks_mid_point(
3✔
1157
        &self,
1158
        contract: &Contract,
1159
        start: Option<OffsetDateTime>,
1160
        end: Option<OffsetDateTime>,
1161
        number_of_ticks: i32,
1162
        trading_hours: TradingHours,
1163
    ) -> Result<historical::blocking::TickSubscription<historical::TickMidpoint>, Error> {
1164
        historical::blocking::historical_ticks_mid_point(self, contract, start, end, number_of_ticks, trading_hours)
21✔
1165
    }
1166

1167
    /// Requests historical time & sales data (Trades) for an instrument.
1168
    ///
1169
    /// # Arguments
1170
    /// * `contract` - [Contract] object that is subject of query
1171
    /// * `start`    - Start time. Either start time or end time is specified.
1172
    /// * `end`      - End time. Either start time or end time is specified.
1173
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1174
    /// * `trading_hours`   - Regular trading hours only, or include extended hours
1175
    ///
1176
    /// # Examples
1177
    ///
1178
    /// ```no_run
1179
    /// use time::macros::datetime;
1180
    ///
1181
    /// use ibapi::contracts::Contract;
1182
    /// use ibapi::client::blocking::Client;
1183
    /// use ibapi::market_data::TradingHours;
1184
    ///
1185
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1186
    ///
1187
    /// let contract = Contract::stock("TSLA").build();
1188
    ///
1189
    /// let ticks = client
1190
    ///     .historical_ticks_trade(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, TradingHours::Regular)
1191
    ///     .expect("historical ticks request failed");
1192
    ///
1193
    /// for tick in ticks {
1194
    ///     println!("{tick:?}");
1195
    /// }
1196
    /// ```
1197
    pub fn historical_ticks_trade(
5✔
1198
        &self,
1199
        contract: &Contract,
1200
        start: Option<OffsetDateTime>,
1201
        end: Option<OffsetDateTime>,
1202
        number_of_ticks: i32,
1203
        trading_hours: TradingHours,
1204
    ) -> Result<historical::blocking::TickSubscription<historical::TickLast>, Error> {
1205
        historical::blocking::historical_ticks_trade(self, contract, start, end, number_of_ticks, trading_hours)
35✔
1206
    }
1207

1208
    /// Requests data histogram of specified contract.
1209
    ///
1210
    /// # Arguments
1211
    /// * `contract`  - [Contract] to retrieve [Histogram Entries](historical::HistogramEntry) for.
1212
    /// * `trading_hours` - Regular trading hours only, or include extended hours.
1213
    /// * `period`    - The time period of each histogram bar (e.g., `BarSize::Day`, `BarSize::Week`, `BarSize::Month`).
1214
    ///
1215
    /// # Examples
1216
    ///
1217
    /// ```no_run
1218
    /// use time::macros::datetime;
1219
    //
1220
    /// use ibapi::contracts::Contract;
1221
    /// use ibapi::client::blocking::Client;
1222
    /// use ibapi::market_data::historical::BarSize;
1223
    /// use ibapi::market_data::TradingHours;
1224
    ///
1225
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1226
    ///
1227
    /// let contract = Contract::stock("GM").build();
1228
    ///
1229
    /// let histogram = client
1230
    ///     .histogram_data(&contract, TradingHours::Regular, BarSize::Week)
1231
    ///     .expect("histogram request failed");
1232
    ///
1233
    /// for item in &histogram {
1234
    ///     println!("{item:?}");
1235
    /// }
1236
    /// ```
1237
    pub fn histogram_data(
2✔
1238
        &self,
1239
        contract: &Contract,
1240
        trading_hours: TradingHours,
1241
        period: historical::BarSize,
1242
    ) -> Result<Vec<HistogramEntry>, Error> {
1243
        historical::blocking::histogram_data(self, contract, trading_hours, period)
10✔
1244
    }
1245

1246
    // === Realtime Market Data ===
1247

1248
    /// Requests realtime bars.
1249
    ///
1250
    /// # Arguments
1251
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
1252
    ///
1253
    /// # Examples
1254
    ///
1255
    /// ```no_run
1256
    /// use ibapi::client::blocking::Client;
1257
    /// use ibapi::contracts::Contract;
1258
    /// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1259
    /// use ibapi::market_data::TradingHours;
1260
    ///
1261
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1262
    ///
1263
    /// let contract = Contract::stock("TSLA").build();
1264
    /// let subscription = client.realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, TradingHours::Extended).expect("request failed");
1265
    ///
1266
    /// for (i, bar) in subscription.iter().enumerate().take(60) {
1267
    ///     println!("bar[{i}]: {bar:?}");
1268
    /// }
1269
    /// ```
1270
    pub fn realtime_bars(
3✔
1271
        &self,
1272
        contract: &Contract,
1273
        bar_size: BarSize,
1274
        what_to_show: WhatToShow,
1275
        trading_hours: TradingHours,
1276
    ) -> Result<Subscription<Bar>, Error> {
1277
        realtime::blocking::realtime_bars(self, contract, &bar_size, &what_to_show, trading_hours, Vec::default())
21✔
1278
    }
1279

1280
    /// Requests tick by tick AllLast ticks.
1281
    ///
1282
    /// # Arguments
1283
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1284
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1285
    /// * `ignore_size`     - Specifies if tick sizes should be ignored.
1286
    ///
1287
    /// # Examples
1288
    ///
1289
    /// ```no_run
1290
    /// use ibapi::client::blocking::Client;
1291
    /// use ibapi::contracts::Contract;
1292
    ///
1293
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1294
    ///
1295
    /// let contract = Contract::stock("AAPL").build();
1296
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1297
    /// let ignore_size = false;
1298
    ///
1299
    /// let subscription = client.tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
1300
    ///     .expect("tick-by-tick all last data request failed");
1301
    ///
1302
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1303
    ///     println!("All Last Tick: {tick:?}");
1304
    /// }
1305
    /// ```
1306
    pub fn tick_by_tick_all_last(
2✔
1307
        &self,
1308
        contract: &Contract,
1309
        number_of_ticks: i32,
1310
        ignore_size: bool,
1311
    ) -> Result<Subscription<realtime::Trade>, Error> {
1312
        realtime::blocking::tick_by_tick_all_last(self, contract, number_of_ticks, ignore_size)
10✔
1313
    }
1314

1315
    /// Requests tick by tick BidAsk ticks.
1316
    ///
1317
    /// # Arguments
1318
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1319
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1320
    /// * `ignore_size`     - Specifies if tick sizes should be ignored. (typically true for BidAsk ticks to get changes based on price).
1321
    ///
1322
    /// # Examples
1323
    ///
1324
    /// ```no_run
1325
    /// use ibapi::client::blocking::Client;
1326
    /// use ibapi::contracts::Contract;
1327
    ///
1328
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1329
    ///
1330
    /// let contract = Contract::stock("AAPL").build();
1331
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1332
    /// let ignore_size = false;
1333
    ///
1334
    /// let subscription = client.tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
1335
    ///     .expect("tick-by-tick bid/ask data request failed");
1336
    ///
1337
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1338
    ///     println!("BidAsk Tick: {tick:?}");
1339
    /// }
1340
    /// ```
1341
    pub fn tick_by_tick_bid_ask(
2✔
1342
        &self,
1343
        contract: &Contract,
1344
        number_of_ticks: i32,
1345
        ignore_size: bool,
1346
    ) -> Result<Subscription<realtime::BidAsk>, Error> {
1347
        realtime::blocking::tick_by_tick_bid_ask(self, contract, number_of_ticks, ignore_size)
10✔
1348
    }
1349

1350
    /// Requests tick by tick Last ticks.
1351
    ///
1352
    /// # Arguments
1353
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1354
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1355
    /// * `ignore_size`     - Specifies if tick sizes should be ignored (typically false for Last ticks).
1356
    ///
1357
    /// # Examples
1358
    ///
1359
    /// ```no_run
1360
    /// use ibapi::client::blocking::Client;
1361
    /// use ibapi::contracts::Contract;
1362
    ///
1363
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1364
    ///
1365
    /// let contract = Contract::stock("AAPL").build();
1366
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1367
    /// let ignore_size = false;
1368
    ///
1369
    /// let subscription = client.tick_by_tick_last(&contract, number_of_ticks, ignore_size)
1370
    ///     .expect("tick-by-tick last data request failed");
1371
    ///
1372
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1373
    ///     println!("Last Tick: {tick:?}");
1374
    /// }
1375
    /// ```
1376
    pub fn tick_by_tick_last(&self, contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<Subscription<realtime::Trade>, Error> {
2✔
1377
        realtime::blocking::tick_by_tick_last(self, contract, number_of_ticks, ignore_size)
10✔
1378
    }
1379

1380
    /// Requests tick by tick MidPoint ticks.
1381
    ///
1382
    /// # Arguments
1383
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1384
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1385
    /// * `ignore_size`     - Specifies if tick sizes should be ignored.
1386
    ///
1387
    /// # Examples
1388
    ///
1389
    /// ```no_run
1390
    /// use ibapi::client::blocking::Client;
1391
    /// use ibapi::contracts::Contract;
1392
    ///
1393
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1394
    ///
1395
    /// let contract = Contract::stock("AAPL").build();
1396
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1397
    /// let ignore_size = false;
1398
    ///
1399
    /// let subscription = client.tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
1400
    ///     .expect("tick-by-tick mid-point data request failed");
1401
    ///
1402
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1403
    ///     println!("MidPoint Tick: {tick:?}");
1404
    /// }
1405
    /// ```
1406
    pub fn tick_by_tick_midpoint(&self, contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<Subscription<MidPoint>, Error> {
2✔
1407
        realtime::blocking::tick_by_tick_midpoint(self, contract, number_of_ticks, ignore_size)
10✔
1408
    }
1409

1410
    /// Switches market data type returned from request_market_data requests to Live, Frozen, Delayed, or FrozenDelayed.
1411
    ///
1412
    /// # Arguments
1413
    /// * `market_data_type` - Type of market data to retrieve.
1414
    ///
1415
    /// # Examples
1416
    ///
1417
    /// ```no_run
1418
    /// use ibapi::client::blocking::Client;
1419
    /// use ibapi::market_data::{MarketDataType};
1420
    ///
1421
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1422
    ///
1423
    /// let market_data_type = MarketDataType::Realtime;
1424
    /// client.switch_market_data_type(market_data_type).expect("request failed");
1425
    /// println!("market data switched: {market_data_type:?}");
1426
    /// ```
1427
    pub fn switch_market_data_type(&self, market_data_type: MarketDataType) -> Result<(), Error> {
1✔
1428
        market_data::blocking::switch_market_data_type(self, market_data_type)
3✔
1429
    }
1430

1431
    /// Requests the contract's market depth (order book).
1432
    ///
1433
    /// # Arguments
1434
    ///
1435
    /// * `contract` - The Contract for which the depth is being requested.
1436
    /// * `number_of_rows` - The number of rows on each side of the order book.
1437
    /// * `is_smart_depth` - Flag indicates that this is smart depth request.
1438
    ///
1439
    /// # Examples
1440
    ///
1441
    /// ```no_run
1442
    /// use ibapi::client::blocking::Client;
1443
    /// use ibapi::contracts::Contract;
1444
    ///
1445
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1446
    ///
1447
    /// let contract = Contract::stock("AAPL").build();
1448
    ///
1449
    /// let subscription = client.market_depth(&contract, 5, true).expect("error requesting market depth");
1450
    /// for row in &subscription {
1451
    ///     println!("row: {row:?}");
1452
    /// }
1453
    ///
1454
    /// if let Some(error) = subscription.error() {
1455
    ///     println!("error: {error:?}");
1456
    /// }
1457
    /// ```
1458
    pub fn market_depth(&self, contract: &Contract, number_of_rows: i32, is_smart_depth: bool) -> Result<Subscription<MarketDepths>, Error> {
2✔
1459
        realtime::blocking::market_depth(self, contract, number_of_rows, is_smart_depth)
10✔
1460
    }
1461

1462
    /// Requests venues for which market data is returned to market_depth (those with market makers)
1463
    ///
1464
    /// # Examples
1465
    ///
1466
    /// ```no_run
1467
    /// use ibapi::client::blocking::Client;
1468
    ///
1469
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1470
    /// let exchanges = client.market_depth_exchanges().expect("error requesting market depth exchanges");
1471
    /// for exchange in &exchanges {
1472
    ///     println!("{exchange:?}");
1473
    /// }
1474
    /// ```
1475
    pub fn market_depth_exchanges(&self) -> Result<Vec<DepthMarketDataDescription>, Error> {
2✔
1476
        realtime::blocking::market_depth_exchanges(self)
4✔
1477
    }
1478

1479
    /// Requests real time market data.
1480
    ///
1481
    /// Creates a market data subscription builder with a fluent interface.
1482
    ///
1483
    /// This is the preferred way to subscribe to market data, providing a more
1484
    /// intuitive and discoverable API than the raw method.
1485
    ///
1486
    /// # Arguments
1487
    /// * `contract` - The contract to receive market data for
1488
    ///
1489
    /// # Examples
1490
    ///
1491
    /// ```no_run
1492
    /// use ibapi::client::blocking::Client;
1493
    /// use ibapi::contracts::Contract;
1494
    /// use ibapi::market_data::realtime::TickTypes;
1495
    ///
1496
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1497
    /// let contract = Contract::stock("AAPL").build();
1498
    ///
1499
    /// // Subscribe to real-time streaming data with specific tick types
1500
    /// let subscription = client.market_data(&contract)
1501
    ///     .generic_ticks(&["233", "236"])  // RTVolume and Shortable
1502
    ///     .subscribe()
1503
    ///     .expect("subscription failed");
1504
    ///
1505
    /// for tick in &subscription {
1506
    ///     match tick {
1507
    ///         TickTypes::Price(price) => println!("Price: {price:?}"),
1508
    ///         TickTypes::Size(size) => println!("Size: {size:?}"),
1509
    ///         TickTypes::SnapshotEnd => subscription.cancel(),
1510
    ///         _ => {}
1511
    ///     }
1512
    /// }
1513
    /// ```
1514
    ///
1515
    /// ```no_run
1516
    /// use ibapi::client::blocking::Client;
1517
    /// use ibapi::contracts::Contract;
1518
    /// use ibapi::market_data::realtime::TickTypes;
1519
    ///
1520
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1521
    /// let contract = Contract::stock("AAPL").build();
1522
    ///
1523
    /// // Request a one-time snapshot
1524
    /// let subscription = client.market_data(&contract)
1525
    ///     .snapshot()
1526
    ///     .subscribe()
1527
    ///     .expect("subscription failed");
1528
    ///
1529
    /// for tick in &subscription {
1530
    ///     if let TickTypes::SnapshotEnd = tick {
1531
    ///         println!("Snapshot complete");
1532
    ///         break;
1533
    ///     }
1534
    /// }
1535
    /// ```
1536
    pub fn market_data<'a>(&'a self, contract: &'a Contract) -> MarketDataBuilder<'a, Self> {
12✔
1537
        MarketDataBuilder::new(self, contract)
36✔
1538
    }
1539

1540
    // === News ===
1541

1542
    /// Requests news providers which the user has subscribed to.
1543
    ///
1544
    /// # Examples
1545
    ///
1546
    /// ```no_run
1547
    /// use ibapi::client::blocking::Client;
1548
    ///
1549
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1550
    ///
1551
    /// let news_providers = client.news_providers().expect("request news providers failed");
1552
    /// for news_provider in &news_providers {
1553
    ///   println!("news provider {news_provider:?}");
1554
    /// }
1555
    /// ```
1556
    pub fn news_providers(&self) -> Result<Vec<news::NewsProvider>, Error> {
1✔
1557
        news::blocking::news_providers(self)
2✔
1558
    }
1559

1560
    /// Subscribes to IB's News Bulletins.
1561
    ///
1562
    /// # Arguments
1563
    ///
1564
    /// * `all_messages` - If set to true, will return all the existing bulletins for the current day, set to false to receive only the new bulletins.
1565
    ///
1566
    /// # Examples
1567
    ///
1568
    /// ```no_run
1569
    /// use ibapi::client::blocking::Client;
1570
    ///
1571
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1572
    ///
1573
    /// let news_bulletins = client.news_bulletins(true).expect("request news providers failed");
1574
    /// for news_bulletin in &news_bulletins {
1575
    ///   println!("news bulletin {news_bulletin:?}");
1576
    /// }
1577
    /// ```
1578
    pub fn news_bulletins(&self, all_messages: bool) -> Result<Subscription<news::NewsBulletin>, Error> {
1✔
1579
        news::blocking::news_bulletins(self, all_messages)
3✔
1580
    }
1581

1582
    /// Requests historical news headlines.
1583
    ///
1584
    /// # Arguments
1585
    ///
1586
    /// * `contract_id`    - Contract ID of ticker. See [contract_details](Client::contract_details) for how to retrieve contract ID.
1587
    /// * `provider_codes` - A list of provider codes.
1588
    /// * `start_time`     - Marks the (exclusive) start of the date range.
1589
    /// * `end_time`       - Marks the (inclusive) end of the date range.
1590
    /// * `total_results`  - The maximum number of headlines to fetch (1 – 300)
1591
    ///
1592
    /// # Examples
1593
    ///
1594
    /// ```no_run
1595
    /// use ibapi::client::blocking::Client;
1596
    /// use ibapi::contracts::Contract; // Or remove if conId is always known
1597
    /// use time::macros::datetime;
1598
    ///
1599
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1600
    ///
1601
    /// // Example: Fetch historical news for a known contract ID (e.g., AAPL's conId)
1602
    /// let contract_id = 265598;
1603
    /// let provider_codes = &["DJNL", "BRFG"]; // Example provider codes
1604
    /// // Define a past date range for the news query
1605
    /// let start_time = datetime!(2023-01-01 0:00 UTC);
1606
    /// let end_time = datetime!(2023-01-02 0:00 UTC);
1607
    /// let total_results = 5u8; // Request a small number of articles for the example
1608
    ///
1609
    /// let articles_subscription = client.historical_news(
1610
    ///     contract_id,
1611
    ///     provider_codes,
1612
    ///     start_time,
1613
    ///     end_time,
1614
    ///     total_results,
1615
    /// ).expect("request historical news failed");
1616
    ///
1617
    /// println!("Requested historical news articles:");
1618
    /// for article in articles_subscription.iter().take(total_results as usize) {
1619
    ///     println!("- Headline: {}, ID: {}, Provider: {}, Time: {}",
1620
    ///              article.headline, article.article_id, article.provider_code, article.time);
1621
    /// }
1622
    /// ```
1623
    pub fn historical_news(
1✔
1624
        &self,
1625
        contract_id: i32,
1626
        provider_codes: &[&str],
1627
        start_time: OffsetDateTime,
1628
        end_time: OffsetDateTime,
1629
        total_results: u8,
1630
    ) -> Result<Subscription<news::NewsArticle>, Error> {
1631
        news::blocking::historical_news(self, contract_id, provider_codes, start_time, end_time, total_results)
7✔
1632
    }
1633

1634
    /// Requests news article body given articleId.
1635
    ///
1636
    /// # Arguments
1637
    ///
1638
    /// * `provider_code` - Short code indicating news provider, e.g. FLY.
1639
    /// * `article_id`    - ID of the specific article.
1640
    ///
1641
    /// # Examples
1642
    ///
1643
    /// ```no_run
1644
    /// use ibapi::client::blocking::Client;
1645
    ///
1646
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1647
    ///
1648
    /// // can get these using the historical_news method
1649
    /// let provider_code = "DJ-N";
1650
    /// let article_id = "DJ-N$1915168d";
1651
    ///
1652
    /// let article = client.news_article(provider_code, article_id).expect("request news article failed");
1653
    /// println!("{article:?}");
1654
    /// ```
1655
    pub fn news_article(&self, provider_code: &str, article_id: &str) -> Result<news::NewsArticleBody, Error> {
1✔
1656
        news::blocking::news_article(self, provider_code, article_id)
4✔
1657
    }
1658

1659
    /// Requests realtime contract specific news
1660
    ///
1661
    /// # Arguments
1662
    ///
1663
    /// * `contract`       - Contract for which news is being requested.
1664
    /// * `provider_codes` - Short codes indicating news providers, e.g. DJ-N.
1665
    ///
1666
    /// # Examples
1667
    ///
1668
    /// ```no_run
1669
    /// use ibapi::client::blocking::Client;
1670
    /// use ibapi::contracts::Contract;
1671
    ///
1672
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1673
    ///
1674
    /// let contract = Contract::stock("AAPL").build();
1675
    /// let provider_codes = ["DJ-N"];
1676
    ///
1677
    /// let subscription = client.contract_news(&contract, &provider_codes).expect("request contract news failed");
1678
    /// for article in &subscription {
1679
    ///     println!("{article:?}");
1680
    /// }
1681
    /// ```
1682
    pub fn contract_news(&self, contract: &Contract, provider_codes: &[&str]) -> Result<Subscription<NewsArticle>, Error> {
1✔
1683
        news::blocking::contract_news(self, contract, provider_codes)
4✔
1684
    }
1685

1686
    /// Requests realtime BroadTape News
1687
    ///
1688
    /// # Arguments
1689
    ///
1690
    /// * `provider_code` - Short codes indicating news provider, e.g. DJ-N.
1691
    ///
1692
    /// # Examples
1693
    ///
1694
    /// ```no_run
1695
    /// use ibapi::client::blocking::Client;
1696
    ///
1697
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1698
    ///
1699
    /// let provider_code = "BRFG";
1700
    ///
1701
    /// let subscription = client.broad_tape_news(provider_code).expect("request broad tape news failed");
1702
    /// for article in &subscription {
1703
    ///     println!("{article:?}");
1704
    /// }
1705
    /// ```
1706
    pub fn broad_tape_news(&self, provider_code: &str) -> Result<Subscription<NewsArticle>, Error> {
1✔
1707
        news::blocking::broad_tape_news(self, provider_code)
3✔
1708
    }
1709

1710
    // === Scanner ===
1711

1712
    /// Requests an XML list of scanner parameters valid in TWS.
1713
    ///
1714
    /// # Examples
1715
    ///
1716
    /// ```no_run
1717
    /// use ibapi::client::blocking::Client;
1718
    /// use ibapi::scanner::ScannerSubscription;
1719
    /// use ibapi::orders::TagValue; // Or ensure common::TagValue is the correct path
1720
    ///
1721
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1722
    ///
1723
    /// let mut sub = ScannerSubscription::default();
1724
    /// sub.instrument = Some("STK".to_string());
1725
    /// sub.location_code = Some("STK.US.MAJOR".to_string());
1726
    /// sub.scan_code = Some("TOP_PERC_GAIN".to_string());
1727
    /// // Further customize the subscription object as needed, for example:
1728
    /// // sub.above_price = Some(1.0);
1729
    /// // sub.below_price = Some(100.0);
1730
    /// // sub.number_of_rows = Some(20);
1731
    ///
1732
    /// // Filter options are advanced and not always needed. Pass an empty Vec if not used.
1733
    /// let filter_options: Vec<TagValue> = Vec::new();
1734
    /// // Example of adding a filter:
1735
    /// // filter_options.push(TagValue { tag: "marketCapAbove".to_string(), value: "1000000000".to_string() });
1736
    ///
1737
    /// match client.scanner_subscription(&sub, &filter_options) {
1738
    ///     Ok(subscription) => {
1739
    ///         // Iterate over received scanner data.
1740
    ///         // Note: Scanner subscriptions can be continuous or return a snapshot.
1741
    ///         // This example just takes the first batch if available.
1742
    ///         if let Some(scanner_results_vec) = subscription.iter().next() {
1743
    ///             println!("Scanner Results (first batch):");
1744
    ///             for data in scanner_results_vec {
1745
    ///                 println!("  Rank: {}, Symbol: {}",
1746
    ///                          data.rank,
1747
    ///                          data.contract_details.contract.symbol);
1748
    ///             }
1749
    ///         } else {
1750
    ///             println!("No scanner results received in the first check.");
1751
    ///         }
1752
    ///         // In a real application, you might continuously iterate or handle updates.
1753
    ///         // Remember to cancel the subscription when no longer needed if it's continuous.
1754
    ///         // subscription.cancel();
1755
    ///     }
1756
    ///     Err(e) => {
1757
    ///         eprintln!("Failed to start scanner subscription: {e:?}");
1758
    ///     }
1759
    /// };
1760
    /// ```
1761
    pub fn scanner_parameters(&self) -> Result<String, Error> {
2✔
1762
        scanner::blocking::scanner_parameters(self)
4✔
1763
    }
1764

1765
    /// Starts a subscription to market scan results based on the provided parameters.
1766
    ///
1767
    /// # Examples
1768
    ///
1769
    /// ```no_run
1770
    /// use ibapi::client::blocking::Client;
1771
    /// use ibapi::scanner::ScannerSubscription;
1772
    /// use ibapi::orders::TagValue;
1773
    ///
1774
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1775
    ///
1776
    /// let mut sub = ScannerSubscription::default();
1777
    /// sub.instrument = Some("STK".to_string());
1778
    /// sub.location_code = Some("STK.US.MAJOR".to_string());
1779
    /// sub.scan_code = Some("TOP_PERC_GAIN".to_string());
1780
    /// // Further customize the subscription object as needed, for example:
1781
    /// // sub.above_price = Some(1.0);
1782
    /// // sub.below_price = Some(100.0);
1783
    /// // sub.number_of_rows = Some(20);
1784
    ///
1785
    /// // Filter options are advanced and not always needed. Pass an empty Vec if not used.
1786
    /// let mut filter_options: Vec<TagValue> = Vec::new();
1787
    /// // Example of adding a filter:
1788
    /// // filter_options.push(TagValue { tag: "marketCapAbove".to_string(), value: "1000000000".to_string() });
1789
    ///
1790
    /// match client.scanner_subscription(&sub, &filter_options) {
1791
    ///     Ok(subscription) => {
1792
    ///         // Iterate over received scanner data.
1793
    ///         // Note: Scanner subscriptions can be continuous or return a snapshot.
1794
    ///         // This example just takes the first batch if available.
1795
    ///         if let Some(scanner_results_vec) = subscription.iter().next() {
1796
    ///             println!("Scanner Results (first batch):");
1797
    ///             for data in scanner_results_vec {
1798
    ///                 println!("  Rank: {}, Symbol: {}",
1799
    ///                          data.rank,
1800
    ///                          data.contract_details.contract.symbol);
1801
    ///             }
1802
    ///         } else {
1803
    ///             println!("No scanner results received in the first check.");
1804
    ///         }
1805
    ///         // In a real application, you might continuously iterate or handle updates.
1806
    ///         // Remember to cancel the subscription when no longer needed if it's continuous.
1807
    ///         // subscription.cancel();
1808
    ///     }
1809
    ///     Err(e) => {
1810
    ///         eprintln!("Failed to start scanner subscription: {e:?}");
1811
    ///     }
1812
    /// };
1813
    /// ```
1814
    pub fn scanner_subscription(
2✔
1815
        &self,
1816
        subscription: &scanner::ScannerSubscription,
1817
        filter: &Vec<orders::TagValue>,
1818
    ) -> Result<Subscription<Vec<ScannerData>>, Error> {
1819
        scanner::blocking::scanner_subscription(self, subscription, filter)
8✔
1820
    }
1821

1822
    // == Wall Street Horizon
1823

1824
    /// Requests metadata from the WSH calendar.
1825
    ///
1826
    /// # Examples
1827
    ///
1828
    /// ```no_run
1829
    /// use ibapi::client::blocking::Client;
1830
    ///
1831
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1832
    ///
1833
    /// let metadata = client.wsh_metadata().expect("request wsh metadata failed");
1834
    /// println!("{metadata:?}");
1835
    /// ```
1836
    pub fn wsh_metadata(&self) -> Result<wsh::WshMetadata, Error> {
1✔
1837
        wsh::blocking::wsh_metadata(self)
2✔
1838
    }
1839

1840
    /// Requests event data for a specified contract from the Wall Street Horizons (WSH) calendar.
1841
    ///
1842
    /// # Arguments
1843
    ///
1844
    /// * `contract_id` - Contract identifier for the event request.
1845
    /// * `start_date`  - Start date of the event request.
1846
    /// * `end_date`    - End date of the event request.
1847
    /// * `limit`       - Maximum number of events to return. Maximum of 100.
1848
    /// * `auto_fill`   - Fields to automatically fill in. See [AutoFill] for more information.
1849
    ///
1850
    /// # Examples
1851
    ///
1852
    /// ```no_run
1853
    /// use ibapi::client::blocking::Client;
1854
    ///
1855
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1856
    ///
1857
    /// let contract_id = 76792991; // TSLA
1858
    /// let event_data = client.wsh_event_data_by_contract(contract_id, None, None, None, None).expect("request wsh event data failed");
1859
    /// println!("{event_data:?}");
1860
    /// ```
1861
    pub fn wsh_event_data_by_contract(
1✔
1862
        &self,
1863
        contract_id: i32,
1864
        start_date: Option<Date>,
1865
        end_date: Option<Date>,
1866
        limit: Option<i32>,
1867
        auto_fill: Option<AutoFill>,
1868
    ) -> Result<wsh::WshEventData, Error> {
1869
        wsh::blocking::wsh_event_data_by_contract(self, contract_id, start_date, end_date, limit, auto_fill)
7✔
1870
    }
1871

1872
    /// Requests event data from the Wall Street Horizons (WSH) calendar using a JSON filter.
1873
    ///
1874
    /// # Arguments
1875
    ///
1876
    /// * `filter`    - Json-formatted string containing all filter values.
1877
    /// * `limit`     - Maximum number of events to return. Maximum of 100.
1878
    /// * `auto_fill` - Fields to automatically fill in. See [AutoFill] for more information.
1879
    ///
1880
    /// # Examples
1881
    ///
1882
    /// ```no_run
1883
    /// use ibapi::client::blocking::Client;
1884
    ///
1885
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1886
    ///
1887
    /// let filter = ""; // see https://www.interactivebrokers.com/campus/ibkr-api-page/twsapi-doc/#wsheventdata-object
1888
    /// let event_data = client.wsh_event_data_by_filter(filter, None, None).expect("request wsh event data failed");
1889
    /// for result in event_data {
1890
    ///     println!("{result:?}");
1891
    /// }
1892
    /// ```
1893
    pub fn wsh_event_data_by_filter(
1✔
1894
        &self,
1895
        filter: &str,
1896
        limit: Option<i32>,
1897
        auto_fill: Option<AutoFill>,
1898
    ) -> Result<Subscription<wsh::WshEventData>, Error> {
1899
        wsh::blocking::wsh_event_data_by_filter(self, filter, limit, auto_fill)
5✔
1900
    }
1901

1902
    // == Internal Use ==
1903

1904
    #[cfg(test)]
1905
    pub(crate) fn stubbed(message_bus: Arc<dyn MessageBus>, server_version: i32) -> Client {
145✔
1906
        Client {
1907
            server_version,
1908
            connection_time: None,
1909
            time_zone: None,
1910
            message_bus,
1911
            client_id: 100,
1912
            id_manager: ClientIdManager::new(-1),
145✔
1913
        }
1914
    }
1915

1916
    pub(crate) fn send_request(&self, request_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
127✔
1917
        debug!("send_message({request_id:?}, {message:?})");
127✔
1918
        self.message_bus.send_request(request_id, &message)
381✔
1919
    }
1920

1921
    pub(crate) fn send_order(&self, order_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
7✔
1922
        debug!("send_order({order_id:?}, {message:?})");
7✔
1923
        self.message_bus.send_order_request(order_id, &message)
21✔
1924
    }
1925

1926
    pub(crate) fn send_message(&self, message: RequestMessage) -> Result<(), Error> {
4✔
1927
        debug!("send_message({message:?})");
4✔
1928
        self.message_bus.send_message(&message)
8✔
1929
    }
1930

1931
    /// Creates a subscription for order updates if one is not already active.
1932
    pub(crate) fn create_order_update_subscription(&self) -> Result<InternalSubscription, Error> {
4✔
1933
        self.message_bus.create_order_update_subscription()
4✔
1934
    }
1935

1936
    /// Sends request for the next valid order id.
1937
    pub(crate) fn send_shared_request(&self, message_id: OutgoingMessages, message: RequestMessage) -> Result<InternalSubscription, Error> {
55✔
1938
        self.message_bus.send_shared_request(message_id, &message)
165✔
1939
    }
1940

1941
    pub(crate) fn check_server_version(&self, version: i32, message: &str) -> Result<(), Error> {
17✔
1942
        if version <= self.server_version {
17✔
1943
            Ok(())
16✔
1944
        } else {
1945
            Err(Error::ServerVersion(version, self.server_version, message.into()))
1✔
1946
        }
1947
    }
1948
}
1949

1950
impl Drop for Client {
1951
    fn drop(&mut self) {
224✔
1952
        debug!("dropping basic client");
224✔
1953
        self.message_bus.ensure_shutdown();
224✔
1954
    }
1955
}
1956

1957
impl Debug for Client {
1958
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1959
        f.debug_struct("Client")
×
1960
            .field("server_version", &self.server_version)
×
1961
            .field("server_time", &self.connection_time)
×
1962
            .field("client_id", &self.client_id)
×
1963
            .finish()
1964
    }
1965
}
1966

1967
/// Subscriptions facilitate handling responses from TWS that may be delayed or delivered periodically.
1968
///
1969
/// They offer both blocking and non-blocking methods for retrieving data.
1970
///
1971
/// In the simplest case a subscription can be implicitly converted to blocking iterator
1972
/// that cancels the subscription when it goes out of scope.
1973
///
1974
/// ```no_run
1975
/// use ibapi::contracts::Contract;
1976
/// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1977
/// use ibapi::market_data::TradingHours;
1978
/// use ibapi::client::blocking::Client;
1979
///
1980
/// let connection_url = "127.0.0.1:4002";
1981
/// let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
1982
///
1983
/// // Request real-time bars data for AAPL with 5-second intervals
1984
/// let contract = Contract::stock("AAPL").build();
1985
/// let subscription = client
1986
///     .realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, TradingHours::Extended)
1987
///     .expect("realtime bars request failed!");
1988
///
1989
/// // Use the subscription as a blocking iterator
1990
/// for bar in subscription {
1991
///     // Process each bar here (e.g., print or use in calculations)
1992
///     println!("Received bar: {bar:?}");
1993
/// }
1994
/// // The subscription goes out of scope and is automatically cancelled.
1995
/// ```
1996
///
1997
/// Subscriptions can be explicitly canceled using the [cancel](Subscription::cancel) method.
1998
///
1999
// Re-export SharesChannel trait from subscriptions module
2000
pub use crate::subscriptions::SharesChannel;
2001

2002
#[cfg(test)]
2003
mod tests {
2004
    use std::sync::Arc;
2005

2006
    use super::{Client, TradingHours};
2007
    use crate::client::common::tests::*;
2008
    use crate::contracts::{Currency, Exchange, Symbol};
2009
    use crate::{connection::ConnectionMetadata, stubs::MessageBusStub};
2010

2011
    const CLIENT_ID: i32 = 100;
2012

2013
    #[test]
2014
    fn test_connect() {
2015
        let gateway = setup_connect();
2016

2017
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2018

2019
        assert_eq!(client.client_id(), CLIENT_ID);
2020
        assert_eq!(client.server_version(), gateway.server_version());
2021
        assert_eq!(client.time_zone, gateway.time_zone());
2022

2023
        assert_eq!(gateway.requests().len(), 0, "No requests should be sent on connect");
2024
    }
2025

2026
    #[test]
2027
    fn test_server_time() {
2028
        let (gateway, expectations) = setup_server_time();
2029

2030
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2031

2032
        let server_time = client.server_time().unwrap();
2033
        assert_eq!(server_time, expectations.server_time);
2034

2035
        let requests = gateway.requests();
2036
        assert_eq!(requests[0], "49\01\0");
2037
    }
2038

2039
    #[test]
2040
    fn test_next_valid_order_id() {
2041
        let (gateway, expectations) = setup_next_valid_order_id();
2042

2043
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2044

2045
        let next_valid_order_id = client.next_valid_order_id().unwrap();
2046
        assert_eq!(next_valid_order_id, expectations.next_valid_order_id);
2047

2048
        let requests = gateway.requests();
2049
        assert_eq!(requests[0], "8\01\00\0");
2050
    }
2051

2052
    #[test]
2053
    fn test_managed_accounts() {
2054
        let (gateway, expectations) = setup_managed_accounts();
2055

2056
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2057

2058
        let accounts = client.managed_accounts().unwrap();
2059
        assert_eq!(accounts, expectations.accounts);
2060

2061
        let requests = gateway.requests();
2062
        assert_eq!(requests[0], "17\01\0");
2063
    }
2064

2065
    #[test]
2066
    fn test_positions() {
2067
        let gateway = setup_positions();
2068

2069
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2070

2071
        let positions = client.positions().unwrap();
2072
        let mut position_count = 0;
2073

2074
        for position_update in positions {
2075
            match position_update {
2076
                crate::accounts::PositionUpdate::Position(position) => {
2077
                    assert_eq!(position.account, "DU1234567");
2078
                    assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2079
                    assert_eq!(position.position, 500.0);
2080
                    assert_eq!(position.average_cost, 150.25);
2081
                    position_count += 1;
2082
                }
2083
                crate::accounts::PositionUpdate::PositionEnd => {
2084
                    break;
2085
                }
2086
            }
2087
        }
2088

2089
        assert_eq!(position_count, 1);
2090
        let requests = gateway.requests();
2091
        assert_eq!(requests[0], "61\01\0");
2092
    }
2093

2094
    #[test]
2095
    fn test_positions_multi() {
2096
        use crate::accounts::types::AccountId;
2097

2098
        let gateway = setup_positions_multi();
2099

2100
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2101

2102
        let account = AccountId("DU1234567".to_string());
2103
        let positions = client.positions_multi(Some(&account), None).unwrap();
2104
        let mut position_count = 0;
2105

2106
        for position_update in positions {
2107
            match position_update {
2108
                crate::accounts::PositionUpdateMulti::Position(position) => {
2109
                    position_count += 1;
2110
                    if position_count == 1 {
2111
                        assert_eq!(position.account, "DU1234567");
2112
                        assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2113
                        assert_eq!(position.position, 500.0);
2114
                        assert_eq!(position.average_cost, 150.25);
2115
                        assert_eq!(position.model_code, "MODEL1");
2116
                    } else if position_count == 2 {
2117
                        assert_eq!(position.account, "DU1234568");
2118
                        assert_eq!(position.contract.symbol, Symbol::from("GOOGL"));
2119
                        assert_eq!(position.position, 200.0);
2120
                        assert_eq!(position.average_cost, 2500.00);
2121
                        assert_eq!(position.model_code, "MODEL1");
2122
                    }
2123
                }
2124
                crate::accounts::PositionUpdateMulti::PositionEnd => {
2125
                    break;
2126
                }
2127
            }
2128
        }
2129

2130
        assert_eq!(position_count, 2);
2131
        let requests = gateway.requests();
2132
        assert_eq!(requests[0], "74\01\09000\0DU1234567\0\0");
2133
    }
2134

2135
    #[test]
2136
    fn test_account_summary() {
2137
        use crate::accounts::types::AccountGroup;
2138

2139
        let gateway = setup_account_summary();
2140

2141
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2142

2143
        let group = AccountGroup("All".to_string());
2144
        let tags = vec!["NetLiquidation", "TotalCashValue"];
2145

2146
        let summaries = client.account_summary(&group, &tags).unwrap();
2147
        let mut summary_count = 0;
2148

2149
        for summary_result in summaries {
2150
            match summary_result {
2151
                crate::accounts::AccountSummaryResult::Summary(summary) => {
2152
                    assert_eq!(summary.account, "DU1234567");
2153
                    assert_eq!(summary.currency, "USD");
2154

2155
                    if summary.tag == "NetLiquidation" {
2156
                        assert_eq!(summary.value, "25000.00");
2157
                    } else if summary.tag == "TotalCashValue" {
2158
                        assert_eq!(summary.value, "15000.00");
2159
                    }
2160
                    summary_count += 1;
2161
                }
2162
                crate::accounts::AccountSummaryResult::End => {
2163
                    break;
2164
                }
2165
            }
2166
        }
2167

2168
        assert_eq!(summary_count, 2);
2169
        let requests = gateway.requests();
2170
        assert_eq!(requests[0], "62\01\09000\0All\0NetLiquidation,TotalCashValue\0");
2171
    }
2172

2173
    #[test]
2174
    fn test_pnl() {
2175
        use crate::accounts::types::AccountId;
2176

2177
        let gateway = setup_pnl();
2178

2179
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2180

2181
        let account = AccountId("DU1234567".to_string());
2182
        let pnl = client.pnl(&account, None).unwrap();
2183

2184
        let first_pnl = pnl.into_iter().next().unwrap();
2185
        assert_eq!(first_pnl.daily_pnl, 250.50);
2186
        assert_eq!(first_pnl.unrealized_pnl, Some(1500.00));
2187
        assert_eq!(first_pnl.realized_pnl, Some(750.00));
2188

2189
        let requests = gateway.requests();
2190
        assert_eq!(requests[0], "92\09000\0DU1234567\0\0");
2191
    }
2192

2193
    #[test]
2194
    fn test_pnl_single() {
2195
        use crate::accounts::types::{AccountId, ContractId};
2196

2197
        let gateway = setup_pnl_single();
2198

2199
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2200

2201
        let account = AccountId("DU1234567".to_string());
2202
        let contract_id = ContractId(12345);
2203
        let pnl_single = client.pnl_single(&account, contract_id, None).unwrap();
2204

2205
        let first_pnl = pnl_single.into_iter().next().unwrap();
2206
        assert_eq!(first_pnl.position, 100.0);
2207
        assert_eq!(first_pnl.daily_pnl, 150.25);
2208
        assert_eq!(first_pnl.unrealized_pnl, 500.00);
2209
        assert_eq!(first_pnl.realized_pnl, 250.00);
2210
        assert_eq!(first_pnl.value, 1000.00);
2211

2212
        let requests = gateway.requests();
2213
        assert_eq!(requests[0], "94\09000\0DU1234567\0\012345\0");
2214
    }
2215

2216
    #[test]
2217
    fn test_account_updates() {
2218
        use crate::accounts::types::AccountId;
2219

2220
        let gateway = setup_account_updates();
2221

2222
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2223

2224
        let account = AccountId("DU1234567".to_string());
2225
        let updates = client.account_updates(&account).unwrap();
2226

2227
        let mut value_count = 0;
2228
        let mut portfolio_count = 0;
2229
        let mut has_time_update = false;
2230
        let mut has_end = false;
2231

2232
        for update in updates {
2233
            match update {
2234
                crate::accounts::AccountUpdate::AccountValue(value) => {
2235
                    assert_eq!(value.key, "NetLiquidation");
2236
                    assert_eq!(value.value, "25000.00");
2237
                    assert_eq!(value.currency, "USD");
2238
                    assert_eq!(value.account, Some("DU1234567".to_string()));
2239
                    value_count += 1;
2240
                }
2241
                crate::accounts::AccountUpdate::PortfolioValue(portfolio) => {
2242
                    assert_eq!(portfolio.contract.symbol, Symbol::from("AAPL"));
2243
                    assert_eq!(portfolio.position, 500.0);
2244
                    assert_eq!(portfolio.market_price, 151.50);
2245
                    assert_eq!(portfolio.market_value, 75750.00);
2246
                    assert_eq!(portfolio.average_cost, 150.25);
2247
                    assert_eq!(portfolio.unrealized_pnl, 375.00);
2248
                    assert_eq!(portfolio.realized_pnl, 125.00);
2249
                    assert_eq!(portfolio.account, Some("DU1234567".to_string()));
2250
                    portfolio_count += 1;
2251
                }
2252
                crate::accounts::AccountUpdate::UpdateTime(time) => {
2253
                    assert_eq!(time.timestamp, "20240122 15:30:00");
2254
                    has_time_update = true;
2255
                }
2256
                crate::accounts::AccountUpdate::End => {
2257
                    has_end = true;
2258
                    break;
2259
                }
2260
            }
2261
        }
2262

2263
        assert!(has_end, "Expected End message");
2264
        assert_eq!(value_count, 1);
2265
        assert_eq!(portfolio_count, 1);
2266
        assert!(has_time_update);
2267

2268
        let requests = gateway.requests();
2269
        assert_eq!(requests[0], "6\02\01\0DU1234567\0");
2270
    }
2271

2272
    #[test]
2273
    fn test_family_codes() {
2274
        let gateway = setup_family_codes();
2275

2276
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2277

2278
        let family_codes = client.family_codes().unwrap();
2279

2280
        assert_eq!(family_codes.len(), 2);
2281
        assert_eq!(family_codes[0].account_id, "DU1234567");
2282
        assert_eq!(family_codes[0].family_code, "FAM001");
2283
        assert_eq!(family_codes[1].account_id, "DU1234568");
2284
        assert_eq!(family_codes[1].family_code, "FAM002");
2285

2286
        let requests = gateway.requests();
2287
        assert_eq!(requests[0], "80\01\0");
2288
    }
2289

2290
    #[test]
2291
    fn test_account_updates_multi() {
2292
        use crate::accounts::types::{AccountId, ModelCode};
2293

2294
        let gateway = setup_account_updates_multi();
2295

2296
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2297

2298
        let account = AccountId("DU1234567".to_string());
2299
        let model_code: Option<ModelCode> = None;
2300
        let updates = client.account_updates_multi(Some(&account), model_code.as_ref()).unwrap();
2301

2302
        let mut cash_balance_found = false;
2303
        let mut currency_found = false;
2304
        let mut stock_market_value_found = false;
2305
        let mut has_end = false;
2306

2307
        for update in updates {
2308
            match update {
2309
                crate::accounts::AccountUpdateMulti::AccountMultiValue(value) => {
2310
                    assert_eq!(value.account, "DU1234567");
2311
                    assert_eq!(value.model_code, "");
2312

2313
                    match value.key.as_str() {
2314
                        "CashBalance" => {
2315
                            assert_eq!(value.value, "94629.71");
2316
                            assert_eq!(value.currency, "USD");
2317
                            cash_balance_found = true;
2318
                        }
2319
                        "Currency" => {
2320
                            assert_eq!(value.value, "USD");
2321
                            assert_eq!(value.currency, "USD");
2322
                            currency_found = true;
2323
                        }
2324
                        "StockMarketValue" => {
2325
                            assert_eq!(value.value, "0.00");
2326
                            assert_eq!(value.currency, "BASE");
2327
                            stock_market_value_found = true;
2328
                        }
2329
                        _ => panic!("Unexpected key: {}", value.key),
2330
                    }
2331
                }
2332
                crate::accounts::AccountUpdateMulti::End => {
2333
                    has_end = true;
2334
                    break;
2335
                }
2336
            }
2337
        }
2338

2339
        assert!(cash_balance_found, "Expected CashBalance update");
2340
        assert!(currency_found, "Expected Currency update");
2341
        assert!(stock_market_value_found, "Expected StockMarketValue update");
2342
        assert!(has_end, "Expected End message");
2343

2344
        let requests = gateway.requests();
2345
        assert_eq!(requests[0], "76\01\09000\0DU1234567\0\01\0");
2346
    }
2347

2348
    #[test]
2349
    fn test_client_id() {
2350
        let client_id = 500;
2351
        let connection_metadata = ConnectionMetadata {
2352
            client_id,
2353
            ..ConnectionMetadata::default()
2354
        };
2355
        let message_bus = Arc::new(MessageBusStub::default());
2356

2357
        let client = Client::new(connection_metadata, message_bus).unwrap();
2358

2359
        assert_eq!(client.client_id(), client_id);
2360
    }
2361

2362
    #[test]
2363
    fn test_contract_details() {
2364
        let gateway = setup_contract_details();
2365

2366
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2367

2368
        let contract = crate::contracts::Contract::stock("AAPL").build();
2369
        let details = client.contract_details(&contract).expect("Failed to get contract details");
2370

2371
        assert_eq!(details.len(), 1);
2372
        let detail = &details[0];
2373

2374
        // Verify contract fields
2375
        assert_eq!(detail.contract.symbol, Symbol::from("AAPL"));
2376
        assert_eq!(detail.contract.security_type, crate::contracts::SecurityType::Stock);
2377
        assert_eq!(detail.contract.currency, Currency::from("USD"));
2378
        assert_eq!(detail.contract.exchange, Exchange::from("NASDAQ"));
2379
        assert_eq!(detail.contract.local_symbol, "AAPL");
2380
        assert_eq!(detail.contract.trading_class, "AAPL");
2381
        assert_eq!(detail.contract.contract_id, 265598);
2382
        assert_eq!(detail.contract.primary_exchange, Exchange::from("NASDAQ"));
2383

2384
        // Verify contract details fields
2385
        assert_eq!(detail.market_name, "NMS");
2386
        assert_eq!(detail.min_tick, 0.01);
2387
        assert!(detail.order_types.contains(&"LMT".to_string()));
2388
        assert!(detail.order_types.contains(&"MKT".to_string()));
2389
        assert!(detail.valid_exchanges.contains(&"SMART".to_string()));
2390
        assert_eq!(detail.long_name, "Apple Inc");
2391
        assert_eq!(detail.industry, "Technology");
2392
        assert_eq!(detail.category, "Computers");
2393
        assert_eq!(detail.subcategory, "Computers");
2394
        assert_eq!(detail.time_zone_id, "US/Eastern");
2395
        assert_eq!(detail.stock_type, "NMS");
2396
        assert_eq!(detail.min_size, 1.0);
2397
        assert_eq!(detail.size_increment, 1.0);
2398
        assert_eq!(detail.suggested_size_increment, 1.0);
2399

2400
        let requests = gateway.requests();
2401
        // Request format: OutgoingMessages::RequestContractData(9), version(8), request_id, contract_id(0),
2402
        // symbol, security_type, last_trade_date, strike, right, multiplier, exchange, primary_exchange,
2403
        // currency, local_symbol, trading_class, include_expired, security_id_type, security_id, issuer_id
2404
        assert_eq!(requests[0], "9\08\09000\00\0AAPL\0STK\0\00\0\0\0SMART\0\0USD\0\0\00\0\0\0");
2405
    }
2406

2407
    #[test]
2408
    fn test_subscription_cancel_only_sends_once() {
2409
        // This test verifies that calling cancel() multiple times only sends one cancel message
2410
        // This addresses issue #258 where explicit cancel() followed by Drop could send duplicate messages
2411

2412
        let message_bus = Arc::new(MessageBusStub::default());
2413
        let client = Client::stubbed(message_bus.clone(), 100);
2414

2415
        // Create a subscription using realtime bars as an example
2416
        let contract = crate::contracts::Contract::stock("AAPL").build();
2417
        let subscription = client
2418
            .realtime_bars(
2419
                &contract,
2420
                crate::market_data::realtime::BarSize::Sec5,
2421
                crate::market_data::realtime::WhatToShow::Trades,
2422
                TradingHours::Extended,
2423
            )
2424
            .expect("Failed to create subscription");
2425

2426
        // Get initial request count (should be 1 for the realtime bars request)
2427
        let initial_count = message_bus.request_messages().len();
2428
        assert_eq!(initial_count, 1, "Should have one request for realtime bars");
2429

2430
        // First cancel should add one more message
2431
        subscription.cancel();
2432
        let after_first_cancel = message_bus.request_messages().len();
2433
        assert_eq!(after_first_cancel, 2, "Should have two messages after first cancel");
2434

2435
        // Second cancel should not send another message
2436
        subscription.cancel();
2437
        let after_second_cancel = message_bus.request_messages().len();
2438
        assert_eq!(after_second_cancel, 2, "Should still have two messages after second cancel");
2439

2440
        // Drop should also not send another message (implicitly calls cancel)
2441
        drop(subscription);
2442
        let after_drop = message_bus.request_messages().len();
2443
        assert_eq!(after_drop, 2, "Should still have two messages after drop");
2444
    }
2445

2446
    #[test]
2447
    fn test_matching_symbols() {
2448
        let gateway = setup_matching_symbols();
2449

2450
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2451

2452
        let results = client.matching_symbols("AAP").expect("Failed to get matching symbols");
2453
        let contract_descriptions: Vec<_> = results.collect();
2454

2455
        assert_eq!(contract_descriptions.len(), 2, "Should have 2 matching symbols");
2456

2457
        // First contract description
2458
        assert_eq!(contract_descriptions[0].contract.contract_id, 265598);
2459
        assert_eq!(contract_descriptions[0].contract.symbol, Symbol::from("AAPL"));
2460
        assert_eq!(contract_descriptions[0].contract.security_type, crate::contracts::SecurityType::Stock);
2461
        assert_eq!(contract_descriptions[0].contract.primary_exchange, Exchange::from("NASDAQ"));
2462
        assert_eq!(contract_descriptions[0].contract.currency, Currency::from("USD"));
2463
        assert_eq!(contract_descriptions[0].derivative_security_types.len(), 2);
2464
        assert_eq!(contract_descriptions[0].derivative_security_types[0], "OPT");
2465
        assert_eq!(contract_descriptions[0].derivative_security_types[1], "WAR");
2466
        assert_eq!(contract_descriptions[0].contract.description, "Apple Inc.");
2467
        assert_eq!(contract_descriptions[0].contract.issuer_id, "AAPL123");
2468

2469
        // Second contract description
2470
        assert_eq!(contract_descriptions[1].contract.contract_id, 276821);
2471
        assert_eq!(contract_descriptions[1].contract.symbol, Symbol::from("MSFT"));
2472
        assert_eq!(contract_descriptions[1].contract.security_type, crate::contracts::SecurityType::Stock);
2473
        assert_eq!(contract_descriptions[1].contract.primary_exchange, Exchange::from("NASDAQ"));
2474
        assert_eq!(contract_descriptions[1].contract.currency, Currency::from("USD"));
2475
        assert_eq!(contract_descriptions[1].derivative_security_types.len(), 1);
2476
        assert_eq!(contract_descriptions[1].derivative_security_types[0], "OPT");
2477
        assert_eq!(contract_descriptions[1].contract.description, "Microsoft Corporation");
2478
        assert_eq!(contract_descriptions[1].contract.issuer_id, "MSFT456");
2479

2480
        // Verify request format
2481
        let requests = gateway.requests();
2482
        assert_eq!(requests.len(), 1, "Should have 1 request");
2483
        // Request format: RequestMatchingSymbols(81), request_id, pattern
2484
        assert!(requests[0].starts_with("81\0"), "Request should start with message type 81");
2485
        assert!(requests[0].contains("\0AAP\0"), "Request should contain the pattern AAP");
2486
    }
2487

2488
    #[test]
2489
    fn test_market_rule() {
2490
        let gateway = setup_market_rule();
2491

2492
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2493

2494
        let market_rule = client.market_rule(26).expect("Failed to get market rule");
2495

2496
        // Verify market rule ID
2497
        assert_eq!(market_rule.market_rule_id, 26, "Market rule ID should be 26");
2498

2499
        // Verify price increments
2500
        assert_eq!(market_rule.price_increments.len(), 3, "Should have 3 price increments");
2501

2502
        // First increment: 0-100, increment 0.01
2503
        assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "First increment low edge");
2504
        assert_eq!(market_rule.price_increments[0].increment, 0.01, "First increment value");
2505

2506
        // Second increment: 100-1000, increment 0.05
2507
        assert_eq!(market_rule.price_increments[1].low_edge, 100.0, "Second increment low edge");
2508
        assert_eq!(market_rule.price_increments[1].increment, 0.05, "Second increment value");
2509

2510
        // Third increment: 1000+, increment 0.10
2511
        assert_eq!(market_rule.price_increments[2].low_edge, 1000.0, "Third increment low edge");
2512
        assert_eq!(market_rule.price_increments[2].increment, 0.10, "Third increment value");
2513

2514
        // Verify request format
2515
        let requests = gateway.requests();
2516
        assert_eq!(requests.len(), 1, "Should have 1 request");
2517
        // Request format: RequestMarketRule(91), market_rule_id
2518
        assert_eq!(requests[0], "91\026\0", "Request should be message type 91 with market rule ID 26");
2519
    }
2520

2521
    #[test]
2522
    fn test_calculate_option_price() {
2523
        let gateway = setup_calculate_option_price();
2524

2525
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2526

2527
        // Create an option contract
2528
        let contract = crate::contracts::Contract {
2529
            symbol: Symbol::from("AAPL"),
2530
            security_type: crate::contracts::SecurityType::Option,
2531
            exchange: Exchange::from("SMART"),
2532
            currency: Currency::from("USD"),
2533
            last_trade_date_or_contract_month: "20250120".to_string(),
2534
            strike: 100.0,
2535
            right: "C".to_string(),
2536
            ..Default::default()
2537
        };
2538

2539
        let volatility = 0.25;
2540
        let underlying_price = 100.0;
2541

2542
        let computation = client
2543
            .calculate_option_price(&contract, volatility, underlying_price)
2544
            .expect("Failed to calculate option price");
2545

2546
        // Verify computation results
2547
        assert_eq!(
2548
            computation.field,
2549
            crate::contracts::tick_types::TickType::ModelOption,
2550
            "Should be ModelOption tick type"
2551
        );
2552
        assert_eq!(computation.tick_attribute, Some(0), "Tick attribute should be 0");
2553
        assert_eq!(computation.implied_volatility, Some(0.25), "Implied volatility should match");
2554
        assert_eq!(computation.delta, Some(0.5), "Delta should be 0.5");
2555
        assert_eq!(computation.option_price, Some(12.75), "Option price should be 12.75");
2556
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2557
        assert_eq!(computation.gamma, Some(0.05), "Gamma should be 0.05");
2558
        assert_eq!(computation.vega, Some(0.02), "Vega should be 0.02");
2559
        assert_eq!(computation.theta, Some(-0.01), "Theta should be -0.01");
2560
        assert_eq!(computation.underlying_price, Some(100.0), "Underlying price should be 100");
2561

2562
        // Verify request format
2563
        let requests = gateway.requests();
2564
        assert_eq!(requests.len(), 1, "Should have 1 request");
2565
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, volatility, underlying_price
2566
        assert!(
2567
            requests[0].starts_with("54\03\0"),
2568
            "Request should start with message type 54 and version 3"
2569
        );
2570
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2571
        assert!(requests[0].contains("\00.25\0"), "Request should contain volatility 0.25");
2572
        assert!(requests[0].contains("\0100\0"), "Request should contain underlying price 100");
2573
    }
2574

2575
    #[test]
2576
    fn test_calculate_implied_volatility() {
2577
        let gateway = setup_calculate_implied_volatility();
2578

2579
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2580

2581
        // Create an option contract
2582
        let contract = crate::contracts::Contract {
2583
            symbol: Symbol::from("MSFT"),
2584
            security_type: crate::contracts::SecurityType::Option,
2585
            exchange: Exchange::from("SMART"),
2586
            currency: Currency::from("USD"),
2587
            last_trade_date_or_contract_month: "20250220".to_string(),
2588
            strike: 105.0,
2589
            right: "P".to_string(), // Put option
2590
            ..Default::default()
2591
        };
2592

2593
        let option_price = 15.50;
2594
        let underlying_price = 105.0;
2595

2596
        let computation = client
2597
            .calculate_implied_volatility(&contract, option_price, underlying_price)
2598
            .expect("Failed to calculate implied volatility");
2599

2600
        // Verify computation results
2601
        assert_eq!(
2602
            computation.field,
2603
            crate::contracts::tick_types::TickType::ModelOption,
2604
            "Should be ModelOption tick type"
2605
        );
2606
        assert_eq!(computation.tick_attribute, Some(1), "Tick attribute should be 1 (price-based)");
2607
        assert_eq!(computation.implied_volatility, Some(0.35), "Implied volatility should be 0.35");
2608
        assert_eq!(computation.delta, Some(0.45), "Delta should be 0.45");
2609
        assert_eq!(computation.option_price, Some(15.50), "Option price should be 15.50");
2610
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2611
        assert_eq!(computation.gamma, Some(0.04), "Gamma should be 0.04");
2612
        assert_eq!(computation.vega, Some(0.03), "Vega should be 0.03");
2613
        assert_eq!(computation.theta, Some(-0.02), "Theta should be -0.02");
2614
        assert_eq!(computation.underlying_price, Some(105.0), "Underlying price should be 105");
2615

2616
        // Verify request format
2617
        let requests = gateway.requests();
2618
        assert_eq!(requests.len(), 1, "Should have 1 request");
2619
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, option_price, underlying_price
2620
        assert!(
2621
            requests[0].starts_with("54\03\0"),
2622
            "Request should start with message type 54 and version 3"
2623
        );
2624
        assert!(requests[0].contains("\0MSFT\0"), "Request should contain symbol MSFT");
2625
        assert!(requests[0].contains("\015.5\0"), "Request should contain option price 15.5");
2626
        assert!(requests[0].contains("\0105\0"), "Request should contain underlying price 105");
2627
    }
2628

2629
    #[test]
2630
    fn test_option_chain() {
2631
        let gateway = setup_option_chain();
2632

2633
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2634

2635
        let symbol = "AAPL";
2636
        let exchange = ""; // Empty means all exchanges
2637
        let security_type = crate::contracts::SecurityType::Stock;
2638
        let contract_id = 0; // 0 means use symbol
2639

2640
        let subscription = client
2641
            .option_chain(symbol, exchange, security_type, contract_id)
2642
            .expect("Failed to get option chain");
2643

2644
        let mut chains = Vec::new();
2645
        for chain in subscription {
2646
            chains.push(chain);
2647
        }
2648

2649
        // Should have received 2 option chains (SMART and CBOE)
2650
        assert_eq!(chains.len(), 2, "Should have 2 option chains");
2651

2652
        // First chain - SMART exchange
2653
        assert_eq!(chains[0].exchange, "SMART", "First chain should be SMART");
2654
        assert_eq!(chains[0].underlying_contract_id, 265598, "Underlying contract ID");
2655
        assert_eq!(chains[0].trading_class, "AAPL", "Trading class");
2656
        assert_eq!(chains[0].multiplier, "100", "Multiplier");
2657
        assert_eq!(chains[0].expirations.len(), 3, "Should have 3 expirations");
2658
        assert_eq!(chains[0].expirations[0], "20250117");
2659
        assert_eq!(chains[0].expirations[1], "20250221");
2660
        assert_eq!(chains[0].expirations[2], "20250321");
2661
        assert_eq!(chains[0].strikes.len(), 5, "Should have 5 strikes");
2662
        assert_eq!(chains[0].strikes[0], 90.0);
2663
        assert_eq!(chains[0].strikes[1], 95.0);
2664
        assert_eq!(chains[0].strikes[2], 100.0);
2665
        assert_eq!(chains[0].strikes[3], 105.0);
2666
        assert_eq!(chains[0].strikes[4], 110.0);
2667

2668
        // Second chain - CBOE exchange
2669
        assert_eq!(chains[1].exchange, "CBOE", "Second chain should be CBOE");
2670
        assert_eq!(chains[1].underlying_contract_id, 265598, "Underlying contract ID");
2671
        assert_eq!(chains[1].trading_class, "AAPL", "Trading class");
2672
        assert_eq!(chains[1].multiplier, "100", "Multiplier");
2673
        assert_eq!(chains[1].expirations.len(), 2, "Should have 2 expirations");
2674
        assert_eq!(chains[1].expirations[0], "20250117");
2675
        assert_eq!(chains[1].expirations[1], "20250221");
2676
        assert_eq!(chains[1].strikes.len(), 4, "Should have 4 strikes");
2677
        assert_eq!(chains[1].strikes[0], 95.0);
2678
        assert_eq!(chains[1].strikes[1], 100.0);
2679
        assert_eq!(chains[1].strikes[2], 105.0);
2680
        assert_eq!(chains[1].strikes[3], 110.0);
2681

2682
        // Verify request format
2683
        let requests = gateway.requests();
2684
        assert_eq!(requests.len(), 1, "Should have 1 request");
2685
        // Request format: RequestSecurityDefinitionOptionalParameters(78), request_id, symbol, exchange, security_type, contract_id
2686
        assert!(requests[0].starts_with("78\0"), "Request should start with message type 78");
2687
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2688
        assert!(requests[0].contains("\0STK\0"), "Request should contain security type STK");
2689
    }
2690

2691
    #[test]
2692
    fn test_place_order() {
2693
        use crate::client::common::tests::setup_place_order;
2694
        use crate::contracts::Contract;
2695
        use crate::orders::{order_builder, Action, PlaceOrder};
2696

2697
        // Initialize env_logger for debug output
2698
        let _ = env_logger::try_init();
2699

2700
        let gateway = setup_place_order();
2701
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2702

2703
        // Create a stock contract
2704
        let contract = Contract::stock("AAPL").build();
2705

2706
        // Create a market order
2707
        let order = order_builder::market_order(Action::Buy, 100.0);
2708

2709
        // Use order ID 1001 to match the mock responses
2710
        let order_id = 1001;
2711

2712
        // Place the order
2713
        let subscription = client.place_order(order_id, &contract, &order).expect("Failed to place order");
2714

2715
        // Collect all events from the subscription
2716
        let mut order_status_count = 0;
2717
        let mut _open_order_count = 0;
2718
        let mut execution_count = 0;
2719
        let mut commission_count = 0;
2720

2721
        // We expect 5 messages total (2 order statuses, 1 open order, 1 execution, 1 commission)
2722
        // Take only the expected number of events to avoid reading the shutdown message
2723
        let mut events_received = 0;
2724

2725
        // Use the iterator directly
2726
        for event in subscription.into_iter() {
2727
            if events_received >= 10 {
2728
                println!("Reached event limit");
2729
                break;
2730
            }
2731
            events_received += 1;
2732
            let event_type = match &event {
2733
                PlaceOrder::OrderStatus(_) => "OrderStatus",
2734
                PlaceOrder::OpenOrder(_) => "OpenOrder",
2735
                PlaceOrder::ExecutionData(_) => "ExecutionData",
2736
                PlaceOrder::CommissionReport(_) => "CommissionReport",
2737
                PlaceOrder::Message(_) => "Message",
2738
            };
2739
            println!("Event {}: {} received", events_received, event_type);
2740
            match event {
2741
                PlaceOrder::OrderStatus(status) => {
2742
                    order_status_count += 1;
2743
                    assert_eq!(status.order_id, order_id);
2744

2745
                    if order_status_count == 1 {
2746
                        // First status: PreSubmitted
2747
                        assert_eq!(status.status, "PreSubmitted");
2748
                        assert_eq!(status.filled, 0.0);
2749
                        assert_eq!(status.remaining, 100.0);
2750
                    } else if order_status_count == 2 {
2751
                        // Second status: Submitted
2752
                        assert_eq!(status.status, "Submitted");
2753
                        assert_eq!(status.filled, 0.0);
2754
                        assert_eq!(status.remaining, 100.0);
2755
                    } else if order_status_count == 3 {
2756
                        // Third status: Filled
2757
                        assert_eq!(status.status, "Filled");
2758
                        assert_eq!(status.filled, 100.0);
2759
                        assert_eq!(status.remaining, 0.0);
2760
                        assert_eq!(status.average_fill_price, 150.25);
2761
                    }
2762
                }
2763
                PlaceOrder::OpenOrder(order_data) => {
2764
                    _open_order_count += 1;
2765
                    assert_eq!(order_data.order_id, order_id);
2766
                    assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2767
                    assert_eq!(order_data.contract.contract_id, 265598);
2768
                    assert_eq!(order_data.order.action, Action::Buy);
2769
                    assert_eq!(order_data.order.total_quantity, 100.0);
2770
                    assert_eq!(order_data.order.order_type, "LMT");
2771
                    assert_eq!(order_data.order.limit_price, Some(1.0));
2772
                }
2773
                PlaceOrder::ExecutionData(exec_data) => {
2774
                    execution_count += 1;
2775
                    assert_eq!(exec_data.execution.order_id, order_id);
2776
                    assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2777
                    assert_eq!(exec_data.execution.shares, 100.0);
2778
                    assert_eq!(exec_data.execution.price, 150.25);
2779
                }
2780
                PlaceOrder::CommissionReport(report) => {
2781
                    commission_count += 1;
2782
                    assert_eq!(report.commission, 1.25);
2783
                    assert_eq!(report.currency, "USD");
2784
                }
2785
                PlaceOrder::Message(_) => {
2786
                    // Skip any messages
2787
                }
2788
            }
2789
        }
2790

2791
        println!("Total events received: {}", events_received);
2792
        println!(
2793
            "OrderStatus: {}, Execution: {}, Commission: {}",
2794
            order_status_count, execution_count, commission_count
2795
        );
2796

2797
        // Verify we received all expected events
2798
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2799
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2800
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2801
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2802

2803
        // Verify the request was sent
2804
        let requests = gateway.requests();
2805
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2806
        // PlaceOrder message type is 3
2807
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2808
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2809
    }
2810

2811
    #[test]
2812
    fn test_submit_order_with_order_update_stream() {
2813
        use crate::client::common::tests::setup_place_order;
2814
        use crate::contracts::Contract;
2815
        use crate::orders::{order_builder, Action, OrderUpdate};
2816

2817
        // Initialize env_logger for debug output
2818
        let _ = env_logger::try_init();
2819

2820
        let gateway = setup_place_order();
2821
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2822

2823
        // Create a stock contract
2824
        let contract = Contract::stock("AAPL").build();
2825

2826
        // Create a market order
2827
        let order = order_builder::market_order(Action::Buy, 100.0);
2828

2829
        // Use order ID 1001 to match the mock responses
2830
        let order_id = 1001;
2831

2832
        // First, start the order update stream BEFORE submitting the order
2833
        let update_stream = client.order_update_stream().expect("Failed to create order update stream");
2834

2835
        // Submit the order (fire and forget)
2836
        client.submit_order(order_id, &contract, &order).expect("Failed to submit order");
2837

2838
        // Collect events from the update stream
2839
        let mut order_status_count = 0;
2840
        let mut _open_order_count = 0;
2841
        let mut execution_count = 0;
2842
        let mut commission_count = 0;
2843
        let mut events_received = 0;
2844

2845
        // Read events from the update stream
2846
        // Use next_timeout to avoid blocking forever
2847
        println!("Starting to read from update stream...");
2848
        let timeout = std::time::Duration::from_millis(500);
2849

2850
        while events_received < 6 {
2851
            if let Some(update) = update_stream.next_timeout(timeout) {
2852
                events_received += 1;
2853
                println!("Event {}: {:?}", events_received, &update);
2854

2855
                match update {
2856
                    OrderUpdate::OrderStatus(status) => {
2857
                        order_status_count += 1;
2858
                        assert_eq!(status.order_id, order_id);
2859

2860
                        if order_status_count == 1 {
2861
                            // First status: PreSubmitted
2862
                            assert_eq!(status.status, "PreSubmitted");
2863
                            assert_eq!(status.filled, 0.0);
2864
                            assert_eq!(status.remaining, 100.0);
2865
                        } else if order_status_count == 2 {
2866
                            // Second status: Submitted
2867
                            assert_eq!(status.status, "Submitted");
2868
                            assert_eq!(status.filled, 0.0);
2869
                            assert_eq!(status.remaining, 100.0);
2870
                        } else if order_status_count == 3 {
2871
                            // Third status: Filled
2872
                            assert_eq!(status.status, "Filled");
2873
                            assert_eq!(status.filled, 100.0);
2874
                            assert_eq!(status.remaining, 0.0);
2875
                            assert_eq!(status.average_fill_price, 150.25);
2876
                        }
2877
                    }
2878
                    OrderUpdate::OpenOrder(order_data) => {
2879
                        _open_order_count += 1;
2880
                        assert_eq!(order_data.order_id, order_id);
2881
                        assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2882
                        assert_eq!(order_data.contract.contract_id, 265598);
2883
                        assert_eq!(order_data.order.action, Action::Buy);
2884
                        assert_eq!(order_data.order.total_quantity, 100.0);
2885
                        assert_eq!(order_data.order.order_type, "LMT");
2886
                        assert_eq!(order_data.order.limit_price, Some(1.0));
2887
                    }
2888
                    OrderUpdate::ExecutionData(exec_data) => {
2889
                        execution_count += 1;
2890
                        assert_eq!(exec_data.execution.order_id, order_id);
2891
                        assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2892
                        assert_eq!(exec_data.execution.shares, 100.0);
2893
                        assert_eq!(exec_data.execution.price, 150.25);
2894
                    }
2895
                    OrderUpdate::CommissionReport(report) => {
2896
                        commission_count += 1;
2897
                        assert_eq!(report.commission, 1.25);
2898
                        assert_eq!(report.currency, "USD");
2899
                    }
2900
                    OrderUpdate::Message(_) => {
2901
                        // Skip any messages
2902
                    }
2903
                }
2904
            } else {
2905
                // Timeout reached, no more messages available
2906
                break;
2907
            }
2908
        }
2909

2910
        // Verify we received all expected events
2911
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2912
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2913
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2914
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2915

2916
        // Verify the request was sent
2917
        let requests = gateway.requests();
2918
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2919
        // PlaceOrder message type is 3
2920
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2921
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2922
    }
2923

2924
    #[test]
2925
    fn test_open_orders() {
2926
        use crate::client::common::tests::setup_open_orders;
2927
        use crate::orders::{Action, Orders};
2928

2929
        // Initialize env_logger for debug output
2930
        let _ = env_logger::try_init();
2931

2932
        let gateway = setup_open_orders();
2933
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2934

2935
        // Request open orders
2936
        let subscription = client.open_orders().expect("Failed to request open orders");
2937

2938
        // Collect orders from the subscription
2939
        let mut orders = Vec::new();
2940
        for result in subscription {
2941
            match result {
2942
                Orders::OrderData(order_data) => {
2943
                    orders.push(order_data);
2944
                }
2945
                Orders::OrderStatus(_) => {
2946
                    // Skip order status messages for this test
2947
                }
2948
                Orders::Notice(_) => {
2949
                    // Skip notices
2950
                }
2951
            }
2952
        }
2953

2954
        // Verify we received 2 orders
2955
        assert_eq!(orders.len(), 2, "Should receive 2 open orders");
2956

2957
        // Verify first order (AAPL)
2958
        let order1 = &orders[0];
2959
        assert_eq!(order1.order_id, 1001);
2960
        assert_eq!(order1.contract.symbol, Symbol::from("AAPL"));
2961
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
2962
        assert_eq!(order1.order.action, Action::Buy);
2963
        assert_eq!(order1.order.total_quantity, 100.0);
2964
        assert_eq!(order1.order.order_type, "MKT");
2965
        assert_eq!(order1.order_state.status, "PreSubmitted");
2966

2967
        // Verify second order (MSFT)
2968
        let order2 = &orders[1];
2969
        assert_eq!(order2.order_id, 1002);
2970
        assert_eq!(order2.contract.symbol, Symbol::from("MSFT"));
2971
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
2972
        assert_eq!(order2.order.action, Action::Sell);
2973
        assert_eq!(order2.order.total_quantity, 50.0);
2974
        assert_eq!(order2.order.order_type, "LMT");
2975
        assert_eq!(order2.order.limit_price, Some(350.0));
2976
        assert_eq!(order2.order_state.status, "Submitted");
2977

2978
        // Verify the request was sent correctly
2979
        let requests = gateway.requests();
2980
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2981
        assert_eq!(requests[0], "5\01\0", "Request should be RequestOpenOrders with version 1");
2982
    }
2983

2984
    #[test]
2985
    fn test_all_open_orders() {
2986
        use crate::client::common::tests::setup_all_open_orders;
2987
        use crate::orders::{Action, Orders};
2988

2989
        // Initialize env_logger for debug output
2990
        let _ = env_logger::try_init();
2991

2992
        let gateway = setup_all_open_orders();
2993
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2994

2995
        // Request all open orders
2996
        let subscription = client.all_open_orders().expect("Failed to request all open orders");
2997

2998
        // Collect orders from the subscription
2999
        let mut orders = Vec::new();
3000
        for result in subscription {
3001
            match result {
3002
                Orders::OrderData(order_data) => {
3003
                    orders.push(order_data);
3004
                }
3005
                Orders::OrderStatus(_) => {
3006
                    // Skip order status messages for this test
3007
                }
3008
                Orders::Notice(_) => {
3009
                    // Skip notices
3010
                }
3011
            }
3012
        }
3013

3014
        // Verify we received 3 orders (from different clients)
3015
        assert_eq!(orders.len(), 3, "Should receive 3 open orders from all accounts");
3016

3017
        // Verify first order (TSLA from client 101)
3018
        let order1 = &orders[0];
3019
        assert_eq!(order1.order_id, 2001);
3020
        assert_eq!(order1.contract.symbol, Symbol::from("TSLA"));
3021
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
3022
        assert_eq!(order1.order.action, Action::Buy);
3023
        assert_eq!(order1.order.total_quantity, 10.0);
3024
        assert_eq!(order1.order.order_type, "LMT");
3025
        assert_eq!(order1.order.limit_price, Some(420.0));
3026
        assert_eq!(order1.order.account, "DU1236110");
3027

3028
        // Verify second order (AMZN from client 102)
3029
        let order2 = &orders[1];
3030
        assert_eq!(order2.order_id, 2002);
3031
        assert_eq!(order2.contract.symbol, Symbol::from("AMZN"));
3032
        assert_eq!(order2.order.action, Action::Sell);
3033
        assert_eq!(order2.order.total_quantity, 5.0);
3034
        assert_eq!(order2.order.order_type, "MKT");
3035
        assert_eq!(order2.order.account, "DU1236111");
3036

3037
        // Verify third order (GOOGL from current client 100)
3038
        let order3 = &orders[2];
3039
        assert_eq!(order3.order_id, 1003);
3040
        assert_eq!(order3.contract.symbol, Symbol::from("GOOGL"));
3041
        assert_eq!(order3.order.action, Action::Buy);
3042
        assert_eq!(order3.order.total_quantity, 20.0);
3043
        assert_eq!(order3.order.order_type, "LMT");
3044
        assert_eq!(order3.order.limit_price, Some(2800.0));
3045
        assert_eq!(order3.order.account, "DU1236109");
3046

3047
        // Verify the request was sent correctly
3048
        let requests = gateway.requests();
3049
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3050
        assert_eq!(requests[0], "16\01\0", "Request should be RequestAllOpenOrders with version 1");
3051
    }
3052

3053
    #[test]
3054
    fn test_auto_open_orders() {
3055
        use crate::client::common::tests::setup_auto_open_orders;
3056
        use crate::orders::Orders;
3057

3058
        // Initialize env_logger for debug output
3059
        let _ = env_logger::try_init();
3060

3061
        let gateway = setup_auto_open_orders();
3062
        // Note: auto_open_orders usually requires client_id 0 for real TWS connections,
3063
        // but for testing we use CLIENT_ID (100) to match the mock gateway expectation
3064
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3065

3066
        // Request auto open orders with auto_bind=true
3067
        let subscription = client.auto_open_orders(true).expect("Failed to request auto open orders");
3068

3069
        // Collect messages from the subscription
3070
        let mut order_statuses = Vec::new();
3071
        let mut orders = Vec::new();
3072
        for result in subscription {
3073
            match result {
3074
                Orders::OrderData(order_data) => {
3075
                    orders.push(order_data);
3076
                }
3077
                Orders::OrderStatus(status) => {
3078
                    order_statuses.push(status);
3079
                }
3080
                Orders::Notice(_) => {
3081
                    // Skip notices
3082
                }
3083
            }
3084
        }
3085

3086
        // Verify we received order status updates
3087
        assert_eq!(order_statuses.len(), 2, "Should receive 2 order status updates");
3088

3089
        // Verify first status (PreSubmitted)
3090
        let status1 = &order_statuses[0];
3091
        assert_eq!(status1.order_id, 3001);
3092
        assert_eq!(status1.status, "PreSubmitted");
3093

3094
        // Verify second status (Submitted)
3095
        let status2 = &order_statuses[1];
3096
        assert_eq!(status2.order_id, 3001);
3097
        assert_eq!(status2.status, "Submitted");
3098

3099
        // Verify we received 1 order
3100
        assert_eq!(orders.len(), 1, "Should receive 1 order");
3101

3102
        // Verify the order (FB from TWS)
3103
        let order = &orders[0];
3104
        assert_eq!(order.order_id, 3001);
3105
        assert_eq!(order.contract.symbol, Symbol::from("FB"));
3106
        assert_eq!(order.contract.security_type, crate::contracts::SecurityType::Stock);
3107
        assert_eq!(order.order.action, crate::orders::Action::Buy);
3108
        assert_eq!(order.order.total_quantity, 50.0);
3109
        assert_eq!(order.order.order_type, "MKT");
3110
        assert_eq!(order.order.account, "TWS");
3111

3112
        // Verify the request was sent correctly
3113
        let requests = gateway.requests();
3114
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3115
        assert_eq!(
3116
            requests[0], "15\01\01\0",
3117
            "Request should be RequestAutoOpenOrders with version 1 and auto_bind=true"
3118
        );
3119
    }
3120

3121
    #[test]
3122
    fn test_completed_orders() {
3123
        use crate::client::common::tests::setup_completed_orders;
3124
        use crate::orders::{Action, Orders};
3125

3126
        // Initialize env_logger for debug output
3127
        let _ = env_logger::try_init();
3128

3129
        let gateway = setup_completed_orders();
3130
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3131

3132
        // Request completed orders (api_only=false to get all completed orders)
3133
        let subscription = client.completed_orders(false).expect("Failed to request completed orders");
3134

3135
        // Collect orders from the subscription
3136
        let mut orders = Vec::new();
3137
        for result in subscription {
3138
            match result {
3139
                Orders::OrderData(order_data) => {
3140
                    orders.push(order_data);
3141
                }
3142
                Orders::OrderStatus(_) => {
3143
                    // Skip order status messages
3144
                }
3145
                Orders::Notice(_) => {
3146
                    // Skip notices
3147
                }
3148
            }
3149
        }
3150

3151
        // Verify we received 2 completed orders
3152
        assert_eq!(orders.len(), 2, "Should receive 2 completed orders");
3153

3154
        // Verify first completed order (ES futures - based on captured data)
3155
        let order1 = &orders[0];
3156
        // CompletedOrder messages don't have order_id in the message, defaults to -1
3157
        assert_eq!(order1.order_id, -1);
3158
        assert_eq!(order1.contract.symbol, Symbol::from("ES"));
3159
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Future);
3160
        assert_eq!(order1.order.action, Action::Buy);
3161
        assert_eq!(order1.order.total_quantity, 1.0);
3162
        assert_eq!(order1.order.order_type, "LMT");
3163
        assert_eq!(order1.order_state.status, "Cancelled");
3164
        assert_eq!(order1.order.perm_id, 616088517);
3165

3166
        // Verify second completed order (AAPL)
3167
        let order2 = &orders[1];
3168
        assert_eq!(order2.order_id, -1); // CompletedOrder messages don't have order_id
3169
        assert_eq!(order2.contract.symbol, Symbol::from("AAPL"));
3170
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3171
        assert_eq!(order2.order.action, Action::Buy);
3172
        assert_eq!(order2.order.total_quantity, 100.0);
3173
        assert_eq!(order2.order.order_type, "MKT");
3174
        assert_eq!(order2.order_state.status, "Filled");
3175
        assert_eq!(order2.order.perm_id, 1377295418);
3176

3177
        // Verify the request was sent correctly
3178
        let requests = gateway.requests();
3179
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3180
        assert_eq!(requests[0], "99\00\0", "Request should be RequestCompletedOrders with api_only=false");
3181
    }
3182

3183
    #[test]
3184
    fn test_cancel_order() {
3185
        use crate::client::common::tests::setup_cancel_order;
3186
        use crate::messages::Notice;
3187
        use crate::orders::CancelOrder;
3188

3189
        // Initialize env_logger for debug output
3190
        let _ = env_logger::try_init();
3191

3192
        let gateway = setup_cancel_order();
3193
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3194

3195
        // Cancel order with ID 1001
3196
        let order_id = 1001;
3197
        let manual_order_cancel_time = "";
3198

3199
        // Call cancel_order and get the result
3200
        let result = client.cancel_order(order_id, manual_order_cancel_time);
3201

3202
        // Verify the result
3203
        match result {
3204
            Ok(cancel_result) => {
3205
                // Iterate through the cancellation results
3206
                let mut order_status_received = false;
3207
                let mut notice_received = false;
3208

3209
                for item in cancel_result {
3210
                    match item {
3211
                        CancelOrder::OrderStatus(status) => {
3212
                            assert_eq!(status.order_id, order_id);
3213
                            assert_eq!(status.status, "Cancelled");
3214
                            assert_eq!(status.filled, 0.0);
3215
                            assert_eq!(status.remaining, 100.0);
3216
                            order_status_received = true;
3217
                            println!("Received OrderStatus: {:?}", status);
3218
                        }
3219
                        CancelOrder::Notice(Notice { code, message }) => {
3220
                            // Notice messages with code 202 are order cancellation confirmations
3221
                            // The message should contain the order ID in the format
3222
                            assert_eq!(code, 202);
3223
                            assert!(message.contains("Order Cancelled"));
3224
                            notice_received = true;
3225
                            println!("Received Notice: code={}, message={}", code, message);
3226
                        }
3227
                    }
3228
                }
3229

3230
                assert!(order_status_received, "Should have received OrderStatus");
3231
                assert!(notice_received, "Should have received Notice confirmation");
3232
            }
3233
            Err(e) => panic!("Failed to cancel order: {}", e),
3234
        }
3235

3236
        // Verify the request was sent correctly
3237
        let requests = gateway.requests();
3238
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3239
        assert!(requests[0].starts_with("4\0"), "Request should be a CancelOrder message");
3240
        assert!(requests[0].contains(&format!("{}\0", order_id)), "Request should contain order ID");
3241
    }
3242

3243
    #[test]
3244
    fn test_global_cancel() {
3245
        use crate::client::common::tests::setup_global_cancel;
3246

3247
        // Initialize env_logger for debug output
3248
        let _ = env_logger::try_init();
3249

3250
        let gateway = setup_global_cancel();
3251
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3252

3253
        // Call global_cancel
3254
        let result = client.global_cancel();
3255

3256
        // Verify the result
3257
        match result {
3258
            Ok(()) => {
3259
                println!("Global cancel request sent successfully");
3260
            }
3261
            Err(e) => panic!("Failed to send global cancel: {}", e),
3262
        }
3263

3264
        // Give the gateway time to process the request
3265
        std::thread::sleep(std::time::Duration::from_millis(100));
3266

3267
        // Verify the request was sent correctly
3268
        let requests = gateway.requests();
3269
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3270
        assert_eq!(requests[0], "58\01\0", "Request should be a RequestGlobalCancel message with version 1");
3271
    }
3272

3273
    #[test]
3274
    fn test_executions() {
3275
        use crate::client::common::tests::setup_executions;
3276
        use crate::contracts::SecurityType;
3277
        use crate::orders::{ExecutionFilter, Executions};
3278

3279
        // Initialize env_logger for debug output
3280
        let _ = env_logger::try_init();
3281

3282
        let gateway = setup_executions();
3283
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3284

3285
        // Create an execution filter
3286
        let filter = ExecutionFilter {
3287
            client_id: Some(CLIENT_ID),
3288
            account_code: "DU1234567".to_string(),
3289
            time: "".to_string(),          // Empty means all time
3290
            symbol: "".to_string(),        // Empty means all symbols
3291
            security_type: "".to_string(), // Empty means all types
3292
            exchange: "".to_string(),      // Empty means all exchanges
3293
            side: "".to_string(),          // Empty means all sides
3294
        };
3295

3296
        // Request executions
3297
        let subscription = client.executions(filter).expect("Failed to request executions");
3298

3299
        // Collect executions from the subscription
3300
        let mut execution_data = Vec::new();
3301
        let mut commission_reports = Vec::new();
3302

3303
        for result in subscription {
3304
            match result {
3305
                Executions::ExecutionData(data) => {
3306
                    execution_data.push(data);
3307
                }
3308
                Executions::CommissionReport(report) => {
3309
                    commission_reports.push(report);
3310
                }
3311
                Executions::Notice(_) => {
3312
                    // Skip notices
3313
                }
3314
            }
3315
        }
3316

3317
        // Verify we received 3 executions and 3 commission reports
3318
        assert_eq!(execution_data.len(), 3, "Should receive 3 execution data messages");
3319
        assert_eq!(commission_reports.len(), 3, "Should receive 3 commission reports");
3320

3321
        // Verify first execution (AAPL stock)
3322
        let exec1 = &execution_data[0];
3323
        assert_eq!(exec1.request_id, 9000);
3324
        assert_eq!(exec1.execution.order_id, 1001);
3325
        assert_eq!(exec1.contract.symbol, Symbol::from("AAPL"));
3326
        assert_eq!(exec1.contract.security_type, SecurityType::Stock);
3327
        assert_eq!(exec1.execution.execution_id, "000e1a2b.67890abc.01.01");
3328
        assert_eq!(exec1.execution.side, "BOT");
3329
        assert_eq!(exec1.execution.shares, 100.0);
3330
        assert_eq!(exec1.execution.price, 150.25);
3331

3332
        // Verify first commission report
3333
        let comm1 = &commission_reports[0];
3334
        assert_eq!(comm1.execution_id, "000e1a2b.67890abc.01.01");
3335
        assert_eq!(comm1.commission, 1.25);
3336
        assert_eq!(comm1.currency, "USD");
3337

3338
        // Verify second execution (ES futures)
3339
        let exec2 = &execution_data[1];
3340
        assert_eq!(exec2.request_id, 9000);
3341
        assert_eq!(exec2.execution.order_id, 1002);
3342
        assert_eq!(exec2.contract.symbol, Symbol::from("ES"));
3343
        assert_eq!(exec2.contract.security_type, SecurityType::Future);
3344
        assert_eq!(exec2.execution.execution_id, "000e1a2b.67890def.02.01");
3345
        assert_eq!(exec2.execution.side, "SLD");
3346
        assert_eq!(exec2.execution.shares, 5.0);
3347
        assert_eq!(exec2.execution.price, 5050.25);
3348

3349
        // Verify second commission report
3350
        let comm2 = &commission_reports[1];
3351
        assert_eq!(comm2.execution_id, "000e1a2b.67890def.02.01");
3352
        assert_eq!(comm2.commission, 2.50);
3353
        assert_eq!(comm2.realized_pnl, Some(125.50));
3354

3355
        // Verify third execution (SPY options)
3356
        let exec3 = &execution_data[2];
3357
        assert_eq!(exec3.request_id, 9000);
3358
        assert_eq!(exec3.execution.order_id, 1003);
3359
        assert_eq!(exec3.contract.symbol, Symbol::from("SPY"));
3360
        assert_eq!(exec3.contract.security_type, SecurityType::Option);
3361
        assert_eq!(exec3.execution.execution_id, "000e1a2b.67890ghi.03.01");
3362
        assert_eq!(exec3.execution.side, "BOT");
3363
        assert_eq!(exec3.execution.shares, 10.0);
3364
        assert_eq!(exec3.execution.price, 2.50);
3365

3366
        // Verify third commission report
3367
        let comm3 = &commission_reports[2];
3368
        assert_eq!(comm3.execution_id, "000e1a2b.67890ghi.03.01");
3369
        assert_eq!(comm3.commission, 0.65);
3370
        assert_eq!(comm3.realized_pnl, Some(250.00));
3371

3372
        // Verify the request was sent correctly
3373
        let requests = gateway.requests();
3374
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3375
        // Request format: RequestExecutions(7), version(3), request_id(9000), client_id, account_code, time, symbol, security_type, exchange, side
3376
        assert_eq!(
3377
            requests[0], "7\03\09000\0100\0DU1234567\0\0\0\0\0\0",
3378
            "Request should be RequestExecutions with correct filter parameters"
3379
        );
3380
    }
3381

3382
    #[test]
3383
    fn test_exercise_options() {
3384
        use crate::client::common::tests::setup_exercise_options;
3385
        use crate::contracts::{Contract, SecurityType};
3386
        use crate::orders::{ExerciseAction, ExerciseOptions};
3387
        use time::macros::datetime;
3388

3389
        // Initialize env_logger for debug output
3390
        let _ = env_logger::try_init();
3391

3392
        let gateway = setup_exercise_options();
3393
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3394

3395
        // Create option contract for SPY
3396
        let contract = Contract {
3397
            contract_id: 123456789,
3398
            symbol: Symbol::from("SPY"),
3399
            security_type: SecurityType::Option,
3400
            last_trade_date_or_contract_month: "20240126".to_string(),
3401
            strike: 450.0,
3402
            right: "C".to_string(), // Call option
3403
            multiplier: "100".to_string(),
3404
            exchange: Exchange::from("CBOE"),
3405
            currency: Currency::from("USD"),
3406
            local_symbol: "SPY240126C00450000".to_string(),
3407
            trading_class: "SPY".to_string(),
3408
            ..Default::default()
3409
        };
3410

3411
        // Exercise the option
3412
        let exercise_action = ExerciseAction::Exercise;
3413
        let exercise_quantity = 10;
3414
        let account = "DU1234567";
3415
        let ovrd = false;
3416
        let manual_order_time = Some(datetime!(2024-01-25 10:30:00 UTC));
3417

3418
        let subscription = client
3419
            .exercise_options(&contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
3420
            .expect("Failed to exercise options");
3421

3422
        // Collect results
3423
        let mut order_statuses = Vec::new();
3424
        let mut open_orders = Vec::new();
3425

3426
        for result in subscription {
3427
            match result {
3428
                ExerciseOptions::OrderStatus(status) => order_statuses.push(status),
3429
                ExerciseOptions::OpenOrder(order) => open_orders.push(order),
3430
                ExerciseOptions::Notice(_notice) => {
3431
                    // Note: Warning messages (2100-2200) are currently not routed to subscriptions
3432
                    // They are only logged. See TODO.md for future improvements.
3433
                }
3434
            }
3435
        }
3436

3437
        // Verify we got the expected responses
3438
        assert_eq!(order_statuses.len(), 3, "Should have 3 order status updates");
3439
        assert_eq!(open_orders.len(), 1, "Should have 1 open order");
3440

3441
        // Verify order statuses
3442
        assert_eq!(order_statuses[0].status, "PreSubmitted");
3443
        assert_eq!(order_statuses[0].filled, 0.0);
3444
        assert_eq!(order_statuses[0].remaining, 10.0);
3445

3446
        assert_eq!(order_statuses[1].status, "Submitted");
3447
        assert_eq!(order_statuses[2].status, "Filled");
3448
        assert_eq!(order_statuses[2].filled, 10.0);
3449
        assert_eq!(order_statuses[2].remaining, 0.0);
3450

3451
        // Verify open order
3452
        let open_order = &open_orders[0];
3453
        assert_eq!(open_order.order.order_id, 90);
3454
        assert_eq!(open_order.contract.symbol, Symbol::from("SPY"));
3455
        assert_eq!(open_order.contract.security_type, SecurityType::Option);
3456
        assert_eq!(open_order.order.order_type, "EXERCISE");
3457

3458
        // Verify the request was sent correctly
3459
        let requests = gateway.requests();
3460
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3461

3462
        // Request format: ExerciseOptions(21), version(2), order_id, contract fields, exercise_action, exercise_quantity, account, ovrd, manual_order_time
3463
        let expected_request = format!(
3464
            "21\02\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0",
3465
            90, // order_id (using next_order_id from client)
3466
            contract.contract_id,
3467
            contract.symbol,
3468
            contract.security_type,
3469
            contract.last_trade_date_or_contract_month,
3470
            contract.strike,
3471
            contract.right,
3472
            contract.multiplier,
3473
            contract.exchange,
3474
            contract.currency,
3475
            contract.local_symbol,
3476
            contract.trading_class,
3477
            exercise_action as i32,
3478
            exercise_quantity,
3479
            account,
3480
            if ovrd { 1 } else { 0 },
3481
            "20240125 10:30:00 UTC" // manual_order_time formatted
3482
        );
3483

3484
        assert_eq!(requests[0], expected_request, "Request should be ExerciseOptions with correct parameters");
3485
    }
3486

3487
    // === Real-time Market Data Tests ===
3488

3489
    #[test]
3490
    fn test_market_data() {
3491
        use crate::client::common::tests::setup_market_data;
3492
        use crate::contracts::tick_types::TickType;
3493
        use crate::contracts::Contract;
3494
        use crate::market_data::realtime::TickTypes;
3495

3496
        // Initialize env_logger for debug output
3497
        let _ = env_logger::try_init();
3498

3499
        let gateway = setup_market_data();
3500
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3501

3502
        let contract = Contract::stock("AAPL").build();
3503
        let generic_ticks = vec!["100", "101", "104"]; // Option volume, option open interest, historical volatility
3504

3505
        let subscription = client
3506
            .market_data(&contract)
3507
            .generic_ticks(&generic_ticks)
3508
            .snapshot()
3509
            .subscribe()
3510
            .expect("Failed to request market data");
3511

3512
        let mut tick_count = 0;
3513
        let mut has_bid_price = false;
3514
        let mut has_bid_size = false;
3515
        let mut has_ask_price = false;
3516
        let mut has_ask_size = false;
3517
        let mut has_last_price = false;
3518
        let mut has_last_size = false;
3519
        let mut has_volume = false;
3520
        let mut has_snapshot_end = false;
3521

3522
        for tick in subscription {
3523
            tick_count += 1;
3524
            match tick {
3525
                TickTypes::PriceSize(price_size) => {
3526
                    match price_size.price_tick_type {
3527
                        TickType::Bid => {
3528
                            assert_eq!(price_size.price, 150.50);
3529
                            has_bid_price = true;
3530
                        }
3531
                        TickType::Ask => {
3532
                            assert_eq!(price_size.price, 151.00);
3533
                            has_ask_price = true;
3534
                        }
3535
                        TickType::Last => {
3536
                            assert_eq!(price_size.price, 150.75);
3537
                            has_last_price = true;
3538
                        }
3539
                        _ => {}
3540
                    }
3541
                    // Note: size_tick_type might be present but size value is 0 in PriceSize
3542
                }
3543
                TickTypes::Size(size_tick) => match size_tick.tick_type {
3544
                    TickType::BidSize => {
3545
                        assert_eq!(size_tick.size, 100.0);
3546
                        has_bid_size = true;
3547
                    }
3548
                    TickType::AskSize => {
3549
                        assert_eq!(size_tick.size, 200.0);
3550
                        has_ask_size = true;
3551
                    }
3552
                    TickType::LastSize => {
3553
                        assert_eq!(size_tick.size, 50.0);
3554
                        has_last_size = true;
3555
                    }
3556
                    _ => {}
3557
                },
3558
                TickTypes::Generic(generic_tick) => {
3559
                    if generic_tick.tick_type == TickType::Volume {
3560
                        assert_eq!(generic_tick.value, 1500000.0);
3561
                        has_volume = true;
3562
                    }
3563
                }
3564
                TickTypes::String(_) => {
3565
                    // Ignore string ticks like LastTimestamp
3566
                }
3567
                TickTypes::SnapshotEnd => {
3568
                    has_snapshot_end = true;
3569
                    break; // Snapshot complete
3570
                }
3571
                _ => {}
3572
            }
3573

3574
            if tick_count > 20 {
3575
                break; // Safety limit
3576
            }
3577
        }
3578

3579
        assert!(has_bid_price, "Should receive bid price");
3580
        assert!(has_bid_size, "Should receive bid size");
3581
        assert!(has_ask_price, "Should receive ask price");
3582
        assert!(has_ask_size, "Should receive ask size");
3583
        assert!(has_last_price, "Should receive last price");
3584
        assert!(has_last_size, "Should receive last size");
3585
        assert!(has_volume, "Should receive volume");
3586
        assert!(has_snapshot_end, "Should receive snapshot end");
3587

3588
        let requests = gateway.requests();
3589
        // Verify request format: RequestMarketData(1), version(11), request_id, contract_id,
3590
        // symbol, sec_type, expiry, strike, right, multiplier, exchange, primary_exchange,
3591
        // currency, local_symbol, trading_class, con_id_flag, combo_legs_description,
3592
        // generic_ticks, snapshot, regulatory_snapshot, market_data_options
3593
        assert!(requests[0].starts_with("1\011\09000\0"), "Request should be RequestMarketData");
3594
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3595
        assert!(requests[0].contains("100,101,104\0"), "Request should contain generic ticks");
3596
        assert!(requests[0].contains("\01\0"), "Request should have snapshot=true");
3597
    }
3598

3599
    #[test]
3600
    fn test_realtime_bars() {
3601
        use crate::client::common::tests::setup_realtime_bars;
3602
        use crate::contracts::Contract;
3603
        use crate::market_data::realtime::{BarSize, WhatToShow};
3604

3605
        // Initialize env_logger for debug output
3606
        let _ = env_logger::try_init();
3607

3608
        let gateway = setup_realtime_bars();
3609
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3610

3611
        let contract = Contract::stock("AAPL").build();
3612
        let bar_size = BarSize::Sec5;
3613
        let what_to_show = WhatToShow::Trades;
3614
        let trading_hours = TradingHours::Extended;
3615

3616
        let subscription = client
3617
            .realtime_bars(&contract, bar_size, what_to_show, trading_hours)
3618
            .expect("Failed to request realtime bars");
3619

3620
        let mut bars = Vec::new();
3621
        for bar in subscription.into_iter().take(3) {
3622
            bars.push(bar);
3623
        }
3624

3625
        assert_eq!(bars.len(), 3, "Should receive 3 bars");
3626

3627
        // Verify first bar
3628
        let bar1 = &bars[0];
3629
        assert_eq!(bar1.open, 150.25);
3630
        assert_eq!(bar1.high, 150.75);
3631
        assert_eq!(bar1.low, 150.00);
3632
        assert_eq!(bar1.close, 150.50);
3633
        assert_eq!(bar1.volume, 1000.0);
3634
        assert_eq!(bar1.wap, 150.40);
3635
        assert_eq!(bar1.count, 25);
3636

3637
        // Verify second bar
3638
        let bar2 = &bars[1];
3639
        assert_eq!(bar2.open, 150.50);
3640
        assert_eq!(bar2.high, 151.00);
3641
        assert_eq!(bar2.low, 150.40);
3642
        assert_eq!(bar2.close, 150.90);
3643
        assert_eq!(bar2.volume, 1200.0);
3644

3645
        // Verify third bar
3646
        let bar3 = &bars[2];
3647
        assert_eq!(bar3.open, 150.90);
3648
        assert_eq!(bar3.high, 151.25);
3649
        assert_eq!(bar3.low, 150.85);
3650
        assert_eq!(bar3.close, 151.20);
3651

3652
        let requests = gateway.requests();
3653
        // Verify request format: RequestRealTimeBars(50), version(8), request_id, contract,
3654
        // bar_size(5), what_to_show, use_rth, realtime_bars_options
3655
        assert!(requests[0].starts_with("50\08\09000\0"), "Request should be RequestRealTimeBars");
3656
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3657
        assert!(
3658
            requests[0].contains("\00\0TRADES\00\0"),
3659
            "Request should have bar_size=0 (5 sec) and TRADES"
3660
        );
3661
    }
3662

3663
    #[test]
3664
    fn test_tick_by_tick_last() {
3665
        use crate::client::common::tests::setup_tick_by_tick_last;
3666
        use crate::contracts::Contract;
3667

3668
        // Initialize env_logger for debug output
3669
        let _ = env_logger::try_init();
3670

3671
        let gateway = setup_tick_by_tick_last();
3672
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3673

3674
        let contract = Contract::stock("AAPL").build();
3675
        let number_of_ticks = 0;
3676
        let ignore_size = false;
3677

3678
        let subscription = client
3679
            .tick_by_tick_last(&contract, number_of_ticks, ignore_size)
3680
            .expect("Failed to request tick by tick last");
3681

3682
        let mut trades = Vec::new();
3683
        for trade in subscription.into_iter().take(3) {
3684
            trades.push(trade);
3685
        }
3686

3687
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3688

3689
        // Verify first trade
3690
        let trade1 = &trades[0];
3691
        assert_eq!(trade1.tick_type, "1"); // 1 = Last
3692
        assert_eq!(trade1.price, 150.75);
3693
        assert_eq!(trade1.size, 100.0);
3694
        assert_eq!(trade1.exchange, "NASDAQ");
3695
        assert!(!trade1.trade_attribute.past_limit);
3696
        assert!(!trade1.trade_attribute.unreported);
3697

3698
        // Verify second trade (unreported)
3699
        let trade2 = &trades[1];
3700
        assert_eq!(trade2.price, 150.80);
3701
        assert_eq!(trade2.size, 50.0);
3702
        assert_eq!(trade2.exchange, "NYSE");
3703
        assert!(trade2.trade_attribute.unreported);
3704

3705
        // Verify third trade
3706
        let trade3 = &trades[2];
3707
        assert_eq!(trade3.price, 150.70);
3708
        assert_eq!(trade3.size, 150.0);
3709

3710
        let requests = gateway.requests();
3711
        // Verify request format: RequestTickByTickData(97), request_id, contract,
3712
        // tick_type("Last"), number_of_ticks, ignore_size
3713
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3714
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3715
        assert!(requests[0].contains("Last\0"), "Request should have Last tick type");
3716
    }
3717

3718
    #[test]
3719
    fn test_tick_by_tick_all_last() {
3720
        use crate::client::common::tests::setup_tick_by_tick_all_last;
3721
        use crate::contracts::Contract;
3722

3723
        // Initialize env_logger for debug output
3724
        let _ = env_logger::try_init();
3725

3726
        let gateway = setup_tick_by_tick_all_last();
3727
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3728

3729
        let contract = Contract::stock("AAPL").build();
3730
        let number_of_ticks = 0;
3731
        let ignore_size = false;
3732

3733
        let subscription = client
3734
            .tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
3735
            .expect("Failed to request tick by tick all last");
3736

3737
        let mut trades = Vec::new();
3738
        for trade in subscription.into_iter().take(3) {
3739
            trades.push(trade);
3740
        }
3741

3742
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3743

3744
        // Verify first trade
3745
        let trade1 = &trades[0];
3746
        assert_eq!(trade1.tick_type, "2"); // 2 = AllLast
3747
        assert_eq!(trade1.price, 150.75);
3748
        assert_eq!(trade1.exchange, "NASDAQ");
3749

3750
        // Verify second trade (unreported dark pool trade)
3751
        let trade2 = &trades[1];
3752
        assert_eq!(trade2.price, 150.80);
3753
        assert_eq!(trade2.exchange, "DARK");
3754
        assert_eq!(trade2.special_conditions, "ISO");
3755
        assert!(trade2.trade_attribute.unreported);
3756

3757
        // Verify third trade
3758
        let trade3 = &trades[2];
3759
        assert_eq!(trade3.price, 150.70);
3760
        assert_eq!(trade3.exchange, "NYSE");
3761

3762
        let requests = gateway.requests();
3763
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3764
        assert!(requests[0].contains("AllLast\0"), "Request should have AllLast tick type");
3765
    }
3766

3767
    #[test]
3768
    fn test_tick_by_tick_bid_ask() {
3769
        use crate::client::common::tests::setup_tick_by_tick_bid_ask;
3770
        use crate::contracts::Contract;
3771

3772
        // Initialize env_logger for debug output
3773
        let _ = env_logger::try_init();
3774

3775
        let gateway = setup_tick_by_tick_bid_ask();
3776
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3777

3778
        let contract = Contract::stock("AAPL").build();
3779
        let number_of_ticks = 0;
3780
        let ignore_size = false;
3781

3782
        let subscription = client
3783
            .tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
3784
            .expect("Failed to request tick by tick bid ask");
3785

3786
        let mut bid_asks = Vec::new();
3787
        for bid_ask in subscription.into_iter().take(3) {
3788
            bid_asks.push(bid_ask);
3789
        }
3790

3791
        assert_eq!(bid_asks.len(), 3, "Should receive 3 bid/ask updates");
3792

3793
        // Verify first bid/ask
3794
        let ba1 = &bid_asks[0];
3795
        assert_eq!(ba1.bid_price, 150.50);
3796
        assert_eq!(ba1.ask_price, 150.55);
3797
        assert_eq!(ba1.bid_size, 100.0);
3798
        assert_eq!(ba1.ask_size, 200.0);
3799
        assert!(!ba1.bid_ask_attribute.bid_past_low);
3800
        assert!(!ba1.bid_ask_attribute.ask_past_high);
3801

3802
        // Verify second bid/ask (bid past low)
3803
        let ba2 = &bid_asks[1];
3804
        assert_eq!(ba2.bid_price, 150.45);
3805
        assert_eq!(ba2.ask_price, 150.55);
3806
        assert!(ba2.bid_ask_attribute.bid_past_low);
3807

3808
        // Verify third bid/ask (ask past high)
3809
        let ba3 = &bid_asks[2];
3810
        assert_eq!(ba3.ask_price, 150.60);
3811
        assert!(ba3.bid_ask_attribute.ask_past_high);
3812

3813
        let requests = gateway.requests();
3814
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3815
        assert!(requests[0].contains("BidAsk\0"), "Request should have BidAsk tick type");
3816
    }
3817

3818
    #[test]
3819
    fn test_tick_by_tick_midpoint() {
3820
        use crate::client::common::tests::setup_tick_by_tick_midpoint;
3821
        use crate::contracts::Contract;
3822

3823
        // Initialize env_logger for debug output
3824
        let _ = env_logger::try_init();
3825

3826
        let gateway = setup_tick_by_tick_midpoint();
3827
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3828

3829
        let contract = Contract::stock("AAPL").build();
3830
        let number_of_ticks = 0;
3831
        let ignore_size = false;
3832

3833
        let subscription = client
3834
            .tick_by_tick_midpoint(&contract, number_of_ticks, ignore_size)
3835
            .expect("Failed to request tick by tick midpoint");
3836

3837
        let mut midpoints = Vec::new();
3838
        for midpoint in subscription.into_iter().take(3) {
3839
            midpoints.push(midpoint);
3840
        }
3841

3842
        assert_eq!(midpoints.len(), 3, "Should receive 3 midpoint updates");
3843

3844
        assert_eq!(midpoints[0].mid_point, 150.525);
3845
        assert_eq!(midpoints[1].mid_point, 150.50);
3846
        assert_eq!(midpoints[2].mid_point, 150.525);
3847

3848
        let requests = gateway.requests();
3849
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3850
        assert!(requests[0].contains("MidPoint\0"), "Request should have MidPoint tick type");
3851
    }
3852

3853
    #[test]
3854
    fn test_market_depth() {
3855
        use crate::client::common::tests::setup_market_depth;
3856
        use crate::contracts::Contract;
3857
        use crate::market_data::realtime::MarketDepths;
3858

3859
        // Initialize env_logger for debug output
3860
        let _ = env_logger::try_init();
3861

3862
        let gateway = setup_market_depth();
3863
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3864

3865
        let contract = Contract::stock("AAPL").build();
3866
        let num_rows = 5;
3867
        let is_smart_depth = false;
3868

3869
        let subscription = client
3870
            .market_depth(&contract, num_rows, is_smart_depth)
3871
            .expect("Failed to request market depth");
3872

3873
        let mut updates = Vec::new();
3874
        for update in subscription.into_iter().take(4) {
3875
            if let MarketDepths::MarketDepth(depth) = update {
3876
                updates.push(depth);
3877
            }
3878
        }
3879

3880
        assert_eq!(updates.len(), 4, "Should receive 4 depth updates");
3881

3882
        // Verify bid insert
3883
        let update1 = &updates[0];
3884
        assert_eq!(update1.position, 0);
3885
        // MarketDepth (L1) doesn't have market_maker field
3886
        assert_eq!(update1.operation, 0); // Insert
3887
        assert_eq!(update1.side, 1); // Bid
3888
        assert_eq!(update1.price, 150.50);
3889
        assert_eq!(update1.size, 100.0);
3890

3891
        // Verify ask insert
3892
        let update2 = &updates[1];
3893
        assert_eq!(update2.operation, 0); // Insert
3894
        assert_eq!(update2.side, 0); // Ask
3895
        assert_eq!(update2.price, 150.55);
3896
        assert_eq!(update2.size, 200.0);
3897

3898
        // Verify bid update
3899
        let update3 = &updates[2];
3900
        assert_eq!(update3.operation, 1); // Update
3901
        assert_eq!(update3.price, 150.49);
3902

3903
        // Verify ask delete
3904
        let update4 = &updates[3];
3905
        assert_eq!(update4.operation, 2); // Delete
3906

3907
        let requests = gateway.requests();
3908
        // Verify request format: RequestMarketDepth(10), version(5), request_id, contract,
3909
        // num_rows, is_smart_depth, market_depth_options
3910
        assert!(requests[0].starts_with("10\05\09000\0"), "Request should be RequestMarketDepth");
3911
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3912
        assert!(requests[0].contains("5\00\0"), "Request should have 5 rows and smart_depth=false");
3913
    }
3914

3915
    #[test]
3916
    fn test_market_depth_exchanges() {
3917
        use crate::client::common::tests::setup_market_depth_exchanges;
3918

3919
        // Initialize env_logger for debug output
3920
        let _ = env_logger::try_init();
3921

3922
        let gateway = setup_market_depth_exchanges();
3923
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3924

3925
        let exchanges = client.market_depth_exchanges().expect("Failed to get market depth exchanges");
3926

3927
        assert_eq!(exchanges.len(), 3, "Should receive 3 exchange descriptions");
3928

3929
        // Verify first exchange
3930
        let ex1 = &exchanges[0];
3931
        assert_eq!(ex1.exchange_name, "ISLAND");
3932
        assert_eq!(ex1.security_type, "STK");
3933
        assert_eq!(ex1.listing_exchange, "NASDAQ");
3934
        assert_eq!(ex1.service_data_type, "Deep2");
3935
        assert_eq!(ex1.aggregated_group, Some("1".to_string()));
3936

3937
        // Verify second exchange
3938
        let ex2 = &exchanges[1];
3939
        assert_eq!(ex2.exchange_name, "NYSE");
3940
        assert_eq!(ex2.security_type, "STK");
3941
        assert_eq!(ex2.service_data_type, "Deep");
3942
        assert_eq!(ex2.aggregated_group, Some("2".to_string()));
3943

3944
        // Verify third exchange
3945
        let ex3 = &exchanges[2];
3946
        assert_eq!(ex3.exchange_name, "ARCA");
3947
        assert_eq!(ex3.aggregated_group, Some("2".to_string()));
3948

3949
        let requests = gateway.requests();
3950
        assert_eq!(requests[0], "82\0", "Request should be RequestMktDepthExchanges");
3951
    }
3952

3953
    #[test]
3954
    fn test_switch_market_data_type() {
3955
        use crate::client::common::tests::setup_switch_market_data_type;
3956
        use crate::market_data::MarketDataType;
3957

3958
        // Initialize env_logger for debug output
3959
        let _ = env_logger::try_init();
3960

3961
        let gateway = setup_switch_market_data_type();
3962
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3963

3964
        // Test switching to delayed market data
3965
        client
3966
            .switch_market_data_type(MarketDataType::Delayed)
3967
            .expect("Failed to switch market data type");
3968

3969
        // Give the mock gateway time to receive the request
3970
        std::thread::sleep(std::time::Duration::from_millis(100));
3971

3972
        let requests = gateway.requests();
3973
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3974
        // Verify request format: RequestMarketDataType(59), version(1), market_data_type(3=Delayed)
3975
        assert_eq!(requests[0], "59\01\03\0", "Request should be RequestMarketDataType with Delayed(3)");
3976
    }
3977

3978
    // === Historical Data Tests ===
3979

3980
    #[test]
3981
    fn test_head_timestamp() {
3982
        use crate::client::common::tests::setup_head_timestamp;
3983
        use crate::contracts::Contract;
3984
        use crate::market_data::historical::WhatToShow;
3985

3986
        let gateway = setup_head_timestamp();
3987
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3988

3989
        let contract = Contract::stock("AAPL").build();
3990
        let what_to_show = WhatToShow::Trades;
3991
        let trading_hours = TradingHours::Regular;
3992

3993
        let timestamp = client
3994
            .head_timestamp(&contract, what_to_show, trading_hours)
3995
            .expect("Failed to get head timestamp");
3996

3997
        // Verify the timestamp is as expected (2024-01-15 09:30:00)
3998
        assert_eq!(timestamp.year(), 2024);
3999
        assert_eq!(timestamp.month() as u8, 1);
4000
        assert_eq!(timestamp.day(), 15);
4001
        assert_eq!(timestamp.hour(), 9);
4002
        assert_eq!(timestamp.minute(), 30);
4003

4004
        let requests = gateway.requests();
4005
        assert!(requests[0].starts_with("87\0"), "Request should be RequestHeadTimestamp");
4006
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4007
        assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4008
    }
4009

4010
    #[test]
4011
    fn test_historical_data() {
4012
        use crate::client::common::tests::setup_historical_data;
4013
        use crate::contracts::Contract;
4014
        use crate::market_data::historical::{BarSize, Duration, WhatToShow};
4015
        use time::macros::datetime;
4016

4017
        let gateway = setup_historical_data();
4018
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4019

4020
        let contract = Contract::stock("AAPL").build();
4021
        let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4022
        let duration = Duration::days(1);
4023
        let bar_size = BarSize::Min5;
4024
        let what_to_show = WhatToShow::Trades;
4025
        let trading_hours = TradingHours::Regular;
4026

4027
        let bars = client
4028
            .historical_data(&contract, Some(end_date_time), duration, bar_size, what_to_show, trading_hours)
4029
            .expect("Failed to get historical data");
4030

4031
        // Bars are in the HistoricalData struct
4032
        assert_eq!(bars.bars.len(), 3, "Should receive 3 bars");
4033
        let bars = &bars.bars;
4034

4035
        // Verify first bar
4036
        assert_eq!(bars[0].open, 150.25);
4037
        assert_eq!(bars[0].high, 150.75);
4038
        assert_eq!(bars[0].low, 150.00);
4039
        assert_eq!(bars[0].close, 150.50);
4040
        assert_eq!(bars[0].volume, 1000.0);
4041
        assert_eq!(bars[0].wap, 150.40);
4042
        assert_eq!(bars[0].count, 25);
4043

4044
        // Verify second bar
4045
        assert_eq!(bars[1].open, 150.50);
4046
        assert_eq!(bars[1].high, 151.00);
4047
        assert_eq!(bars[1].low, 150.40);
4048
        assert_eq!(bars[1].close, 150.90);
4049
        assert_eq!(bars[1].volume, 1200.0);
4050

4051
        // Verify third bar
4052
        assert_eq!(bars[2].open, 150.90);
4053
        assert_eq!(bars[2].high, 151.25);
4054
        assert_eq!(bars[2].low, 150.85);
4055
        assert_eq!(bars[2].close, 151.20);
4056

4057
        let requests = gateway.requests();
4058
        assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4059
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4060
    }
4061

4062
    #[test]
4063
    fn test_historical_schedules() {
4064
        use crate::client::common::tests::setup_historical_schedules;
4065
        use crate::contracts::Contract;
4066
        use crate::market_data::historical::Duration;
4067
        use time::macros::datetime;
4068

4069
        let gateway = setup_historical_schedules();
4070
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4071

4072
        let contract = Contract::stock("AAPL").build();
4073
        let duration = Duration::days(1);
4074
        let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4075

4076
        let schedule = client
4077
            .historical_schedules(&contract, end_date_time, duration)
4078
            .expect("Failed to get historical schedule");
4079

4080
        // Schedule has start and end as OffsetDateTime, not strings
4081
        assert_eq!(schedule.time_zone, "US/Eastern");
4082
        assert!(!schedule.sessions.is_empty(), "Should have at least one session");
4083

4084
        let requests = gateway.requests();
4085
        assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4086
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4087
        assert!(requests[0].contains("2\0"), "Request should contain formatDate=2 for schedule");
4088
    }
4089

4090
    #[test]
4091
    fn test_historical_ticks_bid_ask() {
4092
        use crate::client::common::tests::setup_historical_ticks_bid_ask;
4093
        use crate::contracts::Contract;
4094
        use time::macros::datetime;
4095

4096
        let gateway = setup_historical_ticks_bid_ask();
4097
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4098

4099
        let contract = Contract::stock("AAPL").build();
4100
        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4101
        let number_of_ticks = 100;
4102
        let trading_hours = TradingHours::Regular;
4103

4104
        let ticks = client
4105
            .historical_ticks_bid_ask(&contract, Some(start_date_time), None, number_of_ticks, trading_hours, false)
4106
            .expect("Failed to get historical ticks bid/ask");
4107

4108
        // Collect ticks from the subscription
4109
        let ticks: Vec<_> = ticks.into_iter().collect();
4110

4111
        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4112

4113
        // Verify first tick
4114
        assert_eq!(ticks[0].price_bid, 150.25);
4115
        assert_eq!(ticks[0].price_ask, 150.50);
4116
        assert_eq!(ticks[0].size_bid, 100);
4117
        assert_eq!(ticks[0].size_ask, 200);
4118

4119
        // Verify second tick
4120
        assert_eq!(ticks[1].price_bid, 150.30);
4121
        assert_eq!(ticks[1].price_ask, 150.55);
4122
        assert_eq!(ticks[1].size_bid, 150);
4123
        assert_eq!(ticks[1].size_ask, 250);
4124

4125
        // Verify third tick
4126
        assert_eq!(ticks[2].price_bid, 150.35);
4127
        assert_eq!(ticks[2].price_ask, 150.60);
4128

4129
        let requests = gateway.requests();
4130
        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4131
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4132
        assert!(requests[0].contains("BID_ASK\0"), "Request should contain BID_ASK");
4133
    }
4134

4135
    #[test]
4136
    fn test_historical_ticks_mid_point() {
4137
        use crate::client::common::tests::setup_historical_ticks_mid_point;
4138
        use crate::contracts::Contract;
4139
        use time::macros::datetime;
4140

4141
        let gateway = setup_historical_ticks_mid_point();
4142
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4143

4144
        let contract = Contract::stock("AAPL").build();
4145
        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4146
        let number_of_ticks = 100;
4147
        let trading_hours = TradingHours::Regular;
4148

4149
        let ticks = client
4150
            .historical_ticks_mid_point(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4151
            .expect("Failed to get historical ticks midpoint");
4152

4153
        // Collect ticks from the subscription
4154
        let ticks: Vec<_> = ticks.into_iter().collect();
4155

4156
        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4157

4158
        // Verify ticks
4159
        assert_eq!(ticks[0].price, 150.375);
4160
        assert_eq!(ticks[0].size, 0);
4161
        assert_eq!(ticks[1].price, 150.425);
4162
        assert_eq!(ticks[1].size, 0);
4163
        assert_eq!(ticks[2].price, 150.475);
4164
        assert_eq!(ticks[2].size, 0);
4165

4166
        let requests = gateway.requests();
4167
        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4168
        assert!(requests[0].contains("MIDPOINT\0"), "Request should contain MIDPOINT");
4169
    }
4170

4171
    #[test]
4172
    fn test_historical_ticks_trade() {
4173
        use crate::client::common::tests::setup_historical_ticks_trade;
4174
        use crate::contracts::Contract;
4175
        use time::macros::datetime;
4176

4177
        let gateway = setup_historical_ticks_trade();
4178
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4179

4180
        let contract = Contract::stock("AAPL").build();
4181
        let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4182
        let number_of_ticks = 100;
4183
        let trading_hours = TradingHours::Regular;
4184

4185
        let ticks = client
4186
            .historical_ticks_trade(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4187
            .expect("Failed to get historical ticks trade");
4188

4189
        // Collect ticks from the subscription
4190
        let ticks: Vec<_> = ticks.into_iter().collect();
4191

4192
        assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4193

4194
        // Verify ticks
4195
        assert_eq!(ticks[0].price, 150.50);
4196
        assert_eq!(ticks[0].size, 100);
4197
        assert_eq!(ticks[0].exchange, "NASDAQ");
4198
        assert_eq!(ticks[0].special_conditions, "T");
4199

4200
        assert_eq!(ticks[1].price, 150.55);
4201
        assert_eq!(ticks[1].size, 200);
4202
        assert_eq!(ticks[1].exchange, "NYSE");
4203

4204
        assert_eq!(ticks[2].price, 150.60);
4205
        assert_eq!(ticks[2].size, 150);
4206

4207
        let requests = gateway.requests();
4208
        assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4209
        assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4210
    }
4211

4212
    #[test]
4213
    fn test_histogram_data() {
4214
        use crate::client::common::tests::setup_histogram_data;
4215
        use crate::contracts::Contract;
4216
        use crate::market_data::historical::BarSize;
4217

4218
        let gateway = setup_histogram_data();
4219
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4220

4221
        let contract = Contract::stock("AAPL").build();
4222
        let trading_hours = TradingHours::Regular;
4223
        let period = BarSize::Day;
4224

4225
        let entries = client
4226
            .histogram_data(&contract, trading_hours, period)
4227
            .expect("Failed to get histogram data");
4228

4229
        assert_eq!(entries.len(), 3, "Should receive 3 entries");
4230

4231
        // Verify entries
4232
        assert_eq!(entries[0].price, 150.00);
4233
        assert_eq!(entries[0].size, 1000);
4234

4235
        assert_eq!(entries[1].price, 150.50);
4236
        assert_eq!(entries[1].size, 1500);
4237

4238
        assert_eq!(entries[2].price, 151.00);
4239
        assert_eq!(entries[2].size, 800);
4240

4241
        let requests = gateway.requests();
4242
        assert!(requests[0].starts_with("88\0"), "Request should be RequestHistogramData");
4243
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4244
    }
4245

4246
    // === News Tests ===
4247

4248
    #[test]
4249
    fn test_news_providers() {
4250
        use crate::client::common::tests::setup_news_providers;
4251

4252
        let _ = env_logger::try_init();
4253

4254
        let gateway = setup_news_providers();
4255
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4256

4257
        // Request news providers
4258
        let providers = client.news_providers().expect("Failed to get news providers");
4259

4260
        // Verify we received 3 providers
4261
        assert_eq!(providers.len(), 3, "Should receive 3 news providers");
4262

4263
        // Verify provider details
4264
        assert_eq!(providers[0].code, "BRFG");
4265
        assert_eq!(providers[0].name, "Briefing.com General Market Columns");
4266

4267
        assert_eq!(providers[1].code, "BRFUPDN");
4268
        assert_eq!(providers[1].name, "Briefing.com Analyst Actions");
4269

4270
        assert_eq!(providers[2].code, "DJ-RT");
4271
        assert_eq!(providers[2].name, "Dow Jones Real-Time News");
4272

4273
        // Verify the request was sent correctly
4274
        let requests = gateway.requests();
4275
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
4276
        assert_eq!(requests[0], "85\0", "Request should be RequestNewsProviders");
4277
    }
4278

4279
    #[test]
4280
    fn test_news_bulletins() {
4281
        use crate::client::common::tests::setup_news_bulletins;
4282

4283
        let _ = env_logger::try_init();
4284

4285
        let gateway = setup_news_bulletins();
4286
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4287

4288
        // Request news bulletins with all_messages=true
4289
        let subscription = client.news_bulletins(true).expect("Failed to get news bulletins");
4290

4291
        // Collect news bulletins
4292
        let mut bulletins = Vec::new();
4293
        for bulletin in subscription {
4294
            bulletins.push(bulletin);
4295
            if bulletins.len() >= 2 {
4296
                break; // We expect 2 bulletins
4297
            }
4298
        }
4299

4300
        // Verify we received 2 bulletins
4301
        assert_eq!(bulletins.len(), 2, "Should receive 2 news bulletins");
4302

4303
        // Verify bulletin details
4304
        assert_eq!(bulletins[0].message_id, 123);
4305
        assert_eq!(bulletins[0].message_type, 1);
4306
        assert_eq!(bulletins[0].message, "Important market announcement");
4307
        assert_eq!(bulletins[0].exchange, "NYSE");
4308

4309
        assert_eq!(bulletins[1].message_id, 124);
4310
        assert_eq!(bulletins[1].message_type, 2);
4311
        assert_eq!(bulletins[1].message, "Trading halt on symbol XYZ");
4312
        assert_eq!(bulletins[1].exchange, "NASDAQ");
4313

4314
        // Verify the request was sent correctly
4315
        let requests = gateway.requests();
4316
        assert!(
4317
            requests[0].starts_with("12\01\01\0"),
4318
            "Request should be RequestNewsBulletins with version 1 and all_messages=true"
4319
        );
4320
    }
4321

4322
    #[test]
4323
    fn test_historical_news() {
4324
        use crate::client::common::tests::setup_historical_news;
4325
        use time::macros::datetime;
4326

4327
        let _ = env_logger::try_init();
4328

4329
        let gateway = setup_historical_news();
4330
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4331

4332
        // Request historical news
4333
        let start_time = datetime!(2024-01-15 14:00:00 UTC);
4334
        let end_time = datetime!(2024-01-15 15:00:00 UTC);
4335
        let subscription = client
4336
            .historical_news(
4337
                1234,               // contract_id
4338
                &["DJ-RT", "BRFG"], // provider_codes
4339
                start_time,
4340
                end_time,
4341
                10, // total_results
4342
            )
4343
            .expect("Failed to get historical news");
4344

4345
        // Collect news articles
4346
        let mut articles = Vec::new();
4347
        for article in subscription {
4348
            articles.push(article);
4349
            if articles.len() >= 2 {
4350
                break; // We expect 2 articles
4351
            }
4352
        }
4353

4354
        // Verify we received 2 articles
4355
        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4356

4357
        // Verify article details
4358
        assert_eq!(articles[0].provider_code, "DJ-RT");
4359
        assert_eq!(articles[0].article_id, "DJ001234");
4360
        assert_eq!(articles[0].headline, "Market hits new highs amid positive earnings");
4361

4362
        assert_eq!(articles[1].provider_code, "BRFG");
4363
        assert_eq!(articles[1].article_id, "BRF5678");
4364
        assert_eq!(articles[1].headline, "Federal Reserve announces policy decision");
4365

4366
        // Verify the request was sent correctly
4367
        let requests = gateway.requests();
4368
        assert!(requests[0].starts_with("86\0"), "Request should be RequestHistoricalNews");
4369
        assert!(requests[0].contains("1234\0"), "Request should contain contract_id 1234");
4370
        assert!(requests[0].contains("DJ-RT+BRFG\0"), "Request should contain provider codes");
4371
    }
4372

4373
    #[test]
4374
    fn test_news_article() {
4375
        use crate::client::common::tests::setup_news_article;
4376

4377
        let _ = env_logger::try_init();
4378

4379
        let gateway = setup_news_article();
4380
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4381

4382
        // Request news article
4383
        let article = client
4384
            .news_article(
4385
                "DJ-RT",    // provider_code
4386
                "DJ001234", // article_id
4387
            )
4388
            .expect("Failed to get news article");
4389

4390
        // Verify article details
4391
        assert_eq!(article.article_type, crate::news::ArticleType::Text);
4392
        assert_eq!(
4393
            article.article_text,
4394
            "This is the full text of the news article. It contains detailed information about the market event described in the headline."
4395
        );
4396

4397
        // Verify the request was sent correctly
4398
        let requests = gateway.requests();
4399
        assert!(requests[0].starts_with("84\0"), "Request should be RequestNewsArticle");
4400
        assert!(requests[0].contains("DJ-RT\0"), "Request should contain provider code");
4401
        assert!(requests[0].contains("DJ001234\0"), "Request should contain article ID");
4402
    }
4403

4404
    #[test]
4405
    fn test_scanner_parameters() {
4406
        use crate::client::common::tests::setup_scanner_parameters;
4407

4408
        let _ = env_logger::try_init();
4409

4410
        let gateway = setup_scanner_parameters();
4411
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4412

4413
        // Request scanner parameters
4414
        let xml = client.scanner_parameters().expect("Failed to get scanner parameters");
4415

4416
        // Verify we received XML content
4417
        assert!(xml.contains("<ScanParameterResponse>"), "Should contain ScanParameterResponse");
4418
        assert!(xml.contains("<Instrument>STK</Instrument>"), "Should contain STK instrument");
4419
        assert!(xml.contains("<Instrument>OPT</Instrument>"), "Should contain OPT instrument");
4420
        assert!(xml.contains("<Location>US</Location>"), "Should contain US location");
4421
        assert!(
4422
            xml.contains("<ScanType>TOP_PERC_GAIN</ScanType>"),
4423
            "Should contain TOP_PERC_GAIN scan type"
4424
        );
4425

4426
        // Verify the request was sent correctly
4427
        let requests = gateway.requests();
4428
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
4429
        assert_eq!(requests[0], "24\01\0", "Request should be RequestScannerParameters with version 1");
4430
    }
4431

4432
    #[test]
4433
    fn test_scanner_subscription() {
4434
        use crate::client::common::tests::setup_scanner_subscription;
4435
        use crate::scanner::ScannerSubscription;
4436

4437
        let _ = env_logger::try_init();
4438

4439
        let gateway = setup_scanner_subscription();
4440
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4441

4442
        // Create scanner subscription parameters
4443
        let scanner_subscription = ScannerSubscription {
4444
            instrument: Some("STK".to_string()),
4445
            location_code: Some("STK.US.MAJOR".to_string()),
4446
            scan_code: Some("TOP_PERC_GAIN".to_string()),
4447
            number_of_rows: 10,
4448
            ..Default::default()
4449
        };
4450

4451
        // Request scanner subscription
4452
        let subscription = client
4453
            .scanner_subscription(&scanner_subscription, &vec![])
4454
            .expect("Failed to get scanner subscription");
4455

4456
        // Collect scanner data - subscription yields Vec<ScannerData>, not individual items
4457
        let scan_data_vecs: Vec<_> = subscription.into_iter().take(1).collect();
4458
        assert_eq!(scan_data_vecs.len(), 1, "Should receive 1 batch of scan data");
4459

4460
        let scan_data = &scan_data_vecs[0];
4461

4462
        // Verify we received 2 scan items
4463
        assert_eq!(scan_data.len(), 2, "Should receive 2 scan data items");
4464

4465
        // Verify scan data details
4466
        assert_eq!(scan_data[0].rank, 1);
4467
        assert_eq!(scan_data[0].contract_details.contract.contract_id, 1234);
4468
        assert_eq!(scan_data[0].contract_details.contract.symbol, Symbol::from("AAPL"));
4469

4470
        assert_eq!(scan_data[1].rank, 2);
4471
        assert_eq!(scan_data[1].contract_details.contract.contract_id, 5678);
4472
        assert_eq!(scan_data[1].contract_details.contract.symbol, Symbol::from("GOOGL"));
4473

4474
        // Verify the request was sent correctly
4475
        let requests = gateway.requests();
4476
        assert!(requests[0].starts_with("22\0"), "Request should be RequestScannerSubscription");
4477
    }
4478

4479
    #[test]
4480
    fn test_wsh_metadata() {
4481
        use crate::client::common::tests::setup_wsh_metadata;
4482

4483
        let _ = env_logger::try_init();
4484

4485
        let gateway = setup_wsh_metadata();
4486
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4487

4488
        // Request WSH metadata
4489
        let metadata = client.wsh_metadata().expect("Failed to get WSH metadata");
4490

4491
        // Verify metadata
4492
        assert_eq!(metadata.data_json, "{\"dataJson\":\"sample_metadata\"}");
4493

4494
        // Verify the request was sent correctly
4495
        let requests = gateway.requests();
4496
        assert!(requests[0].starts_with("100\0"), "Request should be RequestWshMetaData");
4497
    }
4498

4499
    #[test]
4500
    fn test_wsh_event_data() {
4501
        use crate::client::common::tests::setup_wsh_event_data;
4502

4503
        let _ = env_logger::try_init();
4504

4505
        let gateway = setup_wsh_event_data();
4506
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4507

4508
        // Request WSH event data by contract_id - returns a single WshEventData
4509
        let event_data = client
4510
            .wsh_event_data_by_contract(1234, None, None, None, None)
4511
            .expect("Failed to get WSH event data");
4512

4513
        // Verify we received the event data (only the first message is processed)
4514
        assert_eq!(event_data.data_json, "{\"dataJson\":\"event1\"}");
4515

4516
        // Verify the request was sent correctly
4517
        let requests = gateway.requests();
4518
        assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4519
    }
4520

4521
    #[test]
4522
    fn test_contract_news() {
4523
        use crate::client::common::tests::setup_contract_news;
4524
        use crate::contracts::Contract;
4525

4526
        let _ = env_logger::try_init();
4527

4528
        let gateway = setup_contract_news();
4529
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4530

4531
        // Create a contract for the request
4532
        let contract = Contract::stock("AAPL").build();
4533
        let provider_codes = &["DJ-RT", "BRFG"];
4534

4535
        // Request contract news
4536
        let subscription = client.contract_news(&contract, provider_codes).expect("Failed to get contract news");
4537

4538
        // Collect news articles
4539
        let mut articles = Vec::new();
4540
        for article in subscription {
4541
            articles.push(article);
4542
            if articles.len() >= 2 {
4543
                break; // We expect 2 articles
4544
            }
4545
        }
4546

4547
        // Verify we received 2 articles
4548
        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4549

4550
        // Verify article details
4551
        assert_eq!(articles[0].provider_code, "DJ-RT");
4552
        assert_eq!(articles[0].article_id, "DJ001234");
4553
        assert_eq!(articles[0].headline, "Stock rises on earnings beat");
4554
        assert_eq!(articles[0].extra_data, "extraData1");
4555

4556
        assert_eq!(articles[1].provider_code, "BRFG");
4557
        assert_eq!(articles[1].article_id, "BRF5678");
4558
        assert_eq!(articles[1].headline, "Company announces expansion");
4559
        assert_eq!(articles[1].extra_data, "extraData2");
4560

4561
        // Verify the request was sent correctly
4562
        let requests = gateway.requests();
4563
        assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4564
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4565
        assert!(
4566
            requests[0].contains("mdoff,292:DJ-RT,292:BRFG\0"),
4567
            "Request should contain news generic ticks"
4568
        );
4569
    }
4570

4571
    #[test]
4572
    fn test_broad_tape_news() {
4573
        use crate::client::common::tests::setup_broad_tape_news;
4574

4575
        let _ = env_logger::try_init();
4576

4577
        let gateway = setup_broad_tape_news();
4578
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4579

4580
        // Request broad tape news
4581
        let subscription = client.broad_tape_news("BRFG").expect("Failed to get broad tape news");
4582

4583
        // Collect news articles
4584
        let mut articles = Vec::new();
4585
        for article in subscription {
4586
            articles.push(article);
4587
            if articles.len() >= 2 {
4588
                break; // We expect 2 articles
4589
            }
4590
        }
4591

4592
        // Verify we received 2 articles
4593
        assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4594

4595
        // Verify article details
4596
        assert_eq!(articles[0].provider_code, "BRFG");
4597
        assert_eq!(articles[0].article_id, "BRF001");
4598
        assert_eq!(articles[0].headline, "Market update: Tech sector rallies");
4599
        assert_eq!(articles[0].extra_data, "extraData1");
4600

4601
        assert_eq!(articles[1].provider_code, "BRFG");
4602
        assert_eq!(articles[1].article_id, "BRF002");
4603
        assert_eq!(articles[1].headline, "Fed minutes released");
4604
        assert_eq!(articles[1].extra_data, "extraData2");
4605

4606
        // Verify the request was sent correctly
4607
        let requests = gateway.requests();
4608
        assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4609

4610
        // Debug: print the actual request to understand the format
4611
        if !requests[0].contains("BRFG") || !requests[0].contains("NEWS") {
4612
            eprintln!("Actual request: {:?}", requests[0]);
4613
        }
4614

4615
        // Check for the contract components (symbol, sec_type, exchange)
4616
        assert!(requests[0].contains("BRFG:BRFG_ALL"), "Request should contain BRFG:BRFG_ALL symbol");
4617
        assert!(requests[0].contains("NEWS"), "Request should contain NEWS security type");
4618
        assert!(requests[0].contains("mdoff,292\0"), "Request should contain news generic ticks");
4619
    }
4620

4621
    #[test]
4622
    fn test_wsh_event_data_by_filter() {
4623
        use crate::client::common::tests::setup_wsh_event_data_by_filter;
4624

4625
        let _ = env_logger::try_init();
4626

4627
        let gateway = setup_wsh_event_data_by_filter();
4628
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
4629

4630
        // Request WSH event data by filter (no limit param to avoid version check)
4631
        let filter = "{\"watchlist\":[\"AAPL\"],\"country\":\"ALL\"}";
4632
        let subscription = client
4633
            .wsh_event_data_by_filter(filter, None, None)
4634
            .expect("Failed to get WSH event data by filter");
4635

4636
        // Collect events
4637
        let events: Vec<_> = subscription.into_iter().take(2).collect();
4638

4639
        // Verify we received 2 events
4640
        assert_eq!(events.len(), 2, "Should receive 2 WSH events");
4641
        assert_eq!(events[0].data_json, "{\"dataJson\":\"filtered_event1\"}");
4642
        assert_eq!(events[1].data_json, "{\"dataJson\":\"filtered_event2\"}");
4643

4644
        // Verify the request was sent correctly
4645
        let requests = gateway.requests();
4646
        assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4647
        assert!(requests[0].contains(filter), "Request should contain the filter");
4648
    }
4649
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc