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

wboayue / rust-ibapi / 17340791942

30 Aug 2025 07:01AM UTC coverage: 77.112% (-0.3%) from 77.446%
17340791942

push

github

web-flow
feat: Add Client.is_connected() method and async reconnection support (#301)

This PR addresses issue #300 by implementing Client.is_connected() to allow monitoring of connection status in long-running applications. Additionally, it adds full reconnection support to the async transport layer, bringing it to feature parity with the sync implementation.

19 of 68 new or added lines in 6 files covered. (27.94%)

1 existing line in 1 file now uncovered.

5377 of 6973 relevant lines covered (77.11%)

62.76 hits per line

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

81.44
/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::historical::{self, HistogramEntry};
22
use crate::market_data::realtime::{self, Bar, BarSize, DepthMarketDataDescription, MarketDepths, MidPoint, TickTypes, WhatToShow};
23
use crate::market_data::MarketDataType;
24
use crate::messages::{OutgoingMessages, RequestMessage};
25
use crate::news::NewsArticle;
26
use crate::orders::{CancelOrder, Executions, ExerciseOptions, Order, OrderUpdate, Orders, PlaceOrder};
27
use crate::scanner::ScannerData;
28
use crate::subscriptions::Subscription;
29
use crate::transport::{InternalSubscription, MessageBus, TcpMessageBus, TcpSocket};
30
use crate::wsh::AutoFill;
31
use crate::{accounts, contracts, market_data, news, orders, scanner, wsh};
32

33
use super::id_generator::ClientIdManager;
34

35
// Client
36

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

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

51
impl Client {
52
    /// Establishes connection to TWS or Gateway
53
    ///
54
    /// Connects to server using the given connection string
55
    ///
56
    /// # Arguments
57
    /// * `address`   - address of server. e.g. 127.0.0.1:4002
58
    /// * `client_id` - id of client. e.g. 100
59
    ///
60
    /// # Examples
61
    ///
62
    /// ```no_run
63
    /// use ibapi::Client;
64
    ///
65
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
66
    ///
67
    /// println!("server_version: {}", client.server_version());
68
    /// println!("connection_time: {:?}", client.connection_time());
69
    /// println!("next_order_id: {}", client.next_order_id());
70
    /// ```
71
    pub fn connect(address: &str, client_id: i32) -> Result<Client, Error> {
37✔
72
        let stream = TcpStream::connect(address)?;
111✔
73
        let socket = TcpSocket::new(stream, address)?;
37✔
74

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

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

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

83
        Client::new(connection_metadata, message_bus)
37✔
84
    }
85

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

96
        Ok(client)
38✔
97
    }
98

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

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

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

116
    /// Gets the next valid order ID from the TWS server.
117
    ///
118
    /// 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.
119
    /// This can be for ensuring that order IDs are unique across multiple clients.
120
    ///
121
    /// 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.
122
    ///
123
    /// # Examples
124
    ///
125
    /// ```no_run
126
    /// use ibapi::Client;
127
    ///
128
    /// // Connect to the TWS server at the given address with client ID.
129
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
130
    ///
131
    /// // Request the next valid order ID from the server.
132
    /// let next_valid_order_id = client.next_valid_order_id().expect("request failed");
133
    /// println!("next_valid_order_id: {next_valid_order_id}");
134
    /// ```
135
    pub fn next_valid_order_id(&self) -> Result<i32, Error> {
1✔
136
        orders::next_valid_order_id(self)
2✔
137
    }
138

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

144
    /// Returns the version of the TWS API server to which the client is connected.
145
    /// This version is determined during the initial connection handshake.
146
    ///
147
    /// # Examples
148
    ///
149
    /// ```no_run
150
    /// use ibapi::Client;
151
    ///
152
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
153
    /// let server_version = client.server_version();
154
    /// println!("Connected to TWS server version: {server_version:?}");
155
    /// ```
156
    pub fn server_version(&self) -> i32 {
126✔
157
        self.server_version
126✔
158
    }
159

160
    /// The time of the server when the client connected
161
    pub fn connection_time(&self) -> Option<OffsetDateTime> {
×
162
        self.connection_time
×
163
    }
164

165
    /// Returns true if the client is currently connected to TWS/IB Gateway.
166
    ///
167
    /// This method checks if the underlying connection to TWS or IB Gateway is active.
168
    /// Returns false if the connection has been lost, shut down, or reset.
169
    ///
170
    /// # Examples
171
    ///
172
    /// ```no_run
173
    /// use ibapi::Client;
174
    ///
175
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
176
    ///
177
    /// if client.is_connected() {
178
    ///     println!("Client is connected to TWS/Gateway");
179
    /// } else {
180
    ///     println!("Client is not connected");
181
    /// }
182
    /// ```
NEW
183
    pub fn is_connected(&self) -> bool {
×
NEW
184
        self.message_bus.is_connected()
×
185
    }
186

187
    // === Accounts ===
188

189
    /// 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.
190
    ///
191
    /// # Examples
192
    ///
193
    /// ```no_run
194
    /// use ibapi::Client;
195
    ///
196
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
197
    /// let server_time = client.server_time().expect("error requesting server time");
198
    /// println!("server time: {server_time:?}");
199
    /// ```
200
    pub fn server_time(&self) -> Result<OffsetDateTime, Error> {
10✔
201
        accounts::server_time(self)
20✔
202
    }
203

204
    /// Subscribes to [PositionUpdate]s for all accessible accounts.
205
    /// All positions sent initially, and then only updates as positions change.
206
    ///
207
    /// # Examples
208
    ///
209
    /// ```no_run
210
    /// use ibapi::Client;
211
    /// use ibapi::accounts::PositionUpdate;
212
    ///
213
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
214
    /// let subscription = client.positions().expect("error requesting positions");
215
    /// for position_response in subscription.iter() {
216
    ///     match position_response {
217
    ///         PositionUpdate::Position(position) => println!("{position:?}"),
218
    ///         PositionUpdate::PositionEnd => println!("initial set of positions received"),
219
    ///     }
220
    /// }
221
    /// ```
222
    pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
5✔
223
        accounts::positions(self)
10✔
224
    }
225

226
    /// Subscribes to [PositionUpdateMulti] updates for account and/or model.
227
    /// Initially all positions are returned, and then updates are returned for any position changes in real time.
228
    ///
229
    /// # Arguments
230
    /// * `account`    - If an account Id is provided, only the account’s positions belonging to the specified model will be delivered.
231
    /// * `model_code` - The code of the model’s positions we are interested in.
232
    ///
233
    /// # Examples
234
    ///
235
    /// ```no_run
236
    /// use ibapi::Client;
237
    ///
238
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
239
    ///
240
    /// use ibapi::accounts::types::AccountId;
241
    ///
242
    /// let account = AccountId("U1234567".to_string());
243
    /// let subscription = client.positions_multi(Some(&account), None).expect("error requesting positions by model");
244
    /// for position in subscription.iter() {
245
    ///     println!("{position:?}")
246
    /// }
247
    /// ```
248
    pub fn positions_multi(&self, account: Option<&AccountId>, model_code: Option<&ModelCode>) -> Result<Subscription<PositionUpdateMulti>, Error> {
9✔
249
        accounts::positions_multi(self, account, model_code)
36✔
250
    }
251

252
    /// Creates subscription for real time daily PnL and unrealized PnL updates.
253
    ///
254
    /// # Arguments
255
    /// * `account`    - account for which to receive PnL updates
256
    /// * `model_code` - specify to request PnL updates for a specific model
257
    ///
258
    /// # Examples
259
    ///
260
    /// ```no_run
261
    /// use ibapi::Client;
262
    ///
263
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
264
    /// use ibapi::accounts::types::AccountId;
265
    ///
266
    /// let account = AccountId("account id".to_string());
267
    /// let subscription = client.pnl(&account, None).expect("error requesting pnl");
268
    /// for pnl in subscription.iter() {
269
    ///     println!("{pnl:?}")
270
    /// }
271
    /// ```
272
    pub fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
12✔
273
        accounts::pnl(self, account, model_code)
48✔
274
    }
275

276
    /// Requests real time updates for daily PnL of individual positions.
277
    ///
278
    /// # Arguments
279
    /// * `account`     - Account in which position exists
280
    /// * `contract_id` - Contract ID of contract to receive daily PnL updates for. Note: does not return response if invalid conId is entered.
281
    /// * `model_code`  - Model in which position exists
282
    ///
283
    /// # Examples
284
    ///
285
    /// ```no_run
286
    /// use ibapi::Client;
287
    ///
288
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
289
    ///
290
    /// use ibapi::accounts::types::{AccountId, ContractId};
291
    ///
292
    /// let account = AccountId("<account id>".to_string());
293
    /// let contract_id = ContractId(1001);
294
    ///
295
    /// let subscription = client.pnl_single(&account, contract_id, None).expect("error requesting pnl");
296
    /// for pnl in &subscription {
297
    ///     println!("{pnl:?}")
298
    /// }
299
    /// ```
300
    pub fn pnl_single(&self, account: &AccountId, contract_id: ContractId, model_code: Option<&ModelCode>) -> Result<Subscription<PnLSingle>, Error> {
10✔
301
        accounts::pnl_single(self, account, contract_id, model_code)
50✔
302
    }
303

304
    /// 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.
305
    ///
306
    /// # Arguments
307
    /// * `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.
308
    /// * `tags`  - List of the desired tags.
309
    ///
310
    /// # Examples
311
    ///
312
    /// ```no_run
313
    /// use ibapi::Client;
314
    /// use ibapi::accounts::AccountSummaryTags;
315
    /// use ibapi::accounts::types::AccountGroup;
316
    ///
317
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
318
    ///
319
    /// let group = AccountGroup("All".to_string());
320
    ///
321
    /// let subscription = client.account_summary(&group, &[AccountSummaryTags::ACCOUNT_TYPE]).expect("error requesting account summary");
322
    /// for summary in &subscription {
323
    ///     println!("{summary:?}")
324
    /// }
325
    /// ```
326
    pub fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
11✔
327
        accounts::account_summary(self, group, tags)
44✔
328
    }
329

330
    /// Subscribes to a specific account’s information and portfolio.
331
    ///
332
    /// 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.
333
    ///
334
    /// # Arguments
335
    /// * `account` - The account id (i.e. U1234567) for which the information is requested.
336
    ///
337
    /// # Examples
338
    ///
339
    /// ```no_run
340
    /// use ibapi::Client;
341
    /// use ibapi::accounts::AccountUpdate;
342
    ///
343
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
344
    ///
345
    /// use ibapi::accounts::types::AccountId;
346
    ///
347
    /// let account = AccountId("U1234567".to_string());
348
    ///
349
    /// let subscription = client.account_updates(&account).expect("error requesting account updates");
350
    /// for update in &subscription {
351
    ///     println!("{update:?}");
352
    ///
353
    ///     // stop after full initial update
354
    ///     if let AccountUpdate::End = update {
355
    ///         subscription.cancel();
356
    ///     }
357
    /// }
358
    /// ```
359
    pub fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
3✔
360
        accounts::account_updates(self, account)
9✔
361
    }
362

363
    /// Requests account updates for account and/or model.
364
    ///
365
    /// 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.
366
    ///
367
    /// # Arguments
368
    /// * `account`        - Account values can be requested for a particular account.
369
    /// * `model_code`     - Account values can also be requested for a model.
370
    ///
371
    /// # Examples
372
    ///
373
    /// ```no_run
374
    /// use ibapi::Client;
375
    /// use ibapi::accounts::AccountUpdateMulti;
376
    ///
377
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
378
    ///
379
    /// use ibapi::accounts::types::AccountId;
380
    ///
381
    /// let account = AccountId("U1234567".to_string());
382
    ///
383
    /// let subscription = client.account_updates_multi(Some(&account), None).expect("error requesting account updates multi");
384
    /// for update in &subscription {
385
    ///     println!("{update:?}");
386
    ///
387
    ///     // stop after full initial update
388
    ///     if let AccountUpdateMulti::End = update {
389
    ///         subscription.cancel();
390
    ///     }
391
    /// }
392
    /// ```
393
    pub fn account_updates_multi(
3✔
394
        &self,
395
        account: Option<&AccountId>,
396
        model_code: Option<&ModelCode>,
397
    ) -> Result<Subscription<AccountUpdateMulti>, Error> {
398
        accounts::account_updates_multi(self, account, model_code)
12✔
399
    }
400

401
    /// Requests the accounts to which the logged user has access to.
402
    ///
403
    /// # Examples
404
    ///
405
    /// ```no_run
406
    /// use ibapi::Client;
407
    ///
408
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
409
    ///
410
    /// let accounts = client.managed_accounts().expect("error requesting managed accounts");
411
    /// println!("managed accounts: {accounts:?}")
412
    /// ```
413
    pub fn managed_accounts(&self) -> Result<Vec<String>, Error> {
11✔
414
        accounts::managed_accounts(self)
22✔
415
    }
416

417
    // === Contracts ===
418

419
    /// Requests contract information.
420
    ///
421
    /// 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.
422
    ///
423
    /// # Arguments
424
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
425
    ///
426
    /// # Examples
427
    ///
428
    /// ```no_run
429
    /// use ibapi::Client;
430
    /// use ibapi::contracts::Contract;
431
    ///
432
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
433
    ///
434
    /// let contract = Contract::stock("TSLA");
435
    /// let results = client.contract_details(&contract).expect("request failed");
436
    /// for contract_detail in results {
437
    ///     println!("contract: {contract_detail:?}");
438
    /// }
439
    /// ```
440
    pub fn contract_details(&self, contract: &Contract) -> Result<Vec<contracts::ContractDetails>, Error> {
2✔
441
        contracts::contract_details(self, contract)
6✔
442
    }
443

444
    /// Get current [FamilyCode]s for all accessible accounts.
445
    pub fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
5✔
446
        accounts::family_codes(self)
10✔
447
    }
448

449
    /// Requests details about a given market rule
450
    ///
451
    /// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
452
    /// A list of market rule ids can be obtained by invoking [Self::contract_details()] for a particular contract.
453
    /// The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [contracts::ContractDetails].
454
    pub fn market_rule(&self, market_rule_id: i32) -> Result<contracts::MarketRule, Error> {
1✔
455
        contracts::market_rule(self, market_rule_id)
3✔
456
    }
457

458
    /// Requests matching stock symbols.
459
    ///
460
    /// # Arguments
461
    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
462
    ///
463
    /// # Examples
464
    ///
465
    /// ```no_run
466
    /// use ibapi::Client;
467
    ///
468
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
469
    ///
470
    /// let contracts = client.matching_symbols("IB").expect("request failed");
471
    /// for contract in contracts {
472
    ///     println!("contract: {contract:?}");
473
    /// }
474
    /// ```
475
    pub fn matching_symbols(&self, pattern: &str) -> Result<impl Iterator<Item = contracts::ContractDescription>, Error> {
1✔
476
        Ok(contracts::matching_symbols(self, pattern)?.into_iter())
4✔
477
    }
478

479
    /// Calculates an option’s price based on the provided volatility and its underlying’s price.
480
    ///
481
    /// # Arguments
482
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
483
    /// * `volatility`      - Hypothetical volatility as a percentage (e.g., 20.0 for 20%).
484
    /// * `underlying_price` - Hypothetical price of the underlying asset.
485
    ///
486
    /// # Examples
487
    ///
488
    /// ```no_run
489
    /// use ibapi::Client;
490
    /// use ibapi::contracts::Contract;
491
    ///
492
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
493
    ///
494
    /// let contract = Contract::option("AAPL", "20251219", 150.0, "C");
495
    /// let calculation = client.calculate_option_price(&contract, 100.0, 235.0).expect("request failed");
496
    /// println!("calculation: {calculation:?}");
497
    /// ```
498
    pub fn calculate_option_price(&self, contract: &Contract, volatility: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
2✔
499
        contracts::calculate_option_price(self, contract, volatility, underlying_price)
10✔
500
    }
501

502
    /// Calculates the implied volatility based on the hypothetical option price and underlying price.
503
    ///
504
    /// # Arguments
505
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
506
    /// * `option_price`    - Hypothetical option price.
507
    /// * `underlying_price` - Hypothetical price of the underlying asset.
508
    ///
509
    /// # Examples
510
    ///
511
    /// ```no_run
512
    /// use ibapi::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", "20230519", 150.0, "C");
518
    /// let calculation = client.calculate_implied_volatility(&contract, 25.0, 235.0).expect("request failed");
519
    /// println!("calculation: {calculation:?}");
520
    /// ```
521
    pub fn calculate_implied_volatility(&self, contract: &Contract, option_price: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
2✔
522
        contracts::calculate_implied_volatility(self, contract, option_price, underlying_price)
10✔
523
    }
524

525
    /// Requests security definition option parameters for viewing a contract’s option chain.
526
    ///
527
    /// # Arguments
528
    /// `symbol`   - Contract symbol of the underlying.
529
    /// `exchange` - The exchange on which the returned options are trading. Can be set to the empty string for all exchanges.
530
    /// `security_type` - The type of the underlying security, i.e. STK
531
    /// `contract_id`   - The contract ID of the underlying security.
532
    ///
533
    /// # Examples
534
    ///
535
    /// ```no_run
536
    /// use ibapi::{contracts::SecurityType, Client};
537
    ///
538
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
539
    ///
540
    /// let symbol = "AAPL";
541
    /// let exchange = ""; // all exchanges
542
    /// let security_type = SecurityType::Stock;
543
    /// let contract_id = 265598;
544
    ///
545
    /// let subscription = client
546
    ///     .option_chain(symbol, exchange, security_type, contract_id)
547
    ///     .expect("request option chain failed!");
548
    ///
549
    /// for option_chain in &subscription {
550
    ///     println!("{option_chain:?}")
551
    /// }
552
    /// ```
553
    pub fn option_chain(
1✔
554
        &self,
555
        symbol: &str,
556
        exchange: &str,
557
        security_type: SecurityType,
558
        contract_id: i32,
559
    ) -> Result<Subscription<contracts::OptionChain>, Error> {
560
        contracts::option_chain(self, symbol, exchange, security_type, contract_id)
6✔
561
    }
562

563
    // === Orders ===
564

565
    /// Requests all *current* open orders in associated accounts at the current moment.
566
    /// Open orders are returned once; this function does not initiate a subscription.
567
    ///
568
    /// # Examples
569
    ///
570
    /// ```no_run
571
    /// use ibapi::Client;
572
    ///
573
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
574
    ///
575
    /// let subscription = client.all_open_orders().expect("request failed");
576
    /// for order_data in &subscription {
577
    ///    println!("{order_data:?}")
578
    /// }
579
    /// ```
580
    pub fn all_open_orders(&self) -> Result<Subscription<Orders>, Error> {
2✔
581
        orders::all_open_orders(self)
4✔
582
    }
583

584
    /// Requests status updates about future orders placed from TWS. Can only be used with client ID 0.
585
    ///
