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

wboayue / rust-ibapi / 17258804429

27 Aug 2025 06:08AM UTC coverage: 77.523% (+0.4%) from 77.105%
17258804429

push

github

web-flow
Refactor Subscription to reduce coupling with Client (#298)

74 of 91 new or added lines in 12 files covered. (81.32%)

4 existing lines in 2 files now uncovered.

5370 of 6927 relevant lines covered (77.52%)

63.16 hits per line

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

82.42
/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
    // === Accounts ===
166

167
    /// 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.
168
    ///
169
    /// # Examples
170
    ///
171
    /// ```no_run
172
    /// use ibapi::Client;
173
    ///
174
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
175
    /// let server_time = client.server_time().expect("error requesting server time");
176
    /// println!("server time: {server_time:?}");
177
    /// ```
178
    pub fn server_time(&self) -> Result<OffsetDateTime, Error> {
10✔
179
        accounts::server_time(self)
20✔
180
    }
181

182
    /// Subscribes to [PositionUpdate]s for all accessible accounts.
183
    /// All positions sent initially, and then only updates as positions change.
184
    ///
185
    /// # Examples
186
    ///
187
    /// ```no_run
188
    /// use ibapi::Client;
189
    /// use ibapi::accounts::PositionUpdate;
190
    ///
191
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
192
    /// let subscription = client.positions().expect("error requesting positions");
193
    /// for position_response in subscription.iter() {
194
    ///     match position_response {
195
    ///         PositionUpdate::Position(position) => println!("{position:?}"),
196
    ///         PositionUpdate::PositionEnd => println!("initial set of positions received"),
197
    ///     }
198
    /// }
199
    /// ```
200
    pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
5✔
201
        accounts::positions(self)
10✔
202
    }
203

204
    /// Subscribes to [PositionUpdateMulti] updates for account and/or model.
205
    /// Initially all positions are returned, and then updates are returned for any position changes in real time.
206
    ///
207
    /// # Arguments
208
    /// * `account`    - If an account Id is provided, only the account’s positions belonging to the specified model will be delivered.
209
    /// * `model_code` - The code of the model’s positions we are interested in.
210
    ///
211
    /// # Examples
212
    ///
213
    /// ```no_run
214
    /// use ibapi::Client;
215
    ///
216
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
217
    ///
218
    /// use ibapi::accounts::types::AccountId;
219
    ///
220
    /// let account = AccountId("U1234567".to_string());
221
    /// let subscription = client.positions_multi(Some(&account), None).expect("error requesting positions by model");
222
    /// for position in subscription.iter() {
223
    ///     println!("{position:?}")
224
    /// }
225
    /// ```
226
    pub fn positions_multi(&self, account: Option<&AccountId>, model_code: Option<&ModelCode>) -> Result<Subscription<PositionUpdateMulti>, Error> {
9✔
227
        accounts::positions_multi(self, account, model_code)
36✔
228
    }
229

230
    /// Creates subscription for real time daily PnL and unrealized PnL updates.
231
    ///
232
    /// # Arguments
233
    /// * `account`    - account for which to receive PnL updates
234
    /// * `model_code` - specify to request PnL updates for a specific model
235
    ///
236
    /// # Examples
237
    ///
238
    /// ```no_run
239
    /// use ibapi::Client;
240
    ///
241
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
242
    /// use ibapi::accounts::types::AccountId;
243
    ///
244
    /// let account = AccountId("account id".to_string());
245
    /// let subscription = client.pnl(&account, None).expect("error requesting pnl");
246
    /// for pnl in subscription.iter() {
247
    ///     println!("{pnl:?}")
248
    /// }
249
    /// ```
250
    pub fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
12✔
251
        accounts::pnl(self, account, model_code)
48✔
252
    }
253

254
    /// Requests real time updates for daily PnL of individual positions.
255
    ///
256
    /// # Arguments
257
    /// * `account`     - Account in which position exists
258
    /// * `contract_id` - Contract ID of contract to receive daily PnL updates for. Note: does not return response if invalid conId is entered.
259
    /// * `model_code`  - Model in which position exists
260
    ///
261
    /// # Examples
262
    ///
263
    /// ```no_run
264
    /// use ibapi::Client;
265
    ///
266
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
267
    ///
268
    /// use ibapi::accounts::types::{AccountId, ContractId};
269
    ///
270
    /// let account = AccountId("<account id>".to_string());
271
    /// let contract_id = ContractId(1001);
272
    ///
273
    /// let subscription = client.pnl_single(&account, contract_id, None).expect("error requesting pnl");
274
    /// for pnl in &subscription {
275
    ///     println!("{pnl:?}")
276
    /// }
277
    /// ```
278
    pub fn pnl_single(&self, account: &AccountId, contract_id: ContractId, model_code: Option<&ModelCode>) -> Result<Subscription<PnLSingle>, Error> {
10✔
279
        accounts::pnl_single(self, account, contract_id, model_code)
50✔
280
    }
281

282
    /// 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.
283
    ///
284
    /// # Arguments
285
    /// * `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.
286
    /// * `tags`  - List of the desired tags.
287
    ///
288
    /// # Examples
289
    ///
290
    /// ```no_run
291
    /// use ibapi::Client;
292
    /// use ibapi::accounts::AccountSummaryTags;
293
    /// use ibapi::accounts::types::AccountGroup;
294
    ///
295
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
296
    ///
297
    /// let group = AccountGroup("All".to_string());
298
    ///
299
    /// let subscription = client.account_summary(&group, &[AccountSummaryTags::ACCOUNT_TYPE]).expect("error requesting account summary");
300
    /// for summary in &subscription {
301
    ///     println!("{summary:?}")
302
    /// }
303
    /// ```
304
    pub fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
11✔
305
        accounts::account_summary(self, group, tags)
44✔
306
    }
307

308
    /// Subscribes to a specific account’s information and portfolio.
309
    ///
310
    /// 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.
311
    ///
312
    /// # Arguments
313
    /// * `account` - The account id (i.e. U1234567) for which the information is requested.
314
    ///
315
    /// # Examples
316
    ///
317
    /// ```no_run
318
    /// use ibapi::Client;
319
    /// use ibapi::accounts::AccountUpdate;
320
    ///
321
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
322
    ///
323
    /// use ibapi::accounts::types::AccountId;
324
    ///
325
    /// let account = AccountId("U1234567".to_string());
326
    ///
327
    /// let subscription = client.account_updates(&account).expect("error requesting account updates");
328
    /// for update in &subscription {
329
    ///     println!("{update:?}");
330
    ///
331
    ///     // stop after full initial update
332
    ///     if let AccountUpdate::End = update {
333
    ///         subscription.cancel();
334
    ///     }
335
    /// }
336
    /// ```
337
    pub fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
3✔
338
        accounts::account_updates(self, account)
9✔
339
    }
340

341
    /// Requests account updates for account and/or model.
342
    ///
343
    /// 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.
344
    ///
345
    /// # Arguments
346
    /// * `account`        - Account values can be requested for a particular account.
347
    /// * `model_code`     - Account values can also be requested for a model.
348
    ///
349
    /// # Examples
350
    ///
351
    /// ```no_run
352
    /// use ibapi::Client;
353
    /// use ibapi::accounts::AccountUpdateMulti;
354
    ///
355
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
356
    ///
357
    /// use ibapi::accounts::types::AccountId;
358
    ///
359
    /// let account = AccountId("U1234567".to_string());
360
    ///
361
    /// let subscription = client.account_updates_multi(Some(&account), None).expect("error requesting account updates multi");
362
    /// for update in &subscription {
363
    ///     println!("{update:?}");
364
    ///
365
    ///     // stop after full initial update
366
    ///     if let AccountUpdateMulti::End = update {
367
    ///         subscription.cancel();
368
    ///     }
369
    /// }
370
    /// ```
371
    pub fn account_updates_multi(
3✔
372
        &self,
373
        account: Option<&AccountId>,
374
        model_code: Option<&ModelCode>,
375
    ) -> Result<Subscription<AccountUpdateMulti>, Error> {
376
        accounts::account_updates_multi(self, account, model_code)
12✔
377
    }
378

379
    /// Requests the accounts to which the logged user has access to.
380
    ///
381
    /// # Examples
382
    ///
383
    /// ```no_run
384
    /// use ibapi::Client;
385
    ///
386
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
387
    ///
388
    /// let accounts = client.managed_accounts().expect("error requesting managed accounts");
389
    /// println!("managed accounts: {accounts:?}")
390
    /// ```
391
    pub fn managed_accounts(&self) -> Result<Vec<String>, Error> {
11✔
392
        accounts::managed_accounts(self)
22✔
393
    }
394

395
    // === Contracts ===
396

397
    /// Requests contract information.
398
    ///
399
    /// 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.
400
    ///
401
    /// # Arguments
402
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
403
    ///
404
    /// # Examples
405
    ///
406
    /// ```no_run
407
    /// use ibapi::Client;
408
    /// use ibapi::contracts::Contract;
409
    ///
410
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
411
    ///
412
    /// let contract = Contract::stock("TSLA");
413
    /// let results = client.contract_details(&contract).expect("request failed");
414
    /// for contract_detail in results {
415
    ///     println!("contract: {contract_detail:?}");
416
    /// }
417
    /// ```
418
    pub fn contract_details(&self, contract: &Contract) -> Result<Vec<contracts::ContractDetails>, Error> {
2✔
419
        contracts::contract_details(self, contract)
6✔
420
    }
421

422
    /// Get current [FamilyCode]s for all accessible accounts.
423
    pub fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
5✔
424
        accounts::family_codes(self)
10✔
425
    }
426

427
    /// Requests details about a given market rule
428
    ///
429
    /// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
430
    /// A list of market rule ids can be obtained by invoking [Self::contract_details()] for a particular contract.
431
    /// The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [contracts::ContractDetails].
432
    pub fn market_rule(&self, market_rule_id: i32) -> Result<contracts::MarketRule, Error> {
1✔
433
        contracts::market_rule(self, market_rule_id)
3✔
434
    }
435

436
    /// Requests matching stock symbols.
437
    ///
438
    /// # Arguments
439
    /// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
440
    ///
441
    /// # Examples
442
    ///
443
    /// ```no_run
444
    /// use ibapi::Client;
445
    ///
446
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
447
    ///
448
    /// let contracts = client.matching_symbols("IB").expect("request failed");
449
    /// for contract in contracts {
450
    ///     println!("contract: {contract:?}");
451
    /// }
452
    /// ```
453
    pub fn matching_symbols(&self, pattern: &str) -> Result<impl Iterator<Item = contracts::ContractDescription>, Error> {
1✔
454
        Ok(contracts::matching_symbols(self, pattern)?.into_iter())
4✔
455
    }
456

457
    /// Calculates an option’s price based on the provided volatility and its underlying’s price.
458
    ///
459
    /// # Arguments
460
    /// * `contract`        - The [Contract] object representing the option for which the calculation is being requested.
461
    /// * `volatility`      - Hypothetical volatility as a percentage (e.g., 20.0 for 20%).
462
    /// * `underlying_price` - Hypothetical price of the underlying asset.
463
    ///
464
    /// # Examples
465
    ///
466
    /// ```no_run
467
    /// use ibapi::Client;
468
    /// use ibapi::contracts::Contract;
469
    ///
470
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
471
    ///
472
    /// let contract = Contract::option("AAPL", "20251219", 150.0, "C");
473
    /// let calculation = client.calculate_option_price(&contract, 100.0, 235.0).expect("request failed");
474
    /// println!("calculation: {calculation:?}");
475
    /// ```
476
    pub fn calculate_option_price(&self, contract: &Contract, volatility: f64, underlying_price: f64) -> Result<OptionComputation, Error> {
2✔
477
        contracts::calculate_option_price(self, contract, volatility, underlying_price)
10✔
478
    }
479

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

503
    /// Requests security definition option parameters for viewing a contract’s option chain.
504
    ///
505
    /// # Arguments
506
    /// `symbol`   - Contract symbol of the underlying.
507
    /// `exchange` - The exchange on which the returned options are trading. Can be set to the empty string for all exchanges.
508
    /// `security_type` - The type of the underlying security, i.e. STK
509
    /// `contract_id`   - The contract ID of the underlying security.
510
    ///
511
    /// # Examples
512
    ///
513
    /// ```no_run
514
    /// use ibapi::{contracts::SecurityType, Client};
515
    ///
516
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
517
    ///
518
    /// let symbol = "AAPL";
519
    /// let exchange = ""; // all exchanges
520
    /// let security_type = SecurityType::Stock;
521
    /// let contract_id = 265598;
522
    ///
523
    /// let subscription = client
524
    ///     .option_chain(symbol, exchange, security_type, contract_id)
525
    ///     .expect("request option chain failed!");
526
    ///
527
    /// for option_chain in &subscription {
528
    ///     println!("{option_chain:?}")
529
    /// }
530
    /// ```
531
    pub fn option_chain(
1✔
532
        &self,
533
        symbol: &str,
534
        exchange: &str,
535
        security_type: SecurityType,
536
        contract_id: i32,
537
    ) -> Result<Subscription<contracts::OptionChain>, Error> {
538
        contracts::option_chain(self, symbol, exchange, security_type, contract_id)
6✔
539
    }
540

541
    // === Orders ===
542

543
    /// Requests all *current* open orders in associated accounts at the current moment.
544
    /// Open orders are returned once; this function does not initiate a subscription.
545
    ///
546
    /// # Examples
547
    ///
548
    /// ```no_run
549
    /// use ibapi::Client;
550
    ///
551
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
552
    ///
553
    /// let subscription = client.all_open_orders().expect("request failed");
554
    /// for order_data in &subscription {
555
    ///    println!("{order_data:?}")
556
    /// }
557
    /// ```
558
    pub fn all_open_orders(&self) -> Result<Subscription<Orders>, Error> {
2✔
559
        orders::all_open_orders(self)
4✔
560
    }
561

562
    /// Requests status updates about future orders placed from TWS. Can only be used with client ID 0.
563
    ///
564
    /// # Arguments
565
    /// * `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.
566
    ///
567
    /// # Examples
568
    ///
569
    /// ```no_run
570
    /// use ibapi::Client;
571
    ///
572
    /// let client = Client::connect("127.0.0.1:4002", 0).expect("connection failed");
573
    ///
574
    /// let subscription = client.auto_open_orders(false).expect("request failed");
575
    /// for order_data in &subscription {
576
    ///    println!("{order_data:?}")
577
    /// }
578
    /// ```
579
    pub fn auto_open_orders(&self, auto_bind: bool) -> Result<Subscription<Orders>, Error> {
2✔
580
        orders::auto_open_orders(self, auto_bind)
6✔
581
    }
582

583
    /// Cancels an active [Order] placed by the same API client ID.
584
    ///
585
    /// # Arguments
586
    /// * `order_id` - ID of the [Order] to cancel.
587
    /// * `manual_order_cancel_time` - Optional timestamp to specify the cancellation time. Use an empty string to use the current time.
588
    ///
589
    /// # Examples
590
    ///
591
    /// ```no_run
592
    /// use ibapi::Client;
593
    ///
594
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
595
    ///
596
    /// let order_id = 15;
597
    /// let subscription = client.cancel_order(order_id, "").expect("request failed");
598
    /// for result in subscription {
599
    ///    println!("{result:?}");
600
    /// }
601
    /// ```