586
    /// # Arguments
587
    /// * `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.
588
    ///
589
    /// # Examples
590
    ///
591
    /// ```no_run
592
    /// use ibapi::Client;
593
    ///
594
    /// let client = Client::connect("127.0.0.1:4002", 0).expect("connection failed");
595
    ///
596
    /// let subscription = client.auto_open_orders(false).expect("request failed");
597
    /// for order_data in &subscription {
598
    ///    println!("{order_data:?}")
599
    /// }
600
    /// ```
601
    pub fn auto_open_orders(&self, auto_bind: bool) -> Result<Subscription<Orders>, Error> {
2✔
602
        orders::auto_open_orders(self, auto_bind)
6✔
603
    }
604

605
    /// Cancels an active [Order] placed by the same API client ID.
606
    ///
607
    /// # Arguments
608
    /// * `order_id` - ID of the [Order] to cancel.
609
    /// * `manual_order_cancel_time` - Optional timestamp to specify the cancellation time. Use an empty string to use the current time.
610
    ///
611
    /// # Examples
612
    ///
613
    /// ```no_run
614
    /// use ibapi::Client;
615
    ///
616
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
617
    ///
618
    /// let order_id = 15;
619
    /// let subscription = client.cancel_order(order_id, "").expect("request failed");
620
    /// for result in subscription {
621
    ///    println!("{result:?}");
622
    /// }
623
    /// ```
624
    pub fn cancel_order(&self, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<CancelOrder>, Error> {
2✔
625
        orders::cancel_order(self, order_id, manual_order_cancel_time)
8✔
626
    }
627

628
    /// Requests completed [Order]s.
629
    ///
630
    /// # Arguments
631
    /// * `api_only` - request only orders placed by the API.
632
    ///
633
    /// # Examples
634
    ///
635
    /// ```no_run
636
    /// use ibapi::Client;
637
    ///
638
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
639
    ///
640
    /// let subscription = client.completed_orders(false).expect("request failed");
641
    /// for order_data in &subscription {
642
    ///    println!("{order_data:?}")
643
    /// }
644
    /// ```
645
    pub fn completed_orders(&self, api_only: bool) -> Result<Subscription<Orders>, Error> {
1✔
646
        orders::completed_orders(self, api_only)
3✔
647
    }
648

649
    /// Requests current day's (since midnight) executions matching the filter.
650
    ///
651
    /// Only the current day's executions can be retrieved.
652
    /// Along with the [orders::ExecutionData], the [orders::CommissionReport] will also be returned.
653
    /// When requesting executions, a filter can be specified to receive only a subset of them
654
    ///
655
    /// # Arguments
656
    /// * `filter` - filter criteria used to determine which execution reports are returned
657
    ///
658
    /// # Examples
659
    ///
660
    /// ```no_run
661
    /// use ibapi::Client;
662
    /// use ibapi::orders::ExecutionFilter;
663
    ///
664
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
665
    ///
666
    /// let filter = ExecutionFilter{
667
    ///    side: "BUY".to_owned(),
668
    ///    ..ExecutionFilter::default()
669
    /// };
670
    ///
671
    /// let subscription = client.executions(filter).expect("request failed");
672
    /// for execution_data in &subscription {
673
    ///    println!("{execution_data:?}")
674
    /// }
675
    /// ```
676
    pub fn executions(&self, filter: orders::ExecutionFilter) -> Result<Subscription<Executions>, Error> {
2✔
677
        orders::executions(self, filter)
6✔
678
    }
679

680
    /// Cancels all open [Order]s.
681
    ///
682
    /// # Examples
683
    ///
684
    /// ```no_run
685
    /// use ibapi::Client;
686
    ///
687
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
688
    ///
689
    /// client.global_cancel().expect("request failed");
690
    /// ```
691
    pub fn global_cancel(&self) -> Result<(), Error> {
1✔
692
        orders::global_cancel(self)
2✔
693
    }
694

695
    /// Requests all open orders places by this specific API client (identified by the API client id).
696
    /// For client ID 0, this will bind previous manual TWS orders.
697
    ///
698
    /// # Examples
699
    ///
700
    /// ```no_run
701
    /// use ibapi::Client;
702
    ///
703
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
704
    ///
705
    /// let subscription = client.open_orders().expect("request failed");
706
    /// for order_data in &subscription {
707
    ///    println!("{order_data:?}")
708
    /// }
709
    /// ```
710
    pub fn open_orders(&self) -> Result<Subscription<Orders>, Error> {
1✔
711
        orders::open_orders(self)
2✔
712
    }
713

714
    /// Places or modifies an [Order].
715
    ///
716
    /// Submits an [Order] using [Client] for the given [Contract].
717
    /// 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.
718
    ///
719
    /// # Arguments
720
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
721
    /// * `contract` - [Contract] to submit order for.
722
    /// * `order` - [Order] to submit.
723
    ///
724
    /// # Examples
725
    ///
726
    /// ```no_run
727
    /// use ibapi::Client;
728
    /// use ibapi::contracts::Contract;
729
    /// use ibapi::orders::{order_builder, Action, PlaceOrder};
730
    ///
731
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
732
    ///
733
    /// let contract = Contract::stock("MSFT");
734
    /// let order = order_builder::market_order(Action::Buy, 100.0);
735
    /// let order_id = client.next_order_id();
736
    ///
737
    /// let events = client.place_order(order_id, &contract, &order).expect("request failed");
738
    ///
739
    /// for event in &events {
740
    ///     match event {
741
    ///         PlaceOrder::OrderStatus(order_status) => {
742
    ///             println!("order status: {order_status:?}")
743
    ///         }
744
    ///         PlaceOrder::OpenOrder(open_order) => println!("open order: {open_order:?}"),
745
    ///         PlaceOrder::ExecutionData(execution) => println!("execution: {execution:?}"),
746
    ///         PlaceOrder::CommissionReport(report) => println!("commission report: {report:?}"),
747
    ///         PlaceOrder::Message(message) => println!("message: {message:?}"),
748
    ///    }
749
    /// }
750
    /// ```
751
    pub fn place_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<Subscription<PlaceOrder>, Error> {
4✔
752
        orders::place_order(self, order_id, contract, order)
20✔
753
    }
754

755
    /// Submits or modifies an [Order] without returning a subscription.
756
    ///
757
    /// This is a fire-and-forget method that submits an [Order] for the given [Contract]
758
    /// but does not return a subscription for order updates. To receive order status updates,
759
    /// fills, and commission reports, use the [`order_update_stream`](Client::order_update_stream) method
760
    /// or use [`place_order`](Client::place_order) instead which returns a subscription.
761
    ///
762
    /// # Arguments
763
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
764
    /// * `contract` - [Contract] to submit order for.
765
    /// * `order` - [Order] to submit.
766
    ///
767
    /// # Returns
768
    /// * `Ok(())` if the order was successfully sent
769
    /// * `Err(Error)` if validation failed or sending failed
770
    ///
771
    /// # Examples
772
    ///
773
    /// ```no_run
774
    /// use ibapi::Client;
775
    /// use ibapi::contracts::Contract;
776
    /// use ibapi::orders::{order_builder, Action};
777
    ///
778
    /// # fn main() -> Result<(), ibapi::Error> {
779
    ///
780
    /// let client = Client::connect("127.0.0.1:4002", 100)?;
781
    ///
782
    /// let contract = Contract::stock("MSFT");
783
    /// let order = order_builder::market_order(Action::Buy, 100.0);
784
    /// let order_id = client.next_order_id();
785
    ///
786
    /// // Submit order without waiting for confirmation
787
    /// client.submit_order(order_id, &contract, &order)?;
788
    ///
789
    /// // Monitor all order updates via the order update stream
790
    /// // This will receive updates for ALL orders, not just this one
791
    /// use ibapi::orders::OrderUpdate;
792
    /// for event in client.order_update_stream()? {
793
    ///     match event {
794
    ///         OrderUpdate::OrderStatus(status) => println!("Order Status: {status:?}"),
795
    ///         OrderUpdate::ExecutionData(exec) => println!("Execution: {exec:?}"),
796
    ///         OrderUpdate::CommissionReport(report) => println!("Commission: {report:?}"),
797
    ///         _ => {}
798
    ///     }
799
    /// }
800
    ///
801
    /// # Ok(())
802
    /// # }
803
    /// ```
804
    pub fn submit_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<(), Error> {
2✔
805
        orders::submit_order(self, order_id, contract, order)
10✔
806
    }
807

808
    /// Creates a subscription stream for receiving real-time order updates.
809
    ///
810
    /// This method establishes a stream that receives all order-related events including:
811
    /// - Order status updates (e.g., submitted, filled, cancelled)
812
    /// - Open order information
813
    /// - Execution data for trades
814
    /// - Commission reports
815
    /// - Order-related messages and notices
816
    ///
817
    /// The stream will receive updates for all orders placed through this client connection,
818
    /// including both new orders submitted after creating the stream and existing orders.
819
    ///
820
    /// # Returns
821
    ///
822
    /// Returns a `Subscription<OrderUpdate>` that yields `OrderUpdate` enum variants containing:
823
    /// - `OrderStatus`: Current status of an order (filled amount, average price, etc.)
824
    /// - `OpenOrder`: Complete order details including contract and order parameters
825
    /// - `ExecutionData`: Details about individual trade executions
826
    /// - `CommissionReport`: Commission information for executed trades
827
    /// - `Message`: Notices or error messages related to orders
828
    ///
829
    /// # Errors
830
    ///
831
    /// Returns an error if the subscription cannot be created, typically due to
832
    /// connection issues or internal errors.
833
    ///
834
    /// # Examples
835
    ///
836
    /// ```no_run
837
    /// use ibapi::Client;
838
    /// use ibapi::orders::OrderUpdate;
839
    ///
840
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
841
    ///
842
    /// // Create order update stream
843
    /// let updates = client.order_update_stream().expect("failed to create stream");
844
    ///
845
    /// // Process order updates
846
    /// for update in updates {
847
    ///     match update {
848
    ///         OrderUpdate::OrderStatus(status) => {
849
    ///             println!("Order {} status: {} - filled: {}/{}",
850
    ///                 status.order_id, status.status, status.filled, status.remaining);
851
    ///         },
852
    ///         OrderUpdate::OpenOrder(order_data) => {
853
    ///             println!("Open order {}: {} {} @ {}",
854
    ///                 order_data.order.order_id,
855
    ///                 order_data.order.action,
856
    ///                 order_data.order.total_quantity,
857
    ///                 order_data.order.limit_price.unwrap_or(0.0));
858
    ///         },
859
    ///         OrderUpdate::ExecutionData(exec) => {
860
    ///             println!("Execution: {} {} @ {} on {}",
861
    ///                 exec.execution.side,
862
    ///                 exec.execution.shares,
863
    ///                 exec.execution.price,
864
    ///                 exec.execution.exchange);
865
    ///         },
866
    ///         OrderUpdate::CommissionReport(report) => {
867
    ///             println!("Commission: ${} for execution {}",
868
    ///                 report.commission, report.execution_id);
869
    ///         },
870
    ///         OrderUpdate::Message(notice) => {
871
    ///             println!("Order message: {}", notice.message);
872
    ///         }
873
    ///     }
874
    /// }
875
    /// ```
876
    ///
877
    /// # Note
878
    ///
879
    /// This stream provides updates for all orders, not just a specific order.
880
    /// To track a specific order, filter the updates by order ID.
881
    pub fn order_update_stream(&self) -> Result<Subscription<OrderUpdate>, Error> {
4✔
882
        orders::order_update_stream(self)
8✔
883
    }
884

885
    /// Exercises an options contract.
886
    ///
887
    /// Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized.
888
    ///
889
    /// # Arguments
890
    /// * `contract`          - The option [Contract] to be exercised.
891
    /// * `exercise_action`   - Exercise option. ExerciseAction::Exercise or ExerciseAction::Lapse.
892
    /// * `exercise_quantity` - Number of contracts to be exercised.
893
    /// * `account`           - Destination account.
894
    /// * `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.
895
    /// * `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.
896
    pub fn exercise_options(
1✔
897
        &self,
898
        contract: &Contract,
899
        exercise_action: orders::ExerciseAction,
900
        exercise_quantity: i32,
901
        account: &str,
902
        ovrd: bool,
903
        manual_order_time: Option<OffsetDateTime>,
904
    ) -> Result<Subscription<ExerciseOptions>, Error> {
905
        orders::exercise_options(self, contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
8✔
906
    }
907

908
    // === Historical Market Data ===
909

910
    /// Returns the timestamp of earliest available historical data for a contract and data type.
911
    ///
912
    /// ```no_run
913
    /// use ibapi::Client;
914
    /// use ibapi::contracts::Contract;
915
    /// use ibapi::market_data::historical::{self, WhatToShow};
916
    ///
917
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
918
    ///
919
    /// let contract = Contract::stock("MSFT");
920
    /// let what_to_show = WhatToShow::Trades;
921
    /// let use_rth = true;
922
    ///
923
    /// let result = client.head_timestamp(&contract, what_to_show, use_rth).expect("head timestamp failed");
924
    ///
925
    /// print!("head_timestamp: {result:?}");
926
    /// ```
927
    pub fn head_timestamp(&self, contract: &Contract, what_to_show: historical::WhatToShow, use_rth: bool) -> Result<OffsetDateTime, Error> {
1✔
928
        historical::head_timestamp(self, contract, what_to_show, use_rth)
5✔
929
    }
930

931
    /// Requests interval of historical data ending at specified time for [Contract].
932
    ///
933
    /// # Arguments
934
    /// * `contract`     - [Contract] to retrieve [historical::HistoricalData] for.
935
    /// * `interval_end` - optional end date of interval to retrieve [historical::HistoricalData] for. If `None` current time or last trading of contract is implied.
936
    /// * `duration`     - duration of interval to retrieve [historical::HistoricalData] for.
937
    /// * `bar_size`     - [historical::BarSize] to return.
938
    /// * `what_to_show` - requested bar type: [historical::WhatToShow].
939
    /// * `use_rth`      - use regular trading hours.
940
    ///
941
    /// # Examples
942
    ///
943
    /// ```no_run
944
    /// use time::macros::datetime;
945
    ///
946
    /// use ibapi::contracts::Contract;
947
    /// use ibapi::Client;
948
    /// use ibapi::market_data::historical::{BarSize, ToDuration, WhatToShow};
949
    ///
950
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
951
    ///
952
    /// let contract = Contract::stock("TSLA");
953
    ///
954
    /// let historical_data = client
955
    ///     .historical_data(&contract, Some(datetime!(2023-04-15 0:00 UTC)), 7.days(), BarSize::Day, WhatToShow::Trades, true)
956
    ///     .expect("historical data request failed");
957
    ///
958
    /// println!("start_date: {}, end_date: {}", historical_data.start, historical_data.end);
959
    ///
960
    /// for bar in &historical_data.bars {
961
    ///     println!("{bar:?}");
962
    /// }
963
    /// ```
964
    pub fn historical_data(
6✔
965
        &self,
966
        contract: &Contract,
967
        interval_end: Option<OffsetDateTime>,
968
        duration: historical::Duration,
969
        bar_size: historical::BarSize,
970
        what_to_show: historical::WhatToShow,
971
        use_rth: bool,
972
    ) -> Result<historical::HistoricalData, Error> {
973
        historical::historical_data(self, contract, interval_end, duration, bar_size, Some(what_to_show), use_rth)
48✔
974
    }
975

976
    /// Requests [Schedule](historical::Schedule) for an interval of given duration
977
    /// ending at specified date.
978
    ///
979
    /// # Arguments
980
    /// * `contract`     - [Contract] to retrieve [Schedule](historical::Schedule) for.
981
    /// * `interval_end` - end date of interval to retrieve [Schedule](historical::Schedule) for.
982
    /// * `duration`     - duration of interval to retrieve [Schedule](historical::Schedule) for.
983
    ///
984
    /// # Examples
985
    ///
986
    /// ```no_run
987
    /// use time::macros::datetime;
988
    /// use ibapi::contracts::Contract;
989
    /// use ibapi::Client;
990
    /// use ibapi::market_data::historical::ToDuration;
991
    ///
992
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
993
    ///
994
    /// let contract = Contract::stock("GM");
995
    ///
996
    /// let historical_data = client
997
    ///     .historical_schedules(&contract, datetime!(2023-04-15 0:00 UTC), 30.days())
998
    ///     .expect("historical schedule request failed");
999
    ///
1000
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
1001
    ///
1002
    /// for session in &historical_data.sessions {
1003
    ///     println!("{session:?}");
1004
    /// }
1005
    /// ```
1006
    pub fn historical_schedules(
1✔
1007
        &self,
1008
        contract: &Contract,
1009
        interval_end: OffsetDateTime,
1010
        duration: historical::Duration,
1011
    ) -> Result<historical::Schedule, Error> {
1012
        historical::historical_schedule(self, contract, Some(interval_end), duration)
5✔
1013
    }
1014

1015
    /// Requests [historical::Schedule] for interval ending at current time.
1016
    ///
1017
    /// # Arguments
1018
    /// * `contract` - [Contract] to retrieve [historical::Schedule] for.
1019
    /// * `duration` - [historical::Duration] for interval to retrieve [historical::Schedule] for.
1020
    ///
1021
    /// # Examples
1022
    ///
1023
    /// ```no_run
1024
    /// use ibapi::contracts::Contract;
1025
    /// use ibapi::Client;
1026
    /// use ibapi::market_data::historical::ToDuration;
1027
    ///
1028
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1029
    ///
1030
    /// let contract = Contract::stock("GM");
1031
    ///
1032
    /// let historical_data = client
1033
    ///     .historical_schedules_ending_now(&contract, 30.days())
1034
    ///     .expect("historical schedule request failed");
1035
    ///
1036
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
1037
    ///
1038
    /// for session in &historical_data.sessions {
1039
    ///     println!("{session:?}");
1040
    /// }
1041
    /// ```
1042
    pub fn historical_schedules_ending_now(&self, contract: &Contract, duration: historical::Duration) -> Result<historical::Schedule, Error> {
×
1043
        historical::historical_schedule(self, contract, None, duration)
×
1044
    }
1045

1046
    /// Requests historical time & sales data (Bid/Ask) for an instrument.
1047
    ///
1048
    /// # Arguments
1049
    /// * `contract` - [Contract] object that is subject of query
1050
    /// * `start`    - Start time. Either start time or end time is specified.
1051
    /// * `end`      - End time. Either start time or end time is specified.
1052
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1053
    /// * `use_rth`         - Data from regular trading hours (true), or all available hours (false)
1054
    /// * `ignore_size`     - A filter only used when the source price is Bid_Ask
1055
    ///
1056
    /// # Examples
1057
    ///
1058
    /// ```no_run
1059
    /// use time::macros::datetime;
1060
    ///
1061
    /// use ibapi::contracts::Contract;
1062
    /// use ibapi::Client;
1063
    ///
1064
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1065
    ///
1066
    /// let contract = Contract::stock("TSLA");
1067
    ///
1068
    /// let ticks = client
1069
    ///     .historical_ticks_bid_ask(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, true, false)
1070
    ///     .expect("historical ticks request failed");
1071
    ///
1072
    /// for tick in ticks {
1073
    ///     println!("{tick:?}");
1074
    /// }
1075
    /// ```
1076
    pub fn historical_ticks_bid_ask(
2✔
1077
        &self,
1078
        contract: &Contract,
1079
        start: Option<OffsetDateTime>,
1080
        end: Option<OffsetDateTime>,
1081
        number_of_ticks: i32,
1082
        use_rth: bool,
1083
        ignore_size: bool,
1084
    ) -> Result<historical::TickSubscription<historical::TickBidAsk>, Error> {
1085
        historical::historical_ticks_bid_ask(self, contract, start, end, number_of_ticks, use_rth, ignore_size)
16✔
1086
    }
1087

1088
    /// Requests historical time & sales data (Midpoint) for an instrument.
1089
    ///
1090
    /// # Arguments
1091
    /// * `contract` - [Contract] object that is subject of query
1092
    /// * `start`    - Start time. Either start time or end time is specified.
1093
    /// * `end`      - End time. Either start time or end time is specified.
1094
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1095
    /// * `use_rth`         - Data from regular trading hours (true), or all available hours (false)
1096
    ///
1097
    /// # Examples
1098
    ///
1099
    /// ```no_run
1100
    /// use time::macros::datetime;
1101
    ///
1102
    /// use ibapi::contracts::Contract;
1103
    /// use ibapi::Client;
1104
    ///
1105
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1106
    ///
1107
    /// let contract = Contract::stock("TSLA");
1108
    ///
1109
    /// let ticks = client
1110
    ///     .historical_ticks_mid_point(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, true)
1111
    ///     .expect("historical ticks request failed");
1112
    ///
1113
    /// for tick in ticks {
1114
    ///     println!("{tick:?}");
1115
    /// }
1116
    /// ```
1117
    pub fn historical_ticks_mid_point(
2✔
1118
        &self,
1119
        contract: &Contract,
1120
        start: Option<OffsetDateTime>,
1121
        end: Option<OffsetDateTime>,
1122
        number_of_ticks: i32,
1123
        use_rth: bool,
1124
    ) -> Result<historical::TickSubscription<historical::TickMidpoint>, Error> {
1125
        historical::historical_ticks_mid_point(self, contract, start, end, number_of_ticks, use_rth)
14✔
1126
    }
1127

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

1168
    /// Requests data histogram of specified contract.
1169
    ///
1170
    /// # Arguments
1171
    /// * `contract`  - [Contract] to retrieve [Histogram Entries](historical::HistogramEntry) for.
1172
    /// * `use_rth`   - Data from regular trading hours (true), or all available hours (false).
1173
    /// * `period`    - The time period of each histogram bar (e.g., `BarSize::Day`, `BarSize::Week`, `BarSize::Month`).
1174
    ///
1175
    /// # Examples
1176
    ///
1177
    /// ```no_run
1178
    /// use time::macros::datetime;
1179
    //
1180
    /// use ibapi::contracts::Contract;
1181
    /// use ibapi::Client;
1182
    /// use ibapi::market_data::historical::BarSize;
1183
    ///
1184
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1185
    ///
1186
    /// let contract = Contract::stock("GM");
1187
    ///
1188
    /// let histogram = client
1189
    ///     .histogram_data(&contract, true, BarSize::Week)
1190
    ///     .expect("histogram request failed");
1191
    ///
1192
    /// for item in &histogram {
1193
    ///     println!("{item:?}");
1194
    /// }
1195
    /// ```
1196
    pub fn histogram_data(&self, contract: &Contract, use_rth: bool, period: historical::BarSize) -> Result<Vec<HistogramEntry>, Error> {
1✔
1197
        historical::histogram_data(self, contract, use_rth, period)
5✔
1198
    }
1199

1200
    // === Realtime Market Data ===
1201

1202
    /// Requests realtime bars.
1203
    ///
1204
    /// # Arguments
1205
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
1206
    ///
1207
    /// # Examples
1208
    ///
1209
    /// ```no_run
1210
    /// use ibapi::Client;
1211
    /// use ibapi::contracts::Contract;
1212
    /// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1213
    ///
1214
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1215
    ///
1216
    /// let contract = Contract::stock("TSLA");
1217
    /// let subscription = client.realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, false).expect("request failed");
1218
    ///
1219
    /// for (i, bar) in subscription.iter().enumerate().take(60) {
1220
    ///     println!("bar[{i}]: {bar:?}");
1221
    /// }
1222
    /// ```
1223
    pub fn realtime_bars(&self, contract: &Contract, bar_size: BarSize, what_to_show: WhatToShow, use_rth: bool) -> Result<Subscription<Bar>, Error> {
3✔
1224
        realtime::realtime_bars(self, contract, &bar_size, &what_to_show, use_rth, Vec::default())
21✔
1225
    }
1226

1227
    /// Requests tick by tick AllLast ticks.
1228
    ///
1229
    /// # Arguments
1230
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1231
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1232
    /// * `ignore_size`     - Specifies if tick sizes should be ignored.
1233
    ///
1234
    /// # Examples
1235
    ///
1236
    /// ```no_run
1237
    /// use ibapi::Client;
1238
    /// use ibapi::contracts::Contract;
1239
    ///
1240
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1241
    ///
1242
    /// let contract = Contract::stock("AAPL");
1243
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1244
    /// let ignore_size = false;
1245
    ///
1246
    /// let subscription = client.tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
1247
    ///     .expect("tick-by-tick all last data request failed");
1248
    ///
1249
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1250
    ///     println!("All Last Tick: {tick:?}");
1251
    /// }
1252
    /// ```
1253
    pub fn tick_by_tick_all_last(
2✔
1254
        &self,
1255
        contract: &Contract,
1256
        number_of_ticks: i32,
1257
        ignore_size: bool,
1258
    ) -> Result<Subscription<realtime::Trade>, Error> {
1259
        realtime::tick_by_tick_all_last(self, contract, number_of_ticks, ignore_size)
10✔
1260
    }
1261

1262
    /// Requests tick by tick BidAsk ticks.
1263
    ///
1264
    /// # Arguments
1265
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1266
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1267
    /// * `ignore_size`     - Specifies if tick sizes should be ignored. (typically true for BidAsk ticks to get changes based on price).
1268
    ///
1269
    /// # Examples
1270
    ///
1271
    /// ```no_run
1272
    /// use ibapi::Client;
1273
    /// use ibapi::contracts::Contract;
1274
    ///
1275
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1276
    ///
1277
    /// let contract = Contract::stock("AAPL");
1278
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1279
    /// let ignore_size = false;
1280
    ///
1281
    /// let subscription = client.tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
1282
    ///     .expect("tick-by-tick bid/ask data request failed");
1283
    ///
1284
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1285
    ///     println!("BidAsk Tick: {tick:?}");
1286
    /// }
1287
    /// ```
1288
    pub fn tick_by_tick_bid_ask(
2✔
1289
        &self,
1290
        contract: &Contract,
1291
        number_of_ticks: i32,
1292
        ignore_size: bool,
1293
    ) -> Result<Subscription<realtime::BidAsk>, Error> {
1294
        realtime::tick_by_tick_bid_ask(self, contract, number_of_ticks, ignore_size)
10✔
1295
    }
1296

1297
    /// Requests tick by tick Last ticks.
1298
    ///
1299
    /// # Arguments
1300
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1301
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1302
    /// * `ignore_size`     - Specifies if tick sizes should be ignored (typically false for Last ticks).
1303
    ///
1304
    /// # Examples
1305
    ///
1306
    /// ```no_run
1307
    /// use ibapi::Client;
1308
    /// use ibapi::contracts::Contract;
1309
    ///
1310
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1311
    ///
1312
    /// let contract = Contract::stock("AAPL");
1313
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1314
    /// let ignore_size = false;
1315
    ///
1316
    /// let subscription = client.tick_by_tick_last(&contract, number_of_ticks, ignore_size)
1317
    ///     .expect("tick-by-tick last data request failed");
1318
    ///
1319
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1320
    ///     println!("Last Tick: {tick:?}");
1321
    /// }
1322
    /// ```
1323
    pub fn tick_by_tick_last(&self, contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<Subscription<realtime::Trade>, Error> {
2✔
1324
        realtime::tick_by_tick_last(self, contract, number_of_ticks, ignore_size)
10✔
1325
    }
1326

1327
    /// Requests tick by tick MidPoint ticks.
1328
    ///
1329
    /// # Arguments
1330
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1331
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1332
    /// * `ignore_size`     - Specifies if tick sizes should be ignored.
1333
    ///
1334
    /// # Examples
1335
    ///
1336
    /// ```no_run
1337
    /// use ibapi::Client;
1338
    /// use ibapi::contracts::Contract;
1339
    ///
1340
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1341
    ///
1342
    /// let contract = Contract::stock("AAPL");
1343
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1344
    /// let ignore_size = false;
1345
    ///
1346
    /// let subscription = client.tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
1347
    ///     .expect("tick-by-tick mid-point data request failed");
1348
    ///
1349
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1350
    ///     println!("MidPoint Tick: {tick:?}");
1351
    /// }
1352
    /// ```
1353
    pub fn tick_by_tick_midpoint(&self, contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<Subscription<MidPoint>, Error> {
2✔
1354
        realtime::tick_by_tick_midpoint(self, contract, number_of_ticks, ignore_size)
10✔
1355
    }
1356

1357
    /// Switches market data type returned from request_market_data requests to Live, Frozen, Delayed, or FrozenDelayed.
1358
    ///
1359
    /// # Arguments
1360
    /// * `market_data_type` - Type of market data to retrieve.
1361
    ///
1362
    /// # Examples
1363
    ///
1364
    /// ```no_run
1365
    /// use ibapi::Client;
1366
    /// use ibapi::market_data::{MarketDataType};
1367
    ///
1368
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1369
    ///
1370
    /// let market_data_type = MarketDataType::Live;
1371
    /// client.switch_market_data_type(market_data_type).expect("request failed");
1372
    /// println!("market data switched: {market_data_type:?}");
1373
    /// ```
1374
    pub fn switch_market_data_type(&self, market_data_type: MarketDataType) -> Result<(), Error> {
2✔
1375
        market_data::switch_market_data_type(self, market_data_type)
6✔
1376
    }
1377

1378
    /// Requests the contract's market depth (order book).
1379
    ///
1380
    /// # Arguments
1381
    ///
1382
    /// * `contract` - The Contract for which the depth is being requested.
1383
    /// * `number_of_rows` - The number of rows on each side of the order book.
1384
    /// * `is_smart_depth` - Flag indicates that this is smart depth request.
1385
    ///
1386
    /// # Examples
1387
    ///
1388
    /// ```no_run
1389
    /// use ibapi::Client;
1390
    /// use ibapi::contracts::Contract;
1391
    ///
1392
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1393
    ///
1394
    /// let contract = Contract::stock("AAPL");
1395
    ///
1396
    /// let subscription = client.market_depth(&contract, 5, true).expect("error requesting market depth");
1397
    /// for row in &subscription {
1398
    ///     println!("row: {row:?}");
1399
    /// }
1400
    ///
1401
    /// if let Some(error) = subscription.error() {
1402
    ///     println!("error: {error:?}");
1403
    /// }
1404
    /// ```
1405
    pub fn market_depth(&self, contract: &Contract, number_of_rows: i32, is_smart_depth: bool) -> Result<Subscription<MarketDepths>, Error> {
2✔
1406
        realtime::market_depth(self, contract, number_of_rows, is_smart_depth)
10✔
1407
    }
1408

1409
    /// Requests venues for which market data is returned to market_depth (those with market makers)
1410
    ///
1411
    /// # Examples
1412
    ///
1413
    /// ```no_run
1414
    /// use ibapi::Client;
1415
    ///
1416
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1417
    /// let exchanges = client.market_depth_exchanges().expect("error requesting market depth exchanges");
1418
    /// for exchange in &exchanges {
1419
    ///     println!("{exchange:?}");
1420
    /// }
1421
    /// ```
1422
    pub fn market_depth_exchanges(&self) -> Result<Vec<DepthMarketDataDescription>, Error> {
2✔
1423
        realtime::market_depth_exchanges(self)
4✔
1424
    }
1425

1426
    /// Requests real time market data.
1427
    ///
1428
    /// Returns market data for an instrument either in real time or 10-15 minutes delayed data.
1429
    ///
1430
    /// # Arguments
1431
    ///
1432
    /// * `contract` - Contract for which the data is being requested.
1433
    /// * `generic_ticks` - IDs of the available generic ticks:
1434
    ///   - 100 Option Volume (currently for stocks)
1435
    ///   - 101 Option Open Interest (currently for stocks)
1436
    ///   - 104 Historical Volatility (currently for stocks)
1437
    ///   - 105 Average Option Volume (currently for stocks)
1438
    ///   - 106 Option Implied Volatility (currently for stocks)
1439
    ///   - 162 Index Future Premium
1440
    ///   - 165 Miscellaneous Stats
1441
    ///   - 221 Mark Price (used in TWS P&L computations)
1442
    ///   - 225 Auction values (volume, price and imbalance)
1443
    ///   - 233 RTVolume - contains the last trade price, last trade size, last trade time, total volume, VWAP, and single trade flag.
1444
    ///   - 236 Shortable
1445
    ///   - 256 Inventory
1446
    ///   - 258 Fundamental Ratios
1447
    ///   - 411 Realtime Historical Volatility
1448
    ///   - 456 IBDividends
1449
    /// * `snapshot` - for users with corresponding real time market data subscriptions. A true value will return a one-time snapshot, while a false value will provide streaming data.
1450
    /// * `regulatory_snapshot` - snapshot for US stocks requests NBBO snapshots for users which have "US Securities Snapshot Bundle" subscription but not corresponding Network A, B, or C subscription necessary for streaming market data. One-time snapshot of current market price that will incur a fee of 1 cent to the account per snapshot.
1451
    ///
1452
    /// # Examples
1453
    ///
1454
    /// ```no_run
1455
    /// use ibapi::{contracts::Contract, market_data::realtime::TickTypes, Client};
1456
    ///
1457
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1458
    ///
1459
    /// let contract = Contract::stock("AAPL");
1460
    ///
1461
    /// // https://www.interactivebrokers.com/campus/ibkr-api-page/twsapi-doc/#available-tick-types
1462
    /// let generic_ticks = &["233", "293"];
1463
    /// let snapshot = false;
1464
    /// let regulatory_snapshot = false;
1465
    ///
1466
    /// let subscription = client
1467
    ///     .market_data(&contract, generic_ticks, snapshot, regulatory_snapshot)
1468
    ///     .expect("error requesting market data");
1469
    ///
1470
    /// for tick in &subscription {
1471
    ///     match tick {
1472
    ///         TickTypes::Price(tick_price) => println!("{tick_price:?}"),
1473
    ///         TickTypes::Size(tick_size) => println!("{tick_size:?}"),
1474
    ///         TickTypes::PriceSize(tick_price_size) => println!("{tick_price_size:?}"),
1475
    ///         TickTypes::Generic(tick_generic) => println!("{tick_generic:?}"),
1476
    ///         TickTypes::String(tick_string) => println!("{tick_string:?}"),
1477
    ///         TickTypes::EFP(tick_efp) => println!("{tick_efp:?}"),
1478
    ///         TickTypes::OptionComputation(option_computation) => println!("{option_computation:?}"),
1479
    ///         TickTypes::RequestParameters(tick_request_parameters) => println!("{tick_request_parameters:?}"),
1480
    ///         TickTypes::Notice(notice) => println!("{notice:?}"),
1481
    ///         TickTypes::SnapshotEnd => subscription.cancel(),
1482
    ///     }
1483
    /// }
1484
    /// ```
1485
    pub fn market_data(
6✔
1486
        &self,
1487
        contract: &Contract,
1488
        generic_ticks: &[&str],
1489
        snapshot: bool,
1490
        regulatory_snapshot: bool,
1491
    ) -> Result<Subscription<TickTypes>, Error> {
1492
        realtime::market_data(self, contract, generic_ticks, snapshot, regulatory_snapshot)
36✔
1493
    }
1494

1495
    // === News ===
1496

1497
    /// Requests news providers which the user has subscribed to.
1498
    ///
1499
    /// # Examples
1500
    ///
1501
    /// ```no_run
1502
    /// use ibapi::Client;
1503
    ///
1504
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1505
    ///
1506
    /// let news_providers = client.news_providers().expect("request news providers failed");
1507
    /// for news_provider in &news_providers {
1508
    ///   println!("news provider {news_provider:?}");
1509
    /// }
1510
    /// ```
1511
    pub fn news_providers(&self) -> Result<Vec<news::NewsProvider>, Error> {
×
1512
        news::news_providers(self)
×
1513
    }
1514

1515
    /// Subscribes to IB's News Bulletins.
1516
    ///
1517
    /// # Arguments
1518
    ///
1519
    /// * `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.
1520
    ///
1521
    /// # Examples
1522
    ///
1523
    /// ```no_run
1524
    /// use ibapi::Client;
1525
    ///
1526
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1527
    ///
1528
    /// let news_bulletins = client.news_bulletins(true).expect("request news providers failed");
1529
    /// for news_bulletin in &news_bulletins {
1530
    ///   println!("news bulletin {news_bulletin:?}");
1531
    /// }
1532
    /// ```
1533
    pub fn news_bulletins(&self, all_messages: bool) -> Result<Subscription<news::NewsBulletin>, Error> {
×
1534
        news::news_bulletins(self, all_messages)
×
1535
    }
1536

1537
    /// Requests historical news headlines.
1538
    ///
1539
    /// # Arguments
1540
    ///
1541
    /// * `contract_id`    - Contract ID of ticker. See [contract_details](Client::contract_details) for how to retrieve contract ID.
1542
    /// * `provider_codes` - A list of provider codes.
1543
    /// * `start_time`     - Marks the (exclusive) start of the date range.
1544
    /// * `end_time`       - Marks the (inclusive) end of the date range.
1545
    /// * `total_results`  - The maximum number of headlines to fetch (1 – 300)
1546
    ///
1547
    /// # Examples
1548
    ///
1549
    /// ```no_run
1550
    /// use ibapi::Client;
1551
    /// use ibapi::contracts::Contract; // Or remove if conId is always known
1552
    /// use time::macros::datetime;
1553
    ///
1554
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1555
    ///
1556
    /// // Example: Fetch historical news for a known contract ID (e.g., AAPL's conId)
1557
    /// let contract_id = 265598;
1558
    /// let provider_codes = &["DJNL", "BRFG"]; // Example provider codes
1559
    /// // Define a past date range for the news query
1560
    /// let start_time = datetime!(2023-01-01 0:00 UTC);
1561
    /// let end_time = datetime!(2023-01-02 0:00 UTC);
1562
    /// let total_results = 5u8; // Request a small number of articles for the example
1563
    ///
1564
    /// let articles_subscription = client.historical_news(
1565
    ///     contract_id,
1566
    ///     provider_codes,
1567
    ///     start_time,
1568
    ///     end_time,
1569
    ///     total_results,
1570
    /// ).expect("request historical news failed");
1571
    ///
1572
    /// println!("Requested historical news articles:");
1573
    /// for article in articles_subscription.iter().take(total_results as usize) {
1574
    ///     println!("- Headline: {}, ID: {}, Provider: {}, Time: {}",
1575
    ///              article.headline, article.article_id, article.provider_code, article.time);
1576
    /// }
1577
    /// ```
1578
    pub fn historical_news(
×
1579
        &self,
1580
        contract_id: i32,
1581
        provider_codes: &[&str],
1582
        start_time: OffsetDateTime,
1583
        end_time: OffsetDateTime,
1584
        total_results: u8,
1585
    ) -> Result<Subscription<news::NewsArticle>, Error> {
1586
        news::historical_news(self, contract_id, provider_codes, start_time, end_time, total_results)
×
1587
    }
1588

1589
    /// Requests news article body given articleId.
1590
    ///
1591
    /// # Arguments
1592
    ///
1593
    /// * `provider_code` - Short code indicating news provider, e.g. FLY.
1594
    /// * `article_id`    - ID of the specific article.
1595
    ///
1596
    /// # Examples
1597
    ///
1598
    /// ```no_run
1599
    /// use ibapi::Client;
1600
    ///
1601
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1602
    ///
1603
    /// // can get these using the historical_news method
1604
    /// let provider_code = "DJ-N";
1605
    /// let article_id = "DJ-N$1915168d";
1606
    ///
1607
    /// let article = client.news_article(provider_code, article_id).expect("request news article failed");
1608
    /// println!("{article:?}");
1609
    /// ```
1610
    pub fn news_article(&self, provider_code: &str, article_id: &str) -> Result<news::NewsArticleBody, Error> {
×
1611
        news::news_article(self, provider_code, article_id)
×
1612
    }
1613

1614
    /// Requests realtime contract specific news
1615
    ///
1616
    /// # Arguments
1617
    ///
1618
    /// * `contract`       - Contract for which news is being requested.
1619
    /// * `provider_codes` - Short codes indicating news providers, e.g. DJ-N.
1620
    ///
1621
    /// # Examples
1622
    ///
1623
    /// ```no_run
1624
    /// use ibapi::Client;
1625
    /// use ibapi::contracts::Contract;
1626
    ///
1627
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1628
    ///
1629
    /// let contract = Contract::stock("AAPL");
1630
    /// let provider_codes = ["DJ-N"];
1631
    ///
1632
    /// let subscription = client.contract_news(&contract, &provider_codes).expect("request contract news failed");
1633
    /// for article in &subscription {
1634
    ///     println!("{article:?}");
1635
    /// }
1636
    /// ```
1637
    pub fn contract_news(&self, contract: &Contract, provider_codes: &[&str]) -> Result<Subscription<NewsArticle>, Error> {
×
1638
        news::contract_news(self, contract, provider_codes)
×
1639
    }
1640

1641
    /// Requests realtime BroadTape News
1642
    ///
1643
    /// # Arguments
1644
    ///
1645
    /// * `provider_code` - Short codes indicating news provider, e.g. DJ-N.
1646
    ///
1647
    /// # Examples
1648
    ///
1649
    /// ```no_run
1650
    /// use ibapi::Client;
1651
    ///
1652
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1653
    ///
1654
    /// let provider_code = "BRFG";
1655
    ///
1656
    /// let subscription = client.broad_tape_news(provider_code).expect("request broad tape news failed");
1657
    /// for article in &subscription {
1658
    ///     println!("{article:?}");
1659
    /// }
1660
    /// ```
1661
    pub fn broad_tape_news(&self, provider_code: &str) -> Result<Subscription<NewsArticle>, Error> {
×
1662
        news::broad_tape_news(self, provider_code)
×
1663
    }
1664

1665
    // === Scanner ===
1666

1667
    /// Requests an XML list of scanner parameters valid in TWS.
1668
    ///
1669
    /// # Examples
1670
    ///
1671
    /// ```no_run
1672
    /// use ibapi::Client;
1673
    /// use ibapi::scanner::ScannerSubscription;
1674
    /// use ibapi::orders::TagValue; // Or ensure common::TagValue is the correct path
1675
    ///
1676
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1677
    ///
1678
    /// let mut sub = ScannerSubscription::default();
1679
    /// sub.instrument = Some("STK".to_string());
1680
    /// sub.location_code = Some("STK.US.MAJOR".to_string());
1681
    /// sub.scan_code = Some("TOP_PERC_GAIN".to_string());
1682
    /// // Further customize the subscription object as needed, for example:
1683
    /// // sub.above_price = Some(1.0);
1684
    /// // sub.below_price = Some(100.0);
1685
    /// // sub.number_of_rows = Some(20);
1686
    ///
1687
    /// // Filter options are advanced and not always needed. Pass an empty Vec if not used.
1688
    /// let filter_options: Vec<TagValue> = Vec::new();
1689
    /// // Example of adding a filter:
1690
    /// // filter_options.push(TagValue { tag: "marketCapAbove".to_string(), value: "1000000000".to_string() });
1691
    ///
1692
    /// match client.scanner_subscription(&sub, &filter_options) {
1693
    ///     Ok(subscription) => {
1694
    ///         // Iterate over received scanner data.
1695
    ///         // Note: Scanner subscriptions can be continuous or return a snapshot.
1696
    ///         // This example just takes the first batch if available.
1697
    ///         if let Some(scanner_results_vec) = subscription.iter().next() {
1698
    ///             println!("Scanner Results (first batch):");
1699
    ///             for data in scanner_results_vec {
1700
    ///                 println!("  Rank: {}, Symbol: {}",
1701
    ///                          data.rank,
1702
    ///                          data.contract_details.contract.symbol);
1703
    ///             }
1704
    ///         } else {
1705
    ///             println!("No scanner results received in the first check.");
1706
    ///         }
1707
    ///         // In a real application, you might continuously iterate or handle updates.
1708
    ///         // Remember to cancel the subscription when no longer needed if it's continuous.
1709
    ///         // subscription.cancel();
1710
    ///     }
1711
    ///     Err(e) => {
1712
    ///         eprintln!("Failed to start scanner subscription: {e:?}");
1713
    ///     }
1714
    /// };
1715
    /// ```
1716
    pub fn scanner_parameters(&self) -> Result<String, Error> {
1✔
1717
        scanner::scanner_parameters(self)
2✔
1718
    }
1719

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

1777
    // == Wall Street Horizon
1778

1779
    /// Requests metadata from the WSH calendar.
1780
    ///
1781
    /// # Examples
1782
    ///
1783
    /// ```no_run
1784
    /// use ibapi::Client;
1785
    ///
1786
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1787
    ///
1788
    /// let metadata = client.wsh_metadata().expect("request wsh metadata failed");
1789
    /// println!("{metadata:?}");
1790
    /// ```
1791
    pub fn wsh_metadata(&self) -> Result<wsh::WshMetadata, Error> {
×
1792
        wsh::wsh_metadata(self)
×
1793
    }
1794

1795
    /// Requests event data for a specified contract from the Wall Street Horizons (WSH) calendar.
1796
    ///
1797
    /// # Arguments
1798
    ///
1799
    /// * `contract_id` - Contract identifier for the event request.
1800
    /// * `start_date`  - Start date of the event request.
1801
    /// * `end_date`    - End date of the event request.
1802
    /// * `limit`       - Maximum number of events to return. Maximum of 100.
1803
    /// * `auto_fill`   - Fields to automatically fill in. See [AutoFill] for more information.
1804
    ///
1805
    /// # Examples
1806
    ///
1807
    /// ```no_run
1808
    /// use ibapi::Client;
1809
    ///
1810
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1811
    ///
1812
    /// let contract_id = 76792991; // TSLA
1813
    /// let event_data = client.wsh_event_data_by_contract(contract_id, None, None, None, None).expect("request wsh event data failed");
1814
    /// println!("{event_data:?}");
1815
    /// ```
1816
    pub fn wsh_event_data_by_contract(
×
1817
        &self,
1818
        contract_id: i32,
1819
        start_date: Option<Date>,
1820
        end_date: Option<Date>,
1821
        limit: Option<i32>,
1822
        auto_fill: Option<AutoFill>,
1823
    ) -> Result<wsh::WshEventData, Error> {
1824
        wsh::wsh_event_data_by_contract(self, contract_id, start_date, end_date, limit, auto_fill)
×
1825
    }
1826

1827
    /// Requests event data from the Wall Street Horizons (WSH) calendar using a JSON filter.
1828
    ///
1829
    /// # Arguments
1830
    ///
1831
    /// * `filter`    - Json-formatted string containing all filter values.
1832
    /// * `limit`     - Maximum number of events to return. Maximum of 100.
1833
    /// * `auto_fill` - Fields to automatically fill in. See [AutoFill] for more information.
1834
    ///
1835
    /// # Examples
1836
    ///
1837
    /// ```no_run
1838
    /// use ibapi::Client;
1839
    ///
1840
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1841
    ///
1842
    /// let filter = ""; // see https://www.interactivebrokers.com/campus/ibkr-api-page/twsapi-doc/#wsheventdata-object
1843
    /// let event_data = client.wsh_event_data_by_filter(filter, None, None).expect("request wsh event data failed");
1844
    /// for result in event_data {
1845
    ///     println!("{result:?}");
1846
    /// }
1847
    /// ```
1848
    pub fn wsh_event_data_by_filter(
×
1849
        &self,
1850
        filter: &str,
1851
        limit: Option<i32>,
1852
        auto_fill: Option<AutoFill>,
1853
    ) -> Result<Subscription<wsh::WshEventData>, Error> {
1854
        wsh::wsh_event_data_by_filter(self, filter, limit, auto_fill)
×
1855
    }
1856

1857
    // == Internal Use ==
1858

1859
    #[cfg(test)]
1860
    pub(crate) fn stubbed(message_bus: Arc<dyn MessageBus>, server_version: i32) -> Client {
146✔
1861
        Client {
1862
            server_version,
1863
            connection_time: None,
1864
            time_zone: None,
1865
            message_bus,
1866
            client_id: 100,
1867
            id_manager: ClientIdManager::new(-1),
146✔
1868
        }
1869
    }
1870

1871
    pub(crate) fn send_request(&self, request_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
103✔
1872
        debug!("send_message({request_id:?}, {message:?})");
103✔
1873
        self.message_bus.send_request(request_id, &message)
309✔
1874
    }
1875

1876
    pub(crate) fn send_order(&self, order_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
7✔
1877
        debug!("send_order({order_id:?}, {message:?})");
7✔
1878
        self.message_bus.send_order_request(order_id, &message)
21✔
1879
    }
1880

1881
    pub(crate) fn send_message(&self, message: RequestMessage) -> Result<(), Error> {
4✔
1882
        debug!("send_message({message:?})");
4✔
1883
        self.message_bus.send_message(&message)
8✔
1884
    }
1885

1886
    /// Creates a subscription for order updates if one is not already active.
1887
    pub(crate) fn create_order_update_subscription(&self) -> Result<InternalSubscription, Error> {
4✔
1888
        self.message_bus.create_order_update_subscription()
4✔
1889
    }
1890

1891
    /// Sends request for the next valid order id.
1892
    pub(crate) fn send_shared_request(&self, message_id: OutgoingMessages, message: RequestMessage) -> Result<InternalSubscription, Error> {
56✔
1893
        self.message_bus.send_shared_request(message_id, &message)
168✔
1894
    }
1895

1896
    pub(crate) fn check_server_version(&self, version: i32, message: &str) -> Result<(), Error> {
11✔
1897
        if version <= self.server_version {
11✔
1898
            Ok(())
11✔
1899
        } else {
1900
            Err(Error::ServerVersion(version, self.server_version, message.into()))
×
1901
        }
1902
    }
1903
}
1904

1905
impl Drop for Client {
1906
    fn drop(&mut self) {
184✔
1907
        debug!("dropping basic client");
184✔
1908
        self.message_bus.ensure_shutdown();
184✔
1909
    }
1910
}
1911

1912
impl Debug for Client {
1913
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1914
        f.debug_struct("Client")
×
1915
            .field("server_version", &self.server_version)
×
1916
            .field("server_time", &self.connection_time)
×
1917
            .field("client_id", &self.client_id)
×
1918
            .finish()
1919
    }
1920
}
1921

1922
/// Subscriptions facilitate handling responses from TWS that may be delayed or delivered periodically.
1923
///
1924
/// They offer both blocking and non-blocking methods for retrieving data.
1925
///
1926
/// In the simplest case a subscription can be implicitly converted to blocking iterator
1927
/// that cancels the subscription when it goes out of scope.
1928
///
1929
/// ```no_run
1930
/// use ibapi::contracts::Contract;
1931
/// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1932
/// use ibapi::Client;
1933
///
1934
/// let connection_url = "127.0.0.1:4002";
1935
/// let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
1936
///
1937
/// // Request real-time bars data for AAPL with 5-second intervals
1938
/// let contract = Contract::stock("AAPL");
1939
/// let subscription = client
1940
///     .realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, false)
1941
///     .expect("realtime bars request failed!");
1942
///
1943
/// // Use the subscription as a blocking iterator
1944
/// for bar in subscription {
1945
///     // Process each bar here (e.g., print or use in calculations)
1946
///     println!("Received bar: {bar:?}");
1947
/// }
1948
/// // The subscription goes out of scope and is automatically cancelled.
1949
/// ```
1950
///
1951
/// Subscriptions can be explicitly canceled using the [cancel](Subscription::cancel) method.
1952
///
1953
// Re-export SharesChannel trait from subscriptions module
1954
pub use crate::subscriptions::SharesChannel;
1955

1956
#[cfg(test)]
1957
mod tests {
1958
    use std::sync::Arc;
1959

1960
    use super::Client;
1961
    use crate::client::common::tests::*;
1962
    use crate::{connection::ConnectionMetadata, stubs::MessageBusStub};
1963

1964
    const CLIENT_ID: i32 = 100;
1965

1966
    #[test]
1967
    fn test_connect() {
1968
        let gateway = setup_connect();
1969

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

1972
        assert_eq!(client.client_id(), CLIENT_ID);
1973
        assert_eq!(client.server_version(), gateway.server_version());
1974
        assert_eq!(client.time_zone, gateway.time_zone());
1975

1976
        assert_eq!(gateway.requests().len(), 0, "No requests should be sent on connect");
1977
    }
1978

1979
    #[test]
1980
    fn test_server_time() {
1981
        let (gateway, expectations) = setup_server_time();
1982

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

1985
        let server_time = client.server_time().unwrap();
1986
        assert_eq!(server_time, expectations.server_time);
1987

1988
        let requests = gateway.requests();
1989
        assert_eq!(requests[0], "49\01\0");
1990
    }
1991

1992
    #[test]
1993
    fn test_next_valid_order_id() {
1994
        let (gateway, expectations) = setup_next_valid_order_id();
1995

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

1998
        let next_valid_order_id = client.next_valid_order_id().unwrap();
1999
        assert_eq!(next_valid_order_id, expectations.next_valid_order_id);
2000

2001
        let requests = gateway.requests();
2002
        assert_eq!(requests[0], "8\01\00\0");
2003
    }
2004

2005
    #[test]
2006
    fn test_managed_accounts() {
2007
        let (gateway, expectations) = setup_managed_accounts();
2008

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

2011
        let accounts = client.managed_accounts().unwrap();
2012
        assert_eq!(accounts, expectations.accounts);
2013

2014
        let requests = gateway.requests();
2015
        assert_eq!(requests[0], "17\01\0");
2016
    }
2017

2018
    #[test]
2019
    fn test_positions() {
2020
        let gateway = setup_positions();
2021

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

2024
        let positions = client.positions().unwrap();
2025
        let mut position_count = 0;
2026

2027
        for position_update in positions {
2028
            match position_update {
2029
                crate::accounts::PositionUpdate::Position(position) => {
2030
                    assert_eq!(position.account, "DU1234567");
2031
                    assert_eq!(position.contract.symbol, "AAPL");
2032
                    assert_eq!(position.position, 500.0);
2033
                    assert_eq!(position.average_cost, 150.25);
2034
                    position_count += 1;
2035
                }
2036
                crate::accounts::PositionUpdate::PositionEnd => {
2037
                    break;
2038
                }
2039
            }
2040
        }
2041

2042
        assert_eq!(position_count, 1);
2043
        let requests = gateway.requests();
2044
        assert_eq!(requests[0], "61\01\0");
2045
    }
2046

2047
    #[test]
2048
    fn test_positions_multi() {
2049
        use crate::accounts::types::AccountId;
2050

2051
        let gateway = setup_positions_multi();
2052

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

2055
        let account = AccountId("DU1234567".to_string());
2056
        let positions = client.positions_multi(Some(&account), None).unwrap();
2057
        let mut position_count = 0;
2058

2059
        for position_update in positions {
2060
            match position_update {
2061
                crate::accounts::PositionUpdateMulti::Position(position) => {
2062
                    position_count += 1;
2063
                    if position_count == 1 {
2064
                        assert_eq!(position.account, "DU1234567");
2065
                        assert_eq!(position.contract.symbol, "AAPL");
2066
                        assert_eq!(position.position, 500.0);
2067
                        assert_eq!(position.average_cost, 150.25);
2068
                        assert_eq!(position.model_code, "MODEL1");
2069
                    } else if position_count == 2 {
2070
                        assert_eq!(position.account, "DU1234568");
2071
                        assert_eq!(position.contract.symbol, "GOOGL");
2072
                        assert_eq!(position.position, 200.0);
2073
                        assert_eq!(position.average_cost, 2500.00);
2074
                        assert_eq!(position.model_code, "MODEL1");
2075
                    }
2076
                }
2077
                crate::accounts::PositionUpdateMulti::PositionEnd => {
2078
                    break;
2079
                }
2080
            }
2081
        }
2082

2083
        assert_eq!(position_count, 2);
2084
        let requests = gateway.requests();
2085
        assert_eq!(requests[0], "74\01\09000\0DU1234567\0\0");
2086
    }
2087

2088
    #[test]
2089
    fn test_account_summary() {
2090
        use crate::accounts::types::AccountGroup;
2091

2092
        let gateway = setup_account_summary();
2093

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

2096
        let group = AccountGroup("All".to_string());
2097
        let tags = vec!["NetLiquidation", "TotalCashValue"];
2098

2099
        let summaries = client.account_summary(&group, &tags).unwrap();
2100
        let mut summary_count = 0;
2101

2102
        for summary_result in summaries {
2103
            match summary_result {
2104
                crate::accounts::AccountSummaryResult::Summary(summary) => {
2105
                    assert_eq!(summary.account, "DU1234567");
2106
                    assert_eq!(summary.currency, "USD");
2107

2108
                    if summary.tag == "NetLiquidation" {
2109
                        assert_eq!(summary.value, "25000.00");
2110
                    } else if summary.tag == "TotalCashValue" {
2111
                        assert_eq!(summary.value, "15000.00");
2112
                    }
2113
                    summary_count += 1;
2114
                }
2115
                crate::accounts::AccountSummaryResult::End => {
2116
                    break;
2117
                }
2118
            }
2119
        }
2120

2121
        assert_eq!(summary_count, 2);
2122
        let requests = gateway.requests();
2123
        assert_eq!(requests[0], "62\01\09000\0All\0NetLiquidation,TotalCashValue\0");
2124
    }
2125

2126
    #[test]
2127
    fn test_pnl() {
2128
        use crate::accounts::types::AccountId;
2129

2130
        let gateway = setup_pnl();
2131

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

2134
        let account = AccountId("DU1234567".to_string());
2135
        let pnl = client.pnl(&account, None).unwrap();
2136

2137
        let first_pnl = pnl.into_iter().next().unwrap();
2138
        assert_eq!(first_pnl.daily_pnl, 250.50);
2139
        assert_eq!(first_pnl.unrealized_pnl, Some(1500.00));
2140
        assert_eq!(first_pnl.realized_pnl, Some(750.00));
2141

2142
        let requests = gateway.requests();
2143
        assert_eq!(requests[0], "92\09000\0DU1234567\0\0");
2144
    }
2145

2146
    #[test]
2147
    fn test_pnl_single() {
2148
        use crate::accounts::types::{AccountId, ContractId};
2149

2150
        let gateway = setup_pnl_single();
2151

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

2154
        let account = AccountId("DU1234567".to_string());
2155
        let contract_id = ContractId(12345);
2156
        let pnl_single = client.pnl_single(&account, contract_id, None).unwrap();
2157

2158
        let first_pnl = pnl_single.into_iter().next().unwrap();
2159
        assert_eq!(first_pnl.position, 100.0);
2160
        assert_eq!(first_pnl.daily_pnl, 150.25);
2161
        assert_eq!(first_pnl.unrealized_pnl, 500.00);
2162
        assert_eq!(first_pnl.realized_pnl, 250.00);
2163
        assert_eq!(first_pnl.value, 1000.00);
2164

2165
        let requests = gateway.requests();
2166
        assert_eq!(requests[0], "94\09000\0DU1234567\0\012345\0");
2167
    }
2168

2169
    #[test]
2170
    fn test_account_updates() {
2171
        use crate::accounts::types::AccountId;
2172

2173
        let gateway = setup_account_updates();
2174

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

2177
        let account = AccountId("DU1234567".to_string());
2178
        let updates = client.account_updates(&account).unwrap();
2179

2180
        let mut value_count = 0;
2181
        let mut portfolio_count = 0;
2182
        let mut has_time_update = false;
2183
        let mut has_end = false;
2184

2185
        for update in updates {
2186
            match update {
2187
                crate::accounts::AccountUpdate::AccountValue(value) => {
2188
                    assert_eq!(value.key, "NetLiquidation");
2189
                    assert_eq!(value.value, "25000.00");
2190
                    assert_eq!(value.currency, "USD");
2191
                    assert_eq!(value.account, Some("DU1234567".to_string()));
2192
                    value_count += 1;
2193
                }
2194
                crate::accounts::AccountUpdate::PortfolioValue(portfolio) => {
2195
                    assert_eq!(portfolio.contract.symbol, "AAPL");
2196
                    assert_eq!(portfolio.position, 500.0);
2197
                    assert_eq!(portfolio.market_price, 151.50);
2198
                    assert_eq!(portfolio.market_value, 75750.00);
2199
                    assert_eq!(portfolio.average_cost, 150.25);
2200
                    assert_eq!(portfolio.unrealized_pnl, 375.00);
2201
                    assert_eq!(portfolio.realized_pnl, 125.00);
2202
                    assert_eq!(portfolio.account, Some("DU1234567".to_string()));
2203
                    portfolio_count += 1;
2204
                }
2205
                crate::accounts::AccountUpdate::UpdateTime(time) => {
2206
                    assert_eq!(time.timestamp, "20240122 15:30:00");
2207
                    has_time_update = true;
2208
                }
2209
                crate::accounts::AccountUpdate::End => {
2210
                    has_end = true;
2211
                    break;
2212
                }
2213
            }
2214
        }
2215

2216
        assert!(has_end, "Expected End message");
2217
        assert_eq!(value_count, 1);
2218
        assert_eq!(portfolio_count, 1);
2219
        assert!(has_time_update);
2220

2221
        let requests = gateway.requests();
2222
        assert_eq!(requests[0], "6\02\01\0DU1234567\0");
2223
    }
2224

2225
    #[test]
2226
    fn test_family_codes() {
2227
        let gateway = setup_family_codes();
2228

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

2231
        let family_codes = client.family_codes().unwrap();
2232

2233
        assert_eq!(family_codes.len(), 2);
2234
        assert_eq!(family_codes[0].account_id, "DU1234567");
2235
        assert_eq!(family_codes[0].family_code, "FAM001");
2236
        assert_eq!(family_codes[1].account_id, "DU1234568");
2237
        assert_eq!(family_codes[1].family_code, "FAM002");
2238

2239
        let requests = gateway.requests();
2240
        assert_eq!(requests[0], "80\01\0");
2241
    }
2242

2243
    #[test]
2244
    fn test_account_updates_multi() {
2245
        use crate::accounts::types::{AccountId, ModelCode};
2246

2247
        let gateway = setup_account_updates_multi();
2248

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

2251
        let account = AccountId("DU1234567".to_string());
2252
        let model_code: Option<ModelCode> = None;
2253
        let updates = client.account_updates_multi(Some(&account), model_code.as_ref()).unwrap();
2254

2255
        let mut cash_balance_found = false;
2256
        let mut currency_found = false;
2257
        let mut stock_market_value_found = false;
2258
        let mut has_end = false;
2259

2260
        for update in updates {
2261
            match update {
2262
                crate::accounts::AccountUpdateMulti::AccountMultiValue(value) => {
2263
                    assert_eq!(value.account, "DU1234567");
2264
                    assert_eq!(value.model_code, "");
2265

2266
                    match value.key.as_str() {
2267
                        "CashBalance" => {
2268
                            assert_eq!(value.value, "94629.71");
2269
                            assert_eq!(value.currency, "USD");
2270
                            cash_balance_found = true;
2271
                        }
2272
                        "Currency" => {
2273
                            assert_eq!(value.value, "USD");
2274
                            assert_eq!(value.currency, "USD");
2275
                            currency_found = true;
2276
                        }
2277
                        "StockMarketValue" => {
2278
                            assert_eq!(value.value, "0.00");
2279
                            assert_eq!(value.currency, "BASE");
2280
                            stock_market_value_found = true;
2281
                        }
2282
                        _ => panic!("Unexpected key: {}", value.key),
2283
                    }
2284
                }
2285
                crate::accounts::AccountUpdateMulti::End => {
2286
                    has_end = true;
2287
                    break;
2288
                }
2289
            }
2290
        }
2291

2292
        assert!(cash_balance_found, "Expected CashBalance update");
2293
        assert!(currency_found, "Expected Currency update");
2294
        assert!(stock_market_value_found, "Expected StockMarketValue update");
2295
        assert!(has_end, "Expected End message");
2296

2297
        let requests = gateway.requests();
2298
        assert_eq!(requests[0], "76\01\09000\0DU1234567\0\01\0");
2299
    }
2300

2301
    #[test]
2302
    fn test_client_id() {
2303
        let client_id = 500;
2304
        let connection_metadata = ConnectionMetadata {
2305
            client_id,
2306
            ..ConnectionMetadata::default()
2307
        };
2308
        let message_bus = Arc::new(MessageBusStub::default());
2309

2310
        let client = Client::new(connection_metadata, message_bus).unwrap();
2311

2312
        assert_eq!(client.client_id(), client_id);
2313
    }
2314

2315
    #[test]
2316
    fn test_contract_details() {
2317
        let gateway = setup_contract_details();
2318

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

2321
        let contract = crate::contracts::Contract::stock("AAPL");
2322
        let details = client.contract_details(&contract).expect("Failed to get contract details");
2323

2324
        assert_eq!(details.len(), 1);
2325
        let detail = &details[0];
2326

2327
        // Verify contract fields
2328
        assert_eq!(detail.contract.symbol, "AAPL");
2329
        assert_eq!(detail.contract.security_type, crate::contracts::SecurityType::Stock);
2330
        assert_eq!(detail.contract.currency, "USD");
2331
        assert_eq!(detail.contract.exchange, "NASDAQ");
2332
        assert_eq!(detail.contract.local_symbol, "AAPL");
2333
        assert_eq!(detail.contract.trading_class, "AAPL");
2334
        assert_eq!(detail.contract.contract_id, 265598);
2335
        assert_eq!(detail.contract.primary_exchange, "NASDAQ");
2336

2337
        // Verify contract details fields
2338
        assert_eq!(detail.market_name, "NMS");
2339
        assert_eq!(detail.min_tick, 0.01);
2340
        assert!(detail.order_types.contains(&"LMT".to_string()));
2341
        assert!(detail.order_types.contains(&"MKT".to_string()));
2342
        assert!(detail.valid_exchanges.contains(&"SMART".to_string()));
2343
        assert_eq!(detail.long_name, "Apple Inc");
2344
        assert_eq!(detail.industry, "Technology");
2345
        assert_eq!(detail.category, "Computers");
2346
        assert_eq!(detail.subcategory, "Computers");
2347
        assert_eq!(detail.time_zone_id, "US/Eastern");
2348
        assert_eq!(detail.stock_type, "NMS");
2349
        assert_eq!(detail.min_size, 1.0);
2350
        assert_eq!(detail.size_increment, 1.0);
2351
        assert_eq!(detail.suggested_size_increment, 1.0);
2352

2353
        let requests = gateway.requests();
2354
        // Request format: OutgoingMessages::RequestContractData(9), version(8), request_id, contract_id(0),
2355
        // symbol, security_type, last_trade_date, strike, right, multiplier, exchange, primary_exchange,
2356
        // currency, local_symbol, trading_class, include_expired, security_id_type, security_id, issuer_id
2357
        assert_eq!(requests[0], "9\08\09000\00\0AAPL\0STK\0\00\0\0\0SMART\0\0USD\0\0\00\0\0\0");
2358
    }
2359

2360
    #[test]
2361
    fn test_subscription_cancel_only_sends_once() {
2362
        // This test verifies that calling cancel() multiple times only sends one cancel message
2363
        // This addresses issue #258 where explicit cancel() followed by Drop could send duplicate messages
2364

2365
        let message_bus = Arc::new(MessageBusStub::default());
2366
        let client = Client::stubbed(message_bus.clone(), 100);
2367

2368
        // Create a subscription using realtime bars as an example
2369
        let contract = crate::contracts::Contract::stock("AAPL");
2370
        let subscription = client
2371
            .realtime_bars(
2372
                &contract,
2373
                crate::market_data::realtime::BarSize::Sec5,
2374
                crate::market_data::realtime::WhatToShow::Trades,
2375
                false,
2376
            )
2377
            .expect("Failed to create subscription");
2378

2379
        // Get initial request count (should be 1 for the realtime bars request)
2380
        let initial_count = message_bus.request_messages().len();
2381
        assert_eq!(initial_count, 1, "Should have one request for realtime bars");
2382

2383
        // First cancel should add one more message
2384
        subscription.cancel();
2385
        let after_first_cancel = message_bus.request_messages().len();
2386
        assert_eq!(after_first_cancel, 2, "Should have two messages after first cancel");
2387

2388
        // Second cancel should not send another message
2389
        subscription.cancel();
2390
        let after_second_cancel = message_bus.request_messages().len();
2391
        assert_eq!(after_second_cancel, 2, "Should still have two messages after second cancel");
2392

2393
        // Drop should also not send another message (implicitly calls cancel)
2394
        drop(subscription);
2395
        let after_drop = message_bus.request_messages().len();
2396
        assert_eq!(after_drop, 2, "Should still have two messages after drop");
2397
    }
2398

2399
    #[test]
2400
    fn test_matching_symbols() {
2401
        let gateway = setup_matching_symbols();
2402

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

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

2408
        assert_eq!(contract_descriptions.len(), 2, "Should have 2 matching symbols");
2409

2410
        // First contract description
2411
        assert_eq!(contract_descriptions[0].contract.contract_id, 265598);
2412
        assert_eq!(contract_descriptions[0].contract.symbol, "AAPL");
2413
        assert_eq!(contract_descriptions[0].contract.security_type, crate::contracts::SecurityType::Stock);
2414
        assert_eq!(contract_descriptions[0].contract.primary_exchange, "NASDAQ");
2415
        assert_eq!(contract_descriptions[0].contract.currency, "USD");
2416
        assert_eq!(contract_descriptions[0].derivative_security_types.len(), 2);
2417
        assert_eq!(contract_descriptions[0].derivative_security_types[0], "OPT");
2418
        assert_eq!(contract_descriptions[0].derivative_security_types[1], "WAR");
2419
        assert_eq!(contract_descriptions[0].contract.description, "Apple Inc.");
2420
        assert_eq!(contract_descriptions[0].contract.issuer_id, "AAPL123");
2421

2422
        // Second contract description
2423
        assert_eq!(contract_descriptions[1].contract.contract_id, 276821);
2424
        assert_eq!(contract_descriptions[1].contract.symbol, "MSFT");
2425
        assert_eq!(contract_descriptions[1].contract.security_type, crate::contracts::SecurityType::Stock);
2426
        assert_eq!(contract_descriptions[1].contract.primary_exchange, "NASDAQ");
2427
        assert_eq!(contract_descriptions[1].contract.currency, "USD");
2428
        assert_eq!(contract_descriptions[1].derivative_security_types.len(), 1);
2429
        assert_eq!(contract_descriptions[1].derivative_security_types[0], "OPT");
2430
        assert_eq!(contract_descriptions[1].contract.description, "Microsoft Corporation");
2431
        assert_eq!(contract_descriptions[1].contract.issuer_id, "MSFT456");
2432

2433
        // Verify request format
2434
        let requests = gateway.requests();
2435
        assert_eq!(requests.len(), 1, "Should have 1 request");
2436
        // Request format: RequestMatchingSymbols(81), request_id, pattern
2437
        assert!(requests[0].starts_with("81\0"), "Request should start with message type 81");
2438
        assert!(requests[0].contains("\0AAP\0"), "Request should contain the pattern AAP");
2439
    }
2440

2441
    #[test]
2442
    fn test_market_rule() {
2443
        let gateway = setup_market_rule();
2444

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

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

2449
        // Verify market rule ID
2450
        assert_eq!(market_rule.market_rule_id, 26, "Market rule ID should be 26");
2451

2452
        // Verify price increments
2453
        assert_eq!(market_rule.price_increments.len(), 3, "Should have 3 price increments");
2454

2455
        // First increment: 0-100, increment 0.01
2456
        assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "First increment low edge");
2457
        assert_eq!(market_rule.price_increments[0].increment, 0.01, "First increment value");
2458

2459
        // Second increment: 100-1000, increment 0.05
2460
        assert_eq!(market_rule.price_increments[1].low_edge, 100.0, "Second increment low edge");
2461
        assert_eq!(market_rule.price_increments[1].increment, 0.05, "Second increment value");
2462

2463
        // Third increment: 1000+, increment 0.10
2464
        assert_eq!(market_rule.price_increments[2].low_edge, 1000.0, "Third increment low edge");
2465
        assert_eq!(market_rule.price_increments[2].increment, 0.10, "Third increment value");
2466

2467
        // Verify request format
2468
        let requests = gateway.requests();
2469
        assert_eq!(requests.len(), 1, "Should have 1 request");
2470
        // Request format: RequestMarketRule(91), market_rule_id
2471
        assert_eq!(requests[0], "91\026\0", "Request should be message type 91 with market rule ID 26");
2472
    }
2473

2474
    #[test]
2475
    fn test_calculate_option_price() {
2476
        let gateway = setup_calculate_option_price();
2477

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

2480
        // Create an option contract
2481
        let contract = crate::contracts::Contract {
2482
            symbol: "AAPL".to_string(),
2483
            security_type: crate::contracts::SecurityType::Option,
2484
            exchange: "SMART".to_string(),
2485
            currency: "USD".to_string(),
2486
            last_trade_date_or_contract_month: "20250120".to_string(),
2487
            strike: 100.0,
2488
            right: "C".to_string(),
2489
            ..Default::default()
2490
        };
2491

2492
        let volatility = 0.25;
2493
        let underlying_price = 100.0;
2494

2495
        let computation = client
2496
            .calculate_option_price(&contract, volatility, underlying_price)
2497
            .expect("Failed to calculate option price");
2498

2499
        // Verify computation results
2500
        assert_eq!(
2501
            computation.field,
2502
            crate::contracts::tick_types::TickType::ModelOption,
2503
            "Should be ModelOption tick type"
2504
        );
2505
        assert_eq!(computation.tick_attribute, Some(0), "Tick attribute should be 0");
2506
        assert_eq!(computation.implied_volatility, Some(0.25), "Implied volatility should match");
2507
        assert_eq!(computation.delta, Some(0.5), "Delta should be 0.5");
2508
        assert_eq!(computation.option_price, Some(12.75), "Option price should be 12.75");
2509
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2510
        assert_eq!(computation.gamma, Some(0.05), "Gamma should be 0.05");
2511
        assert_eq!(computation.vega, Some(0.02), "Vega should be 0.02");
2512
        assert_eq!(computation.theta, Some(-0.01), "Theta should be -0.01");
2513
        assert_eq!(computation.underlying_price, Some(100.0), "Underlying price should be 100");
2514

2515
        // Verify request format
2516
        let requests = gateway.requests();
2517
        assert_eq!(requests.len(), 1, "Should have 1 request");
2518
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, volatility, underlying_price
2519
        assert!(
2520
            requests[0].starts_with("54\03\0"),
2521
            "Request should start with message type 54 and version 3"
2522
        );
2523
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2524
        assert!(requests[0].contains("\00.25\0"), "Request should contain volatility 0.25");
2525
        assert!(requests[0].contains("\0100\0"), "Request should contain underlying price 100");
2526
    }
2527

2528
    #[test]
2529
    fn test_calculate_implied_volatility() {
2530
        let gateway = setup_calculate_implied_volatility();
2531

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

2534
        // Create an option contract
2535
        let contract = crate::contracts::Contract {
2536
            symbol: "MSFT".to_string(),
2537
            security_type: crate::contracts::SecurityType::Option,
2538
            exchange: "SMART".to_string(),
2539
            currency: "USD".to_string(),
2540
            last_trade_date_or_contract_month: "20250220".to_string(),
2541
            strike: 105.0,
2542
            right: "P".to_string(), // Put option
2543
            ..Default::default()
2544
        };
2545

2546
        let option_price = 15.50;
2547
        let underlying_price = 105.0;
2548

2549
        let computation = client
2550
            .calculate_implied_volatility(&contract, option_price, underlying_price)
2551
            .expect("Failed to calculate implied volatility");
2552

2553
        // Verify computation results
2554
        assert_eq!(
2555
            computation.field,
2556
            crate::contracts::tick_types::TickType::ModelOption,
2557
            "Should be ModelOption tick type"
2558
        );
2559
        assert_eq!(computation.tick_attribute, Some(1), "Tick attribute should be 1 (price-based)");
2560
        assert_eq!(computation.implied_volatility, Some(0.35), "Implied volatility should be 0.35");
2561
        assert_eq!(computation.delta, Some(0.45), "Delta should be 0.45");
2562
        assert_eq!(computation.option_price, Some(15.50), "Option price should be 15.50");
2563
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2564
        assert_eq!(computation.gamma, Some(0.04), "Gamma should be 0.04");
2565
        assert_eq!(computation.vega, Some(0.03), "Vega should be 0.03");
2566
        assert_eq!(computation.theta, Some(-0.02), "Theta should be -0.02");
2567
        assert_eq!(computation.underlying_price, Some(105.0), "Underlying price should be 105");
2568

2569
        // Verify request format
2570
        let requests = gateway.requests();
2571
        assert_eq!(requests.len(), 1, "Should have 1 request");
2572
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, option_price, underlying_price
2573
        assert!(
2574
            requests[0].starts_with("54\03\0"),
2575
            "Request should start with message type 54 and version 3"
2576
        );
2577
        assert!(requests[0].contains("\0MSFT\0"), "Request should contain symbol MSFT");
2578
        assert!(requests[0].contains("\015.5\0"), "Request should contain option price 15.5");
2579
        assert!(requests[0].contains("\0105\0"), "Request should contain underlying price 105");
2580
    }
2581

2582
    #[test]
2583
    fn test_option_chain() {
2584
        let gateway = setup_option_chain();
2585

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

2588
        let symbol = "AAPL";
2589
        let exchange = ""; // Empty means all exchanges
2590
        let security_type = crate::contracts::SecurityType::Stock;
2591
        let contract_id = 0; // 0 means use symbol
2592

2593
        let subscription = client
2594
            .option_chain(symbol, exchange, security_type, contract_id)
2595
            .expect("Failed to get option chain");
2596

2597
        let mut chains = Vec::new();
2598
        for chain in subscription {
2599
            chains.push(chain);
2600
        }
2601

2602
        // Should have received 2 option chains (SMART and CBOE)
2603
        assert_eq!(chains.len(), 2, "Should have 2 option chains");
2604

2605
        // First chain - SMART exchange
2606
        assert_eq!(chains[0].exchange, "SMART", "First chain should be SMART");
2607
        assert_eq!(chains[0].underlying_contract_id, 265598, "Underlying contract ID");
2608
        assert_eq!(chains[0].trading_class, "AAPL", "Trading class");
2609
        assert_eq!(chains[0].multiplier, "100", "Multiplier");
2610
        assert_eq!(chains[0].expirations.len(), 3, "Should have 3 expirations");
2611
        assert_eq!(chains[0].expirations[0], "20250117");
2612
        assert_eq!(chains[0].expirations[1], "20250221");
2613
        assert_eq!(chains[0].expirations[2], "20250321");
2614
        assert_eq!(chains[0].strikes.len(), 5, "Should have 5 strikes");
2615
        assert_eq!(chains[0].strikes[0], 90.0);
2616
        assert_eq!(chains[0].strikes[1], 95.0);
2617
        assert_eq!(chains[0].strikes[2], 100.0);
2618
        assert_eq!(chains[0].strikes[3], 105.0);
2619
        assert_eq!(chains[0].strikes[4], 110.0);
2620

2621
        // Second chain - CBOE exchange
2622
        assert_eq!(chains[1].exchange, "CBOE", "Second chain should be CBOE");
2623
        assert_eq!(chains[1].underlying_contract_id, 265598, "Underlying contract ID");
2624
        assert_eq!(chains[1].trading_class, "AAPL", "Trading class");
2625
        assert_eq!(chains[1].multiplier, "100", "Multiplier");
2626
        assert_eq!(chains[1].expirations.len(), 2, "Should have 2 expirations");
2627
        assert_eq!(chains[1].expirations[0], "20250117");
2628
        assert_eq!(chains[1].expirations[1], "20250221");
2629
        assert_eq!(chains[1].strikes.len(), 4, "Should have 4 strikes");
2630
        assert_eq!(chains[1].strikes[0], 95.0);
2631
        assert_eq!(chains[1].strikes[1], 100.0);
2632
        assert_eq!(chains[1].strikes[2], 105.0);
2633
        assert_eq!(chains[1].strikes[3], 110.0);
2634

2635
        // Verify request format
2636
        let requests = gateway.requests();
2637
        assert_eq!(requests.len(), 1, "Should have 1 request");
2638
        // Request format: RequestSecurityDefinitionOptionalParameters(78), request_id, symbol, exchange, security_type, contract_id
2639
        assert!(requests[0].starts_with("78\0"), "Request should start with message type 78");
2640
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2641
        assert!(requests[0].contains("\0STK\0"), "Request should contain security type STK");
2642
    }
2643

2644
    #[test]
2645
    fn test_place_order() {
2646
        use crate::client::common::tests::setup_place_order;
2647
        use crate::contracts::Contract;
2648
        use crate::orders::{order_builder, Action, PlaceOrder};
2649

2650
        // Initialize env_logger for debug output
2651
        let _ = env_logger::try_init();
2652

2653
        let gateway = setup_place_order();
2654
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2655

2656
        // Create a stock contract
2657
        let contract = Contract::stock("AAPL");
2658

2659
        // Create a market order
2660
        let order = order_builder::market_order(Action::Buy, 100.0);
2661

2662
        // Use order ID 1001 to match the mock responses
2663
        let order_id = 1001;
2664

2665
        // Place the order
2666
        let subscription = client.place_order(order_id, &contract, &order).expect("Failed to place order");
2667

2668
        // Collect all events from the subscription
2669
        let mut order_status_count = 0;
2670
        let mut _open_order_count = 0;
2671
        let mut execution_count = 0;
2672
        let mut commission_count = 0;
2673

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

2678
        // Use the iterator directly
2679
        for event in subscription.into_iter() {
2680
            if events_received >= 10 {
2681
                println!("Reached event limit");
2682
                break;
2683
            }
2684
            events_received += 1;
2685
            let event_type = match &event {
2686
                PlaceOrder::OrderStatus(_) => "OrderStatus",
2687
                PlaceOrder::OpenOrder(_) => "OpenOrder",
2688
                PlaceOrder::ExecutionData(_) => "ExecutionData",
2689
                PlaceOrder::CommissionReport(_) => "CommissionReport",
2690
                PlaceOrder::Message(_) => "Message",
2691
            };
2692
            println!("Event {}: {} received", events_received, event_type);
2693
            match event {
2694
                PlaceOrder::OrderStatus(status) => {
2695
                    order_status_count += 1;
2696
                    assert_eq!(status.order_id, order_id);
2697

2698
                    if order_status_count == 1 {
2699
                        // First status: PreSubmitted
2700
                        assert_eq!(status.status, "PreSubmitted");
2701
                        assert_eq!(status.filled, 0.0);
2702
                        assert_eq!(status.remaining, 100.0);
2703
                    } else if order_status_count == 2 {
2704
                        // Second status: Submitted
2705
                        assert_eq!(status.status, "Submitted");
2706
                        assert_eq!(status.filled, 0.0);
2707
                        assert_eq!(status.remaining, 100.0);
2708
                    } else if order_status_count == 3 {
2709
                        // Third status: Filled
2710
                        assert_eq!(status.status, "Filled");
2711
                        assert_eq!(status.filled, 100.0);
2712
                        assert_eq!(status.remaining, 0.0);
2713
                        assert_eq!(status.average_fill_price, 150.25);
2714
                    }
2715
                }
2716
                PlaceOrder::OpenOrder(order_data) => {
2717
                    _open_order_count += 1;
2718
                    assert_eq!(order_data.order_id, order_id);
2719
                    assert_eq!(order_data.contract.symbol, "AAPL");
2720
                    assert_eq!(order_data.contract.contract_id, 265598);
2721
                    assert_eq!(order_data.order.action, Action::Buy);
2722
                    assert_eq!(order_data.order.total_quantity, 100.0);
2723
                    assert_eq!(order_data.order.order_type, "LMT");
2724
                    assert_eq!(order_data.order.limit_price, Some(1.0));
2725
                }
2726
                PlaceOrder::ExecutionData(exec_data) => {
2727
                    execution_count += 1;
2728
                    assert_eq!(exec_data.execution.order_id, order_id);
2729
                    assert_eq!(exec_data.contract.symbol, "AAPL");
2730
                    assert_eq!(exec_data.execution.shares, 100.0);
2731
                    assert_eq!(exec_data.execution.price, 150.25);
2732
                }
2733
                PlaceOrder::CommissionReport(report) => {
2734
                    commission_count += 1;
2735
                    assert_eq!(report.commission, 1.25);
2736
                    assert_eq!(report.currency, "USD");
2737
                }
2738
                PlaceOrder::Message(_) => {
2739
                    // Skip any messages
2740
                }
2741
            }
2742
        }
2743

2744
        println!("Total events received: {}", events_received);
2745
        println!(
2746
            "OrderStatus: {}, Execution: {}, Commission: {}",
2747
            order_status_count, execution_count, commission_count
2748
        );
2749

2750
        // Verify we received all expected events
2751
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2752
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2753
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2754
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2755

2756
        // Verify the request was sent
2757
        let requests = gateway.requests();
2758
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2759
        // PlaceOrder message type is 3
2760
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2761
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2762
    }
2763

2764
    #[test]
2765
    fn test_submit_order_with_order_update_stream() {
2766
        use crate::client::common::tests::setup_place_order;
2767
        use crate::contracts::Contract;
2768
        use crate::orders::{order_builder, Action, OrderUpdate};
2769

2770
        // Initialize env_logger for debug output
2771
        let _ = env_logger::try_init();
2772

2773
        let gateway = setup_place_order();
2774
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2775

2776
        // Create a stock contract
2777
        let contract = Contract::stock("AAPL");
2778

2779
        // Create a market order
2780
        let order = order_builder::market_order(Action::Buy, 100.0);
2781

2782
        // Use order ID 1001 to match the mock responses
2783
        let order_id = 1001;
2784

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

2788
        // Submit the order (fire and forget)
2789
        client.submit_order(order_id, &contract, &order).expect("Failed to submit order");
2790

2791
        // Collect events from the update stream
2792
        let mut order_status_count = 0;
2793
        let mut _open_order_count = 0;
2794
        let mut execution_count = 0;
2795
        let mut commission_count = 0;
2796
        let mut events_received = 0;
2797

2798
        // Read events from the update stream
2799
        // Use next_timeout to avoid blocking forever
2800
        println!("Starting to read from update stream...");
2801
        let timeout = std::time::Duration::from_millis(500);
2802

2803
        while events_received < 6 {
2804
            if let Some(update) = update_stream.next_timeout(timeout) {
2805
                events_received += 1;
2806
                println!("Event {}: {:?}", events_received, &update);
2807

2808
                match update {
2809
                    OrderUpdate::OrderStatus(status) => {
2810
                        order_status_count += 1;
2811
                        assert_eq!(status.order_id, order_id);
2812

2813
                        if order_status_count == 1 {
2814
                            // First status: PreSubmitted
2815
                            assert_eq!(status.status, "PreSubmitted");
2816
                            assert_eq!(status.filled, 0.0);
2817
                            assert_eq!(status.remaining, 100.0);
2818
                        } else if order_status_count == 2 {
2819
                            // Second status: Submitted
2820
                            assert_eq!(status.status, "Submitted");
2821
                            assert_eq!(status.filled, 0.0);
2822
                            assert_eq!(status.remaining, 100.0);
2823
                        } else if order_status_count == 3 {
2824
                            // Third status: Filled
2825
                            assert_eq!(status.status, "Filled");
2826
                            assert_eq!(status.filled, 100.0);
2827
                            assert_eq!(status.remaining, 0.0);
2828
                            assert_eq!(status.average_fill_price, 150.25);
2829
                        }
2830
                    }
2831
                    OrderUpdate::OpenOrder(order_data) => {
2832
                        _open_order_count += 1;
2833
                        assert_eq!(order_data.order_id, order_id);
2834
                        assert_eq!(order_data.contract.symbol, "AAPL");
2835
                        assert_eq!(order_data.contract.contract_id, 265598);
2836
                        assert_eq!(order_data.order.action, Action::Buy);
2837
                        assert_eq!(order_data.order.total_quantity, 100.0);
2838
                        assert_eq!(order_data.order.order_type, "LMT");
2839
                        assert_eq!(order_data.order.limit_price, Some(1.0));
2840
                    }
2841
                    OrderUpdate::ExecutionData(exec_data) => {
2842
                        execution_count += 1;
2843
                        assert_eq!(exec_data.execution.order_id, order_id);
2844
                        assert_eq!(exec_data.contract.symbol, "AAPL");
2845
                        assert_eq!(exec_data.execution.shares, 100.0);
2846
                        assert_eq!(exec_data.execution.price, 150.25);
2847
                    }
2848
                    OrderUpdate::CommissionReport(report) => {
2849
                        commission_count += 1;
2850
                        assert_eq!(report.commission, 1.25);
2851
                        assert_eq!(report.currency, "USD");
2852
                    }
2853
                    OrderUpdate::Message(_) => {
2854
                        // Skip any messages
2855
                    }
2856
                }
2857
            } else {
2858
                // Timeout reached, no more messages available
2859
                break;
2860
            }
2861
        }
2862

2863
        // Verify we received all expected events
2864
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2865
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2866
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2867
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2868

2869
        // Verify the request was sent
2870
        let requests = gateway.requests();
2871
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2872
        // PlaceOrder message type is 3
2873
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2874
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2875
    }
2876

2877
    #[test]
2878
    fn test_open_orders() {
2879
        use crate::client::common::tests::setup_open_orders;
2880
        use crate::orders::{Action, Orders};
2881

2882
        // Initialize env_logger for debug output
2883
        let _ = env_logger::try_init();
2884

2885
        let gateway = setup_open_orders();
2886
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2887

2888
        // Request open orders
2889
        let subscription = client.open_orders().expect("Failed to request open orders");
2890

2891
        // Collect orders from the subscription
2892
        let mut orders = Vec::new();
2893
        for result in subscription {
2894
            match result {
2895
                Orders::OrderData(order_data) => {
2896
                    orders.push(order_data);
2897
                }
2898
                Orders::OrderStatus(_) => {
2899
                    // Skip order status messages for this test
2900
                }
2901
                Orders::Notice(_) => {
2902
                    // Skip notices
2903
                }
2904
            }
2905
        }
2906

2907
        // Verify we received 2 orders
2908
        assert_eq!(orders.len(), 2, "Should receive 2 open orders");
2909

2910
        // Verify first order (AAPL)
2911
        let order1 = &orders[0];
2912
        assert_eq!(order1.order_id, 1001);
2913
        assert_eq!(order1.contract.symbol, "AAPL");
2914
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
2915
        assert_eq!(order1.order.action, Action::Buy);
2916
        assert_eq!(order1.order.total_quantity, 100.0);
2917
        assert_eq!(order1.order.order_type, "MKT");
2918
        assert_eq!(order1.order_state.status, "PreSubmitted");
2919

2920
        // Verify second order (MSFT)
2921
        let order2 = &orders[1];
2922
        assert_eq!(order2.order_id, 1002);
2923
        assert_eq!(order2.contract.symbol, "MSFT");
2924
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
2925
        assert_eq!(order2.order.action, Action::Sell);
2926
        assert_eq!(order2.order.total_quantity, 50.0);
2927
        assert_eq!(order2.order.order_type, "LMT");
2928
        assert_eq!(order2.order.limit_price, Some(350.0));
2929
        assert_eq!(order2.order_state.status, "Submitted");
2930

2931
        // Verify the request was sent correctly
2932
        let requests = gateway.requests();
2933
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2934
        assert_eq!(requests[0], "5\01\0", "Request should be RequestOpenOrders with version 1");
2935
    }
2936

2937
    #[test]
2938
    fn test_all_open_orders() {
2939
        use crate::client::common::tests::setup_all_open_orders;
2940
        use crate::orders::{Action, Orders};
2941

2942
        // Initialize env_logger for debug output
2943
        let _ = env_logger::try_init();
2944

2945
        let gateway = setup_all_open_orders();
2946
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2947

2948
        // Request all open orders
2949
        let subscription = client.all_open_orders().expect("Failed to request all open orders");
2950

2951
        // Collect orders from the subscription
2952
        let mut orders = Vec::new();
2953
        for result in subscription {
2954
            match result {
2955
                Orders::OrderData(order_data) => {
2956
                    orders.push(order_data);
2957
                }
2958
                Orders::OrderStatus(_) => {
2959
                    // Skip order status messages for this test
2960
                }
2961
                Orders::Notice(_) => {
2962
                    // Skip notices
2963
                }
2964
            }
2965
        }
2966

2967
        // Verify we received 3 orders (from different clients)
2968
        assert_eq!(orders.len(), 3, "Should receive 3 open orders from all accounts");
2969

2970
        // Verify first order (TSLA from client 101)
2971
        let order1 = &orders[0];
2972
        assert_eq!(order1.order_id, 2001);
2973
        assert_eq!(order1.contract.symbol, "TSLA");
2974
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
2975
        assert_eq!(order1.order.action, Action::Buy);
2976
        assert_eq!(order1.order.total_quantity, 10.0);
2977
        assert_eq!(order1.order.order_type, "LMT");
2978
        assert_eq!(order1.order.limit_price, Some(420.0));
2979
        assert_eq!(order1.order.account, "DU1236110");
2980

2981
        // Verify second order (AMZN from client 102)
2982
        let order2 = &orders[1];
2983
        assert_eq!(order2.order_id, 2002);
2984
        assert_eq!(order2.contract.symbol, "AMZN");
2985
        assert_eq!(order2.order.action, Action::Sell);
2986
        assert_eq!(order2.order.total_quantity, 5.0);
2987
        assert_eq!(order2.order.order_type, "MKT");
2988
        assert_eq!(order2.order.account, "DU1236111");
2989

2990
        // Verify third order (GOOGL from current client 100)
2991
        let order3 = &orders[2];
2992
        assert_eq!(order3.order_id, 1003);
2993
        assert_eq!(order3.contract.symbol, "GOOGL");
2994
        assert_eq!(order3.order.action, Action::Buy);
2995
        assert_eq!(order3.order.total_quantity, 20.0);
2996
        assert_eq!(order3.order.order_type, "LMT");
2997
        assert_eq!(order3.order.limit_price, Some(2800.0));
2998
        assert_eq!(order3.order.account, "DU1236109");
2999

3000
        // Verify the request was sent correctly
3001
        let requests = gateway.requests();
3002
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3003
        assert_eq!(requests[0], "16\01\0", "Request should be RequestAllOpenOrders with version 1");
3004
    }
3005

3006
    #[test]
3007
    fn test_auto_open_orders() {
3008
        use crate::client::common::tests::setup_auto_open_orders;
3009
        use crate::orders::Orders;
3010

3011
        // Initialize env_logger for debug output
3012
        let _ = env_logger::try_init();
3013

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

3019
        // Request auto open orders with auto_bind=true
3020
        let subscription = client.auto_open_orders(true).expect("Failed to request auto open orders");
3021

3022
        // Collect messages from the subscription
3023
        let mut order_statuses = Vec::new();
3024
        let mut orders = Vec::new();
3025
        for result in subscription {
3026
            match result {
3027
                Orders::OrderData(order_data) => {
3028
                    orders.push(order_data);
3029
                }
3030
                Orders::OrderStatus(status) => {
3031
                    order_statuses.push(status);
3032
                }
3033
                Orders::Notice(_) => {
3034
                    // Skip notices
3035
                }
3036
            }
3037
        }
3038

3039
        // Verify we received order status updates
3040
        assert_eq!(order_statuses.len(), 2, "Should receive 2 order status updates");
3041

3042
        // Verify first status (PreSubmitted)
3043
        let status1 = &order_statuses[0];
3044
        assert_eq!(status1.order_id, 3001);
3045
        assert_eq!(status1.status, "PreSubmitted");
3046

3047
        // Verify second status (Submitted)
3048
        let status2 = &order_statuses[1];
3049
        assert_eq!(status2.order_id, 3001);
3050
        assert_eq!(status2.status, "Submitted");
3051

3052
        // Verify we received 1 order
3053
        assert_eq!(orders.len(), 1, "Should receive 1 order");
3054

3055
        // Verify the order (FB from TWS)
3056
        let order = &orders[0];
3057
        assert_eq!(order.order_id, 3001);
3058
        assert_eq!(order.contract.symbol, "FB");
3059
        assert_eq!(order.contract.security_type, crate::contracts::SecurityType::Stock);
3060
        assert_eq!(order.order.action, crate::orders::Action::Buy);
3061
        assert_eq!(order.order.total_quantity, 50.0);
3062
        assert_eq!(order.order.order_type, "MKT");
3063
        assert_eq!(order.order.account, "TWS");
3064

3065
        // Verify the request was sent correctly
3066
        let requests = gateway.requests();
3067
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3068
        assert_eq!(
3069
            requests[0], "15\01\01\0",
3070
            "Request should be RequestAutoOpenOrders with version 1 and auto_bind=true"
3071
        );
3072
    }
3073

3074
    #[test]
3075
    fn test_completed_orders() {
3076
        use crate::client::common::tests::setup_completed_orders;
3077
        use crate::orders::{Action, Orders};
3078

3079
        // Initialize env_logger for debug output
3080
        let _ = env_logger::try_init();
3081

3082
        let gateway = setup_completed_orders();
3083
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3084

3085
        // Request completed orders (api_only=false to get all completed orders)
3086
        let subscription = client.completed_orders(false).expect("Failed to request completed orders");
3087

3088
        // Collect orders from the subscription
3089
        let mut orders = Vec::new();
3090
        for result in subscription {
3091
            match result {
3092
                Orders::OrderData(order_data) => {
3093
                    orders.push(order_data);
3094
                }
3095
                Orders::OrderStatus(_) => {
3096
                    // Skip order status messages
3097
                }
3098
                Orders::Notice(_) => {
3099
                    // Skip notices
3100
                }
3101
            }
3102
        }
3103

3104
        // Verify we received 2 completed orders
3105
        assert_eq!(orders.len(), 2, "Should receive 2 completed orders");
3106

3107
        // Verify first completed order (ES futures - based on captured data)
3108
        let order1 = &orders[0];
3109
        // CompletedOrder messages don't have order_id in the message, defaults to -1
3110
        assert_eq!(order1.order_id, -1);
3111
        assert_eq!(order1.contract.symbol, "ES");
3112
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Future);
3113
        assert_eq!(order1.order.action, Action::Buy);
3114
        assert_eq!(order1.order.total_quantity, 1.0);
3115
        assert_eq!(order1.order.order_type, "LMT");
3116
        assert_eq!(order1.order_state.status, "Cancelled");
3117
        assert_eq!(order1.order.perm_id, 616088517);
3118

3119
        // Verify second completed order (AAPL)
3120
        let order2 = &orders[1];
3121
        assert_eq!(order2.order_id, -1); // CompletedOrder messages don't have order_id
3122
        assert_eq!(order2.contract.symbol, "AAPL");
3123
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3124
        assert_eq!(order2.order.action, Action::Buy);
3125
        assert_eq!(order2.order.total_quantity, 100.0);
3126
        assert_eq!(order2.order.order_type, "MKT");
3127
        assert_eq!(order2.order_state.status, "Filled");
3128
        assert_eq!(order2.order.perm_id, 1377295418);
3129

3130
        // Verify the request was sent correctly
3131
        let requests = gateway.requests();
3132
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3133
        assert_eq!(requests[0], "99\00\0", "Request should be RequestCompletedOrders with api_only=false");
3134
    }
3135

3136
    #[test]
3137
    fn test_cancel_order() {
3138
        use crate::client::common::tests::setup_cancel_order;
3139
        use crate::messages::Notice;
3140
        use crate::orders::CancelOrder;
3141

3142
        // Initialize env_logger for debug output
3143
        let _ = env_logger::try_init();
3144

3145
        let gateway = setup_cancel_order();
3146
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3147

3148
        // Cancel order with ID 1001
3149
        let order_id = 1001;
3150
        let manual_order_cancel_time = "";
3151

3152
        // Call cancel_order and get the result
3153
        let result = client.cancel_order(order_id, manual_order_cancel_time);
3154

3155
        // Verify the result
3156
        match result {
3157
            Ok(cancel_result) => {
3158
                // Iterate through the cancellation results
3159
                let mut order_status_received = false;
3160
                let mut notice_received = false;
3161

3162
                for item in cancel_result {
3163
                    match item {
3164
                        CancelOrder::OrderStatus(status) => {
3165
                            assert_eq!(status.order_id, order_id);
3166
                            assert_eq!(status.status, "Cancelled");
3167
                            assert_eq!(status.filled, 0.0);
3168
                            assert_eq!(status.remaining, 100.0);
3169
                            order_status_received = true;
3170
                            println!("Received OrderStatus: {:?}", status);
3171
                        }
3172
                        CancelOrder::Notice(Notice { code, message }) => {
3173
                            // Notice messages with code 202 are order cancellation confirmations
3174
                            // The message should contain the order ID in the format
3175
                            assert_eq!(code, 202);
3176
                            assert!(message.contains("Order Cancelled"));
3177
                            notice_received = true;
3178
                            println!("Received Notice: code={}, message={}", code, message);
3179
                        }
3180
                    }
3181
                }
3182

3183
                assert!(order_status_received, "Should have received OrderStatus");
3184
                assert!(notice_received, "Should have received Notice confirmation");
3185
            }
3186
            Err(e) => panic!("Failed to cancel order: {}", e),
3187
        }
3188

3189
        // Verify the request was sent correctly
3190
        let requests = gateway.requests();
3191
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3192
        assert!(requests[0].starts_with("4\0"), "Request should be a CancelOrder message");
3193
        assert!(requests[0].contains(&format!("{}\0", order_id)), "Request should contain order ID");
3194
    }
3195

3196
    #[test]
3197
    fn test_global_cancel() {
3198
        use crate::client::common::tests::setup_global_cancel;
3199

3200
        // Initialize env_logger for debug output
3201
        let _ = env_logger::try_init();
3202

3203
        let gateway = setup_global_cancel();
3204
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3205

3206
        // Call global_cancel
3207
        let result = client.global_cancel();
3208

3209
        // Verify the result
3210
        match result {
3211
            Ok(()) => {
3212
                println!("Global cancel request sent successfully");
3213
            }
3214
            Err(e) => panic!("Failed to send global cancel: {}", e),
3215
        }
3216

3217
        // Give the gateway time to process the request
3218
        std::thread::sleep(std::time::Duration::from_millis(100));
3219

3220
        // Verify the request was sent correctly
3221
        let requests = gateway.requests();
3222
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3223
        assert_eq!(requests[0], "58\01\0", "Request should be a RequestGlobalCancel message with version 1");
3224
    }
3225

3226
    #[test]
3227
    fn test_executions() {
3228
        use crate::client::common::tests::setup_executions;
3229
        use crate::contracts::SecurityType;
3230
        use crate::orders::{ExecutionFilter, Executions};
3231

3232
        // Initialize env_logger for debug output
3233
        let _ = env_logger::try_init();
3234

3235
        let gateway = setup_executions();
3236
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3237

3238
        // Create an execution filter
3239
        let filter = ExecutionFilter {
3240
            client_id: Some(CLIENT_ID),
3241
            account_code: "DU1234567".to_string(),
3242
            time: "".to_string(),          // Empty means all time
3243
            symbol: "".to_string(),        // Empty means all symbols
3244
            security_type: "".to_string(), // Empty means all types
3245
            exchange: "".to_string(),      // Empty means all exchanges
3246
            side: "".to_string(),          // Empty means all sides
3247
        };
3248

3249
        // Request executions
3250
        let subscription = client.executions(filter).expect("Failed to request executions");
3251

3252
        // Collect executions from the subscription
3253
        let mut execution_data = Vec::new();
3254
        let mut commission_reports = Vec::new();
3255

3256
        for result in subscription {
3257
            match result {
3258
                Executions::ExecutionData(data) => {
3259
                    execution_data.push(data);
3260
                }
3261
                Executions::CommissionReport(report) => {
3262
                    commission_reports.push(report);
3263
                }
3264
                Executions::Notice(_) => {
3265
                    // Skip notices
3266
                }
3267
            }
3268
        }
3269

3270
        // Verify we received 3 executions and 3 commission reports
3271
        assert_eq!(execution_data.len(), 3, "Should receive 3 execution data messages");
3272
        assert_eq!(commission_reports.len(), 3, "Should receive 3 commission reports");
3273

3274
        // Verify first execution (AAPL stock)
3275
        let exec1 = &execution_data[0];
3276
        assert_eq!(exec1.request_id, 9000);
3277
        assert_eq!(exec1.execution.order_id, 1001);
3278
        assert_eq!(exec1.contract.symbol, "AAPL");
3279
        assert_eq!(exec1.contract.security_type, SecurityType::Stock);
3280
        assert_eq!(exec1.execution.execution_id, "000e1a2b.67890abc.01.01");
3281
        assert_eq!(exec1.execution.side, "BOT");
3282
        assert_eq!(exec1.execution.shares, 100.0);
3283
        assert_eq!(exec1.execution.price, 150.25);
3284

3285
        // Verify first commission report
3286
        let comm1 = &commission_reports[0];
3287
        assert_eq!(comm1.execution_id, "000e1a2b.67890abc.01.01");
3288
        assert_eq!(comm1.commission, 1.25);
3289
        assert_eq!(comm1.currency, "USD");
3290

3291
        // Verify second execution (ES futures)
3292
        let exec2 = &execution_data[1];
3293
        assert_eq!(exec2.request_id, 9000);
3294
        assert_eq!(exec2.execution.order_id, 1002);
3295
        assert_eq!(exec2.contract.symbol, "ES");
3296
        assert_eq!(exec2.contract.security_type, SecurityType::Future);
3297
        assert_eq!(exec2.execution.execution_id, "000e1a2b.67890def.02.01");
3298
        assert_eq!(exec2.execution.side, "SLD");
3299
        assert_eq!(exec2.execution.shares, 5.0);
3300
        assert_eq!(exec2.execution.price, 5050.25);
3301

3302
        // Verify second commission report
3303
        let comm2 = &commission_reports[1];
3304
        assert_eq!(comm2.execution_id, "000e1a2b.67890def.02.01");
3305
        assert_eq!(comm2.commission, 2.50);
3306
        assert_eq!(comm2.realized_pnl, Some(125.50));
3307

3308
        // Verify third execution (SPY options)
3309
        let exec3 = &execution_data[2];
3310
        assert_eq!(exec3.request_id, 9000);
3311
        assert_eq!(exec3.execution.order_id, 1003);
3312
        assert_eq!(exec3.contract.symbol, "SPY");
3313
        assert_eq!(exec3.contract.security_type, SecurityType::Option);
3314
        assert_eq!(exec3.execution.execution_id, "000e1a2b.67890ghi.03.01");
3315
        assert_eq!(exec3.execution.side, "BOT");
3316
        assert_eq!(exec3.execution.shares, 10.0);
3317
        assert_eq!(exec3.execution.price, 2.50);
3318

3319
        // Verify third commission report
3320
        let comm3 = &commission_reports[2];
3321
        assert_eq!(comm3.execution_id, "000e1a2b.67890ghi.03.01");
3322
        assert_eq!(comm3.commission, 0.65);
3323
        assert_eq!(comm3.realized_pnl, Some(250.00));
3324

3325
        // Verify the request was sent correctly
3326
        let requests = gateway.requests();
3327
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3328
        // Request format: RequestExecutions(7), version(3), request_id(9000), client_id, account_code, time, symbol, security_type, exchange, side
3329
        assert_eq!(
3330
            requests[0], "7\03\09000\0100\0DU1234567\0\0\0\0\0\0",
3331
            "Request should be RequestExecutions with correct filter parameters"
3332
        );
3333
    }
3334

3335
    #[test]
3336
    fn test_exercise_options() {
3337
        use crate::client::common::tests::setup_exercise_options;
3338
        use crate::contracts::{Contract, SecurityType};
3339
        use crate::orders::{ExerciseAction, ExerciseOptions};
3340
        use time::macros::datetime;
3341

3342
        // Initialize env_logger for debug output
3343
        let _ = env_logger::try_init();
3344

3345
        let gateway = setup_exercise_options();
3346
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3347

3348
        // Create option contract for SPY
3349
        let contract = Contract {
3350
            contract_id: 123456789,
3351
            symbol: "SPY".to_string(),
3352
            security_type: SecurityType::Option,
3353
            last_trade_date_or_contract_month: "20240126".to_string(),
3354
            strike: 450.0,
3355
            right: "C".to_string(), // Call option
3356
            multiplier: "100".to_string(),
3357
            exchange: "CBOE".to_string(),
3358
            currency: "USD".to_string(),
3359
            local_symbol: "SPY240126C00450000".to_string(),
3360
            trading_class: "SPY".to_string(),
3361
            ..Default::default()
3362
        };
3363

3364
        // Exercise the option
3365
        let exercise_action = ExerciseAction::Exercise;
3366
        let exercise_quantity = 10;
3367
        let account = "DU1234567";
3368
        let ovrd = false;
3369
        let manual_order_time = Some(datetime!(2024-01-25 10:30:00 UTC));
3370

3371
        let subscription = client
3372
            .exercise_options(&contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
3373
            .expect("Failed to exercise options");
3374

3375
        // Collect results
3376
        let mut order_statuses = Vec::new();
3377
        let mut open_orders = Vec::new();
3378

3379
        for result in subscription {
3380
            match result {
3381
                ExerciseOptions::OrderStatus(status) => order_statuses.push(status),
3382
                ExerciseOptions::OpenOrder(order) => open_orders.push(order),
3383
                ExerciseOptions::Notice(_notice) => {
3384
                    // Note: Warning messages (2100-2200) are currently not routed to subscriptions
3385
                    // They are only logged. See TODO.md for future improvements.
3386
                }
3387
            }
3388
        }
3389

3390
        // Verify we got the expected responses
3391
        assert_eq!(order_statuses.len(), 3, "Should have 3 order status updates");
3392
        assert_eq!(open_orders.len(), 1, "Should have 1 open order");
3393

3394
        // Verify order statuses
3395
        assert_eq!(order_statuses[0].status, "PreSubmitted");
3396
        assert_eq!(order_statuses[0].filled, 0.0);
3397
        assert_eq!(order_statuses[0].remaining, 10.0);
3398

3399
        assert_eq!(order_statuses[1].status, "Submitted");
3400
        assert_eq!(order_statuses[2].status, "Filled");
3401
        assert_eq!(order_statuses[2].filled, 10.0);
3402
        assert_eq!(order_statuses[2].remaining, 0.0);
3403

3404
        // Verify open order
3405
        let open_order = &open_orders[0];
3406
        assert_eq!(open_order.order.order_id, 90);
3407
        assert_eq!(open_order.contract.symbol, "SPY");
3408
        assert_eq!(open_order.contract.security_type, SecurityType::Option);
3409
        assert_eq!(open_order.order.order_type, "EXERCISE");
3410

3411
        // Verify the request was sent correctly
3412
        let requests = gateway.requests();
3413
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3414

3415
        // Request format: ExerciseOptions(21), version(2), order_id, contract fields, exercise_action, exercise_quantity, account, ovrd, manual_order_time
3416
        let expected_request = format!(
3417
            "21\02\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0",
3418
            90, // order_id (using next_order_id from client)
3419
            contract.contract_id,
3420
            contract.symbol,
3421
            contract.security_type,
3422
            contract.last_trade_date_or_contract_month,
3423
            contract.strike,
3424
            contract.right,
3425
            contract.multiplier,
3426
            contract.exchange,
3427
            contract.currency,
3428
            contract.local_symbol,
3429
            contract.trading_class,
3430
            exercise_action as i32,
3431
            exercise_quantity,
3432
            account,
3433
            if ovrd { 1 } else { 0 },
3434
            "20240125 10:30:00 UTC" // manual_order_time formatted
3435
        );
3436

3437
        assert_eq!(requests[0], expected_request, "Request should be ExerciseOptions with correct parameters");
3438
    }
3439

3440
    // === Real-time Market Data Tests ===
3441

3442
    #[test]
3443
    fn test_market_data() {
3444
        use crate::client::common::tests::setup_market_data;
3445
        use crate::contracts::tick_types::TickType;
3446
        use crate::contracts::Contract;
3447
        use crate::market_data::realtime::TickTypes;
3448

3449
        // Initialize env_logger for debug output
3450
        let _ = env_logger::try_init();
3451

3452
        let gateway = setup_market_data();
3453
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3454

3455
        let contract = Contract::stock("AAPL");
3456
        let generic_ticks = vec!["100", "101", "104"]; // Option volume, option open interest, historical volatility
3457
        let snapshot = true;
3458
        let regulatory_snapshot = false;
3459

3460
        let subscription = client
3461
            .market_data(&contract, &generic_ticks, snapshot, regulatory_snapshot)
3462
            .expect("Failed to request market data");
3463

3464
        let mut tick_count = 0;
3465
        let mut has_bid_price = false;
3466
        let mut has_bid_size = false;
3467
        let mut has_ask_price = false;
3468
        let mut has_ask_size = false;
3469
        let mut has_last_price = false;
3470
        let mut has_last_size = false;
3471
        let mut has_volume = false;
3472
        let mut _has_snapshot_end = false;
3473

3474
        for tick in subscription {
3475
            tick_count += 1;
3476
            match tick {
3477
                TickTypes::PriceSize(price_size) => {
3478
                    match price_size.price_tick_type {
3479
                        TickType::Bid => {
3480
                            assert_eq!(price_size.price, 150.50);
3481
                            has_bid_price = true;
3482
                        }
3483
                        TickType::Ask => {
3484
                            assert_eq!(price_size.price, 151.00);
3485
                            has_ask_price = true;
3486
                        }
3487
                        TickType::Last => {
3488
                            assert_eq!(price_size.price, 150.75);
3489
                            has_last_price = true;
3490
                        }
3491
                        _ => {}
3492
                    }
3493
                    // Note: size_tick_type might be present but size value is 0 in PriceSize
3494
                }
3495
                TickTypes::Size(size_tick) => match size_tick.tick_type {
3496
                    TickType::BidSize => {
3497
                        assert_eq!(size_tick.size, 100.0);
3498
                        has_bid_size = true;
3499
                    }
3500
                    TickType::AskSize => {
3501
                        assert_eq!(size_tick.size, 200.0);
3502
                        has_ask_size = true;
3503
                    }
3504
                    TickType::LastSize => {
3505
                        assert_eq!(size_tick.size, 50.0);
3506
                        has_last_size = true;
3507
                    }
3508
                    _ => {}
3509
                },
3510
                TickTypes::Generic(generic_tick) => {
3511
                    if generic_tick.tick_type == TickType::Volume {
3512
                        assert_eq!(generic_tick.value, 1500000.0);
3513
                        has_volume = true;
3514
                    }
3515
                }
3516
                TickTypes::String(_) => {
3517
                    // Ignore string ticks like LastTimestamp
3518
                }
3519
                TickTypes::SnapshotEnd => {
3520
                    _has_snapshot_end = true;
3521
                    break; // Snapshot complete
3522
                }
3523
                _ => {}
3524
            }
3525

3526
            if tick_count > 20 {
3527
                break; // Safety limit
3528
            }
3529
        }
3530

3531
        assert!(has_bid_price, "Should receive bid price");
3532
        assert!(has_bid_size, "Should receive bid size");
3533
        assert!(has_ask_price, "Should receive ask price");
3534
        assert!(has_ask_size, "Should receive ask size");
3535
        assert!(has_last_price, "Should receive last price");
3536
        assert!(has_last_size, "Should receive last size");
3537
        assert!(has_volume, "Should receive volume");
3538
        // TODO: SnapshotEnd message not being routed properly in tests - see TODO.md
3539
        // assert!(has_snapshot_end, "Should receive snapshot end");
3540

3541
        let requests = gateway.requests();
3542
        // Verify request format: RequestMarketData(1), version(11), request_id, contract_id,
3543
        // symbol, sec_type, expiry, strike, right, multiplier, exchange, primary_exchange,
3544
        // currency, local_symbol, trading_class, con_id_flag, combo_legs_description,
3545
        // generic_ticks, snapshot, regulatory_snapshot, market_data_options
3546
        assert!(requests[0].starts_with("1\011\09000\0"), "Request should be RequestMarketData");
3547
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3548
        assert!(requests[0].contains("100,101,104\0"), "Request should contain generic ticks");
3549
        assert!(requests[0].contains("\01\0"), "Request should have snapshot=true");
3550
    }
3551

3552
    #[test]
3553
    fn test_realtime_bars() {
3554
        use crate::client::common::tests::setup_realtime_bars;
3555
        use crate::contracts::Contract;
3556
        use crate::market_data::realtime::{BarSize, WhatToShow};
3557

3558
        // Initialize env_logger for debug output
3559
        let _ = env_logger::try_init();
3560

3561
        let gateway = setup_realtime_bars();
3562
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3563

3564
        let contract = Contract::stock("AAPL");
3565
        let bar_size = BarSize::Sec5;
3566
        let what_to_show = WhatToShow::Trades;
3567
        let use_rth = false;
3568

3569
        let subscription = client
3570
            .realtime_bars(&contract, bar_size, what_to_show, use_rth)
3571
            .expect("Failed to request realtime bars");
3572

3573
        let mut bars = Vec::new();
3574
        for bar in subscription.into_iter().take(3) {
3575
            bars.push(bar);
3576
        }
3577

3578
        assert_eq!(bars.len(), 3, "Should receive 3 bars");
3579

3580
        // Verify first bar
3581
        let bar1 = &bars[0];
3582
        assert_eq!(bar1.open, 150.25);
3583
        assert_eq!(bar1.high, 150.75);
3584
        assert_eq!(bar1.low, 150.00);
3585
        assert_eq!(bar1.close, 150.50);
3586
        assert_eq!(bar1.volume, 1000.0);
3587
        assert_eq!(bar1.wap, 150.40);
3588
        assert_eq!(bar1.count, 25);
3589

3590
        // Verify second bar
3591
        let bar2 = &bars[1];
3592
        assert_eq!(bar2.open, 150.50);
3593
        assert_eq!(bar2.high, 151.00);
3594
        assert_eq!(bar2.low, 150.40);
3595
        assert_eq!(bar2.close, 150.90);
3596
        assert_eq!(bar2.volume, 1200.0);
3597

3598
        // Verify third bar
3599
        let bar3 = &bars[2];
3600
        assert_eq!(bar3.open, 150.90);
3601
        assert_eq!(bar3.high, 151.25);
3602
        assert_eq!(bar3.low, 150.85);
3603
        assert_eq!(bar3.close, 151.20);
3604

3605
        let requests = gateway.requests();
3606
        // Verify request format: RequestRealTimeBars(50), version(8), request_id, contract,
3607
        // bar_size(5), what_to_show, use_rth, realtime_bars_options
3608
        assert!(requests[0].starts_with("50\08\09000\0"), "Request should be RequestRealTimeBars");
3609
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3610
        assert!(
3611
            requests[0].contains("\00\0TRADES\00\0"),
3612
            "Request should have bar_size=0 (5 sec) and TRADES"
3613
        );
3614
    }
3615

3616
    #[test]
3617
    fn test_tick_by_tick_last() {
3618
        use crate::client::common::tests::setup_tick_by_tick_last;
3619
        use crate::contracts::Contract;
3620

3621
        // Initialize env_logger for debug output
3622
        let _ = env_logger::try_init();
3623

3624
        let gateway = setup_tick_by_tick_last();
3625
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3626

3627
        let contract = Contract::stock("AAPL");
3628
        let number_of_ticks = 0;
3629
        let ignore_size = false;
3630

3631
        let subscription = client
3632
            .tick_by_tick_last(&contract, number_of_ticks, ignore_size)
3633
            .expect("Failed to request tick by tick last");
3634

3635
        let mut trades = Vec::new();
3636
        for trade in subscription.into_iter().take(3) {
3637
            trades.push(trade);
3638
        }
3639

3640
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3641

3642
        // Verify first trade
3643
        let trade1 = &trades[0];
3644
        assert_eq!(trade1.tick_type, "1"); // 1 = Last
3645
        assert_eq!(trade1.price, 150.75);
3646
        assert_eq!(trade1.size, 100.0);
3647
        assert_eq!(trade1.exchange, "NASDAQ");
3648
        assert!(!trade1.trade_attribute.past_limit);
3649
        assert!(!trade1.trade_attribute.unreported);
3650

3651
        // Verify second trade (unreported)
3652
        let trade2 = &trades[1];
3653
        assert_eq!(trade2.price, 150.80);
3654
        assert_eq!(trade2.size, 50.0);
3655
        assert_eq!(trade2.exchange, "NYSE");
3656
        assert!(trade2.trade_attribute.unreported);
3657

3658
        // Verify third trade
3659
        let trade3 = &trades[2];
3660
        assert_eq!(trade3.price, 150.70);
3661
        assert_eq!(trade3.size, 150.0);
3662

3663
        let requests = gateway.requests();
3664
        // Verify request format: RequestTickByTickData(97), request_id, contract,
3665
        // tick_type("Last"), number_of_ticks, ignore_size
3666
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3667
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3668
        assert!(requests[0].contains("Last\0"), "Request should have Last tick type");
3669
    }
3670

3671
    #[test]
3672
    fn test_tick_by_tick_all_last() {
3673
        use crate::client::common::tests::setup_tick_by_tick_all_last;
3674
        use crate::contracts::Contract;
3675

3676
        // Initialize env_logger for debug output
3677
        let _ = env_logger::try_init();
3678

3679
        let gateway = setup_tick_by_tick_all_last();
3680
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3681

3682
        let contract = Contract::stock("AAPL");
3683
        let number_of_ticks = 0;
3684
        let ignore_size = false;
3685

3686
        let subscription = client
3687
            .tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
3688
            .expect("Failed to request tick by tick all last");
3689

3690
        let mut trades = Vec::new();
3691
        for trade in subscription.into_iter().take(3) {
3692
            trades.push(trade);
3693
        }
3694

3695
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3696

3697
        // Verify first trade
3698
        let trade1 = &trades[0];
3699
        assert_eq!(trade1.tick_type, "2"); // 2 = AllLast
3700
        assert_eq!(trade1.price, 150.75);
3701
        assert_eq!(trade1.exchange, "NASDAQ");
3702

3703
        // Verify second trade (unreported dark pool trade)
3704
        let trade2 = &trades[1];
3705
        assert_eq!(trade2.price, 150.80);
3706
        assert_eq!(trade2.exchange, "DARK");
3707
        assert_eq!(trade2.special_conditions, "ISO");
3708
        assert!(trade2.trade_attribute.unreported);
3709

3710
        // Verify third trade
3711
        let trade3 = &trades[2];
3712
        assert_eq!(trade3.price, 150.70);
3713
        assert_eq!(trade3.exchange, "NYSE");
3714

3715
        let requests = gateway.requests();
3716
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3717
        assert!(requests[0].contains("AllLast\0"), "Request should have AllLast tick type");
3718
    }
3719

3720
    #[test]
3721
    fn test_tick_by_tick_bid_ask() {
3722
        use crate::client::common::tests::setup_tick_by_tick_bid_ask;
3723
        use crate::contracts::Contract;
3724

3725
        // Initialize env_logger for debug output
3726
        let _ = env_logger::try_init();
3727

3728
        let gateway = setup_tick_by_tick_bid_ask();
3729
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3730

3731
        let contract = Contract::stock("AAPL");
3732
        let number_of_ticks = 0;
3733
        let ignore_size = false;
3734

3735
        let subscription = client
3736
            .tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
3737
            .expect("Failed to request tick by tick bid ask");
3738

3739
        let mut bid_asks = Vec::new();
3740
        for bid_ask in subscription.into_iter().take(3) {
3741
            bid_asks.push(bid_ask);
3742
        }
3743

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

3746
        // Verify first bid/ask
3747
        let ba1 = &bid_asks[0];
3748
        assert_eq!(ba1.bid_price, 150.50);
3749
        assert_eq!(ba1.ask_price, 150.55);
3750
        assert_eq!(ba1.bid_size, 100.0);
3751
        assert_eq!(ba1.ask_size, 200.0);
3752
        assert!(!ba1.bid_ask_attribute.bid_past_low);
3753
        assert!(!ba1.bid_ask_attribute.ask_past_high);
3754

3755
        // Verify second bid/ask (bid past low)
3756
        let ba2 = &bid_asks[1];
3757
        assert_eq!(ba2.bid_price, 150.45);
3758
        assert_eq!(ba2.ask_price, 150.55);
3759
        assert!(ba2.bid_ask_attribute.bid_past_low);
3760

3761
        // Verify third bid/ask (ask past high)
3762
        let ba3 = &bid_asks[2];
3763
        assert_eq!(ba3.ask_price, 150.60);
3764
        assert!(ba3.bid_ask_attribute.ask_past_high);
3765

3766
        let requests = gateway.requests();
3767
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3768
        assert!(requests[0].contains("BidAsk\0"), "Request should have BidAsk tick type");
3769
    }
3770

3771
    #[test]
3772
    fn test_tick_by_tick_midpoint() {
3773
        use crate::client::common::tests::setup_tick_by_tick_midpoint;
3774
        use crate::contracts::Contract;
3775

3776
        // Initialize env_logger for debug output
3777
        let _ = env_logger::try_init();
3778

3779
        let gateway = setup_tick_by_tick_midpoint();
3780
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3781

3782
        let contract = Contract::stock("AAPL");
3783
        let number_of_ticks = 0;
3784
        let ignore_size = false;
3785

3786
        let subscription = client
3787
            .tick_by_tick_midpoint(&contract, number_of_ticks, ignore_size)
3788
            .expect("Failed to request tick by tick midpoint");
3789

3790
        let mut midpoints = Vec::new();
3791
        for midpoint in subscription.into_iter().take(3) {
3792
            midpoints.push(midpoint);
3793
        }
3794

3795
        assert_eq!(midpoints.len(), 3, "Should receive 3 midpoint updates");
3796

3797
        assert_eq!(midpoints[0].mid_point, 150.525);
3798
        assert_eq!(midpoints[1].mid_point, 150.50);
3799
        assert_eq!(midpoints[2].mid_point, 150.525);
3800

3801
        let requests = gateway.requests();
3802
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3803
        assert!(requests[0].contains("MidPoint\0"), "Request should have MidPoint tick type");
3804
    }
3805

3806
    #[test]
3807
    fn test_market_depth() {
3808
        use crate::client::common::tests::setup_market_depth;
3809
        use crate::contracts::Contract;
3810
        use crate::market_data::realtime::MarketDepths;
3811

3812
        // Initialize env_logger for debug output
3813
        let _ = env_logger::try_init();
3814

3815
        let gateway = setup_market_depth();
3816
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3817

3818
        let contract = Contract::stock("AAPL");
3819
        let num_rows = 5;
3820
        let is_smart_depth = false;
3821

3822
        let subscription = client
3823
            .market_depth(&contract, num_rows, is_smart_depth)
3824
            .expect("Failed to request market depth");
3825

3826
        let mut updates = Vec::new();
3827
        for update in subscription.into_iter().take(4) {
3828
            if let MarketDepths::MarketDepth(depth) = update {
3829
                updates.push(depth);
3830
            }
3831
        }
3832

3833
        assert_eq!(updates.len(), 4, "Should receive 4 depth updates");
3834

3835
        // Verify bid insert
3836
        let update1 = &updates[0];
3837
        assert_eq!(update1.position, 0);
3838
        // MarketDepth (L1) doesn't have market_maker field
3839
        assert_eq!(update1.operation, 0); // Insert
3840
        assert_eq!(update1.side, 1); // Bid
3841
        assert_eq!(update1.price, 150.50);
3842
        assert_eq!(update1.size, 100.0);
3843

3844
        // Verify ask insert
3845
        let update2 = &updates[1];
3846
        assert_eq!(update2.operation, 0); // Insert
3847
        assert_eq!(update2.side, 0); // Ask
3848
        assert_eq!(update2.price, 150.55);
3849
        assert_eq!(update2.size, 200.0);
3850

3851
        // Verify bid update
3852
        let update3 = &updates[2];
3853
        assert_eq!(update3.operation, 1); // Update
3854
        assert_eq!(update3.price, 150.49);
3855

3856
        // Verify ask delete
3857
        let update4 = &updates[3];
3858
        assert_eq!(update4.operation, 2); // Delete
3859

3860
        let requests = gateway.requests();
3861
        // Verify request format: RequestMarketDepth(10), version(5), request_id, contract,
3862
        // num_rows, is_smart_depth, market_depth_options
3863
        assert!(requests[0].starts_with("10\05\09000\0"), "Request should be RequestMarketDepth");
3864
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3865
        assert!(requests[0].contains("5\00\0"), "Request should have 5 rows and smart_depth=false");
3866
    }
3867

3868
    #[test]
3869
    fn test_market_depth_exchanges() {
3870
        use crate::client::common::tests::setup_market_depth_exchanges;
3871

3872
        // Initialize env_logger for debug output
3873
        let _ = env_logger::try_init();
3874

3875
        let gateway = setup_market_depth_exchanges();
3876
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3877

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

3880
        assert_eq!(exchanges.len(), 3, "Should receive 3 exchange descriptions");
3881

3882
        // Verify first exchange
3883
        let ex1 = &exchanges[0];
3884
        assert_eq!(ex1.exchange_name, "ISLAND");
3885
        assert_eq!(ex1.security_type, "STK");
3886
        assert_eq!(ex1.listing_exchange, "NASDAQ");
3887
        assert_eq!(ex1.service_data_type, "Deep2");
3888
        assert_eq!(ex1.aggregated_group, Some("1".to_string()));
3889

3890
        // Verify second exchange
3891
        let ex2 = &exchanges[1];
3892
        assert_eq!(ex2.exchange_name, "NYSE");
3893
        assert_eq!(ex2.security_type, "STK");
3894
        assert_eq!(ex2.service_data_type, "Deep");
3895
        assert_eq!(ex2.aggregated_group, Some("2".to_string()));
3896

3897
        // Verify third exchange
3898
        let ex3 = &exchanges[2];
3899
        assert_eq!(ex3.exchange_name, "ARCA");
3900
        assert_eq!(ex3.aggregated_group, Some("2".to_string()));
3901

3902
        let requests = gateway.requests();
3903
        assert_eq!(requests[0], "82\0", "Request should be RequestMktDepthExchanges");
3904
    }
3905

3906
    #[test]
3907
    fn test_switch_market_data_type() {
3908
        use crate::client::common::tests::setup_switch_market_data_type;
3909
        use crate::market_data::MarketDataType;
3910

3911
        // Initialize env_logger for debug output
3912
        let _ = env_logger::try_init();
3913

3914
        let gateway = setup_switch_market_data_type();
3915
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3916

3917
        // Test switching to delayed market data
3918
        client
3919
            .switch_market_data_type(MarketDataType::Delayed)
3920
            .expect("Failed to switch market data type");
3921

3922
        // Give the mock gateway time to receive the request
3923
        std::thread::sleep(std::time::Duration::from_millis(100));
3924

3925
        let requests = gateway.requests();
3926
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3927
        // Verify request format: RequestMarketDataType(59), version(1), market_data_type(3=Delayed)
3928
        assert_eq!(requests[0], "59\01\03\0", "Request should be RequestMarketDataType with Delayed(3)");
3929
    }
3930
}
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