602
    pub fn cancel_order(&self, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<CancelOrder>, Error> {
2✔
603
        orders::cancel_order(self, order_id, manual_order_cancel_time)
8✔
604
    }
605

606
    /// Requests completed [Order]s.
607
    ///
608
    /// # Arguments
609
    /// * `api_only` - request only orders placed by the API.
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 subscription = client.completed_orders(false).expect("request failed");
619
    /// for order_data in &subscription {
620
    ///    println!("{order_data:?}")
621
    /// }
622
    /// ```
623
    pub fn completed_orders(&self, api_only: bool) -> Result<Subscription<Orders>, Error> {
1✔
624
        orders::completed_orders(self, api_only)
3✔
625
    }
626

627
    /// Requests current day's (since midnight) executions matching the filter.
628
    ///
629
    /// Only the current day's executions can be retrieved.
630
    /// Along with the [orders::ExecutionData], the [orders::CommissionReport] will also be returned.
631
    /// When requesting executions, a filter can be specified to receive only a subset of them
632
    ///
633
    /// # Arguments
634
    /// * `filter` - filter criteria used to determine which execution reports are returned
635
    ///
636
    /// # Examples
637
    ///
638
    /// ```no_run
639
    /// use ibapi::Client;
640
    /// use ibapi::orders::ExecutionFilter;
641
    ///
642
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
643
    ///
644
    /// let filter = ExecutionFilter{
645
    ///    side: "BUY".to_owned(),
646
    ///    ..ExecutionFilter::default()
647
    /// };
648
    ///
649
    /// let subscription = client.executions(filter).expect("request failed");
650
    /// for execution_data in &subscription {
651
    ///    println!("{execution_data:?}")
652
    /// }
653
    /// ```
654
    pub fn executions(&self, filter: orders::ExecutionFilter) -> Result<Subscription<Executions>, Error> {
2✔
655
        orders::executions(self, filter)
6✔
656
    }
657

658
    /// Cancels all open [Order]s.
659
    ///
660
    /// # Examples
661
    ///
662
    /// ```no_run
663
    /// use ibapi::Client;
664
    ///
665
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
666
    ///
667
    /// client.global_cancel().expect("request failed");
668
    /// ```
669
    pub fn global_cancel(&self) -> Result<(), Error> {
1✔
670
        orders::global_cancel(self)
2✔
671
    }
672

673
    /// Requests all open orders places by this specific API client (identified by the API client id).
674
    /// For client ID 0, this will bind previous manual TWS orders.
675
    ///
676
    /// # Examples
677
    ///
678
    /// ```no_run
679
    /// use ibapi::Client;
680
    ///
681
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
682
    ///
683
    /// let subscription = client.open_orders().expect("request failed");
684
    /// for order_data in &subscription {
685
    ///    println!("{order_data:?}")
686
    /// }
687
    /// ```
688
    pub fn open_orders(&self) -> Result<Subscription<Orders>, Error> {
1✔
689
        orders::open_orders(self)
2✔
690
    }
691

692
    /// Places or modifies an [Order].
693
    ///
694
    /// Submits an [Order] using [Client] for the given [Contract].
695
    /// 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.
696
    ///
697
    /// # Arguments
698
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
699
    /// * `contract` - [Contract] to submit order for.
700
    /// * `order` - [Order] to submit.
701
    ///
702
    /// # Examples
703
    ///
704
    /// ```no_run
705
    /// use ibapi::Client;
706
    /// use ibapi::contracts::Contract;
707
    /// use ibapi::orders::{order_builder, Action, PlaceOrder};
708
    ///
709
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
710
    ///
711
    /// let contract = Contract::stock("MSFT");
712
    /// let order = order_builder::market_order(Action::Buy, 100.0);
713
    /// let order_id = client.next_order_id();
714
    ///
715
    /// let events = client.place_order(order_id, &contract, &order).expect("request failed");
716
    ///
717
    /// for event in &events {
718
    ///     match event {
719
    ///         PlaceOrder::OrderStatus(order_status) => {
720
    ///             println!("order status: {order_status:?}")
721
    ///         }
722
    ///         PlaceOrder::OpenOrder(open_order) => println!("open order: {open_order:?}"),
723
    ///         PlaceOrder::ExecutionData(execution) => println!("execution: {execution:?}"),
724
    ///         PlaceOrder::CommissionReport(report) => println!("commission report: {report:?}"),
725
    ///         PlaceOrder::Message(message) => println!("message: {message:?}"),
726
    ///    }
727
    /// }
728
    /// ```
729
    pub fn place_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<Subscription<PlaceOrder>, Error> {
4✔
730
        orders::place_order(self, order_id, contract, order)
20✔
731
    }
732

733
    /// Submits or modifies an [Order] without returning a subscription.
734
    ///
735
    /// This is a fire-and-forget method that submits an [Order] for the given [Contract]
736
    /// but does not return a subscription for order updates. To receive order status updates,
737
    /// fills, and commission reports, use the [`order_update_stream`](Client::order_update_stream) method
738
    /// or use [`place_order`](Client::place_order) instead which returns a subscription.
739
    ///
740
    /// # Arguments
741
    /// * `order_id` - ID for [Order]. Get next valid ID using [Client::next_order_id].
742
    /// * `contract` - [Contract] to submit order for.
743
    /// * `order` - [Order] to submit.
744
    ///
745
    /// # Returns
746
    /// * `Ok(())` if the order was successfully sent
747
    /// * `Err(Error)` if validation failed or sending failed
748
    ///
749
    /// # Examples
750
    ///
751
    /// ```no_run
752
    /// use ibapi::Client;
753
    /// use ibapi::contracts::Contract;
754
    /// use ibapi::orders::{order_builder, Action};
755
    ///
756
    /// # fn main() -> Result<(), ibapi::Error> {
757
    ///
758
    /// let client = Client::connect("127.0.0.1:4002", 100)?;
759
    ///
760
    /// let contract = Contract::stock("MSFT");
761
    /// let order = order_builder::market_order(Action::Buy, 100.0);
762
    /// let order_id = client.next_order_id();
763
    ///
764
    /// // Submit order without waiting for confirmation
765
    /// client.submit_order(order_id, &contract, &order)?;
766
    ///
767
    /// // Monitor all order updates via the order update stream
768
    /// // This will receive updates for ALL orders, not just this one
769
    /// use ibapi::orders::OrderUpdate;
770
    /// for event in client.order_update_stream()? {
771
    ///     match event {
772
    ///         OrderUpdate::OrderStatus(status) => println!("Order Status: {status:?}"),
773
    ///         OrderUpdate::ExecutionData(exec) => println!("Execution: {exec:?}"),
774
    ///         OrderUpdate::CommissionReport(report) => println!("Commission: {report:?}"),
775
    ///         _ => {}
776
    ///     }
777
    /// }
778
    ///
779
    /// # Ok(())
780
    /// # }
781
    /// ```
782
    pub fn submit_order(&self, order_id: i32, contract: &Contract, order: &Order) -> Result<(), Error> {
2✔
783
        orders::submit_order(self, order_id, contract, order)
10✔
784
    }
785

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

863
    /// Exercises an options contract.
864
    ///
865
    /// Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized.
866
    ///
867
    /// # Arguments
868
    /// * `contract`          - The option [Contract] to be exercised.
869
    /// * `exercise_action`   - Exercise option. ExerciseAction::Exercise or ExerciseAction::Lapse.
870
    /// * `exercise_quantity` - Number of contracts to be exercised.
871
    /// * `account`           - Destination account.
872
    /// * `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.
873
    /// * `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.
874
    pub fn exercise_options(
1✔
875
        &self,
876
        contract: &Contract,
877
        exercise_action: orders::ExerciseAction,
878
        exercise_quantity: i32,
879
        account: &str,
880
        ovrd: bool,
881
        manual_order_time: Option<OffsetDateTime>,
882
    ) -> Result<Subscription<ExerciseOptions>, Error> {
883
        orders::exercise_options(self, contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
8✔
884
    }
885

886
    // === Historical Market Data ===
887

888
    /// Returns the timestamp of earliest available historical data for a contract and data type.
889
    ///
890
    /// ```no_run
891
    /// use ibapi::Client;
892
    /// use ibapi::contracts::Contract;
893
    /// use ibapi::market_data::historical::{self, WhatToShow};
894
    ///
895
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
896
    ///
897
    /// let contract = Contract::stock("MSFT");
898
    /// let what_to_show = WhatToShow::Trades;
899
    /// let use_rth = true;
900
    ///
901
    /// let result = client.head_timestamp(&contract, what_to_show, use_rth).expect("head timestamp failed");
902
    ///
903
    /// print!("head_timestamp: {result:?}");
904
    /// ```
905
    pub fn head_timestamp(&self, contract: &Contract, what_to_show: historical::WhatToShow, use_rth: bool) -> Result<OffsetDateTime, Error> {
1✔
906
        historical::head_timestamp(self, contract, what_to_show, use_rth)
5✔
907
    }
908

909
    /// Requests interval of historical data ending at specified time for [Contract].
910
    ///
911
    /// # Arguments
912
    /// * `contract`     - [Contract] to retrieve [historical::HistoricalData] for.
913
    /// * `interval_end` - optional end date of interval to retrieve [historical::HistoricalData] for. If `None` current time or last trading of contract is implied.
914
    /// * `duration`     - duration of interval to retrieve [historical::HistoricalData] for.
915
    /// * `bar_size`     - [historical::BarSize] to return.
916
    /// * `what_to_show` - requested bar type: [historical::WhatToShow].
917
    /// * `use_rth`      - use regular trading hours.
918
    ///
919
    /// # Examples
920
    ///
921
    /// ```no_run
922
    /// use time::macros::datetime;
923
    ///
924
    /// use ibapi::contracts::Contract;
925
    /// use ibapi::Client;
926
    /// use ibapi::market_data::historical::{BarSize, ToDuration, WhatToShow};
927
    ///
928
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
929
    ///
930
    /// let contract = Contract::stock("TSLA");
931
    ///
932
    /// let historical_data = client
933
    ///     .historical_data(&contract, Some(datetime!(2023-04-15 0:00 UTC)), 7.days(), BarSize::Day, WhatToShow::Trades, true)
934
    ///     .expect("historical data request failed");
935
    ///
936
    /// println!("start_date: {}, end_date: {}", historical_data.start, historical_data.end);
937
    ///
938
    /// for bar in &historical_data.bars {
939
    ///     println!("{bar:?}");
940
    /// }
941
    /// ```
942
    pub fn historical_data(
6✔
943
        &self,
944
        contract: &Contract,
945
        interval_end: Option<OffsetDateTime>,
946
        duration: historical::Duration,
947
        bar_size: historical::BarSize,
948
        what_to_show: historical::WhatToShow,
949
        use_rth: bool,
950
    ) -> Result<historical::HistoricalData, Error> {
951
        historical::historical_data(self, contract, interval_end, duration, bar_size, Some(what_to_show), use_rth)
48✔
952
    }
953

954
    /// Requests [Schedule](historical::Schedule) for an interval of given duration
955
    /// ending at specified date.
956
    ///
957
    /// # Arguments
958
    /// * `contract`     - [Contract] to retrieve [Schedule](historical::Schedule) for.
959
    /// * `interval_end` - end date of interval to retrieve [Schedule](historical::Schedule) for.
960
    /// * `duration`     - duration of interval to retrieve [Schedule](historical::Schedule) for.
961
    ///
962
    /// # Examples
963
    ///
964
    /// ```no_run
965
    /// use time::macros::datetime;
966
    /// use ibapi::contracts::Contract;
967
    /// use ibapi::Client;
968
    /// use ibapi::market_data::historical::ToDuration;
969
    ///
970
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
971
    ///
972
    /// let contract = Contract::stock("GM");
973
    ///
974
    /// let historical_data = client
975
    ///     .historical_schedules(&contract, datetime!(2023-04-15 0:00 UTC), 30.days())
976
    ///     .expect("historical schedule request failed");
977
    ///
978
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
979
    ///
980
    /// for session in &historical_data.sessions {
981
    ///     println!("{session:?}");
982
    /// }
983
    /// ```
984
    pub fn historical_schedules(
1✔
985
        &self,
986
        contract: &Contract,
987
        interval_end: OffsetDateTime,
988
        duration: historical::Duration,
989
    ) -> Result<historical::Schedule, Error> {
990
        historical::historical_schedule(self, contract, Some(interval_end), duration)
5✔
991
    }
992

993
    /// Requests [historical::Schedule] for interval ending at current time.
994
    ///
995
    /// # Arguments
996
    /// * `contract` - [Contract] to retrieve [historical::Schedule] for.
997
    /// * `duration` - [historical::Duration] for interval to retrieve [historical::Schedule] for.
998
    ///
999
    /// # Examples
1000
    ///
1001
    /// ```no_run
1002
    /// use ibapi::contracts::Contract;
1003
    /// use ibapi::Client;
1004
    /// use ibapi::market_data::historical::ToDuration;
1005
    ///
1006
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1007
    ///
1008
    /// let contract = Contract::stock("GM");
1009
    ///
1010
    /// let historical_data = client
1011
    ///     .historical_schedules_ending_now(&contract, 30.days())
1012
    ///     .expect("historical schedule request failed");
1013
    ///
1014
    /// println!("start: {:?}, end: {:?}", historical_data.start, historical_data.end);
1015
    ///
1016
    /// for session in &historical_data.sessions {
1017
    ///     println!("{session:?}");
1018
    /// }
1019
    /// ```
1020
    pub fn historical_schedules_ending_now(&self, contract: &Contract, duration: historical::Duration) -> Result<historical::Schedule, Error> {
×
1021
        historical::historical_schedule(self, contract, None, duration)
×
1022
    }
1023

1024
    /// Requests historical time & sales data (Bid/Ask) for an instrument.
1025
    ///
1026
    /// # Arguments
1027
    /// * `contract` - [Contract] object that is subject of query
1028
    /// * `start`    - Start time. Either start time or end time is specified.
1029
    /// * `end`      - End time. Either start time or end time is specified.
1030
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1031
    /// * `use_rth`         - Data from regular trading hours (true), or all available hours (false)
1032
    /// * `ignore_size`     - A filter only used when the source price is Bid_Ask
1033
    ///
1034
    /// # Examples
1035
    ///
1036
    /// ```no_run
1037
    /// use time::macros::datetime;
1038
    ///
1039
    /// use ibapi::contracts::Contract;
1040
    /// use ibapi::Client;
1041
    ///
1042
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1043
    ///
1044
    /// let contract = Contract::stock("TSLA");
1045
    ///
1046
    /// let ticks = client
1047
    ///     .historical_ticks_bid_ask(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, true, false)
1048
    ///     .expect("historical ticks request failed");
1049
    ///
1050
    /// for tick in ticks {
1051
    ///     println!("{tick:?}");
1052
    /// }
1053
    /// ```
1054
    pub fn historical_ticks_bid_ask(
2✔
1055
        &self,
1056
        contract: &Contract,
1057
        start: Option<OffsetDateTime>,
1058
        end: Option<OffsetDateTime>,
1059
        number_of_ticks: i32,
1060
        use_rth: bool,
1061
        ignore_size: bool,
1062
    ) -> Result<historical::TickSubscription<historical::TickBidAsk>, Error> {
1063
        historical::historical_ticks_bid_ask(self, contract, start, end, number_of_ticks, use_rth, ignore_size)
16✔
1064
    }
1065

1066
    /// Requests historical time & sales data (Midpoint) for an instrument.
1067
    ///
1068
    /// # Arguments
1069
    /// * `contract` - [Contract] object that is subject of query
1070
    /// * `start`    - Start time. Either start time or end time is specified.
1071
    /// * `end`      - End time. Either start time or end time is specified.
1072
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1073
    /// * `use_rth`         - Data from regular trading hours (true), or all available hours (false)
1074
    ///
1075
    /// # Examples
1076
    ///
1077
    /// ```no_run
1078
    /// use time::macros::datetime;
1079
    ///
1080
    /// use ibapi::contracts::Contract;
1081
    /// use ibapi::Client;
1082
    ///
1083
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1084
    ///
1085
    /// let contract = Contract::stock("TSLA");
1086
    ///
1087
    /// let ticks = client
1088
    ///     .historical_ticks_mid_point(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, true)
1089
    ///     .expect("historical ticks request failed");
1090
    ///
1091
    /// for tick in ticks {
1092
    ///     println!("{tick:?}");
1093
    /// }
1094
    /// ```
1095
    pub fn historical_ticks_mid_point(
2✔
1096
        &self,
1097
        contract: &Contract,
1098
        start: Option<OffsetDateTime>,
1099
        end: Option<OffsetDateTime>,
1100
        number_of_ticks: i32,
1101
        use_rth: bool,
1102
    ) -> Result<historical::TickSubscription<historical::TickMidpoint>, Error> {
1103
        historical::historical_ticks_mid_point(self, contract, start, end, number_of_ticks, use_rth)
14✔
1104
    }
1105

1106
    /// Requests historical time & sales data (Trades) for an instrument.
1107
    ///
1108
    /// # Arguments
1109
    /// * `contract` - [Contract] object that is subject of query
1110
    /// * `start`    - Start time. Either start time or end time is specified.
1111
    /// * `end`      - End time. Either start time or end time is specified.
1112
    /// * `number_of_ticks` - Number of distinct data points. Max currently 1000 per request.
1113
    /// * `use_rth`         - Data from regular trading hours (true), or all available hours (false)
1114
    ///
1115
    /// # Examples
1116
    ///
1117
    /// ```no_run
1118
    /// use time::macros::datetime;
1119
    ///
1120
    /// use ibapi::contracts::Contract;
1121
    /// use ibapi::Client;
1122
    ///
1123
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1124
    ///
1125
    /// let contract = Contract::stock("TSLA");
1126
    ///
1127
    /// let ticks = client
1128
    ///     .historical_ticks_trade(&contract, Some(datetime!(2023-04-15 0:00 UTC)), None, 100, true)
1129
    ///     .expect("historical ticks request failed");
1130
    ///
1131
    /// for tick in ticks {
1132
    ///     println!("{tick:?}");
1133
    /// }
1134
    /// ```
1135
    pub fn historical_ticks_trade(
4✔
1136
        &self,
1137
        contract: &Contract,
1138
        start: Option<OffsetDateTime>,
1139
        end: Option<OffsetDateTime>,
1140
        number_of_ticks: i32,
1141
        use_rth: bool,
1142
    ) -> Result<historical::TickSubscription<historical::TickLast>, Error> {
1143
        historical::historical_ticks_trade(self, contract, start, end, number_of_ticks, use_rth)
28✔
1144
    }
1145

1146
    /// Requests data histogram of specified contract.
1147
    ///
1148
    /// # Arguments
1149
    /// * `contract`  - [Contract] to retrieve [Histogram Entries](historical::HistogramEntry) for.
1150
    /// * `use_rth`   - Data from regular trading hours (true), or all available hours (false).
1151
    /// * `period`    - The time period of each histogram bar (e.g., `BarSize::Day`, `BarSize::Week`, `BarSize::Month`).
1152
    ///
1153
    /// # Examples
1154
    ///
1155
    /// ```no_run
1156
    /// use time::macros::datetime;
1157
    //
1158
    /// use ibapi::contracts::Contract;
1159
    /// use ibapi::Client;
1160
    /// use ibapi::market_data::historical::BarSize;
1161
    ///
1162
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1163
    ///
1164
    /// let contract = Contract::stock("GM");
1165
    ///
1166
    /// let histogram = client
1167
    ///     .histogram_data(&contract, true, BarSize::Week)
1168
    ///     .expect("histogram request failed");
1169
    ///
1170
    /// for item in &histogram {
1171
    ///     println!("{item:?}");
1172
    /// }
1173
    /// ```
1174
    pub fn histogram_data(&self, contract: &Contract, use_rth: bool, period: historical::BarSize) -> Result<Vec<HistogramEntry>, Error> {
1✔
1175
        historical::histogram_data(self, contract, use_rth, period)
5✔
1176
    }
1177

1178
    // === Realtime Market Data ===
1179

1180
    /// Requests realtime bars.
1181
    ///
1182
    /// # Arguments
1183
    /// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
1184
    ///
1185
    /// # Examples
1186
    ///
1187
    /// ```no_run
1188
    /// use ibapi::Client;
1189
    /// use ibapi::contracts::Contract;
1190
    /// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1191
    ///
1192
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1193
    ///
1194
    /// let contract = Contract::stock("TSLA");
1195
    /// let subscription = client.realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, false).expect("request failed");
1196
    ///
1197
    /// for (i, bar) in subscription.iter().enumerate().take(60) {
1198
    ///     println!("bar[{i}]: {bar:?}");
1199
    /// }
1200
    /// ```
1201
    pub fn realtime_bars(&self, contract: &Contract, bar_size: BarSize, what_to_show: WhatToShow, use_rth: bool) -> Result<Subscription<Bar>, Error> {
3✔
1202
        realtime::realtime_bars(self, contract, &bar_size, &what_to_show, use_rth, Vec::default())
21✔
1203
    }
1204

1205
    /// Requests tick by tick AllLast ticks.
1206
    ///
1207
    /// # Arguments
1208
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1209
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1210
    /// * `ignore_size`     - Specifies if tick sizes should be ignored.
1211
    ///
1212
    /// # Examples
1213
    ///
1214
    /// ```no_run
1215
    /// use ibapi::Client;
1216
    /// use ibapi::contracts::Contract;
1217
    ///
1218
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1219
    ///
1220
    /// let contract = Contract::stock("AAPL");
1221
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1222
    /// let ignore_size = false;
1223
    ///
1224
    /// let subscription = client.tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
1225
    ///     .expect("tick-by-tick all last data request failed");
1226
    ///
1227
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1228
    ///     println!("All Last Tick: {tick:?}");
1229
    /// }
1230
    /// ```
1231
    pub fn tick_by_tick_all_last(
2✔
1232
        &self,
1233
        contract: &Contract,
1234
        number_of_ticks: i32,
1235
        ignore_size: bool,
1236
    ) -> Result<Subscription<realtime::Trade>, Error> {
1237
        realtime::tick_by_tick_all_last(self, contract, number_of_ticks, ignore_size)
10✔
1238
    }
1239

1240
    /// Requests tick by tick BidAsk ticks.
1241
    ///
1242
    /// # Arguments
1243
    /// * `contract`        - The [Contract] for which to request tick-by-tick data.
1244
    /// * `number_of_ticks` - The number of ticks to retrieve. TWS usually limits this to 1000.
1245
    /// * `ignore_size`     - Specifies if tick sizes should be ignored. (typically true for BidAsk ticks to get changes based on price).
1246
    ///
1247
    /// # Examples
1248
    ///
1249
    /// ```no_run
1250
    /// use ibapi::Client;
1251
    /// use ibapi::contracts::Contract;
1252
    ///
1253
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1254
    ///
1255
    /// let contract = Contract::stock("AAPL");
1256
    /// let number_of_ticks = 10; // Request a small number of ticks for the example
1257
    /// let ignore_size = false;
1258
    ///
1259
    /// let subscription = client.tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
1260
    ///     .expect("tick-by-tick bid/ask data request failed");
1261
    ///
1262
    /// for tick in subscription.iter().take(number_of_ticks as usize) { // Take to limit example output
1263
    ///     println!("BidAsk Tick: {tick:?}");
1264
    /// }
1265
    /// ```
1266
    pub fn tick_by_tick_bid_ask(
2✔
1267
        &self,
1268
        contract: &Contract,
1269
        number_of_ticks: i32,
1270
        ignore_size: bool,
1271
    ) -> Result<Subscription<realtime::BidAsk>, Error> {
1272
        realtime::tick_by_tick_bid_ask(self, contract, number_of_ticks, ignore_size)
10✔
1273
    }
1274

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

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

1335
    /// Switches market data type returned from request_market_data requests to Live, Frozen, Delayed, or FrozenDelayed.
1336
    ///
1337
    /// # Arguments
1338
    /// * `market_data_type` - Type of market data to retrieve.
1339
    ///
1340
    /// # Examples
1341
    ///
1342
    /// ```no_run
1343
    /// use ibapi::Client;
1344
    /// use ibapi::market_data::{MarketDataType};
1345
    ///
1346
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1347
    ///
1348
    /// let market_data_type = MarketDataType::Live;
1349
    /// client.switch_market_data_type(market_data_type).expect("request failed");
1350
    /// println!("market data switched: {market_data_type:?}");
1351
    /// ```
1352
    pub fn switch_market_data_type(&self, market_data_type: MarketDataType) -> Result<(), Error> {
2✔
1353
        market_data::switch_market_data_type(self, market_data_type)
6✔
1354
    }
1355

1356
    /// Requests the contract's market depth (order book).
1357
    ///
1358
    /// # Arguments
1359
    ///
1360
    /// * `contract` - The Contract for which the depth is being requested.
1361
    /// * `number_of_rows` - The number of rows on each side of the order book.
1362
    /// * `is_smart_depth` - Flag indicates that this is smart depth request.
1363
    ///
1364
    /// # Examples
1365
    ///
1366
    /// ```no_run
1367
    /// use ibapi::Client;
1368
    /// use ibapi::contracts::Contract;
1369
    ///
1370
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1371
    ///
1372
    /// let contract = Contract::stock("AAPL");
1373
    ///
1374
    /// let subscription = client.market_depth(&contract, 5, true).expect("error requesting market depth");
1375
    /// for row in &subscription {
1376
    ///     println!("row: {row:?}");
1377
    /// }
1378
    ///
1379
    /// if let Some(error) = subscription.error() {
1380
    ///     println!("error: {error:?}");
1381
    /// }
1382
    /// ```
1383
    pub fn market_depth(&self, contract: &Contract, number_of_rows: i32, is_smart_depth: bool) -> Result<Subscription<MarketDepths>, Error> {
2✔
1384
        realtime::market_depth(self, contract, number_of_rows, is_smart_depth)
10✔
1385
    }
1386

1387
    /// Requests venues for which market data is returned to market_depth (those with market makers)
1388
    ///
1389
    /// # Examples
1390
    ///
1391
    /// ```no_run
1392
    /// use ibapi::Client;
1393
    ///
1394
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1395
    /// let exchanges = client.market_depth_exchanges().expect("error requesting market depth exchanges");
1396
    /// for exchange in &exchanges {
1397
    ///     println!("{exchange:?}");
1398
    /// }
1399
    /// ```
1400
    pub fn market_depth_exchanges(&self) -> Result<Vec<DepthMarketDataDescription>, Error> {
2✔
1401
        realtime::market_depth_exchanges(self)
4✔
1402
    }
1403

1404
    /// Requests real time market data.
1405
    ///
1406
    /// Returns market data for an instrument either in real time or 10-15 minutes delayed data.
1407
    ///
1408
    /// # Arguments
1409
    ///
1410
    /// * `contract` - Contract for which the data is being requested.
1411
    /// * `generic_ticks` - IDs of the available generic ticks:
1412
    ///   - 100 Option Volume (currently for stocks)
1413
    ///   - 101 Option Open Interest (currently for stocks)
1414
    ///   - 104 Historical Volatility (currently for stocks)
1415
    ///   - 105 Average Option Volume (currently for stocks)
1416
    ///   - 106 Option Implied Volatility (currently for stocks)
1417
    ///   - 162 Index Future Premium
1418
    ///   - 165 Miscellaneous Stats
1419
    ///   - 221 Mark Price (used in TWS P&L computations)
1420
    ///   - 225 Auction values (volume, price and imbalance)
1421
    ///   - 233 RTVolume - contains the last trade price, last trade size, last trade time, total volume, VWAP, and single trade flag.
1422
    ///   - 236 Shortable
1423
    ///   - 256 Inventory
1424
    ///   - 258 Fundamental Ratios
1425
    ///   - 411 Realtime Historical Volatility
1426
    ///   - 456 IBDividends
1427
    /// * `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.
1428
    /// * `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.
1429
    ///
1430
    /// # Examples
1431
    ///
1432
    /// ```no_run
1433
    /// use ibapi::{contracts::Contract, market_data::realtime::TickTypes, Client};
1434
    ///
1435
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1436
    ///
1437
    /// let contract = Contract::stock("AAPL");
1438
    ///
1439
    /// // https://www.interactivebrokers.com/campus/ibkr-api-page/twsapi-doc/#available-tick-types
1440
    /// let generic_ticks = &["233", "293"];
1441
    /// let snapshot = false;
1442
    /// let regulatory_snapshot = false;
1443
    ///
1444
    /// let subscription = client
1445
    ///     .market_data(&contract, generic_ticks, snapshot, regulatory_snapshot)
1446
    ///     .expect("error requesting market data");
1447
    ///
1448
    /// for tick in &subscription {
1449
    ///     match tick {
1450
    ///         TickTypes::Price(tick_price) => println!("{tick_price:?}"),
1451
    ///         TickTypes::Size(tick_size) => println!("{tick_size:?}"),
1452
    ///         TickTypes::PriceSize(tick_price_size) => println!("{tick_price_size:?}"),
1453
    ///         TickTypes::Generic(tick_generic) => println!("{tick_generic:?}"),
1454
    ///         TickTypes::String(tick_string) => println!("{tick_string:?}"),
1455
    ///         TickTypes::EFP(tick_efp) => println!("{tick_efp:?}"),
1456
    ///         TickTypes::OptionComputation(option_computation) => println!("{option_computation:?}"),
1457
    ///         TickTypes::RequestParameters(tick_request_parameters) => println!("{tick_request_parameters:?}"),
1458
    ///         TickTypes::Notice(notice) => println!("{notice:?}"),
1459
    ///         TickTypes::SnapshotEnd => subscription.cancel(),
1460
    ///     }
1461
    /// }
1462
    /// ```
1463
    pub fn market_data(
6✔
1464
        &self,
1465
        contract: &Contract,
1466
        generic_ticks: &[&str],
1467
        snapshot: bool,
1468
        regulatory_snapshot: bool,
1469
    ) -> Result<Subscription<TickTypes>, Error> {
1470
        realtime::market_data(self, contract, generic_ticks, snapshot, regulatory_snapshot)
36✔
1471
    }
1472

1473
    // === News ===
1474

1475
    /// Requests news providers which the user has subscribed to.
1476
    ///
1477
    /// # Examples
1478
    ///
1479
    /// ```no_run
1480
    /// use ibapi::Client;
1481
    ///
1482
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1483
    ///
1484
    /// let news_providers = client.news_providers().expect("request news providers failed");
1485
    /// for news_provider in &news_providers {
1486
    ///   println!("news provider {news_provider:?}");
1487
    /// }
1488
    /// ```
1489
    pub fn news_providers(&self) -> Result<Vec<news::NewsProvider>, Error> {
×
1490
        news::news_providers(self)
×
1491
    }
1492

1493
    /// Subscribes to IB's News Bulletins.
1494
    ///
1495
    /// # Arguments
1496
    ///
1497
    /// * `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.
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_bulletins = client.news_bulletins(true).expect("request news providers failed");
1507
    /// for news_bulletin in &news_bulletins {
1508
    ///   println!("news bulletin {news_bulletin:?}");
1509
    /// }
1510
    /// ```
NEW
1511
    pub fn news_bulletins(&self, all_messages: bool) -> Result<Subscription<news::NewsBulletin>, Error> {
×
1512
        news::news_bulletins(self, all_messages)
×
1513
    }
1514

1515
    /// Requests historical news headlines.
1516
    ///
1517
    /// # Arguments
1518
    ///
1519
    /// * `contract_id`    - Contract ID of ticker. See [contract_details](Client::contract_details) for how to retrieve contract ID.
1520
    /// * `provider_codes` - A list of provider codes.
1521
    /// * `start_time`     - Marks the (exclusive) start of the date range.
1522
    /// * `end_time`       - Marks the (inclusive) end of the date range.
1523
    /// * `total_results`  - The maximum number of headlines to fetch (1 – 300)
1524
    ///
1525
    /// # Examples
1526
    ///
1527
    /// ```no_run
1528
    /// use ibapi::Client;
1529
    /// use ibapi::contracts::Contract; // Or remove if conId is always known
1530
    /// use time::macros::datetime;
1531
    ///
1532
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1533
    ///
1534
    /// // Example: Fetch historical news for a known contract ID (e.g., AAPL's conId)
1535
    /// let contract_id = 265598;
1536
    /// let provider_codes = &["DJNL", "BRFG"]; // Example provider codes
1537
    /// // Define a past date range for the news query
1538
    /// let start_time = datetime!(2023-01-01 0:00 UTC);
1539
    /// let end_time = datetime!(2023-01-02 0:00 UTC);
1540
    /// let total_results = 5u8; // Request a small number of articles for the example
1541
    ///
1542
    /// let articles_subscription = client.historical_news(
1543
    ///     contract_id,
1544
    ///     provider_codes,
1545
    ///     start_time,
1546
    ///     end_time,
1547
    ///     total_results,
1548
    /// ).expect("request historical news failed");
1549
    ///
1550
    /// println!("Requested historical news articles:");
1551
    /// for article in articles_subscription.iter().take(total_results as usize) {
1552
    ///     println!("- Headline: {}, ID: {}, Provider: {}, Time: {}",
1553
    ///              article.headline, article.article_id, article.provider_code, article.time);
1554
    /// }
1555
    /// ```
1556
    pub fn historical_news(
×
1557
        &self,
1558
        contract_id: i32,
1559
        provider_codes: &[&str],
1560
        start_time: OffsetDateTime,
1561
        end_time: OffsetDateTime,
1562
        total_results: u8,
1563
    ) -> Result<Subscription<news::NewsArticle>, Error> {
1564
        news::historical_news(self, contract_id, provider_codes, start_time, end_time, total_results)
×
1565
    }
1566

1567
    /// Requests news article body given articleId.
1568
    ///
1569
    /// # Arguments
1570
    ///
1571
    /// * `provider_code` - Short code indicating news provider, e.g. FLY.
1572
    /// * `article_id`    - ID of the specific article.
1573
    ///
1574
    /// # Examples
1575
    ///
1576
    /// ```no_run
1577
    /// use ibapi::Client;
1578
    ///
1579
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1580
    ///
1581
    /// // can get these using the historical_news method
1582
    /// let provider_code = "DJ-N";
1583
    /// let article_id = "DJ-N$1915168d";
1584
    ///
1585
    /// let article = client.news_article(provider_code, article_id).expect("request news article failed");
1586
    /// println!("{article:?}");
1587
    /// ```
1588
    pub fn news_article(&self, provider_code: &str, article_id: &str) -> Result<news::NewsArticleBody, Error> {
×
1589
        news::news_article(self, provider_code, article_id)
×
1590
    }
1591

1592
    /// Requests realtime contract specific news
1593
    ///
1594
    /// # Arguments
1595
    ///
1596
    /// * `contract`       - Contract for which news is being requested.
1597
    /// * `provider_codes` - Short codes indicating news providers, e.g. DJ-N.
1598
    ///
1599
    /// # Examples
1600
    ///
1601
    /// ```no_run
1602
    /// use ibapi::Client;
1603
    /// use ibapi::contracts::Contract;
1604
    ///
1605
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1606
    ///
1607
    /// let contract = Contract::stock("AAPL");
1608
    /// let provider_codes = ["DJ-N"];
1609
    ///
1610
    /// let subscription = client.contract_news(&contract, &provider_codes).expect("request contract news failed");
1611
    /// for article in &subscription {
1612
    ///     println!("{article:?}");
1613
    /// }
1614
    /// ```
NEW
1615
    pub fn contract_news(&self, contract: &Contract, provider_codes: &[&str]) -> Result<Subscription<NewsArticle>, Error> {
×
1616
        news::contract_news(self, contract, provider_codes)
×
1617
    }
1618

1619
    /// Requests realtime BroadTape News
1620
    ///
1621
    /// # Arguments
1622
    ///
1623
    /// * `provider_code` - Short codes indicating news provider, e.g. DJ-N.
1624
    ///
1625
    /// # Examples
1626
    ///
1627
    /// ```no_run
1628
    /// use ibapi::Client;
1629
    ///
1630
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1631
    ///
1632
    /// let provider_code = "BRFG";
1633
    ///
1634
    /// let subscription = client.broad_tape_news(provider_code).expect("request broad tape news failed");
1635
    /// for article in &subscription {
1636
    ///     println!("{article:?}");
1637
    /// }
1638
    /// ```
NEW
1639
    pub fn broad_tape_news(&self, provider_code: &str) -> Result<Subscription<NewsArticle>, Error> {
×
1640
        news::broad_tape_news(self, provider_code)
×
1641
    }
1642

1643
    // === Scanner ===
1644

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

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

1755
    // == Wall Street Horizon
1756

1757
    /// Requests metadata from the WSH calendar.
1758
    ///
1759
    /// # Examples
1760
    ///
1761
    /// ```no_run
1762
    /// use ibapi::Client;
1763
    ///
1764
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1765
    ///
1766
    /// let metadata = client.wsh_metadata().expect("request wsh metadata failed");
1767
    /// println!("{metadata:?}");
1768
    /// ```
1769
    pub fn wsh_metadata(&self) -> Result<wsh::WshMetadata, Error> {
×
1770
        wsh::wsh_metadata(self)
×
1771
    }
1772

1773
    /// Requests event data for a specified contract from the Wall Street Horizons (WSH) calendar.
1774
    ///
1775
    /// # Arguments
1776
    ///
1777
    /// * `contract_id` - Contract identifier for the event request.
1778
    /// * `start_date`  - Start date of the event request.
1779
    /// * `end_date`    - End date of the event request.
1780
    /// * `limit`       - Maximum number of events to return. Maximum of 100.
1781
    /// * `auto_fill`   - Fields to automatically fill in. See [AutoFill] for more information.
1782
    ///
1783
    /// # Examples
1784
    ///
1785
    /// ```no_run
1786
    /// use ibapi::Client;
1787
    ///
1788
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1789
    ///
1790
    /// let contract_id = 76792991; // TSLA
1791
    /// let event_data = client.wsh_event_data_by_contract(contract_id, None, None, None, None).expect("request wsh event data failed");
1792
    /// println!("{event_data:?}");
1793
    /// ```
1794
    pub fn wsh_event_data_by_contract(
×
1795
        &self,
1796
        contract_id: i32,
1797
        start_date: Option<Date>,
1798
        end_date: Option<Date>,
1799
        limit: Option<i32>,
1800
        auto_fill: Option<AutoFill>,
1801
    ) -> Result<wsh::WshEventData, Error> {
1802
        wsh::wsh_event_data_by_contract(self, contract_id, start_date, end_date, limit, auto_fill)
×
1803
    }
1804

1805
    /// Requests event data from the Wall Street Horizons (WSH) calendar using a JSON filter.
1806
    ///
1807
    /// # Arguments
1808
    ///
1809
    /// * `filter`    - Json-formatted string containing all filter values.
1810
    /// * `limit`     - Maximum number of events to return. Maximum of 100.
1811
    /// * `auto_fill` - Fields to automatically fill in. See [AutoFill] for more information.
1812
    ///
1813
    /// # Examples
1814
    ///
1815
    /// ```no_run
1816
    /// use ibapi::Client;
1817
    ///
1818
    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
1819
    ///
1820
    /// let filter = ""; // see https://www.interactivebrokers.com/campus/ibkr-api-page/twsapi-doc/#wsheventdata-object
1821
    /// let event_data = client.wsh_event_data_by_filter(filter, None, None).expect("request wsh event data failed");
1822
    /// for result in event_data {
1823
    ///     println!("{result:?}");
1824
    /// }
1825
    /// ```
1826
    pub fn wsh_event_data_by_filter(
×
1827
        &self,
1828
        filter: &str,
1829
        limit: Option<i32>,
1830
        auto_fill: Option<AutoFill>,
1831
    ) -> Result<Subscription<wsh::WshEventData>, Error> {
1832
        wsh::wsh_event_data_by_filter(self, filter, limit, auto_fill)
×
1833
    }
1834

1835
    // == Internal Use ==
1836

1837
    #[cfg(test)]
1838
    pub(crate) fn stubbed(message_bus: Arc<dyn MessageBus>, server_version: i32) -> Client {
146✔
1839
        Client {
1840
            server_version,
1841
            connection_time: None,
1842
            time_zone: None,
1843
            message_bus,
1844
            client_id: 100,
1845
            id_manager: ClientIdManager::new(-1),
146✔
1846
        }
1847
    }
1848

1849
    pub(crate) fn send_request(&self, request_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
103✔
1850
        debug!("send_message({request_id:?}, {message:?})");
103✔
1851
        self.message_bus.send_request(request_id, &message)
309✔
1852
    }
1853

1854
    pub(crate) fn send_order(&self, order_id: i32, message: RequestMessage) -> Result<InternalSubscription, Error> {
7✔
1855
        debug!("send_order({order_id:?}, {message:?})");
7✔
1856
        self.message_bus.send_order_request(order_id, &message)
21✔
1857
    }
1858

1859
    pub(crate) fn send_message(&self, message: RequestMessage) -> Result<(), Error> {
4✔
1860
        debug!("send_message({message:?})");
4✔
1861
        self.message_bus.send_message(&message)
8✔
1862
    }
1863

1864
    /// Creates a subscription for order updates if one is not already active.
1865
    pub(crate) fn create_order_update_subscription(&self) -> Result<InternalSubscription, Error> {
4✔
1866
        self.message_bus.create_order_update_subscription()
4✔
1867
    }
1868

1869
    /// Sends request for the next valid order id.
1870
    pub(crate) fn send_shared_request(&self, message_id: OutgoingMessages, message: RequestMessage) -> Result<InternalSubscription, Error> {
56✔
1871
        self.message_bus.send_shared_request(message_id, &message)
168✔
1872
    }
1873

1874
    pub(crate) fn check_server_version(&self, version: i32, message: &str) -> Result<(), Error> {
11✔
1875
        if version <= self.server_version {
11✔
1876
            Ok(())
11✔
1877
        } else {
1878
            Err(Error::ServerVersion(version, self.server_version, message.into()))
×
1879
        }
1880
    }
1881
}
1882

1883
impl Drop for Client {
1884
    fn drop(&mut self) {
184✔
1885
        debug!("dropping basic client");
184✔
1886
        self.message_bus.ensure_shutdown();
184✔
1887
    }
1888
}
1889

1890
impl Debug for Client {
1891
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
1892
        f.debug_struct("Client")
×
1893
            .field("server_version", &self.server_version)
×
1894
            .field("server_time", &self.connection_time)
×
1895
            .field("client_id", &self.client_id)
×
1896
            .finish()
1897
    }
1898
}
1899

1900
/// Subscriptions facilitate handling responses from TWS that may be delayed or delivered periodically.
1901
///
1902
/// They offer both blocking and non-blocking methods for retrieving data.
1903
///
1904
/// In the simplest case a subscription can be implicitly converted to blocking iterator
1905
/// that cancels the subscription when it goes out of scope.
1906
///
1907
/// ```no_run
1908
/// use ibapi::contracts::Contract;
1909
/// use ibapi::market_data::realtime::{BarSize, WhatToShow};
1910
/// use ibapi::Client;
1911
///
1912
/// let connection_url = "127.0.0.1:4002";
1913
/// let client = Client::connect(connection_url, 100).expect("connection to TWS failed!");
1914
///
1915
/// // Request real-time bars data for AAPL with 5-second intervals
1916
/// let contract = Contract::stock("AAPL");
1917
/// let subscription = client
1918
///     .realtime_bars(&contract, BarSize::Sec5, WhatToShow::Trades, false)
1919
///     .expect("realtime bars request failed!");
1920
///
1921
/// // Use the subscription as a blocking iterator
1922
/// for bar in subscription {
1923
///     // Process each bar here (e.g., print or use in calculations)
1924
///     println!("Received bar: {bar:?}");
1925
/// }
1926
/// // The subscription goes out of scope and is automatically cancelled.
1927
/// ```
1928
///
1929
/// Subscriptions can be explicitly canceled using the [cancel](Subscription::cancel) method.
1930
///
1931
// Re-export SharesChannel trait from subscriptions module
1932
pub use crate::subscriptions::SharesChannel;
1933

1934
#[cfg(test)]
1935
mod tests {
1936
    use std::sync::Arc;
1937

1938
    use super::Client;
1939
    use crate::client::common::tests::*;
1940
    use crate::{connection::ConnectionMetadata, stubs::MessageBusStub};
1941

1942
    const CLIENT_ID: i32 = 100;
1943

1944
    #[test]
1945
    fn test_connect() {
1946
        let gateway = setup_connect();
1947

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

1950
        assert_eq!(client.client_id(), CLIENT_ID);
1951
        assert_eq!(client.server_version(), gateway.server_version());
1952
        assert_eq!(client.time_zone, gateway.time_zone());
1953

1954
        assert_eq!(gateway.requests().len(), 0, "No requests should be sent on connect");
1955
    }
1956

1957
    #[test]
1958
    fn test_server_time() {
1959
        let (gateway, expectations) = setup_server_time();
1960

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

1963
        let server_time = client.server_time().unwrap();
1964
        assert_eq!(server_time, expectations.server_time);
1965

1966
        let requests = gateway.requests();
1967
        assert_eq!(requests[0], "49\01\0");
1968
    }
1969

1970
    #[test]
1971
    fn test_next_valid_order_id() {
1972
        let (gateway, expectations) = setup_next_valid_order_id();
1973

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

1976
        let next_valid_order_id = client.next_valid_order_id().unwrap();
1977
        assert_eq!(next_valid_order_id, expectations.next_valid_order_id);
1978

1979
        let requests = gateway.requests();
1980
        assert_eq!(requests[0], "8\01\00\0");
1981
    }
1982

1983
    #[test]
1984
    fn test_managed_accounts() {
1985
        let (gateway, expectations) = setup_managed_accounts();
1986

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

1989
        let accounts = client.managed_accounts().unwrap();
1990
        assert_eq!(accounts, expectations.accounts);
1991

1992
        let requests = gateway.requests();
1993
        assert_eq!(requests[0], "17\01\0");
1994
    }
1995

1996
    #[test]
1997
    fn test_positions() {
1998
        let gateway = setup_positions();
1999

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

2002
        let positions = client.positions().unwrap();
2003
        let mut position_count = 0;
2004

2005
        for position_update in positions {
2006
            match position_update {
2007
                crate::accounts::PositionUpdate::Position(position) => {
2008
                    assert_eq!(position.account, "DU1234567");
2009
                    assert_eq!(position.contract.symbol, "AAPL");
2010
                    assert_eq!(position.position, 500.0);
2011
                    assert_eq!(position.average_cost, 150.25);
2012
                    position_count += 1;
2013
                }
2014
                crate::accounts::PositionUpdate::PositionEnd => {
2015
                    break;
2016
                }
2017
            }
2018
        }
2019

2020
        assert_eq!(position_count, 1);
2021
        let requests = gateway.requests();
2022
        assert_eq!(requests[0], "61\01\0");
2023
    }
2024

2025
    #[test]
2026
    fn test_positions_multi() {
2027
        use crate::accounts::types::AccountId;
2028

2029
        let gateway = setup_positions_multi();
2030

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

2033
        let account = AccountId("DU1234567".to_string());
2034
        let positions = client.positions_multi(Some(&account), None).unwrap();
2035
        let mut position_count = 0;
2036

2037
        for position_update in positions {
2038
            match position_update {
2039
                crate::accounts::PositionUpdateMulti::Position(position) => {
2040
                    position_count += 1;
2041
                    if position_count == 1 {
2042
                        assert_eq!(position.account, "DU1234567");
2043
                        assert_eq!(position.contract.symbol, "AAPL");
2044
                        assert_eq!(position.position, 500.0);
2045
                        assert_eq!(position.average_cost, 150.25);
2046
                        assert_eq!(position.model_code, "MODEL1");
2047
                    } else if position_count == 2 {
2048
                        assert_eq!(position.account, "DU1234568");
2049
                        assert_eq!(position.contract.symbol, "GOOGL");
2050
                        assert_eq!(position.position, 200.0);
2051
                        assert_eq!(position.average_cost, 2500.00);
2052
                        assert_eq!(position.model_code, "MODEL1");
2053
                    }
2054
                }
2055
                crate::accounts::PositionUpdateMulti::PositionEnd => {
2056
                    break;
2057
                }
2058
            }
2059
        }
2060

2061
        assert_eq!(position_count, 2);
2062
        let requests = gateway.requests();
2063
        assert_eq!(requests[0], "74\01\09000\0DU1234567\0\0");
2064
    }
2065

2066
    #[test]
2067
    fn test_account_summary() {
2068
        use crate::accounts::types::AccountGroup;
2069

2070
        let gateway = setup_account_summary();
2071

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

2074
        let group = AccountGroup("All".to_string());
2075
        let tags = vec!["NetLiquidation", "TotalCashValue"];
2076

2077
        let summaries = client.account_summary(&group, &tags).unwrap();
2078
        let mut summary_count = 0;
2079

2080
        for summary_result in summaries {
2081
            match summary_result {
2082
                crate::accounts::AccountSummaryResult::Summary(summary) => {
2083
                    assert_eq!(summary.account, "DU1234567");
2084
                    assert_eq!(summary.currency, "USD");
2085

2086
                    if summary.tag == "NetLiquidation" {
2087
                        assert_eq!(summary.value, "25000.00");
2088
                    } else if summary.tag == "TotalCashValue" {
2089
                        assert_eq!(summary.value, "15000.00");
2090
                    }
2091
                    summary_count += 1;
2092
                }
2093
                crate::accounts::AccountSummaryResult::End => {
2094
                    break;
2095
                }
2096
            }
2097
        }
2098

2099
        assert_eq!(summary_count, 2);
2100
        let requests = gateway.requests();
2101
        assert_eq!(requests[0], "62\01\09000\0All\0NetLiquidation,TotalCashValue\0");
2102
    }
2103

2104
    #[test]
2105
    fn test_pnl() {
2106
        use crate::accounts::types::AccountId;
2107

2108
        let gateway = setup_pnl();
2109

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

2112
        let account = AccountId("DU1234567".to_string());
2113
        let pnl = client.pnl(&account, None).unwrap();
2114

2115
        let first_pnl = pnl.into_iter().next().unwrap();
2116
        assert_eq!(first_pnl.daily_pnl, 250.50);
2117
        assert_eq!(first_pnl.unrealized_pnl, Some(1500.00));
2118
        assert_eq!(first_pnl.realized_pnl, Some(750.00));
2119

2120
        let requests = gateway.requests();
2121
        assert_eq!(requests[0], "92\09000\0DU1234567\0\0");
2122
    }
2123

2124
    #[test]
2125
    fn test_pnl_single() {
2126
        use crate::accounts::types::{AccountId, ContractId};
2127

2128
        let gateway = setup_pnl_single();
2129

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

2132
        let account = AccountId("DU1234567".to_string());
2133
        let contract_id = ContractId(12345);
2134
        let pnl_single = client.pnl_single(&account, contract_id, None).unwrap();
2135

2136
        let first_pnl = pnl_single.into_iter().next().unwrap();
2137
        assert_eq!(first_pnl.position, 100.0);
2138
        assert_eq!(first_pnl.daily_pnl, 150.25);
2139
        assert_eq!(first_pnl.unrealized_pnl, 500.00);
2140
        assert_eq!(first_pnl.realized_pnl, 250.00);
2141
        assert_eq!(first_pnl.value, 1000.00);
2142

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

2147
    #[test]
2148
    fn test_account_updates() {
2149
        use crate::accounts::types::AccountId;
2150

2151
        let gateway = setup_account_updates();
2152

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

2155
        let account = AccountId("DU1234567".to_string());
2156
        let updates = client.account_updates(&account).unwrap();
2157

2158
        let mut value_count = 0;
2159
        let mut portfolio_count = 0;
2160
        let mut has_time_update = false;
2161
        let mut has_end = false;
2162

2163
        for update in updates {
2164
            match update {
2165
                crate::accounts::AccountUpdate::AccountValue(value) => {
2166
                    assert_eq!(value.key, "NetLiquidation");
2167
                    assert_eq!(value.value, "25000.00");
2168
                    assert_eq!(value.currency, "USD");
2169
                    assert_eq!(value.account, Some("DU1234567".to_string()));
2170
                    value_count += 1;
2171
                }
2172
                crate::accounts::AccountUpdate::PortfolioValue(portfolio) => {
2173
                    assert_eq!(portfolio.contract.symbol, "AAPL");
2174
                    assert_eq!(portfolio.position, 500.0);
2175
                    assert_eq!(portfolio.market_price, 151.50);
2176
                    assert_eq!(portfolio.market_value, 75750.00);
2177
                    assert_eq!(portfolio.average_cost, 150.25);
2178
                    assert_eq!(portfolio.unrealized_pnl, 375.00);
2179
                    assert_eq!(portfolio.realized_pnl, 125.00);
2180
                    assert_eq!(portfolio.account, Some("DU1234567".to_string()));
2181
                    portfolio_count += 1;
2182
                }
2183
                crate::accounts::AccountUpdate::UpdateTime(time) => {
2184
                    assert_eq!(time.timestamp, "20240122 15:30:00");
2185
                    has_time_update = true;
2186
                }
2187
                crate::accounts::AccountUpdate::End => {
2188
                    has_end = true;
2189
                    break;
2190
                }
2191
            }
2192
        }
2193

2194
        assert!(has_end, "Expected End message");
2195
        assert_eq!(value_count, 1);
2196
        assert_eq!(portfolio_count, 1);
2197
        assert!(has_time_update);
2198

2199
        let requests = gateway.requests();
2200
        assert_eq!(requests[0], "6\02\01\0DU1234567\0");
2201
    }
2202

2203
    #[test]
2204
    fn test_family_codes() {
2205
        let gateway = setup_family_codes();
2206

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

2209
        let family_codes = client.family_codes().unwrap();
2210

2211
        assert_eq!(family_codes.len(), 2);
2212
        assert_eq!(family_codes[0].account_id, "DU1234567");
2213
        assert_eq!(family_codes[0].family_code, "FAM001");
2214
        assert_eq!(family_codes[1].account_id, "DU1234568");
2215
        assert_eq!(family_codes[1].family_code, "FAM002");
2216

2217
        let requests = gateway.requests();
2218
        assert_eq!(requests[0], "80\01\0");
2219
    }
2220

2221
    #[test]
2222
    fn test_account_updates_multi() {
2223
        use crate::accounts::types::{AccountId, ModelCode};
2224

2225
        let gateway = setup_account_updates_multi();
2226

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

2229
        let account = AccountId("DU1234567".to_string());
2230
        let model_code: Option<ModelCode> = None;
2231
        let updates = client.account_updates_multi(Some(&account), model_code.as_ref()).unwrap();
2232

2233
        let mut cash_balance_found = false;
2234
        let mut currency_found = false;
2235
        let mut stock_market_value_found = false;
2236
        let mut has_end = false;
2237

2238
        for update in updates {
2239
            match update {
2240
                crate::accounts::AccountUpdateMulti::AccountMultiValue(value) => {
2241
                    assert_eq!(value.account, "DU1234567");
2242
                    assert_eq!(value.model_code, "");
2243

2244
                    match value.key.as_str() {
2245
                        "CashBalance" => {
2246
                            assert_eq!(value.value, "94629.71");
2247
                            assert_eq!(value.currency, "USD");
2248
                            cash_balance_found = true;
2249
                        }
2250
                        "Currency" => {
2251
                            assert_eq!(value.value, "USD");
2252
                            assert_eq!(value.currency, "USD");
2253
                            currency_found = true;
2254
                        }
2255
                        "StockMarketValue" => {
2256
                            assert_eq!(value.value, "0.00");
2257
                            assert_eq!(value.currency, "BASE");
2258
                            stock_market_value_found = true;
2259
                        }
2260
                        _ => panic!("Unexpected key: {}", value.key),
2261
                    }
2262
                }
2263
                crate::accounts::AccountUpdateMulti::End => {
2264
                    has_end = true;
2265
                    break;
2266
                }
2267
            }
2268
        }
2269

2270
        assert!(cash_balance_found, "Expected CashBalance update");
2271
        assert!(currency_found, "Expected Currency update");
2272
        assert!(stock_market_value_found, "Expected StockMarketValue update");
2273
        assert!(has_end, "Expected End message");
2274

2275
        let requests = gateway.requests();
2276
        assert_eq!(requests[0], "76\01\09000\0DU1234567\0\01\0");
2277
    }
2278

2279
    #[test]
2280
    fn test_client_id() {
2281
        let client_id = 500;
2282
        let connection_metadata = ConnectionMetadata {
2283
            client_id,
2284
            ..ConnectionMetadata::default()
2285
        };
2286
        let message_bus = Arc::new(MessageBusStub::default());
2287

2288
        let client = Client::new(connection_metadata, message_bus).unwrap();
2289

2290
        assert_eq!(client.client_id(), client_id);
2291
    }
2292

2293
    #[test]
2294
    fn test_contract_details() {
2295
        let gateway = setup_contract_details();
2296

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

2299
        let contract = crate::contracts::Contract::stock("AAPL");
2300
        let details = client.contract_details(&contract).expect("Failed to get contract details");
2301

2302
        assert_eq!(details.len(), 1);
2303
        let detail = &details[0];
2304

2305
        // Verify contract fields
2306
        assert_eq!(detail.contract.symbol, "AAPL");
2307
        assert_eq!(detail.contract.security_type, crate::contracts::SecurityType::Stock);
2308
        assert_eq!(detail.contract.currency, "USD");
2309
        assert_eq!(detail.contract.exchange, "NASDAQ");
2310
        assert_eq!(detail.contract.local_symbol, "AAPL");
2311
        assert_eq!(detail.contract.trading_class, "AAPL");
2312
        assert_eq!(detail.contract.contract_id, 265598);
2313
        assert_eq!(detail.contract.primary_exchange, "NASDAQ");
2314

2315
        // Verify contract details fields
2316
        assert_eq!(detail.market_name, "NMS");
2317
        assert_eq!(detail.min_tick, 0.01);
2318
        assert!(detail.order_types.contains(&"LMT".to_string()));
2319
        assert!(detail.order_types.contains(&"MKT".to_string()));
2320
        assert!(detail.valid_exchanges.contains(&"SMART".to_string()));
2321
        assert_eq!(detail.long_name, "Apple Inc");
2322
        assert_eq!(detail.industry, "Technology");
2323
        assert_eq!(detail.category, "Computers");
2324
        assert_eq!(detail.subcategory, "Computers");
2325
        assert_eq!(detail.time_zone_id, "US/Eastern");
2326
        assert_eq!(detail.stock_type, "NMS");
2327
        assert_eq!(detail.min_size, 1.0);
2328
        assert_eq!(detail.size_increment, 1.0);
2329
        assert_eq!(detail.suggested_size_increment, 1.0);
2330

2331
        let requests = gateway.requests();
2332
        // Request format: OutgoingMessages::RequestContractData(9), version(8), request_id, contract_id(0),
2333
        // symbol, security_type, last_trade_date, strike, right, multiplier, exchange, primary_exchange,
2334
        // currency, local_symbol, trading_class, include_expired, security_id_type, security_id, issuer_id
2335
        assert_eq!(requests[0], "9\08\09000\00\0AAPL\0STK\0\00\0\0\0SMART\0\0USD\0\0\00\0\0\0");
2336
    }
2337

2338
    #[test]
2339
    fn test_subscription_cancel_only_sends_once() {
2340
        // This test verifies that calling cancel() multiple times only sends one cancel message
2341
        // This addresses issue #258 where explicit cancel() followed by Drop could send duplicate messages
2342

2343
        let message_bus = Arc::new(MessageBusStub::default());
2344
        let client = Client::stubbed(message_bus.clone(), 100);
2345

2346
        // Create a subscription using realtime bars as an example
2347
        let contract = crate::contracts::Contract::stock("AAPL");
2348
        let subscription = client
2349
            .realtime_bars(
2350
                &contract,
2351
                crate::market_data::realtime::BarSize::Sec5,
2352
                crate::market_data::realtime::WhatToShow::Trades,
2353
                false,
2354
            )
2355
            .expect("Failed to create subscription");
2356

2357
        // Get initial request count (should be 1 for the realtime bars request)
2358
        let initial_count = message_bus.request_messages().len();
2359
        assert_eq!(initial_count, 1, "Should have one request for realtime bars");
2360

2361
        // First cancel should add one more message
2362
        subscription.cancel();
2363
        let after_first_cancel = message_bus.request_messages().len();
2364
        assert_eq!(after_first_cancel, 2, "Should have two messages after first cancel");
2365

2366
        // Second cancel should not send another message
2367
        subscription.cancel();
2368
        let after_second_cancel = message_bus.request_messages().len();
2369
        assert_eq!(after_second_cancel, 2, "Should still have two messages after second cancel");
2370

2371
        // Drop should also not send another message (implicitly calls cancel)
2372
        drop(subscription);
2373
        let after_drop = message_bus.request_messages().len();
2374
        assert_eq!(after_drop, 2, "Should still have two messages after drop");
2375
    }
2376

2377
    #[test]
2378
    fn test_matching_symbols() {
2379
        let gateway = setup_matching_symbols();
2380

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

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

2386
        assert_eq!(contract_descriptions.len(), 2, "Should have 2 matching symbols");
2387

2388
        // First contract description
2389
        assert_eq!(contract_descriptions[0].contract.contract_id, 265598);
2390
        assert_eq!(contract_descriptions[0].contract.symbol, "AAPL");
2391
        assert_eq!(contract_descriptions[0].contract.security_type, crate::contracts::SecurityType::Stock);
2392
        assert_eq!(contract_descriptions[0].contract.primary_exchange, "NASDAQ");
2393
        assert_eq!(contract_descriptions[0].contract.currency, "USD");
2394
        assert_eq!(contract_descriptions[0].derivative_security_types.len(), 2);
2395
        assert_eq!(contract_descriptions[0].derivative_security_types[0], "OPT");
2396
        assert_eq!(contract_descriptions[0].derivative_security_types[1], "WAR");
2397
        assert_eq!(contract_descriptions[0].contract.description, "Apple Inc.");
2398
        assert_eq!(contract_descriptions[0].contract.issuer_id, "AAPL123");
2399

2400
        // Second contract description
2401
        assert_eq!(contract_descriptions[1].contract.contract_id, 276821);
2402
        assert_eq!(contract_descriptions[1].contract.symbol, "MSFT");
2403
        assert_eq!(contract_descriptions[1].contract.security_type, crate::contracts::SecurityType::Stock);
2404
        assert_eq!(contract_descriptions[1].contract.primary_exchange, "NASDAQ");
2405
        assert_eq!(contract_descriptions[1].contract.currency, "USD");
2406
        assert_eq!(contract_descriptions[1].derivative_security_types.len(), 1);
2407
        assert_eq!(contract_descriptions[1].derivative_security_types[0], "OPT");
2408
        assert_eq!(contract_descriptions[1].contract.description, "Microsoft Corporation");
2409
        assert_eq!(contract_descriptions[1].contract.issuer_id, "MSFT456");
2410

2411
        // Verify request format
2412
        let requests = gateway.requests();
2413
        assert_eq!(requests.len(), 1, "Should have 1 request");
2414
        // Request format: RequestMatchingSymbols(81), request_id, pattern
2415
        assert!(requests[0].starts_with("81\0"), "Request should start with message type 81");
2416
        assert!(requests[0].contains("\0AAP\0"), "Request should contain the pattern AAP");
2417
    }
2418

2419
    #[test]
2420
    fn test_market_rule() {
2421
        let gateway = setup_market_rule();
2422

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

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

2427
        // Verify market rule ID
2428
        assert_eq!(market_rule.market_rule_id, 26, "Market rule ID should be 26");
2429

2430
        // Verify price increments
2431
        assert_eq!(market_rule.price_increments.len(), 3, "Should have 3 price increments");
2432

2433
        // First increment: 0-100, increment 0.01
2434
        assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "First increment low edge");
2435
        assert_eq!(market_rule.price_increments[0].increment, 0.01, "First increment value");
2436

2437
        // Second increment: 100-1000, increment 0.05
2438
        assert_eq!(market_rule.price_increments[1].low_edge, 100.0, "Second increment low edge");
2439
        assert_eq!(market_rule.price_increments[1].increment, 0.05, "Second increment value");
2440

2441
        // Third increment: 1000+, increment 0.10
2442
        assert_eq!(market_rule.price_increments[2].low_edge, 1000.0, "Third increment low edge");
2443
        assert_eq!(market_rule.price_increments[2].increment, 0.10, "Third increment value");
2444

2445
        // Verify request format
2446
        let requests = gateway.requests();
2447
        assert_eq!(requests.len(), 1, "Should have 1 request");
2448
        // Request format: RequestMarketRule(91), market_rule_id
2449
        assert_eq!(requests[0], "91\026\0", "Request should be message type 91 with market rule ID 26");
2450
    }
2451

2452
    #[test]
2453
    fn test_calculate_option_price() {
2454
        let gateway = setup_calculate_option_price();
2455

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

2458
        // Create an option contract
2459
        let contract = crate::contracts::Contract {
2460
            symbol: "AAPL".to_string(),
2461
            security_type: crate::contracts::SecurityType::Option,
2462
            exchange: "SMART".to_string(),
2463
            currency: "USD".to_string(),
2464
            last_trade_date_or_contract_month: "20250120".to_string(),
2465
            strike: 100.0,
2466
            right: "C".to_string(),
2467
            ..Default::default()
2468
        };
2469

2470
        let volatility = 0.25;
2471
        let underlying_price = 100.0;
2472

2473
        let computation = client
2474
            .calculate_option_price(&contract, volatility, underlying_price)
2475
            .expect("Failed to calculate option price");
2476

2477
        // Verify computation results
2478
        assert_eq!(
2479
            computation.field,
2480
            crate::contracts::tick_types::TickType::ModelOption,
2481
            "Should be ModelOption tick type"
2482
        );
2483
        assert_eq!(computation.tick_attribute, Some(0), "Tick attribute should be 0");
2484
        assert_eq!(computation.implied_volatility, Some(0.25), "Implied volatility should match");
2485
        assert_eq!(computation.delta, Some(0.5), "Delta should be 0.5");
2486
        assert_eq!(computation.option_price, Some(12.75), "Option price should be 12.75");
2487
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2488
        assert_eq!(computation.gamma, Some(0.05), "Gamma should be 0.05");
2489
        assert_eq!(computation.vega, Some(0.02), "Vega should be 0.02");
2490
        assert_eq!(computation.theta, Some(-0.01), "Theta should be -0.01");
2491
        assert_eq!(computation.underlying_price, Some(100.0), "Underlying price should be 100");
2492

2493
        // Verify request format
2494
        let requests = gateway.requests();
2495
        assert_eq!(requests.len(), 1, "Should have 1 request");
2496
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, volatility, underlying_price
2497
        assert!(
2498
            requests[0].starts_with("54\03\0"),
2499
            "Request should start with message type 54 and version 3"
2500
        );
2501
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2502
        assert!(requests[0].contains("\00.25\0"), "Request should contain volatility 0.25");
2503
        assert!(requests[0].contains("\0100\0"), "Request should contain underlying price 100");
2504
    }
2505

2506
    #[test]
2507
    fn test_calculate_implied_volatility() {
2508
        let gateway = setup_calculate_implied_volatility();
2509

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

2512
        // Create an option contract
2513
        let contract = crate::contracts::Contract {
2514
            symbol: "MSFT".to_string(),
2515
            security_type: crate::contracts::SecurityType::Option,
2516
            exchange: "SMART".to_string(),
2517
            currency: "USD".to_string(),
2518
            last_trade_date_or_contract_month: "20250220".to_string(),
2519
            strike: 105.0,
2520
            right: "P".to_string(), // Put option
2521
            ..Default::default()
2522
        };
2523

2524
        let option_price = 15.50;
2525
        let underlying_price = 105.0;
2526

2527
        let computation = client
2528
            .calculate_implied_volatility(&contract, option_price, underlying_price)
2529
            .expect("Failed to calculate implied volatility");
2530

2531
        // Verify computation results
2532
        assert_eq!(
2533
            computation.field,
2534
            crate::contracts::tick_types::TickType::ModelOption,
2535
            "Should be ModelOption tick type"
2536
        );
2537
        assert_eq!(computation.tick_attribute, Some(1), "Tick attribute should be 1 (price-based)");
2538
        assert_eq!(computation.implied_volatility, Some(0.35), "Implied volatility should be 0.35");
2539
        assert_eq!(computation.delta, Some(0.45), "Delta should be 0.45");
2540
        assert_eq!(computation.option_price, Some(15.50), "Option price should be 15.50");
2541
        assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2542
        assert_eq!(computation.gamma, Some(0.04), "Gamma should be 0.04");
2543
        assert_eq!(computation.vega, Some(0.03), "Vega should be 0.03");
2544
        assert_eq!(computation.theta, Some(-0.02), "Theta should be -0.02");
2545
        assert_eq!(computation.underlying_price, Some(105.0), "Underlying price should be 105");
2546

2547
        // Verify request format
2548
        let requests = gateway.requests();
2549
        assert_eq!(requests.len(), 1, "Should have 1 request");
2550
        // Request format: ReqCalcImpliedVolat(54), version(3), request_id, contract fields, option_price, underlying_price
2551
        assert!(
2552
            requests[0].starts_with("54\03\0"),
2553
            "Request should start with message type 54 and version 3"
2554
        );
2555
        assert!(requests[0].contains("\0MSFT\0"), "Request should contain symbol MSFT");
2556
        assert!(requests[0].contains("\015.5\0"), "Request should contain option price 15.5");
2557
        assert!(requests[0].contains("\0105\0"), "Request should contain underlying price 105");
2558
    }
2559

2560
    #[test]
2561
    fn test_option_chain() {
2562
        let gateway = setup_option_chain();
2563

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

2566
        let symbol = "AAPL";
2567
        let exchange = ""; // Empty means all exchanges
2568
        let security_type = crate::contracts::SecurityType::Stock;
2569
        let contract_id = 0; // 0 means use symbol
2570

2571
        let subscription = client
2572
            .option_chain(symbol, exchange, security_type, contract_id)
2573
            .expect("Failed to get option chain");
2574

2575
        let mut chains = Vec::new();
2576
        for chain in subscription {
2577
            chains.push(chain);
2578
        }
2579

2580
        // Should have received 2 option chains (SMART and CBOE)
2581
        assert_eq!(chains.len(), 2, "Should have 2 option chains");
2582

2583
        // First chain - SMART exchange
2584
        assert_eq!(chains[0].exchange, "SMART", "First chain should be SMART");
2585
        assert_eq!(chains[0].underlying_contract_id, 265598, "Underlying contract ID");
2586
        assert_eq!(chains[0].trading_class, "AAPL", "Trading class");
2587
        assert_eq!(chains[0].multiplier, "100", "Multiplier");
2588
        assert_eq!(chains[0].expirations.len(), 3, "Should have 3 expirations");
2589
        assert_eq!(chains[0].expirations[0], "20250117");
2590
        assert_eq!(chains[0].expirations[1], "20250221");
2591
        assert_eq!(chains[0].expirations[2], "20250321");
2592
        assert_eq!(chains[0].strikes.len(), 5, "Should have 5 strikes");
2593
        assert_eq!(chains[0].strikes[0], 90.0);
2594
        assert_eq!(chains[0].strikes[1], 95.0);
2595
        assert_eq!(chains[0].strikes[2], 100.0);
2596
        assert_eq!(chains[0].strikes[3], 105.0);
2597
        assert_eq!(chains[0].strikes[4], 110.0);
2598

2599
        // Second chain - CBOE exchange
2600
        assert_eq!(chains[1].exchange, "CBOE", "Second chain should be CBOE");
2601
        assert_eq!(chains[1].underlying_contract_id, 265598, "Underlying contract ID");
2602
        assert_eq!(chains[1].trading_class, "AAPL", "Trading class");
2603
        assert_eq!(chains[1].multiplier, "100", "Multiplier");
2604
        assert_eq!(chains[1].expirations.len(), 2, "Should have 2 expirations");
2605
        assert_eq!(chains[1].expirations[0], "20250117");
2606
        assert_eq!(chains[1].expirations[1], "20250221");
2607
        assert_eq!(chains[1].strikes.len(), 4, "Should have 4 strikes");
2608
        assert_eq!(chains[1].strikes[0], 95.0);
2609
        assert_eq!(chains[1].strikes[1], 100.0);
2610
        assert_eq!(chains[1].strikes[2], 105.0);
2611
        assert_eq!(chains[1].strikes[3], 110.0);
2612

2613
        // Verify request format
2614
        let requests = gateway.requests();
2615
        assert_eq!(requests.len(), 1, "Should have 1 request");
2616
        // Request format: RequestSecurityDefinitionOptionalParameters(78), request_id, symbol, exchange, security_type, contract_id
2617
        assert!(requests[0].starts_with("78\0"), "Request should start with message type 78");
2618
        assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2619
        assert!(requests[0].contains("\0STK\0"), "Request should contain security type STK");
2620
    }
2621

2622
    #[test]
2623
    fn test_place_order() {
2624
        use crate::client::common::tests::setup_place_order;
2625
        use crate::contracts::Contract;
2626
        use crate::orders::{order_builder, Action, PlaceOrder};
2627

2628
        // Initialize env_logger for debug output
2629
        let _ = env_logger::try_init();
2630

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

2634
        // Create a stock contract
2635
        let contract = Contract::stock("AAPL");
2636

2637
        // Create a market order
2638
        let order = order_builder::market_order(Action::Buy, 100.0);
2639

2640
        // Use order ID 1001 to match the mock responses
2641
        let order_id = 1001;
2642

2643
        // Place the order
2644
        let subscription = client.place_order(order_id, &contract, &order).expect("Failed to place order");
2645

2646
        // Collect all events from the subscription
2647
        let mut order_status_count = 0;
2648
        let mut _open_order_count = 0;
2649
        let mut execution_count = 0;
2650
        let mut commission_count = 0;
2651

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

2656
        // Use the iterator directly
2657
        for event in subscription.into_iter() {
2658
            if events_received >= 10 {
2659
                println!("Reached event limit");
2660
                break;
2661
            }
2662
            events_received += 1;
2663
            let event_type = match &event {
2664
                PlaceOrder::OrderStatus(_) => "OrderStatus",
2665
                PlaceOrder::OpenOrder(_) => "OpenOrder",
2666
                PlaceOrder::ExecutionData(_) => "ExecutionData",
2667
                PlaceOrder::CommissionReport(_) => "CommissionReport",
2668
                PlaceOrder::Message(_) => "Message",
2669
            };
2670
            println!("Event {}: {} received", events_received, event_type);
2671
            match event {
2672
                PlaceOrder::OrderStatus(status) => {
2673
                    order_status_count += 1;
2674
                    assert_eq!(status.order_id, order_id);
2675

2676
                    if order_status_count == 1 {
2677
                        // First status: PreSubmitted
2678
                        assert_eq!(status.status, "PreSubmitted");
2679
                        assert_eq!(status.filled, 0.0);
2680
                        assert_eq!(status.remaining, 100.0);
2681
                    } else if order_status_count == 2 {
2682
                        // Second status: Submitted
2683
                        assert_eq!(status.status, "Submitted");
2684
                        assert_eq!(status.filled, 0.0);
2685
                        assert_eq!(status.remaining, 100.0);
2686
                    } else if order_status_count == 3 {
2687
                        // Third status: Filled
2688
                        assert_eq!(status.status, "Filled");
2689
                        assert_eq!(status.filled, 100.0);
2690
                        assert_eq!(status.remaining, 0.0);
2691
                        assert_eq!(status.average_fill_price, 150.25);
2692
                    }
2693
                }
2694
                PlaceOrder::OpenOrder(order_data) => {
2695
                    _open_order_count += 1;
2696
                    assert_eq!(order_data.order_id, order_id);
2697
                    assert_eq!(order_data.contract.symbol, "AAPL");
2698
                    assert_eq!(order_data.contract.contract_id, 265598);
2699
                    assert_eq!(order_data.order.action, Action::Buy);
2700
                    assert_eq!(order_data.order.total_quantity, 100.0);
2701
                    assert_eq!(order_data.order.order_type, "LMT");
2702
                    assert_eq!(order_data.order.limit_price, Some(1.0));
2703
                }
2704
                PlaceOrder::ExecutionData(exec_data) => {
2705
                    execution_count += 1;
2706
                    assert_eq!(exec_data.execution.order_id, order_id);
2707
                    assert_eq!(exec_data.contract.symbol, "AAPL");
2708
                    assert_eq!(exec_data.execution.shares, 100.0);
2709
                    assert_eq!(exec_data.execution.price, 150.25);
2710
                }
2711
                PlaceOrder::CommissionReport(report) => {
2712
                    commission_count += 1;
2713
                    assert_eq!(report.commission, 1.25);
2714
                    assert_eq!(report.currency, "USD");
2715
                }
2716
                PlaceOrder::Message(_) => {
2717
                    // Skip any messages
2718
                }
2719
            }
2720
        }
2721

2722
        println!("Total events received: {}", events_received);
2723
        println!(
2724
            "OrderStatus: {}, Execution: {}, Commission: {}",
2725
            order_status_count, execution_count, commission_count
2726
        );
2727

2728
        // Verify we received all expected events
2729
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2730
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2731
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2732
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2733

2734
        // Verify the request was sent
2735
        let requests = gateway.requests();
2736
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2737
        // PlaceOrder message type is 3
2738
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2739
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2740
    }
2741

2742
    #[test]
2743
    fn test_submit_order_with_order_update_stream() {
2744
        use crate::client::common::tests::setup_place_order;
2745
        use crate::contracts::Contract;
2746
        use crate::orders::{order_builder, Action, OrderUpdate};
2747

2748
        // Initialize env_logger for debug output
2749
        let _ = env_logger::try_init();
2750

2751
        let gateway = setup_place_order();
2752
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2753

2754
        // Create a stock contract
2755
        let contract = Contract::stock("AAPL");
2756

2757
        // Create a market order
2758
        let order = order_builder::market_order(Action::Buy, 100.0);
2759

2760
        // Use order ID 1001 to match the mock responses
2761
        let order_id = 1001;
2762

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

2766
        // Submit the order (fire and forget)
2767
        client.submit_order(order_id, &contract, &order).expect("Failed to submit order");
2768

2769
        // Collect events from the update stream
2770
        let mut order_status_count = 0;
2771
        let mut _open_order_count = 0;
2772
        let mut execution_count = 0;
2773
        let mut commission_count = 0;
2774
        let mut events_received = 0;
2775

2776
        // Read events from the update stream
2777
        // Use next_timeout to avoid blocking forever
2778
        println!("Starting to read from update stream...");
2779
        let timeout = std::time::Duration::from_millis(500);
2780

2781
        while events_received < 6 {
2782
            if let Some(update) = update_stream.next_timeout(timeout) {
2783
                events_received += 1;
2784
                println!("Event {}: {:?}", events_received, &update);
2785

2786
                match update {
2787
                    OrderUpdate::OrderStatus(status) => {
2788
                        order_status_count += 1;
2789
                        assert_eq!(status.order_id, order_id);
2790

2791
                        if order_status_count == 1 {
2792
                            // First status: PreSubmitted
2793
                            assert_eq!(status.status, "PreSubmitted");
2794
                            assert_eq!(status.filled, 0.0);
2795
                            assert_eq!(status.remaining, 100.0);
2796
                        } else if order_status_count == 2 {
2797
                            // Second status: Submitted
2798
                            assert_eq!(status.status, "Submitted");
2799
                            assert_eq!(status.filled, 0.0);
2800
                            assert_eq!(status.remaining, 100.0);
2801
                        } else if order_status_count == 3 {
2802
                            // Third status: Filled
2803
                            assert_eq!(status.status, "Filled");
2804
                            assert_eq!(status.filled, 100.0);
2805
                            assert_eq!(status.remaining, 0.0);
2806
                            assert_eq!(status.average_fill_price, 150.25);
2807
                        }
2808
                    }
2809
                    OrderUpdate::OpenOrder(order_data) => {
2810
                        _open_order_count += 1;
2811
                        assert_eq!(order_data.order_id, order_id);
2812
                        assert_eq!(order_data.contract.symbol, "AAPL");
2813
                        assert_eq!(order_data.contract.contract_id, 265598);
2814
                        assert_eq!(order_data.order.action, Action::Buy);
2815
                        assert_eq!(order_data.order.total_quantity, 100.0);
2816
                        assert_eq!(order_data.order.order_type, "LMT");
2817
                        assert_eq!(order_data.order.limit_price, Some(1.0));
2818
                    }
2819
                    OrderUpdate::ExecutionData(exec_data) => {
2820
                        execution_count += 1;
2821
                        assert_eq!(exec_data.execution.order_id, order_id);
2822
                        assert_eq!(exec_data.contract.symbol, "AAPL");
2823
                        assert_eq!(exec_data.execution.shares, 100.0);
2824
                        assert_eq!(exec_data.execution.price, 150.25);
2825
                    }
2826
                    OrderUpdate::CommissionReport(report) => {
2827
                        commission_count += 1;
2828
                        assert_eq!(report.commission, 1.25);
2829
                        assert_eq!(report.currency, "USD");
2830
                    }
2831
                    OrderUpdate::Message(_) => {
2832
                        // Skip any messages
2833
                    }
2834
                }
2835
            } else {
2836
                // Timeout reached, no more messages available
2837
                break;
2838
            }
2839
        }
2840

2841
        // Verify we received all expected events
2842
        assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2843
        assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2844
        assert_eq!(execution_count, 1, "Should receive 1 execution");
2845
        assert_eq!(commission_count, 1, "Should receive 1 commission report");
2846

2847
        // Verify the request was sent
2848
        let requests = gateway.requests();
2849
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2850
        // PlaceOrder message type is 3
2851
        assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2852
        assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2853
    }
2854

2855
    #[test]
2856
    fn test_open_orders() {
2857
        use crate::client::common::tests::setup_open_orders;
2858
        use crate::orders::{Action, Orders};
2859

2860
        // Initialize env_logger for debug output
2861
        let _ = env_logger::try_init();
2862

2863
        let gateway = setup_open_orders();
2864
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2865

2866
        // Request open orders
2867
        let subscription = client.open_orders().expect("Failed to request open orders");
2868

2869
        // Collect orders from the subscription
2870
        let mut orders = Vec::new();
2871
        for result in subscription {
2872
            match result {
2873
                Orders::OrderData(order_data) => {
2874
                    orders.push(order_data);
2875
                }
2876
                Orders::OrderStatus(_) => {
2877
                    // Skip order status messages for this test
2878
                }
2879
                Orders::Notice(_) => {
2880
                    // Skip notices
2881
                }
2882
            }
2883
        }
2884

2885
        // Verify we received 2 orders
2886
        assert_eq!(orders.len(), 2, "Should receive 2 open orders");
2887

2888
        // Verify first order (AAPL)
2889
        let order1 = &orders[0];
2890
        assert_eq!(order1.order_id, 1001);
2891
        assert_eq!(order1.contract.symbol, "AAPL");
2892
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
2893
        assert_eq!(order1.order.action, Action::Buy);
2894
        assert_eq!(order1.order.total_quantity, 100.0);
2895
        assert_eq!(order1.order.order_type, "MKT");
2896
        assert_eq!(order1.order_state.status, "PreSubmitted");
2897

2898
        // Verify second order (MSFT)
2899
        let order2 = &orders[1];
2900
        assert_eq!(order2.order_id, 1002);
2901
        assert_eq!(order2.contract.symbol, "MSFT");
2902
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
2903
        assert_eq!(order2.order.action, Action::Sell);
2904
        assert_eq!(order2.order.total_quantity, 50.0);
2905
        assert_eq!(order2.order.order_type, "LMT");
2906
        assert_eq!(order2.order.limit_price, Some(350.0));
2907
        assert_eq!(order2.order_state.status, "Submitted");
2908

2909
        // Verify the request was sent correctly
2910
        let requests = gateway.requests();
2911
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
2912
        assert_eq!(requests[0], "5\01\0", "Request should be RequestOpenOrders with version 1");
2913
    }
2914

2915
    #[test]
2916
    fn test_all_open_orders() {
2917
        use crate::client::common::tests::setup_all_open_orders;
2918
        use crate::orders::{Action, Orders};
2919

2920
        // Initialize env_logger for debug output
2921
        let _ = env_logger::try_init();
2922

2923
        let gateway = setup_all_open_orders();
2924
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
2925

2926
        // Request all open orders
2927
        let subscription = client.all_open_orders().expect("Failed to request all open orders");
2928

2929
        // Collect orders from the subscription
2930
        let mut orders = Vec::new();
2931
        for result in subscription {
2932
            match result {
2933
                Orders::OrderData(order_data) => {
2934
                    orders.push(order_data);
2935
                }
2936
                Orders::OrderStatus(_) => {
2937
                    // Skip order status messages for this test
2938
                }
2939
                Orders::Notice(_) => {
2940
                    // Skip notices
2941
                }
2942
            }
2943
        }
2944

2945
        // Verify we received 3 orders (from different clients)
2946
        assert_eq!(orders.len(), 3, "Should receive 3 open orders from all accounts");
2947

2948
        // Verify first order (TSLA from client 101)
2949
        let order1 = &orders[0];
2950
        assert_eq!(order1.order_id, 2001);
2951
        assert_eq!(order1.contract.symbol, "TSLA");
2952
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
2953
        assert_eq!(order1.order.action, Action::Buy);
2954
        assert_eq!(order1.order.total_quantity, 10.0);
2955
        assert_eq!(order1.order.order_type, "LMT");
2956
        assert_eq!(order1.order.limit_price, Some(420.0));
2957
        assert_eq!(order1.order.account, "DU1236110");
2958

2959
        // Verify second order (AMZN from client 102)
2960
        let order2 = &orders[1];
2961
        assert_eq!(order2.order_id, 2002);
2962
        assert_eq!(order2.contract.symbol, "AMZN");
2963
        assert_eq!(order2.order.action, Action::Sell);
2964
        assert_eq!(order2.order.total_quantity, 5.0);
2965
        assert_eq!(order2.order.order_type, "MKT");
2966
        assert_eq!(order2.order.account, "DU1236111");
2967

2968
        // Verify third order (GOOGL from current client 100)
2969
        let order3 = &orders[2];
2970
        assert_eq!(order3.order_id, 1003);
2971
        assert_eq!(order3.contract.symbol, "GOOGL");
2972
        assert_eq!(order3.order.action, Action::Buy);
2973
        assert_eq!(order3.order.total_quantity, 20.0);
2974
        assert_eq!(order3.order.order_type, "LMT");
2975
        assert_eq!(order3.order.limit_price, Some(2800.0));
2976
        assert_eq!(order3.order.account, "DU1236109");
2977

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

2984
    #[test]
2985
    fn test_auto_open_orders() {
2986
        use crate::client::common::tests::setup_auto_open_orders;
2987
        use crate::orders::Orders;
2988

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

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

2997
        // Request auto open orders with auto_bind=true
2998
        let subscription = client.auto_open_orders(true).expect("Failed to request auto open orders");
2999

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

3017
        // Verify we received order status updates
3018
        assert_eq!(order_statuses.len(), 2, "Should receive 2 order status updates");
3019

3020
        // Verify first status (PreSubmitted)
3021
        let status1 = &order_statuses[0];
3022
        assert_eq!(status1.order_id, 3001);
3023
        assert_eq!(status1.status, "PreSubmitted");
3024

3025
        // Verify second status (Submitted)
3026
        let status2 = &order_statuses[1];
3027
        assert_eq!(status2.order_id, 3001);
3028
        assert_eq!(status2.status, "Submitted");
3029

3030
        // Verify we received 1 order
3031
        assert_eq!(orders.len(), 1, "Should receive 1 order");
3032

3033
        // Verify the order (FB from TWS)
3034
        let order = &orders[0];
3035
        assert_eq!(order.order_id, 3001);
3036
        assert_eq!(order.contract.symbol, "FB");
3037
        assert_eq!(order.contract.security_type, crate::contracts::SecurityType::Stock);
3038
        assert_eq!(order.order.action, crate::orders::Action::Buy);
3039
        assert_eq!(order.order.total_quantity, 50.0);
3040
        assert_eq!(order.order.order_type, "MKT");
3041
        assert_eq!(order.order.account, "TWS");
3042

3043
        // Verify the request was sent correctly
3044
        let requests = gateway.requests();
3045
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3046
        assert_eq!(
3047
            requests[0], "15\01\01\0",
3048
            "Request should be RequestAutoOpenOrders with version 1 and auto_bind=true"
3049
        );
3050
    }
3051

3052
    #[test]
3053
    fn test_completed_orders() {
3054
        use crate::client::common::tests::setup_completed_orders;
3055
        use crate::orders::{Action, Orders};
3056

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

3060
        let gateway = setup_completed_orders();
3061
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3062

3063
        // Request completed orders (api_only=false to get all completed orders)
3064
        let subscription = client.completed_orders(false).expect("Failed to request completed orders");
3065

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

3082
        // Verify we received 2 completed orders
3083
        assert_eq!(orders.len(), 2, "Should receive 2 completed orders");
3084

3085
        // Verify first completed order (ES futures - based on captured data)
3086
        let order1 = &orders[0];
3087
        // CompletedOrder messages don't have order_id in the message, defaults to -1
3088
        assert_eq!(order1.order_id, -1);
3089
        assert_eq!(order1.contract.symbol, "ES");
3090
        assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Future);
3091
        assert_eq!(order1.order.action, Action::Buy);
3092
        assert_eq!(order1.order.total_quantity, 1.0);
3093
        assert_eq!(order1.order.order_type, "LMT");
3094
        assert_eq!(order1.order_state.status, "Cancelled");
3095
        assert_eq!(order1.order.perm_id, 616088517);
3096

3097
        // Verify second completed order (AAPL)
3098
        let order2 = &orders[1];
3099
        assert_eq!(order2.order_id, -1); // CompletedOrder messages don't have order_id
3100
        assert_eq!(order2.contract.symbol, "AAPL");
3101
        assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3102
        assert_eq!(order2.order.action, Action::Buy);
3103
        assert_eq!(order2.order.total_quantity, 100.0);
3104
        assert_eq!(order2.order.order_type, "MKT");
3105
        assert_eq!(order2.order_state.status, "Filled");
3106
        assert_eq!(order2.order.perm_id, 1377295418);
3107

3108
        // Verify the request was sent correctly
3109
        let requests = gateway.requests();
3110
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3111
        assert_eq!(requests[0], "99\00\0", "Request should be RequestCompletedOrders with api_only=false");
3112
    }
3113

3114
    #[test]
3115
    fn test_cancel_order() {
3116
        use crate::client::common::tests::setup_cancel_order;
3117
        use crate::messages::Notice;
3118
        use crate::orders::CancelOrder;
3119

3120
        // Initialize env_logger for debug output
3121
        let _ = env_logger::try_init();
3122

3123
        let gateway = setup_cancel_order();
3124
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3125

3126
        // Cancel order with ID 1001
3127
        let order_id = 1001;
3128
        let manual_order_cancel_time = "";
3129

3130
        // Call cancel_order and get the result
3131
        let result = client.cancel_order(order_id, manual_order_cancel_time);
3132

3133
        // Verify the result
3134
        match result {
3135
            Ok(cancel_result) => {
3136
                // Iterate through the cancellation results
3137
                let mut order_status_received = false;
3138
                let mut notice_received = false;
3139

3140
                for item in cancel_result {
3141
                    match item {
3142
                        CancelOrder::OrderStatus(status) => {
3143
                            assert_eq!(status.order_id, order_id);
3144
                            assert_eq!(status.status, "Cancelled");
3145
                            assert_eq!(status.filled, 0.0);
3146
                            assert_eq!(status.remaining, 100.0);
3147
                            order_status_received = true;
3148
                            println!("Received OrderStatus: {:?}", status);
3149
                        }
3150
                        CancelOrder::Notice(Notice { code, message }) => {
3151
                            // Notice messages with code 202 are order cancellation confirmations
3152
                            // The message should contain the order ID in the format
3153
                            assert_eq!(code, 202);
3154
                            assert!(message.contains("Order Cancelled"));
3155
                            notice_received = true;
3156
                            println!("Received Notice: code={}, message={}", code, message);
3157
                        }
3158
                    }
3159
                }
3160

3161
                assert!(order_status_received, "Should have received OrderStatus");
3162
                assert!(notice_received, "Should have received Notice confirmation");
3163
            }
3164
            Err(e) => panic!("Failed to cancel order: {}", e),
3165
        }
3166

3167
        // Verify the request was sent correctly
3168
        let requests = gateway.requests();
3169
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3170
        assert!(requests[0].starts_with("4\0"), "Request should be a CancelOrder message");
3171
        assert!(requests[0].contains(&format!("{}\0", order_id)), "Request should contain order ID");
3172
    }
3173

3174
    #[test]
3175
    fn test_global_cancel() {
3176
        use crate::client::common::tests::setup_global_cancel;
3177

3178
        // Initialize env_logger for debug output
3179
        let _ = env_logger::try_init();
3180

3181
        let gateway = setup_global_cancel();
3182
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3183

3184
        // Call global_cancel
3185
        let result = client.global_cancel();
3186

3187
        // Verify the result
3188
        match result {
3189
            Ok(()) => {
3190
                println!("Global cancel request sent successfully");
3191
            }
3192
            Err(e) => panic!("Failed to send global cancel: {}", e),
3193
        }
3194

3195
        // Give the gateway time to process the request
3196
        std::thread::sleep(std::time::Duration::from_millis(100));
3197

3198
        // Verify the request was sent correctly
3199
        let requests = gateway.requests();
3200
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3201
        assert_eq!(requests[0], "58\01\0", "Request should be a RequestGlobalCancel message with version 1");
3202
    }
3203

3204
    #[test]
3205
    fn test_executions() {
3206
        use crate::client::common::tests::setup_executions;
3207
        use crate::contracts::SecurityType;
3208
        use crate::orders::{ExecutionFilter, Executions};
3209

3210
        // Initialize env_logger for debug output
3211
        let _ = env_logger::try_init();
3212

3213
        let gateway = setup_executions();
3214
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3215

3216
        // Create an execution filter
3217
        let filter = ExecutionFilter {
3218
            client_id: Some(CLIENT_ID),
3219
            account_code: "DU1234567".to_string(),
3220
            time: "".to_string(),          // Empty means all time
3221
            symbol: "".to_string(),        // Empty means all symbols
3222
            security_type: "".to_string(), // Empty means all types
3223
            exchange: "".to_string(),      // Empty means all exchanges
3224
            side: "".to_string(),          // Empty means all sides
3225
        };
3226

3227
        // Request executions
3228
        let subscription = client.executions(filter).expect("Failed to request executions");
3229

3230
        // Collect executions from the subscription
3231
        let mut execution_data = Vec::new();
3232
        let mut commission_reports = Vec::new();
3233

3234
        for result in subscription {
3235
            match result {
3236
                Executions::ExecutionData(data) => {
3237
                    execution_data.push(data);
3238
                }
3239
                Executions::CommissionReport(report) => {
3240
                    commission_reports.push(report);
3241
                }
3242
                Executions::Notice(_) => {
3243
                    // Skip notices
3244
                }
3245
            }
3246
        }
3247

3248
        // Verify we received 3 executions and 3 commission reports
3249
        assert_eq!(execution_data.len(), 3, "Should receive 3 execution data messages");
3250
        assert_eq!(commission_reports.len(), 3, "Should receive 3 commission reports");
3251

3252
        // Verify first execution (AAPL stock)
3253
        let exec1 = &execution_data[0];
3254
        assert_eq!(exec1.request_id, 9000);
3255
        assert_eq!(exec1.execution.order_id, 1001);
3256
        assert_eq!(exec1.contract.symbol, "AAPL");
3257
        assert_eq!(exec1.contract.security_type, SecurityType::Stock);
3258
        assert_eq!(exec1.execution.execution_id, "000e1a2b.67890abc.01.01");
3259
        assert_eq!(exec1.execution.side, "BOT");
3260
        assert_eq!(exec1.execution.shares, 100.0);
3261
        assert_eq!(exec1.execution.price, 150.25);
3262

3263
        // Verify first commission report
3264
        let comm1 = &commission_reports[0];
3265
        assert_eq!(comm1.execution_id, "000e1a2b.67890abc.01.01");
3266
        assert_eq!(comm1.commission, 1.25);
3267
        assert_eq!(comm1.currency, "USD");
3268

3269
        // Verify second execution (ES futures)
3270
        let exec2 = &execution_data[1];
3271
        assert_eq!(exec2.request_id, 9000);
3272
        assert_eq!(exec2.execution.order_id, 1002);
3273
        assert_eq!(exec2.contract.symbol, "ES");
3274
        assert_eq!(exec2.contract.security_type, SecurityType::Future);
3275
        assert_eq!(exec2.execution.execution_id, "000e1a2b.67890def.02.01");
3276
        assert_eq!(exec2.execution.side, "SLD");
3277
        assert_eq!(exec2.execution.shares, 5.0);
3278
        assert_eq!(exec2.execution.price, 5050.25);
3279

3280
        // Verify second commission report
3281
        let comm2 = &commission_reports[1];
3282
        assert_eq!(comm2.execution_id, "000e1a2b.67890def.02.01");
3283
        assert_eq!(comm2.commission, 2.50);
3284
        assert_eq!(comm2.realized_pnl, Some(125.50));
3285

3286
        // Verify third execution (SPY options)
3287
        let exec3 = &execution_data[2];
3288
        assert_eq!(exec3.request_id, 9000);
3289
        assert_eq!(exec3.execution.order_id, 1003);
3290
        assert_eq!(exec3.contract.symbol, "SPY");
3291
        assert_eq!(exec3.contract.security_type, SecurityType::Option);
3292
        assert_eq!(exec3.execution.execution_id, "000e1a2b.67890ghi.03.01");
3293
        assert_eq!(exec3.execution.side, "BOT");
3294
        assert_eq!(exec3.execution.shares, 10.0);
3295
        assert_eq!(exec3.execution.price, 2.50);
3296

3297
        // Verify third commission report
3298
        let comm3 = &commission_reports[2];
3299
        assert_eq!(comm3.execution_id, "000e1a2b.67890ghi.03.01");
3300
        assert_eq!(comm3.commission, 0.65);
3301
        assert_eq!(comm3.realized_pnl, Some(250.00));
3302

3303
        // Verify the request was sent correctly
3304
        let requests = gateway.requests();
3305
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3306
        // Request format: RequestExecutions(7), version(3), request_id(9000), client_id, account_code, time, symbol, security_type, exchange, side
3307
        assert_eq!(
3308
            requests[0], "7\03\09000\0100\0DU1234567\0\0\0\0\0\0",
3309
            "Request should be RequestExecutions with correct filter parameters"
3310
        );
3311
    }
3312

3313
    #[test]
3314
    fn test_exercise_options() {
3315
        use crate::client::common::tests::setup_exercise_options;
3316
        use crate::contracts::{Contract, SecurityType};
3317
        use crate::orders::{ExerciseAction, ExerciseOptions};
3318
        use time::macros::datetime;
3319

3320
        // Initialize env_logger for debug output
3321
        let _ = env_logger::try_init();
3322

3323
        let gateway = setup_exercise_options();
3324
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3325

3326
        // Create option contract for SPY
3327
        let contract = Contract {
3328
            contract_id: 123456789,
3329
            symbol: "SPY".to_string(),
3330
            security_type: SecurityType::Option,
3331
            last_trade_date_or_contract_month: "20240126".to_string(),
3332
            strike: 450.0,
3333
            right: "C".to_string(), // Call option
3334
            multiplier: "100".to_string(),
3335
            exchange: "CBOE".to_string(),
3336
            currency: "USD".to_string(),
3337
            local_symbol: "SPY240126C00450000".to_string(),
3338
            trading_class: "SPY".to_string(),
3339
            ..Default::default()
3340
        };
3341

3342
        // Exercise the option
3343
        let exercise_action = ExerciseAction::Exercise;
3344
        let exercise_quantity = 10;
3345
        let account = "DU1234567";
3346
        let ovrd = false;
3347
        let manual_order_time = Some(datetime!(2024-01-25 10:30:00 UTC));
3348

3349
        let subscription = client
3350
            .exercise_options(&contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
3351
            .expect("Failed to exercise options");
3352

3353
        // Collect results
3354
        let mut order_statuses = Vec::new();
3355
        let mut open_orders = Vec::new();
3356

3357
        for result in subscription {
3358
            match result {
3359
                ExerciseOptions::OrderStatus(status) => order_statuses.push(status),
3360
                ExerciseOptions::OpenOrder(order) => open_orders.push(order),
3361
                ExerciseOptions::Notice(_notice) => {
3362
                    // Note: Warning messages (2100-2200) are currently not routed to subscriptions
3363
                    // They are only logged. See TODO.md for future improvements.
3364
                }
3365
            }
3366
        }
3367

3368
        // Verify we got the expected responses
3369
        assert_eq!(order_statuses.len(), 3, "Should have 3 order status updates");
3370
        assert_eq!(open_orders.len(), 1, "Should have 1 open order");
3371

3372
        // Verify order statuses
3373
        assert_eq!(order_statuses[0].status, "PreSubmitted");
3374
        assert_eq!(order_statuses[0].filled, 0.0);
3375
        assert_eq!(order_statuses[0].remaining, 10.0);
3376

3377
        assert_eq!(order_statuses[1].status, "Submitted");
3378
        assert_eq!(order_statuses[2].status, "Filled");
3379
        assert_eq!(order_statuses[2].filled, 10.0);
3380
        assert_eq!(order_statuses[2].remaining, 0.0);
3381

3382
        // Verify open order
3383
        let open_order = &open_orders[0];
3384
        assert_eq!(open_order.order.order_id, 90);
3385
        assert_eq!(open_order.contract.symbol, "SPY");
3386
        assert_eq!(open_order.contract.security_type, SecurityType::Option);
3387
        assert_eq!(open_order.order.order_type, "EXERCISE");
3388

3389
        // Verify the request was sent correctly
3390
        let requests = gateway.requests();
3391
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3392

3393
        // Request format: ExerciseOptions(21), version(2), order_id, contract fields, exercise_action, exercise_quantity, account, ovrd, manual_order_time
3394
        let expected_request = format!(
3395
            "21\02\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0",
3396
            90, // order_id (using next_order_id from client)
3397
            contract.contract_id,
3398
            contract.symbol,
3399
            contract.security_type,
3400
            contract.last_trade_date_or_contract_month,
3401
            contract.strike,
3402
            contract.right,
3403
            contract.multiplier,
3404
            contract.exchange,
3405
            contract.currency,
3406
            contract.local_symbol,
3407
            contract.trading_class,
3408
            exercise_action as i32,
3409
            exercise_quantity,
3410
            account,
3411
            if ovrd { 1 } else { 0 },
3412
            "20240125 10:30:00 UTC" // manual_order_time formatted
3413
        );
3414

3415
        assert_eq!(requests[0], expected_request, "Request should be ExerciseOptions with correct parameters");
3416
    }
3417

3418
    // === Real-time Market Data Tests ===
3419

3420
    #[test]
3421
    fn test_market_data() {
3422
        use crate::client::common::tests::setup_market_data;
3423
        use crate::contracts::tick_types::TickType;
3424
        use crate::contracts::Contract;
3425
        use crate::market_data::realtime::TickTypes;
3426

3427
        // Initialize env_logger for debug output
3428
        let _ = env_logger::try_init();
3429

3430
        let gateway = setup_market_data();
3431
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3432

3433
        let contract = Contract::stock("AAPL");
3434
        let generic_ticks = vec!["100", "101", "104"]; // Option volume, option open interest, historical volatility
3435
        let snapshot = true;
3436
        let regulatory_snapshot = false;
3437

3438
        let subscription = client
3439
            .market_data(&contract, &generic_ticks, snapshot, regulatory_snapshot)
3440
            .expect("Failed to request market data");
3441

3442
        let mut tick_count = 0;
3443
        let mut has_bid_price = false;
3444
        let mut has_bid_size = false;
3445
        let mut has_ask_price = false;
3446
        let mut has_ask_size = false;
3447
        let mut has_last_price = false;
3448
        let mut has_last_size = false;
3449
        let mut has_volume = false;
3450
        let mut _has_snapshot_end = false;
3451

3452
        for tick in subscription {
3453
            tick_count += 1;
3454
            match tick {
3455
                TickTypes::PriceSize(price_size) => {
3456
                    match price_size.price_tick_type {
3457
                        TickType::Bid => {
3458
                            assert_eq!(price_size.price, 150.50);
3459
                            has_bid_price = true;
3460
                        }
3461
                        TickType::Ask => {
3462
                            assert_eq!(price_size.price, 151.00);
3463
                            has_ask_price = true;
3464
                        }
3465
                        TickType::Last => {
3466
                            assert_eq!(price_size.price, 150.75);
3467
                            has_last_price = true;
3468
                        }
3469
                        _ => {}
3470
                    }
3471
                    // Note: size_tick_type might be present but size value is 0 in PriceSize
3472
                }
3473
                TickTypes::Size(size_tick) => match size_tick.tick_type {
3474
                    TickType::BidSize => {
3475
                        assert_eq!(size_tick.size, 100.0);
3476
                        has_bid_size = true;
3477
                    }
3478
                    TickType::AskSize => {
3479
                        assert_eq!(size_tick.size, 200.0);
3480
                        has_ask_size = true;
3481
                    }
3482
                    TickType::LastSize => {
3483
                        assert_eq!(size_tick.size, 50.0);
3484
                        has_last_size = true;
3485
                    }
3486
                    _ => {}
3487
                },
3488
                TickTypes::Generic(generic_tick) => {
3489
                    if generic_tick.tick_type == TickType::Volume {
3490
                        assert_eq!(generic_tick.value, 1500000.0);
3491
                        has_volume = true;
3492
                    }
3493
                }
3494
                TickTypes::String(_) => {
3495
                    // Ignore string ticks like LastTimestamp
3496
                }
3497
                TickTypes::SnapshotEnd => {
3498
                    _has_snapshot_end = true;
3499
                    break; // Snapshot complete
3500
                }
3501
                _ => {}
3502
            }
3503

3504
            if tick_count > 20 {
3505
                break; // Safety limit
3506
            }
3507
        }
3508

3509
        assert!(has_bid_price, "Should receive bid price");
3510
        assert!(has_bid_size, "Should receive bid size");
3511
        assert!(has_ask_price, "Should receive ask price");
3512
        assert!(has_ask_size, "Should receive ask size");
3513
        assert!(has_last_price, "Should receive last price");
3514
        assert!(has_last_size, "Should receive last size");
3515
        assert!(has_volume, "Should receive volume");
3516
        // TODO: SnapshotEnd message not being routed properly in tests - see TODO.md
3517
        // assert!(has_snapshot_end, "Should receive snapshot end");
3518

3519
        let requests = gateway.requests();
3520
        // Verify request format: RequestMarketData(1), version(11), request_id, contract_id,
3521
        // symbol, sec_type, expiry, strike, right, multiplier, exchange, primary_exchange,
3522
        // currency, local_symbol, trading_class, con_id_flag, combo_legs_description,
3523
        // generic_ticks, snapshot, regulatory_snapshot, market_data_options
3524
        assert!(requests[0].starts_with("1\011\09000\0"), "Request should be RequestMarketData");
3525
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3526
        assert!(requests[0].contains("100,101,104\0"), "Request should contain generic ticks");
3527
        assert!(requests[0].contains("\01\0"), "Request should have snapshot=true");
3528
    }
3529

3530
    #[test]
3531
    fn test_realtime_bars() {
3532
        use crate::client::common::tests::setup_realtime_bars;
3533
        use crate::contracts::Contract;
3534
        use crate::market_data::realtime::{BarSize, WhatToShow};
3535

3536
        // Initialize env_logger for debug output
3537
        let _ = env_logger::try_init();
3538

3539
        let gateway = setup_realtime_bars();
3540
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3541

3542
        let contract = Contract::stock("AAPL");
3543
        let bar_size = BarSize::Sec5;
3544
        let what_to_show = WhatToShow::Trades;
3545
        let use_rth = false;
3546

3547
        let subscription = client
3548
            .realtime_bars(&contract, bar_size, what_to_show, use_rth)
3549
            .expect("Failed to request realtime bars");
3550

3551
        let mut bars = Vec::new();
3552
        for bar in subscription.into_iter().take(3) {
3553
            bars.push(bar);
3554
        }
3555

3556
        assert_eq!(bars.len(), 3, "Should receive 3 bars");
3557

3558
        // Verify first bar
3559
        let bar1 = &bars[0];
3560
        assert_eq!(bar1.open, 150.25);
3561
        assert_eq!(bar1.high, 150.75);
3562
        assert_eq!(bar1.low, 150.00);
3563
        assert_eq!(bar1.close, 150.50);
3564
        assert_eq!(bar1.volume, 1000.0);
3565
        assert_eq!(bar1.wap, 150.40);
3566
        assert_eq!(bar1.count, 25);
3567

3568
        // Verify second bar
3569
        let bar2 = &bars[1];
3570
        assert_eq!(bar2.open, 150.50);
3571
        assert_eq!(bar2.high, 151.00);
3572
        assert_eq!(bar2.low, 150.40);
3573
        assert_eq!(bar2.close, 150.90);
3574
        assert_eq!(bar2.volume, 1200.0);
3575

3576
        // Verify third bar
3577
        let bar3 = &bars[2];
3578
        assert_eq!(bar3.open, 150.90);
3579
        assert_eq!(bar3.high, 151.25);
3580
        assert_eq!(bar3.low, 150.85);
3581
        assert_eq!(bar3.close, 151.20);
3582

3583
        let requests = gateway.requests();
3584
        // Verify request format: RequestRealTimeBars(50), version(8), request_id, contract,
3585
        // bar_size(5), what_to_show, use_rth, realtime_bars_options
3586
        assert!(requests[0].starts_with("50\08\09000\0"), "Request should be RequestRealTimeBars");
3587
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3588
        assert!(
3589
            requests[0].contains("\00\0TRADES\00\0"),
3590
            "Request should have bar_size=0 (5 sec) and TRADES"
3591
        );
3592
    }
3593

3594
    #[test]
3595
    fn test_tick_by_tick_last() {
3596
        use crate::client::common::tests::setup_tick_by_tick_last;
3597
        use crate::contracts::Contract;
3598

3599
        // Initialize env_logger for debug output
3600
        let _ = env_logger::try_init();
3601

3602
        let gateway = setup_tick_by_tick_last();
3603
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3604

3605
        let contract = Contract::stock("AAPL");
3606
        let number_of_ticks = 0;
3607
        let ignore_size = false;
3608

3609
        let subscription = client
3610
            .tick_by_tick_last(&contract, number_of_ticks, ignore_size)
3611
            .expect("Failed to request tick by tick last");
3612

3613
        let mut trades = Vec::new();
3614
        for trade in subscription.into_iter().take(3) {
3615
            trades.push(trade);
3616
        }
3617

3618
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3619

3620
        // Verify first trade
3621
        let trade1 = &trades[0];
3622
        assert_eq!(trade1.tick_type, "1"); // 1 = Last
3623
        assert_eq!(trade1.price, 150.75);
3624
        assert_eq!(trade1.size, 100.0);
3625
        assert_eq!(trade1.exchange, "NASDAQ");
3626
        assert!(!trade1.trade_attribute.past_limit);
3627
        assert!(!trade1.trade_attribute.unreported);
3628

3629
        // Verify second trade (unreported)
3630
        let trade2 = &trades[1];
3631
        assert_eq!(trade2.price, 150.80);
3632
        assert_eq!(trade2.size, 50.0);
3633
        assert_eq!(trade2.exchange, "NYSE");
3634
        assert!(trade2.trade_attribute.unreported);
3635

3636
        // Verify third trade
3637
        let trade3 = &trades[2];
3638
        assert_eq!(trade3.price, 150.70);
3639
        assert_eq!(trade3.size, 150.0);
3640

3641
        let requests = gateway.requests();
3642
        // Verify request format: RequestTickByTickData(97), request_id, contract,
3643
        // tick_type("Last"), number_of_ticks, ignore_size
3644
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3645
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3646
        assert!(requests[0].contains("Last\0"), "Request should have Last tick type");
3647
    }
3648

3649
    #[test]
3650
    fn test_tick_by_tick_all_last() {
3651
        use crate::client::common::tests::setup_tick_by_tick_all_last;
3652
        use crate::contracts::Contract;
3653

3654
        // Initialize env_logger for debug output
3655
        let _ = env_logger::try_init();
3656

3657
        let gateway = setup_tick_by_tick_all_last();
3658
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3659

3660
        let contract = Contract::stock("AAPL");
3661
        let number_of_ticks = 0;
3662
        let ignore_size = false;
3663

3664
        let subscription = client
3665
            .tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
3666
            .expect("Failed to request tick by tick all last");
3667

3668
        let mut trades = Vec::new();
3669
        for trade in subscription.into_iter().take(3) {
3670
            trades.push(trade);
3671
        }
3672

3673
        assert_eq!(trades.len(), 3, "Should receive 3 trades");
3674

3675
        // Verify first trade
3676
        let trade1 = &trades[0];
3677
        assert_eq!(trade1.tick_type, "2"); // 2 = AllLast
3678
        assert_eq!(trade1.price, 150.75);
3679
        assert_eq!(trade1.exchange, "NASDAQ");
3680

3681
        // Verify second trade (unreported dark pool trade)
3682
        let trade2 = &trades[1];
3683
        assert_eq!(trade2.price, 150.80);
3684
        assert_eq!(trade2.exchange, "DARK");
3685
        assert_eq!(trade2.special_conditions, "ISO");
3686
        assert!(trade2.trade_attribute.unreported);
3687

3688
        // Verify third trade
3689
        let trade3 = &trades[2];
3690
        assert_eq!(trade3.price, 150.70);
3691
        assert_eq!(trade3.exchange, "NYSE");
3692

3693
        let requests = gateway.requests();
3694
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3695
        assert!(requests[0].contains("AllLast\0"), "Request should have AllLast tick type");
3696
    }
3697

3698
    #[test]
3699
    fn test_tick_by_tick_bid_ask() {
3700
        use crate::client::common::tests::setup_tick_by_tick_bid_ask;
3701
        use crate::contracts::Contract;
3702

3703
        // Initialize env_logger for debug output
3704
        let _ = env_logger::try_init();
3705

3706
        let gateway = setup_tick_by_tick_bid_ask();
3707
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3708

3709
        let contract = Contract::stock("AAPL");
3710
        let number_of_ticks = 0;
3711
        let ignore_size = false;
3712

3713
        let subscription = client
3714
            .tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
3715
            .expect("Failed to request tick by tick bid ask");
3716

3717
        let mut bid_asks = Vec::new();
3718
        for bid_ask in subscription.into_iter().take(3) {
3719
            bid_asks.push(bid_ask);
3720
        }
3721

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

3724
        // Verify first bid/ask
3725
        let ba1 = &bid_asks[0];
3726
        assert_eq!(ba1.bid_price, 150.50);
3727
        assert_eq!(ba1.ask_price, 150.55);
3728
        assert_eq!(ba1.bid_size, 100.0);
3729
        assert_eq!(ba1.ask_size, 200.0);
3730
        assert!(!ba1.bid_ask_attribute.bid_past_low);
3731
        assert!(!ba1.bid_ask_attribute.ask_past_high);
3732

3733
        // Verify second bid/ask (bid past low)
3734
        let ba2 = &bid_asks[1];
3735
        assert_eq!(ba2.bid_price, 150.45);
3736
        assert_eq!(ba2.ask_price, 150.55);
3737
        assert!(ba2.bid_ask_attribute.bid_past_low);
3738

3739
        // Verify third bid/ask (ask past high)
3740
        let ba3 = &bid_asks[2];
3741
        assert_eq!(ba3.ask_price, 150.60);
3742
        assert!(ba3.bid_ask_attribute.ask_past_high);
3743

3744
        let requests = gateway.requests();
3745
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3746
        assert!(requests[0].contains("BidAsk\0"), "Request should have BidAsk tick type");
3747
    }
3748

3749
    #[test]
3750
    fn test_tick_by_tick_midpoint() {
3751
        use crate::client::common::tests::setup_tick_by_tick_midpoint;
3752
        use crate::contracts::Contract;
3753

3754
        // Initialize env_logger for debug output
3755
        let _ = env_logger::try_init();
3756

3757
        let gateway = setup_tick_by_tick_midpoint();
3758
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3759

3760
        let contract = Contract::stock("AAPL");
3761
        let number_of_ticks = 0;
3762
        let ignore_size = false;
3763

3764
        let subscription = client
3765
            .tick_by_tick_midpoint(&contract, number_of_ticks, ignore_size)
3766
            .expect("Failed to request tick by tick midpoint");
3767

3768
        let mut midpoints = Vec::new();
3769
        for midpoint in subscription.into_iter().take(3) {
3770
            midpoints.push(midpoint);
3771
        }
3772

3773
        assert_eq!(midpoints.len(), 3, "Should receive 3 midpoint updates");
3774

3775
        assert_eq!(midpoints[0].mid_point, 150.525);
3776
        assert_eq!(midpoints[1].mid_point, 150.50);
3777
        assert_eq!(midpoints[2].mid_point, 150.525);
3778

3779
        let requests = gateway.requests();
3780
        assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3781
        assert!(requests[0].contains("MidPoint\0"), "Request should have MidPoint tick type");
3782
    }
3783

3784
    #[test]
3785
    fn test_market_depth() {
3786
        use crate::client::common::tests::setup_market_depth;
3787
        use crate::contracts::Contract;
3788
        use crate::market_data::realtime::MarketDepths;
3789

3790
        // Initialize env_logger for debug output
3791
        let _ = env_logger::try_init();
3792

3793
        let gateway = setup_market_depth();
3794
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3795

3796
        let contract = Contract::stock("AAPL");
3797
        let num_rows = 5;
3798
        let is_smart_depth = false;
3799

3800
        let subscription = client
3801
            .market_depth(&contract, num_rows, is_smart_depth)
3802
            .expect("Failed to request market depth");
3803

3804
        let mut updates = Vec::new();
3805
        for update in subscription.into_iter().take(4) {
3806
            if let MarketDepths::MarketDepth(depth) = update {
3807
                updates.push(depth);
3808
            }
3809
        }
3810

3811
        assert_eq!(updates.len(), 4, "Should receive 4 depth updates");
3812

3813
        // Verify bid insert
3814
        let update1 = &updates[0];
3815
        assert_eq!(update1.position, 0);
3816
        // MarketDepth (L1) doesn't have market_maker field
3817
        assert_eq!(update1.operation, 0); // Insert
3818
        assert_eq!(update1.side, 1); // Bid
3819
        assert_eq!(update1.price, 150.50);
3820
        assert_eq!(update1.size, 100.0);
3821

3822
        // Verify ask insert
3823
        let update2 = &updates[1];
3824
        assert_eq!(update2.operation, 0); // Insert
3825
        assert_eq!(update2.side, 0); // Ask
3826
        assert_eq!(update2.price, 150.55);
3827
        assert_eq!(update2.size, 200.0);
3828

3829
        // Verify bid update
3830
        let update3 = &updates[2];
3831
        assert_eq!(update3.operation, 1); // Update
3832
        assert_eq!(update3.price, 150.49);
3833

3834
        // Verify ask delete
3835
        let update4 = &updates[3];
3836
        assert_eq!(update4.operation, 2); // Delete
3837

3838
        let requests = gateway.requests();
3839
        // Verify request format: RequestMarketDepth(10), version(5), request_id, contract,
3840
        // num_rows, is_smart_depth, market_depth_options
3841
        assert!(requests[0].starts_with("10\05\09000\0"), "Request should be RequestMarketDepth");
3842
        assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3843
        assert!(requests[0].contains("5\00\0"), "Request should have 5 rows and smart_depth=false");
3844
    }
3845

3846
    #[test]
3847
    fn test_market_depth_exchanges() {
3848
        use crate::client::common::tests::setup_market_depth_exchanges;
3849

3850
        // Initialize env_logger for debug output
3851
        let _ = env_logger::try_init();
3852

3853
        let gateway = setup_market_depth_exchanges();
3854
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3855

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

3858
        assert_eq!(exchanges.len(), 3, "Should receive 3 exchange descriptions");
3859

3860
        // Verify first exchange
3861
        let ex1 = &exchanges[0];
3862
        assert_eq!(ex1.exchange_name, "ISLAND");
3863
        assert_eq!(ex1.security_type, "STK");
3864
        assert_eq!(ex1.listing_exchange, "NASDAQ");
3865
        assert_eq!(ex1.service_data_type, "Deep2");
3866
        assert_eq!(ex1.aggregated_group, Some("1".to_string()));
3867

3868
        // Verify second exchange
3869
        let ex2 = &exchanges[1];
3870
        assert_eq!(ex2.exchange_name, "NYSE");
3871
        assert_eq!(ex2.security_type, "STK");
3872
        assert_eq!(ex2.service_data_type, "Deep");
3873
        assert_eq!(ex2.aggregated_group, Some("2".to_string()));
3874

3875
        // Verify third exchange
3876
        let ex3 = &exchanges[2];
3877
        assert_eq!(ex3.exchange_name, "ARCA");
3878
        assert_eq!(ex3.aggregated_group, Some("2".to_string()));
3879

3880
        let requests = gateway.requests();
3881
        assert_eq!(requests[0], "82\0", "Request should be RequestMktDepthExchanges");
3882
    }
3883

3884
    #[test]
3885
    fn test_switch_market_data_type() {
3886
        use crate::client::common::tests::setup_switch_market_data_type;
3887
        use crate::market_data::MarketDataType;
3888

3889
        // Initialize env_logger for debug output
3890
        let _ = env_logger::try_init();
3891

3892
        let gateway = setup_switch_market_data_type();
3893
        let client = Client::connect(&gateway.address(), CLIENT_ID).expect("Failed to connect");
3894

3895
        // Test switching to delayed market data
3896
        client
3897
            .switch_market_data_type(MarketDataType::Delayed)
3898
            .expect("Failed to switch market data type");
3899

3900
        // Give the mock gateway time to receive the request
3901
        std::thread::sleep(std::time::Duration::from_millis(100));
3902

3903
        let requests = gateway.requests();
3904
        assert_eq!(requests.len(), 1, "Should have sent 1 request");
3905
        // Verify request format: RequestMarketDataType(59), version(1), market_data_type(3=Delayed)
3906
        assert_eq!(requests[0], "59\01\03\0", "Request should be RequestMarketDataType with Delayed(3)");
3907
    }
3908
}
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