1use std::sync::Arc;
4use std::time::Duration;
5
6use log::debug;
7use time::OffsetDateTime;
8use time_tz::Tz;
9
10use crate::connection::common::{ConnectionOptions, StartupMessageCallback};
11use crate::connection::{r#async::AsyncConnection, ConnectionMetadata};
12use crate::messages::{OutgoingMessages, RequestMessage};
13use crate::transport::{
14 r#async::{AsyncInternalSubscription, AsyncTcpMessageBus},
15 AsyncMessageBus,
16};
17use crate::Error;
18
19use super::id_generator::ClientIdManager;
20use crate::accounts;
21use crate::accounts::types::{AccountGroup, AccountId, ContractId, ModelCode};
22use crate::accounts::{AccountSummaryResult, AccountUpdate, AccountUpdateMulti, FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti};
23use crate::contracts::Contract;
24use crate::display_groups;
25use crate::market_data::builder::MarketDataBuilder;
26use crate::market_data::TradingHours;
27use crate::orders::OrderBuilder;
28use crate::subscriptions::Subscription;
29
30pub struct Client {
32 pub(crate) server_version: i32,
34 pub(crate) connection_time: Option<OffsetDateTime>,
35 pub(crate) time_zone: Option<&'static Tz>,
36 pub(crate) message_bus: Arc<dyn AsyncMessageBus>,
37
38 client_id: i32, id_manager: Arc<ClientIdManager>, }
41
42impl Drop for Client {
43 fn drop(&mut self) {
44 debug!("dropping async client");
45 self.message_bus.request_shutdown_sync();
47 }
48}
49
50impl Client {
51 pub async fn connect(address: &str, client_id: i32) -> Result<Client, Error> {
74 Self::connect_with_callback(address, client_id, None).await
75 }
76
77 pub async fn connect_with_callback(address: &str, client_id: i32, startup_callback: Option<StartupMessageCallback>) -> Result<Client, Error> {
120 Self::connect_with_options(address, client_id, startup_callback.into()).await
121 }
122
123 pub async fn connect_with_options(address: &str, client_id: i32, options: ConnectionOptions) -> Result<Client, Error> {
150 let connection = AsyncConnection::connect_with_options(address, client_id, options).await?;
151 let connection_metadata = connection.connection_metadata().await;
152
153 let message_bus = Arc::new(AsyncTcpMessageBus::new(connection)?);
154
155 message_bus
157 .clone()
158 .process_messages(connection_metadata.server_version, Duration::from_secs(1))?;
159
160 Client::new(connection_metadata, message_bus)
161 }
162
163 fn new(connection_metadata: ConnectionMetadata, message_bus: Arc<dyn AsyncMessageBus>) -> Result<Client, Error> {
164 let client = Client {
165 server_version: connection_metadata.server_version,
166 connection_time: connection_metadata.connection_time,
167 time_zone: connection_metadata.time_zone,
168 message_bus,
169 client_id: connection_metadata.client_id,
170 id_manager: Arc::new(ClientIdManager::new(connection_metadata.next_order_id)),
171 };
172
173 Ok(client)
174 }
175
176 pub fn server_version(&self) -> i32 {
178 self.server_version
179 }
180
181 pub fn connection_time(&self) -> Option<OffsetDateTime> {
183 self.connection_time
184 }
185
186 pub fn time_zone(&self) -> Option<&'static Tz> {
188 self.time_zone
189 }
190
191 pub(crate) fn decoder_context(&self) -> crate::subscriptions::DecoderContext {
193 crate::subscriptions::DecoderContext::new(self.server_version, self.time_zone)
194 }
195
196 pub fn is_connected(&self) -> bool {
218 self.message_bus.is_connected()
219 }
220
221 pub async fn disconnect(&self) {
247 self.message_bus.ensure_shutdown().await;
248 }
249
250 pub fn client_id(&self) -> i32 {
252 self.client_id
253 }
254
255 pub fn next_order_id(&self) -> i32 {
257 self.id_manager.next_order_id()
258 }
259
260 pub fn next_request_id(&self) -> i32 {
262 self.id_manager.next_request_id()
263 }
264
265 pub(crate) fn set_next_order_id(&self, order_id: i32) {
267 self.id_manager.set_order_id(order_id);
268 }
269
270 pub fn order<'a>(&'a self, contract: &'a Contract) -> OrderBuilder<'a, Self> {
292 OrderBuilder::new(self, contract)
293 }
294
295 pub fn check_server_version(&self, required_version: i32, feature: &str) -> Result<(), Error> {
297 if self.server_version < required_version {
298 return Err(Error::Simple(format!(
299 "Server version {} is too old. {} requires version {}",
300 self.server_version, feature, required_version
301 )));
302 }
303 Ok(())
304 }
305
306 pub(crate) async fn send_request(&self, request_id: i32, message: RequestMessage) -> Result<AsyncInternalSubscription, Error> {
308 self.message_bus.send_request(request_id, message).await
310 }
311
312 pub(crate) async fn send_shared_request(
314 &self,
315 message_type: OutgoingMessages,
316 message: RequestMessage,
317 ) -> Result<AsyncInternalSubscription, Error> {
318 self.message_bus.send_shared_request(message_type, message).await
320 }
321
322 pub(crate) async fn send_order(&self, order_id: i32, message: RequestMessage) -> Result<AsyncInternalSubscription, Error> {
324 self.message_bus.send_order_request(order_id, message).await
326 }
327
328 pub(crate) async fn create_order_update_subscription(&self) -> Result<AsyncInternalSubscription, Error> {
330 self.message_bus.create_order_update_subscription().await
331 }
332
333 pub(crate) async fn send_message(&self, message: RequestMessage) -> Result<(), Error> {
335 self.message_bus.send_message(message).await
336 }
337
338 pub async fn server_time(&self) -> Result<OffsetDateTime, Error> {
355 accounts::server_time(self).await
356 }
357
358 pub async fn server_time_millis(&self) -> Result<OffsetDateTime, Error> {
360 accounts::server_time_millis(self).await
361 }
362
363 pub async fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
388 accounts::positions(self).await
389 }
390
391 pub async fn positions_multi(
418 &self,
419 account: Option<&AccountId>,
420 model_code: Option<&ModelCode>,
421 ) -> Result<Subscription<PositionUpdateMulti>, Error> {
422 accounts::positions_multi(self, account, model_code).await
423 }
424
425 pub async fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
450 accounts::pnl(self, account, model_code).await
451 }
452
453 pub async fn pnl_single(
482 &self,
483 account: &AccountId,
484 contract_id: ContractId,
485 model_code: Option<&ModelCode>,
486 ) -> Result<Subscription<PnLSingle>, Error> {
487 accounts::pnl_single(self, account, contract_id, model_code).await
488 }
489
490 pub async fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
519 accounts::account_summary(self, group, tags).await
520 }
521
522 pub async fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
562 accounts::account_updates(self, account).await
563 }
564
565 pub async fn account_updates_multi(
606 &self,
607 account: Option<&AccountId>,
608 model_code: Option<&ModelCode>,
609 ) -> Result<Subscription<AccountUpdateMulti>, Error> {
610 accounts::account_updates_multi(self, account, model_code).await
611 }
612
613 pub async fn managed_accounts(&self) -> Result<Vec<String>, Error> {
629 accounts::managed_accounts(self).await
630 }
631
632 pub async fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
647 accounts::family_codes(self).await
648 }
649
650 pub async fn subscribe_to_group_events(&self, group_id: i32) -> Result<display_groups::DisplayGroupSubscription, Error> {
680 display_groups::r#async::subscribe_to_group_events(self, group_id).await
681 }
682
683 pub fn market_data<'a>(&'a self, contract: &'a Contract) -> MarketDataBuilder<'a, Self> {
724 MarketDataBuilder::new(self, contract)
725 }
726
727 pub async fn realtime_bars(
764 &self,
765 contract: &crate::contracts::Contract,
766 bar_size: crate::market_data::realtime::BarSize,
767 what_to_show: crate::market_data::realtime::WhatToShow,
768 trading_hours: TradingHours,
769 ) -> Result<Subscription<crate::market_data::realtime::Bar>, Error> {
770 crate::market_data::realtime::realtime_bars(self, contract, &bar_size, &what_to_show, trading_hours, vec![]).await
771 }
772
773 pub async fn tick_by_tick_all_last(
807 &self,
808 contract: &crate::contracts::Contract,
809 number_of_ticks: i32,
810 ignore_size: bool,
811 ) -> Result<Subscription<crate::market_data::realtime::Trade>, Error> {
812 crate::market_data::realtime::tick_by_tick_all_last(self, contract, number_of_ticks, ignore_size).await
813 }
814
815 pub async fn tick_by_tick_last(
822 &self,
823 contract: &crate::contracts::Contract,
824 number_of_ticks: i32,
825 ignore_size: bool,
826 ) -> Result<Subscription<crate::market_data::realtime::Trade>, Error> {
827 crate::market_data::realtime::tick_by_tick_last(self, contract, number_of_ticks, ignore_size).await
828 }
829
830 pub async fn tick_by_tick_bid_ask(
865 &self,
866 contract: &crate::contracts::Contract,
867 number_of_ticks: i32,
868 ignore_size: bool,
869 ) -> Result<Subscription<crate::market_data::realtime::BidAsk>, Error> {
870 crate::market_data::realtime::tick_by_tick_bid_ask(self, contract, number_of_ticks, ignore_size).await
871 }
872
873 pub async fn tick_by_tick_midpoint(
880 &self,
881 contract: &crate::contracts::Contract,
882 number_of_ticks: i32,
883 ignore_size: bool,
884 ) -> Result<Subscription<crate::market_data::realtime::MidPoint>, Error> {
885 crate::market_data::realtime::tick_by_tick_midpoint(self, contract, number_of_ticks, ignore_size).await
886 }
887
888 pub async fn market_depth(
937 &self,
938 contract: &crate::contracts::Contract,
939 number_of_rows: i32,
940 is_smart_depth: bool,
941 ) -> Result<Subscription<crate::market_data::realtime::MarketDepths>, Error> {
942 crate::market_data::realtime::market_depth(self, contract, number_of_rows, is_smart_depth).await
943 }
944
945 pub async fn market_depth_exchanges(&self) -> Result<Vec<crate::market_data::realtime::DepthMarketDataDescription>, Error> {
964 crate::market_data::realtime::market_depth_exchanges(self).await
965 }
966
967 pub async fn switch_market_data_type(&self, market_data_type: crate::market_data::MarketDataType) -> Result<(), Error> {
988 crate::market_data::switch_market_data_type(self, market_data_type).await
989 }
990
991 pub async fn head_timestamp(
1017 &self,
1018 contract: &crate::contracts::Contract,
1019 what_to_show: crate::market_data::historical::WhatToShow,
1020 trading_hours: TradingHours,
1021 ) -> Result<OffsetDateTime, Error> {
1022 crate::market_data::historical::head_timestamp(self, contract, what_to_show, trading_hours).await
1023 }
1024
1025 pub async fn historical_data(
1073 &self,
1074 contract: &crate::contracts::Contract,
1075 end_date: Option<OffsetDateTime>,
1076 duration: crate::market_data::historical::Duration,
1077 bar_size: crate::market_data::historical::BarSize,
1078 what_to_show: Option<crate::market_data::historical::WhatToShow>,
1079 trading_hours: TradingHours,
1080 ) -> Result<crate::market_data::historical::HistoricalData, Error> {
1081 crate::market_data::historical::historical_data(self, contract, end_date, duration, bar_size, what_to_show, trading_hours).await
1082 }
1083
1084 pub async fn historical_data_streaming(
1130 &self,
1131 contract: &crate::contracts::Contract,
1132 duration: crate::market_data::historical::Duration,
1133 bar_size: crate::market_data::historical::BarSize,
1134 what_to_show: Option<crate::market_data::historical::WhatToShow>,
1135 trading_hours: TradingHours,
1136 keep_up_to_date: bool,
1137 ) -> Result<crate::market_data::historical::HistoricalDataStreamingSubscription, Error> {
1138 crate::market_data::historical::historical_data_streaming(self, contract, duration, bar_size, what_to_show, trading_hours, keep_up_to_date)
1139 .await
1140 }
1141
1142 pub async fn historical_schedule(
1179 &self,
1180 contract: &crate::contracts::Contract,
1181 end_date: Option<OffsetDateTime>,
1182 duration: crate::market_data::historical::Duration,
1183 ) -> Result<crate::market_data::historical::Schedule, Error> {
1184 crate::market_data::historical::historical_schedule(self, contract, end_date, duration).await
1185 }
1186
1187 pub async fn historical_ticks_bid_ask(
1230 &self,
1231 contract: &crate::contracts::Contract,
1232 start: Option<OffsetDateTime>,
1233 end: Option<OffsetDateTime>,
1234 number_of_ticks: i32,
1235 trading_hours: TradingHours,
1236 ignore_size: bool,
1237 ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickBidAsk>, Error> {
1238 crate::market_data::historical::historical_ticks_bid_ask(self, contract, start, end, number_of_ticks, trading_hours, ignore_size).await
1239 }
1240
1241 pub async fn historical_ticks_mid_point(
1250 &self,
1251 contract: &crate::contracts::Contract,
1252 start: Option<OffsetDateTime>,
1253 end: Option<OffsetDateTime>,
1254 number_of_ticks: i32,
1255 trading_hours: TradingHours,
1256 ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickMidpoint>, Error> {
1257 crate::market_data::historical::historical_ticks_mid_point(self, contract, start, end, number_of_ticks, trading_hours).await
1258 }
1259
1260 pub async fn historical_ticks_trade(
1269 &self,
1270 contract: &crate::contracts::Contract,
1271 start: Option<OffsetDateTime>,
1272 end: Option<OffsetDateTime>,
1273 number_of_ticks: i32,
1274 trading_hours: TradingHours,
1275 ) -> Result<crate::market_data::historical::TickSubscription<crate::market_data::historical::TickLast>, Error> {
1276 crate::market_data::historical::historical_ticks_trade(self, contract, start, end, number_of_ticks, trading_hours).await
1277 }
1278
1279 pub async fn cancel_historical_ticks(&self, request_id: i32) -> Result<(), Error> {
1284 crate::market_data::historical::cancel_historical_ticks(self, request_id).await
1285 }
1286
1287 pub async fn histogram_data(
1323 &self,
1324 contract: &crate::contracts::Contract,
1325 trading_hours: TradingHours,
1326 period: crate::market_data::historical::BarSize,
1327 ) -> Result<Vec<crate::market_data::historical::HistogramEntry>, Error> {
1328 crate::market_data::historical::histogram_data(self, contract, trading_hours, period).await
1329 }
1330
1331 pub async fn wsh_metadata(&self) -> Result<crate::wsh::WshMetadata, Error> {
1349 crate::wsh::wsh_metadata(self).await
1350 }
1351
1352 pub async fn wsh_event_data_by_contract(
1391 &self,
1392 contract_id: i32,
1393 start_date: Option<time::Date>,
1394 end_date: Option<time::Date>,
1395 limit: Option<i32>,
1396 auto_fill: Option<crate::wsh::AutoFill>,
1397 ) -> Result<crate::wsh::WshEventData, Error> {
1398 crate::wsh::wsh_event_data_by_contract(self, contract_id, start_date, end_date, limit, auto_fill).await
1399 }
1400
1401 pub async fn wsh_event_data_by_filter(
1439 &self,
1440 filter: &str,
1441 limit: Option<i32>,
1442 auto_fill: Option<crate::wsh::AutoFill>,
1443 ) -> Result<Subscription<crate::wsh::WshEventData>, Error> {
1444 crate::wsh::wsh_event_data_by_filter(self, filter, limit, auto_fill).await
1445 }
1446
1447 pub async fn contract_details(&self, contract: &crate::contracts::Contract) -> Result<Vec<crate::contracts::ContractDetails>, Error> {
1476 crate::contracts::contract_details(self, contract).await
1477 }
1478
1479 pub async fn cancel_contract_details(&self, request_id: i32) -> Result<(), Error> {
1484 crate::contracts::cancel_contract_details(self, request_id).await
1485 }
1486
1487 pub async fn matching_symbols(&self, pattern: &str) -> Result<Vec<crate::contracts::ContractDescription>, Error> {
1509 crate::contracts::matching_symbols(self, pattern).await
1510 }
1511
1512 pub async fn market_rule(&self, market_rule_id: i32) -> Result<crate::contracts::MarketRule, Error> {
1536 crate::contracts::market_rule(self, market_rule_id).await
1537 }
1538
1539 pub async fn calculate_option_price(
1566 &self,
1567 contract: &crate::contracts::Contract,
1568 volatility: f64,
1569 underlying_price: f64,
1570 ) -> Result<crate::contracts::OptionComputation, Error> {
1571 crate::contracts::calculate_option_price(self, contract, volatility, underlying_price).await
1572 }
1573
1574 pub async fn calculate_implied_volatility(
1601 &self,
1602 contract: &crate::contracts::Contract,
1603 option_price: f64,
1604 underlying_price: f64,
1605 ) -> Result<crate::contracts::OptionComputation, Error> {
1606 crate::contracts::calculate_implied_volatility(self, contract, option_price, underlying_price).await
1607 }
1608
1609 pub async fn option_chain(
1645 &self,
1646 symbol: &str,
1647 exchange: &str,
1648 security_type: crate::contracts::SecurityType,
1649 contract_id: i32,
1650 ) -> Result<Subscription<crate::contracts::OptionChain>, Error> {
1651 crate::contracts::option_chain(self, symbol, exchange, security_type, contract_id).await
1652 }
1653
1654 pub async fn order_update_stream(&self) -> Result<Subscription<crate::orders::OrderUpdate>, Error> {
1688 crate::orders::order_update_stream(self).await
1689 }
1690
1691 pub async fn submit_order(&self, order_id: i32, contract: &crate::contracts::Contract, order: &crate::orders::Order) -> Result<(), Error> {
1724 crate::orders::submit_order(self, order_id, contract, order).await
1725 }
1726
1727 pub async fn place_order(
1773 &self,
1774 order_id: i32,
1775 contract: &crate::contracts::Contract,
1776 order: &crate::orders::Order,
1777 ) -> Result<Subscription<crate::orders::PlaceOrder>, Error> {
1778 crate::orders::place_order(self, order_id, contract, order).await
1779 }
1780
1781 pub async fn cancel_order(&self, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<crate::orders::CancelOrder>, Error> {
1787 crate::orders::cancel_order(self, order_id, manual_order_cancel_time).await
1788 }
1789
1790 pub async fn global_cancel(&self) -> Result<(), Error> {
1792 crate::orders::global_cancel(self).await
1793 }
1794
1795 pub async fn next_valid_order_id(&self) -> Result<i32, Error> {
1797 crate::orders::next_valid_order_id(self).await
1798 }
1799
1800 pub async fn completed_orders(&self, api_only: bool) -> Result<Subscription<crate::orders::Orders>, Error> {
1805 crate::orders::completed_orders(self, api_only).await
1806 }
1807
1808 pub async fn open_orders(&self) -> Result<Subscription<crate::orders::Orders>, Error> {
1811 crate::orders::open_orders(self).await
1812 }
1813
1814 pub async fn all_open_orders(&self) -> Result<Subscription<crate::orders::Orders>, Error> {
1817 crate::orders::all_open_orders(self).await
1818 }
1819
1820 pub async fn auto_open_orders(&self, auto_bind: bool) -> Result<Subscription<crate::orders::Orders>, Error> {
1825 crate::orders::auto_open_orders(self, auto_bind).await
1826 }
1827
1828 pub async fn executions(&self, filter: crate::orders::ExecutionFilter) -> Result<Subscription<crate::orders::Executions>, Error> {
1836 crate::orders::executions(self, filter).await
1837 }
1838
1839 pub async fn exercise_options(
1849 &self,
1850 contract: &crate::contracts::Contract,
1851 exercise_action: crate::orders::ExerciseAction,
1852 exercise_quantity: i32,
1853 account: &str,
1854 ovrd: bool,
1855 manual_order_time: Option<OffsetDateTime>,
1856 ) -> Result<Subscription<crate::orders::ExerciseOptions>, Error> {
1857 crate::orders::exercise_options(self, contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time).await
1858 }
1859
1860 pub async fn news_providers(&self) -> Result<Vec<crate::news::NewsProvider>, Error> {
1882 crate::news::news_providers(self).await
1883 }
1884
1885 pub async fn news_bulletins(&self, all_messages: bool) -> Result<Subscription<crate::news::NewsBulletin>, Error> {
1910 crate::news::news_bulletins(self, all_messages).await
1911 }
1912
1913 pub async fn historical_news(
1951 &self,
1952 contract_id: i32,
1953 provider_codes: &[&str],
1954 start_time: OffsetDateTime,
1955 end_time: OffsetDateTime,
1956 total_results: u8,
1957 ) -> Result<Subscription<crate::news::NewsArticle>, Error> {
1958 crate::news::historical_news(self, contract_id, provider_codes, start_time, end_time, total_results).await
1959 }
1960
1961 pub async fn news_article(&self, provider_code: &str, article_id: &str) -> Result<crate::news::NewsArticleBody, Error> {
1982 crate::news::news_article(self, provider_code, article_id).await
1983 }
1984
1985 pub async fn contract_news(
2015 &self,
2016 contract: &crate::contracts::Contract,
2017 provider_codes: &[&str],
2018 ) -> Result<Subscription<crate::news::NewsArticle>, Error> {
2019 crate::news::contract_news(self, contract, provider_codes).await
2020 }
2021
2022 pub async fn broad_tape_news(&self, provider_code: &str) -> Result<Subscription<crate::news::NewsArticle>, Error> {
2047 crate::news::broad_tape_news(self, provider_code).await
2048 }
2049
2050 pub async fn scanner_parameters(&self) -> Result<String, Error> {
2071 crate::scanner::scanner_parameters(self).await
2072 }
2073
2074 pub async fn scanner_subscription(
2119 &self,
2120 subscription: &crate::scanner::ScannerSubscription,
2121 filter: &Vec<crate::orders::TagValue>,
2122 ) -> Result<Subscription<Vec<crate::scanner::ScannerData>>, Error> {
2123 crate::scanner::scanner_subscription(self, subscription, filter).await
2124 }
2125
2126 #[cfg(test)]
2128 pub fn stubbed(message_bus: Arc<dyn AsyncMessageBus>, server_version: i32) -> Self {
2129 use crate::connection::ConnectionMetadata;
2130
2131 let connection_metadata = ConnectionMetadata {
2132 client_id: 100,
2133 next_order_id: 9000,
2134 server_version,
2135 managed_accounts: String::new(),
2136 connection_time: None,
2137 time_zone: None,
2138 };
2139
2140 Client::new(connection_metadata, message_bus).expect("Failed to create stubbed client")
2141 }
2142
2143 #[cfg(test)]
2145 pub fn message_bus(&self) -> &Arc<dyn AsyncMessageBus> {
2146 &self.message_bus
2147 }
2148}
2149
2150#[cfg(test)]
2151mod tests {
2152 use super::Client;
2153 use crate::client::common::tests::*;
2154 use crate::contracts::{Currency, Exchange, Symbol};
2155 use crate::market_data::TradingHours;
2156
2157 const CLIENT_ID: i32 = 100;
2158
2159 #[tokio::test]
2160 async fn test_connect() {
2161 let gateway = setup_connect();
2162
2163 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2164
2165 assert_eq!(client.client_id(), CLIENT_ID);
2166 assert_eq!(client.server_version(), gateway.server_version());
2167 assert_eq!(client.time_zone, gateway.time_zone());
2168
2169 assert_eq!(gateway.requests().len(), 0, "No requests should be sent on connect");
2170 }
2171
2172 #[tokio::test]
2173 async fn test_server_time() {
2174 let (gateway, expectations) = setup_server_time();
2175
2176 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2177
2178 let server_time = client.server_time().await.unwrap();
2179 assert_eq!(server_time, expectations.server_time);
2180
2181 let requests = gateway.requests();
2182 assert_eq!(requests[0], "49\01\0");
2183 }
2184
2185 #[tokio::test]
2186 async fn test_next_valid_order_id() {
2187 let (gateway, expectations) = setup_next_valid_order_id();
2188
2189 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2190
2191 let next_valid_order_id = client.next_valid_order_id().await.unwrap();
2192 assert_eq!(next_valid_order_id, expectations.next_valid_order_id);
2193
2194 let requests = gateway.requests();
2195 assert_eq!(requests[0], "8\01\00\0");
2196 }
2197
2198 #[tokio::test]
2199 async fn test_managed_accounts() {
2200 let (gateway, expectations) = setup_managed_accounts();
2201
2202 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2203
2204 let accounts = client.managed_accounts().await.unwrap();
2205 assert_eq!(accounts, expectations.accounts);
2206
2207 let requests = gateway.requests();
2208 assert_eq!(requests[0], "17\01\0");
2209 }
2210
2211 #[tokio::test]
2212 async fn test_positions() {
2213 let gateway = setup_positions();
2214
2215 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2216
2217 let mut positions = client.positions().await.unwrap();
2218 let mut position_count = 0;
2219
2220 while let Some(position_update) = positions.next().await {
2221 match position_update.unwrap() {
2222 crate::accounts::PositionUpdate::Position(position) => {
2223 assert_eq!(position.account, "DU1234567");
2224 assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2225 assert_eq!(position.position, 500.0);
2226 assert_eq!(position.average_cost, 150.25);
2227 position_count += 1;
2228 }
2229 crate::accounts::PositionUpdate::PositionEnd => {
2230 break;
2231 }
2232 }
2233 }
2234
2235 assert_eq!(position_count, 1);
2236 let requests = gateway.requests();
2237 assert_eq!(requests[0], "61\01\0");
2238 }
2239
2240 #[tokio::test]
2241 async fn test_positions_multi() {
2242 use crate::accounts::types::AccountId;
2243
2244 let gateway = setup_positions_multi();
2245
2246 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2247
2248 let account = AccountId("DU1234567".to_string());
2249 let mut positions = client.positions_multi(Some(&account), None).await.unwrap();
2250 let mut position_count = 0;
2251
2252 while let Some(position_update) = positions.next().await {
2253 match position_update.unwrap() {
2254 crate::accounts::PositionUpdateMulti::Position(position) => {
2255 position_count += 1;
2256 if position_count == 1 {
2257 assert_eq!(position.account, "DU1234567");
2258 assert_eq!(position.contract.symbol, Symbol::from("AAPL"));
2259 assert_eq!(position.position, 500.0);
2260 assert_eq!(position.average_cost, 150.25);
2261 assert_eq!(position.model_code, "MODEL1");
2262 } else if position_count == 2 {
2263 assert_eq!(position.account, "DU1234568");
2264 assert_eq!(position.contract.symbol, Symbol::from("GOOGL"));
2265 assert_eq!(position.position, 200.0);
2266 assert_eq!(position.average_cost, 2500.00);
2267 assert_eq!(position.model_code, "MODEL1");
2268 }
2269 }
2270 crate::accounts::PositionUpdateMulti::PositionEnd => {
2271 break;
2272 }
2273 }
2274 }
2275
2276 assert_eq!(position_count, 2);
2277 let requests = gateway.requests();
2278 assert_eq!(requests[0], "74\01\09000\0DU1234567\0\0");
2279 }
2280
2281 #[tokio::test]
2282 async fn test_account_summary() {
2283 use crate::accounts::types::AccountGroup;
2284
2285 let gateway = setup_account_summary();
2286
2287 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2288
2289 let group = AccountGroup("All".to_string());
2290 let tags = vec!["NetLiquidation", "TotalCashValue"];
2291
2292 let mut summaries = client.account_summary(&group, &tags).await.unwrap();
2293 let mut summary_count = 0;
2294
2295 while let Some(summary_result) = summaries.next().await {
2296 match summary_result.unwrap() {
2297 crate::accounts::AccountSummaryResult::Summary(summary) => {
2298 assert_eq!(summary.account, "DU1234567");
2299 assert_eq!(summary.currency, "USD");
2300
2301 if summary.tag == "NetLiquidation" {
2302 assert_eq!(summary.value, "25000.00");
2303 } else if summary.tag == "TotalCashValue" {
2304 assert_eq!(summary.value, "15000.00");
2305 }
2306 summary_count += 1;
2307 }
2308 crate::accounts::AccountSummaryResult::End => {
2309 break;
2310 }
2311 }
2312 }
2313
2314 assert_eq!(summary_count, 2);
2315 let requests = gateway.requests();
2316 assert_eq!(requests[0], "62\01\09000\0All\0NetLiquidation,TotalCashValue\0");
2317 }
2318
2319 #[tokio::test]
2320 async fn test_pnl() {
2321 use crate::accounts::types::AccountId;
2322
2323 let gateway = setup_pnl();
2324
2325 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2326
2327 let account = AccountId("DU1234567".to_string());
2328 let mut pnl = client.pnl(&account, None).await.unwrap();
2329
2330 let first_pnl = pnl.next().await.unwrap().unwrap();
2331 assert_eq!(first_pnl.daily_pnl, 250.50);
2332 assert_eq!(first_pnl.unrealized_pnl, Some(1500.00));
2333 assert_eq!(first_pnl.realized_pnl, Some(750.00));
2334
2335 let requests = gateway.requests();
2336 assert_eq!(requests[0], "92\09000\0DU1234567\0\0");
2337 }
2338
2339 #[tokio::test]
2340 async fn test_pnl_single() {
2341 use crate::accounts::types::{AccountId, ContractId};
2342
2343 let gateway = setup_pnl_single();
2344
2345 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2346
2347 let account = AccountId("DU1234567".to_string());
2348 let contract_id = ContractId(12345);
2349 let mut pnl_single = client.pnl_single(&account, contract_id, None).await.unwrap();
2350
2351 let first_pnl = pnl_single.next().await.unwrap().unwrap();
2352 assert_eq!(first_pnl.position, 100.0);
2353 assert_eq!(first_pnl.daily_pnl, 150.25);
2354 assert_eq!(first_pnl.unrealized_pnl, 500.00);
2355 assert_eq!(first_pnl.realized_pnl, 250.00);
2356 assert_eq!(first_pnl.value, 1000.00);
2357
2358 let requests = gateway.requests();
2359 assert_eq!(requests[0], "94\09000\0DU1234567\0\012345\0");
2360 }
2361
2362 #[tokio::test]
2363 async fn test_account_updates() {
2364 use crate::accounts::types::AccountId;
2365
2366 let gateway = setup_account_updates();
2367
2368 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2369
2370 let account = AccountId("DU1234567".to_string());
2371 let mut updates = client.account_updates(&account).await.unwrap();
2372
2373 let mut value_count = 0;
2374 let mut portfolio_count = 0;
2375 let mut has_time_update = false;
2376 let mut has_end = false;
2377
2378 while let Some(update) = updates.next().await {
2379 match update.unwrap() {
2380 crate::accounts::AccountUpdate::AccountValue(value) => {
2381 assert_eq!(value.key, "NetLiquidation");
2382 assert_eq!(value.value, "25000.00");
2383 assert_eq!(value.currency, "USD");
2384 assert_eq!(value.account, Some("DU1234567".to_string()));
2385 value_count += 1;
2386 }
2387 crate::accounts::AccountUpdate::PortfolioValue(portfolio) => {
2388 assert_eq!(portfolio.contract.symbol, Symbol::from("AAPL"));
2389 assert_eq!(portfolio.position, 500.0);
2390 assert_eq!(portfolio.market_price, 151.50);
2391 assert_eq!(portfolio.market_value, 75750.00);
2392 assert_eq!(portfolio.average_cost, 150.25);
2393 assert_eq!(portfolio.unrealized_pnl, 375.00);
2394 assert_eq!(portfolio.realized_pnl, 125.00);
2395 assert_eq!(portfolio.account, Some("DU1234567".to_string()));
2396 portfolio_count += 1;
2397 }
2398 crate::accounts::AccountUpdate::UpdateTime(time) => {
2399 assert_eq!(time.timestamp, "20240122 15:30:00");
2400 has_time_update = true;
2401 }
2402 crate::accounts::AccountUpdate::End => {
2403 has_end = true;
2404 break;
2405 }
2406 }
2407 }
2408
2409 assert!(has_end, "Expected End message");
2410 assert_eq!(value_count, 1);
2411 assert_eq!(portfolio_count, 1);
2412 assert!(has_time_update);
2413
2414 let requests = gateway.requests();
2415 assert_eq!(requests[0], "6\02\01\0DU1234567\0");
2416 }
2417
2418 #[tokio::test]
2419 async fn test_family_codes() {
2420 let gateway = setup_family_codes();
2421
2422 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2423
2424 let family_codes = client.family_codes().await.unwrap();
2425
2426 assert_eq!(family_codes.len(), 2);
2427 assert_eq!(family_codes[0].account_id, "DU1234567");
2428 assert_eq!(family_codes[0].family_code, "FAM001");
2429 assert_eq!(family_codes[1].account_id, "DU1234568");
2430 assert_eq!(family_codes[1].family_code, "FAM002");
2431
2432 let requests = gateway.requests();
2433 assert_eq!(requests[0], "80\01\0");
2434 }
2435
2436 #[tokio::test]
2437 async fn test_account_updates_multi() {
2438 use crate::accounts::types::{AccountId, ModelCode};
2439
2440 let gateway = setup_account_updates_multi();
2441
2442 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2443
2444 let account = AccountId("DU1234567".to_string());
2445 let model_code: Option<ModelCode> = None;
2446 let mut updates = client.account_updates_multi(Some(&account), model_code.as_ref()).await.unwrap();
2447
2448 let mut cash_balance_found = false;
2449 let mut currency_found = false;
2450 let mut stock_market_value_found = false;
2451 let mut has_end = false;
2452
2453 while let Some(update) = updates.next().await {
2454 match update.unwrap() {
2455 crate::accounts::AccountUpdateMulti::AccountMultiValue(value) => {
2456 assert_eq!(value.account, "DU1234567");
2457 assert_eq!(value.model_code, "");
2458
2459 match value.key.as_str() {
2460 "CashBalance" => {
2461 assert_eq!(value.value, "94629.71");
2462 assert_eq!(value.currency, "USD");
2463 cash_balance_found = true;
2464 }
2465 "Currency" => {
2466 assert_eq!(value.value, "USD");
2467 assert_eq!(value.currency, "USD");
2468 currency_found = true;
2469 }
2470 "StockMarketValue" => {
2471 assert_eq!(value.value, "0.00");
2472 assert_eq!(value.currency, "BASE");
2473 stock_market_value_found = true;
2474 }
2475 _ => panic!("Unexpected key: {}", value.key),
2476 }
2477 }
2478 crate::accounts::AccountUpdateMulti::End => {
2479 has_end = true;
2480 break;
2481 }
2482 }
2483 }
2484
2485 assert!(cash_balance_found, "Expected CashBalance update");
2486 assert!(currency_found, "Expected Currency update");
2487 assert!(stock_market_value_found, "Expected StockMarketValue update");
2488 assert!(has_end, "Expected End message");
2489
2490 let requests = gateway.requests();
2491 assert_eq!(requests[0], "76\01\09000\0DU1234567\0\01\0");
2492 }
2493
2494 #[tokio::test]
2495 async fn test_contract_details() {
2496 let gateway = setup_contract_details();
2497
2498 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2499
2500 let contract = crate::contracts::Contract::stock("AAPL").build();
2501 let details = client.contract_details(&contract).await.expect("Failed to get contract details");
2502
2503 assert_eq!(details.len(), 1);
2504 let detail = &details[0];
2505
2506 assert_eq!(detail.contract.symbol, Symbol::from("AAPL"));
2508 assert_eq!(detail.contract.security_type, crate::contracts::SecurityType::Stock);
2509 assert_eq!(detail.contract.currency, Currency::from("USD"));
2510 assert_eq!(detail.contract.exchange, Exchange::from("NASDAQ"));
2511 assert_eq!(detail.contract.local_symbol, "AAPL");
2512 assert_eq!(detail.contract.trading_class, "AAPL");
2513 assert_eq!(detail.contract.contract_id, 265598);
2514 assert_eq!(detail.contract.primary_exchange, Exchange::from("NASDAQ"));
2515
2516 assert_eq!(detail.market_name, "NMS");
2518 assert_eq!(detail.min_tick, 0.01);
2519 assert!(detail.order_types.contains(&"LMT".to_string()));
2520 assert!(detail.order_types.contains(&"MKT".to_string()));
2521 assert!(detail.valid_exchanges.contains(&"SMART".to_string()));
2522 assert_eq!(detail.long_name, "Apple Inc");
2523 assert_eq!(detail.industry, "Technology");
2524 assert_eq!(detail.category, "Computers");
2525 assert_eq!(detail.subcategory, "Computers");
2526 assert_eq!(detail.time_zone_id, "US/Eastern");
2527 assert_eq!(detail.stock_type, "NMS");
2528 assert_eq!(detail.min_size, 1.0);
2529 assert_eq!(detail.size_increment, 1.0);
2530 assert_eq!(detail.suggested_size_increment, 1.0);
2531
2532 let requests = gateway.requests();
2533 assert_eq!(requests[0], "9\08\09000\00\0AAPL\0STK\0\00\0\0\0SMART\0\0USD\0\0\00\0\0\0");
2537 }
2538
2539 #[tokio::test]
2540 async fn test_matching_symbols() {
2541 let gateway = setup_matching_symbols();
2542
2543 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2544
2545 let contract_descriptions = client.matching_symbols("AAP").await.expect("Failed to get matching symbols");
2546
2547 assert_eq!(contract_descriptions.len(), 2, "Should have 2 matching symbols");
2548
2549 assert_eq!(contract_descriptions[0].contract.contract_id, 265598);
2551 assert_eq!(contract_descriptions[0].contract.symbol, Symbol::from("AAPL"));
2552 assert_eq!(contract_descriptions[0].contract.security_type, crate::contracts::SecurityType::Stock);
2553 assert_eq!(contract_descriptions[0].contract.primary_exchange, Exchange::from("NASDAQ"));
2554 assert_eq!(contract_descriptions[0].contract.currency, Currency::from("USD"));
2555 assert_eq!(contract_descriptions[0].derivative_security_types.len(), 2);
2556 assert_eq!(contract_descriptions[0].derivative_security_types[0], "OPT");
2557 assert_eq!(contract_descriptions[0].derivative_security_types[1], "WAR");
2558 assert_eq!(contract_descriptions[0].contract.description, "Apple Inc.");
2559 assert_eq!(contract_descriptions[0].contract.issuer_id, "AAPL123");
2560
2561 assert_eq!(contract_descriptions[1].contract.contract_id, 276821);
2563 assert_eq!(contract_descriptions[1].contract.symbol, Symbol::from("MSFT"));
2564 assert_eq!(contract_descriptions[1].contract.security_type, crate::contracts::SecurityType::Stock);
2565 assert_eq!(contract_descriptions[1].contract.primary_exchange, Exchange::from("NASDAQ"));
2566 assert_eq!(contract_descriptions[1].contract.currency, Currency::from("USD"));
2567 assert_eq!(contract_descriptions[1].derivative_security_types.len(), 1);
2568 assert_eq!(contract_descriptions[1].derivative_security_types[0], "OPT");
2569 assert_eq!(contract_descriptions[1].contract.description, "Microsoft Corporation");
2570 assert_eq!(contract_descriptions[1].contract.issuer_id, "MSFT456");
2571
2572 let requests = gateway.requests();
2574 assert_eq!(requests.len(), 1, "Should have 1 request");
2575 assert!(requests[0].starts_with("81\0"), "Request should start with message type 81");
2577 assert!(requests[0].contains("\0AAP\0"), "Request should contain the pattern AAP");
2578 }
2579
2580 #[tokio::test]
2581 async fn test_market_rule() {
2582 let gateway = setup_market_rule();
2583
2584 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2585
2586 let market_rule = client.market_rule(26).await.expect("Failed to get market rule");
2587
2588 assert_eq!(market_rule.market_rule_id, 26, "Market rule ID should be 26");
2590
2591 assert_eq!(market_rule.price_increments.len(), 3, "Should have 3 price increments");
2593
2594 assert_eq!(market_rule.price_increments[0].low_edge, 0.0, "First increment low edge");
2596 assert_eq!(market_rule.price_increments[0].increment, 0.01, "First increment value");
2597
2598 assert_eq!(market_rule.price_increments[1].low_edge, 100.0, "Second increment low edge");
2600 assert_eq!(market_rule.price_increments[1].increment, 0.05, "Second increment value");
2601
2602 assert_eq!(market_rule.price_increments[2].low_edge, 1000.0, "Third increment low edge");
2604 assert_eq!(market_rule.price_increments[2].increment, 0.10, "Third increment value");
2605
2606 let requests = gateway.requests();
2608 assert_eq!(requests.len(), 1, "Should have 1 request");
2609 assert_eq!(requests[0], "91\026\0", "Request should be message type 91 with market rule ID 26");
2611 }
2612
2613 #[tokio::test]
2614 async fn test_calculate_option_price() {
2615 let gateway = setup_calculate_option_price();
2616
2617 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2618
2619 let contract = crate::contracts::Contract {
2621 symbol: Symbol::from("AAPL"),
2622 security_type: crate::contracts::SecurityType::Option,
2623 exchange: Exchange::from("SMART"),
2624 currency: Currency::from("USD"),
2625 last_trade_date_or_contract_month: "20250120".to_string(),
2626 strike: 100.0,
2627 right: "C".to_string(),
2628 ..Default::default()
2629 };
2630
2631 let volatility = 0.25;
2632 let underlying_price = 100.0;
2633
2634 let computation = client
2635 .calculate_option_price(&contract, volatility, underlying_price)
2636 .await
2637 .expect("Failed to calculate option price");
2638
2639 assert_eq!(
2641 computation.field,
2642 crate::contracts::tick_types::TickType::ModelOption,
2643 "Should be ModelOption tick type"
2644 );
2645 assert_eq!(computation.tick_attribute, Some(0), "Tick attribute should be 0");
2646 assert_eq!(computation.implied_volatility, Some(0.25), "Implied volatility should match");
2647 assert_eq!(computation.delta, Some(0.5), "Delta should be 0.5");
2648 assert_eq!(computation.option_price, Some(12.75), "Option price should be 12.75");
2649 assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2650 assert_eq!(computation.gamma, Some(0.05), "Gamma should be 0.05");
2651 assert_eq!(computation.vega, Some(0.02), "Vega should be 0.02");
2652 assert_eq!(computation.theta, Some(-0.01), "Theta should be -0.01");
2653 assert_eq!(computation.underlying_price, Some(100.0), "Underlying price should be 100");
2654
2655 let requests = gateway.requests();
2657 assert_eq!(requests.len(), 1, "Should have 1 request");
2658 assert!(
2660 requests[0].starts_with("54\03\0"),
2661 "Request should start with message type 54 and version 3"
2662 );
2663 assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2664 assert!(requests[0].contains("\00.25\0"), "Request should contain volatility 0.25");
2665 assert!(requests[0].contains("\0100\0"), "Request should contain underlying price 100");
2666 }
2667
2668 #[tokio::test]
2669 async fn test_calculate_implied_volatility() {
2670 let gateway = setup_calculate_implied_volatility();
2671
2672 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2673
2674 let contract = crate::contracts::Contract {
2676 symbol: Symbol::from("MSFT"),
2677 security_type: crate::contracts::SecurityType::Option,
2678 exchange: Exchange::from("SMART"),
2679 currency: Currency::from("USD"),
2680 last_trade_date_or_contract_month: "20250220".to_string(),
2681 strike: 105.0,
2682 right: "P".to_string(), ..Default::default()
2684 };
2685
2686 let option_price = 15.50;
2687 let underlying_price = 105.0;
2688
2689 let computation = client
2690 .calculate_implied_volatility(&contract, option_price, underlying_price)
2691 .await
2692 .expect("Failed to calculate implied volatility");
2693
2694 assert_eq!(
2696 computation.field,
2697 crate::contracts::tick_types::TickType::ModelOption,
2698 "Should be ModelOption tick type"
2699 );
2700 assert_eq!(computation.tick_attribute, Some(1), "Tick attribute should be 1 (price-based)");
2701 assert_eq!(computation.implied_volatility, Some(0.35), "Implied volatility should be 0.35");
2702 assert_eq!(computation.delta, Some(0.45), "Delta should be 0.45");
2703 assert_eq!(computation.option_price, Some(15.50), "Option price should be 15.50");
2704 assert_eq!(computation.present_value_dividend, Some(0.0), "PV dividend should be 0");
2705 assert_eq!(computation.gamma, Some(0.04), "Gamma should be 0.04");
2706 assert_eq!(computation.vega, Some(0.03), "Vega should be 0.03");
2707 assert_eq!(computation.theta, Some(-0.02), "Theta should be -0.02");
2708 assert_eq!(computation.underlying_price, Some(105.0), "Underlying price should be 105");
2709
2710 let requests = gateway.requests();
2712 assert_eq!(requests.len(), 1, "Should have 1 request");
2713 assert!(
2715 requests[0].starts_with("54\03\0"),
2716 "Request should start with message type 54 and version 3"
2717 );
2718 assert!(requests[0].contains("\0MSFT\0"), "Request should contain symbol MSFT");
2719 assert!(requests[0].contains("\015.5\0"), "Request should contain option price 15.5");
2720 assert!(requests[0].contains("\0105\0"), "Request should contain underlying price 105");
2721 }
2722
2723 #[tokio::test]
2724 async fn test_option_chain() {
2725 let gateway = setup_option_chain();
2726
2727 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2728
2729 let subscription = client
2730 .option_chain("AAPL", "", crate::contracts::SecurityType::Stock, 0)
2731 .await
2732 .expect("Failed to get option chain");
2733
2734 let mut chains = Vec::new();
2735 let mut subscription = subscription;
2736 while let Some(chain_result) = subscription.next().await {
2737 match chain_result {
2738 Ok(chain) => chains.push(chain),
2739 Err(crate::Error::EndOfStream) => break,
2740 Err(e) => panic!("Unexpected error: {:?}", e),
2741 }
2742 }
2743
2744 assert_eq!(chains.len(), 2, "Should have 2 option chains");
2746
2747 assert_eq!(chains[0].exchange, "SMART", "First chain should be SMART");
2749 assert_eq!(chains[0].underlying_contract_id, 265598, "Should have correct contract ID");
2750 assert_eq!(chains[0].trading_class, "AAPL", "Should have correct trading class");
2751 assert_eq!(chains[0].multiplier, "100", "Should have correct multiplier");
2752 assert_eq!(chains[0].expirations.len(), 3, "SMART should have 3 expirations");
2753 assert_eq!(chains[0].expirations[0], "20250117", "First expiration should be 20250117");
2754 assert_eq!(chains[0].expirations[1], "20250221", "Second expiration should be 20250221");
2755 assert_eq!(chains[0].expirations[2], "20250321", "Third expiration should be 20250321");
2756 assert_eq!(chains[0].strikes.len(), 5, "SMART should have 5 strikes");
2757 assert_eq!(chains[0].strikes[0], 90.0, "First strike should be 90.0");
2758 assert_eq!(chains[0].strikes[4], 110.0, "Last strike should be 110.0");
2759
2760 assert_eq!(chains[1].exchange, "CBOE", "Second chain should be CBOE");
2762 assert_eq!(chains[1].underlying_contract_id, 265598, "Should have correct contract ID");
2763 assert_eq!(chains[1].trading_class, "AAPL", "Should have correct trading class");
2764 assert_eq!(chains[1].multiplier, "100", "Should have correct multiplier");
2765 assert_eq!(chains[1].expirations.len(), 2, "CBOE should have 2 expirations");
2766 assert_eq!(chains[1].strikes.len(), 4, "CBOE should have 4 strikes");
2767
2768 let requests = gateway.requests();
2770 assert_eq!(requests.len(), 1, "Should have 1 request");
2771 assert!(requests[0].starts_with("78\0"), "Request should start with message type 78");
2773 assert!(requests[0].contains("\0AAPL\0"), "Request should contain symbol AAPL");
2774 assert!(requests[0].contains("\0STK\0"), "Request should contain security type STK");
2775 }
2776
2777 #[tokio::test]
2778 async fn test_place_order() {
2779 use crate::client::common::tests::setup_place_order;
2780 use crate::contracts::Contract;
2781 use crate::orders::{order_builder, Action, PlaceOrder};
2782
2783 let _ = env_logger::try_init();
2785
2786 let gateway = setup_place_order();
2787 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2788
2789 let contract = Contract::stock("AAPL").build();
2791
2792 let order = order_builder::market_order(Action::Buy, 100.0);
2794
2795 let order_id = 1001;
2797
2798 let mut subscription = client.place_order(order_id, &contract, &order).await.expect("Failed to place order");
2800
2801 let mut order_status_count = 0;
2803 let mut _open_order_count = 0;
2804 let mut execution_count = 0;
2805 let mut commission_count = 0;
2806
2807 for _ in 0..6 {
2810 let event = match subscription.next().await {
2811 Some(Ok(event)) => event,
2812 Some(Err(crate::Error::EndOfStream)) => break,
2813 Some(Err(e)) => panic!("Unexpected error: {:?}", e),
2814 None => break,
2815 };
2816
2817 match event {
2818 PlaceOrder::OrderStatus(status) => {
2819 order_status_count += 1;
2820 assert_eq!(status.order_id, order_id);
2821
2822 if order_status_count == 1 {
2823 assert_eq!(status.status, "PreSubmitted");
2825 assert_eq!(status.filled, 0.0);
2826 assert_eq!(status.remaining, 100.0);
2827 } else if order_status_count == 2 {
2828 assert_eq!(status.status, "Submitted");
2830 assert_eq!(status.filled, 0.0);
2831 assert_eq!(status.remaining, 100.0);
2832 } else if order_status_count == 3 {
2833 assert_eq!(status.status, "Filled");
2835 assert_eq!(status.filled, 100.0);
2836 assert_eq!(status.remaining, 0.0);
2837 assert_eq!(status.average_fill_price, 150.25);
2838 }
2839 }
2840 PlaceOrder::OpenOrder(order_data) => {
2841 _open_order_count += 1;
2842 assert_eq!(order_data.order_id, order_id);
2843 assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2844 assert_eq!(order_data.contract.contract_id, 265598);
2845 assert_eq!(order_data.order.action, Action::Buy);
2846 assert_eq!(order_data.order.total_quantity, 100.0);
2847 assert_eq!(order_data.order.order_type, "LMT");
2848 assert_eq!(order_data.order.limit_price, Some(1.0));
2849 }
2850 PlaceOrder::ExecutionData(exec_data) => {
2851 execution_count += 1;
2852 assert_eq!(exec_data.execution.order_id, order_id);
2853 assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2854 assert_eq!(exec_data.execution.shares, 100.0);
2855 assert_eq!(exec_data.execution.price, 150.25);
2856 }
2857 PlaceOrder::CommissionReport(report) => {
2858 commission_count += 1;
2859 assert_eq!(report.commission, 1.25);
2860 assert_eq!(report.currency, "USD");
2861 }
2862 PlaceOrder::Message(_) => {
2863 }
2865 }
2866 }
2867
2868 assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2870 assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2871 assert_eq!(execution_count, 1, "Should receive 1 execution");
2872 assert_eq!(commission_count, 1, "Should receive 1 commission report");
2873
2874 let requests = gateway.requests();
2876 assert_eq!(requests.len(), 1, "Should have sent 1 request");
2877 assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2879 assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2880 }
2881
2882 #[tokio::test]
2883 async fn test_submit_order_with_order_update_stream() {
2884 use crate::client::common::tests::setup_place_order;
2885 use crate::contracts::Contract;
2886 use crate::orders::{order_builder, Action, OrderUpdate};
2887
2888 let _ = env_logger::try_init();
2890
2891 let gateway = setup_place_order();
2892 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
2893
2894 let contract = Contract::stock("AAPL").build();
2896
2897 let order = order_builder::market_order(Action::Buy, 100.0);
2899
2900 let order_id = 1001;
2902
2903 let mut update_stream = client.order_update_stream().await.expect("Failed to create order update stream");
2905
2906 client.submit_order(order_id, &contract, &order).await.expect("Failed to submit order");
2908
2909 let mut order_status_count = 0;
2911 let mut _open_order_count = 0;
2912 let mut execution_count = 0;
2913 let mut commission_count = 0;
2914
2915 println!("Starting to read from update stream...");
2917 let timeout_duration = std::time::Duration::from_millis(500);
2918 let mut events_received = 0;
2919
2920 while events_received < 6 {
2921 let update = match tokio::time::timeout(timeout_duration, update_stream.next()).await {
2922 Ok(Some(Ok(update))) => {
2923 events_received += 1;
2924 println!("Event {}: {:?}", events_received, &update);
2925 update
2926 }
2927 Ok(Some(Err(e))) => panic!("Error receiving update: {}", e),
2928 Ok(None) => break, Err(_) => break, };
2931
2932 match update {
2933 OrderUpdate::OrderStatus(status) => {
2934 order_status_count += 1;
2935 assert_eq!(status.order_id, order_id);
2936
2937 if order_status_count == 1 {
2938 assert_eq!(status.status, "PreSubmitted");
2940 assert_eq!(status.filled, 0.0);
2941 assert_eq!(status.remaining, 100.0);
2942 } else if order_status_count == 2 {
2943 assert_eq!(status.status, "Submitted");
2945 assert_eq!(status.filled, 0.0);
2946 assert_eq!(status.remaining, 100.0);
2947 } else if order_status_count == 3 {
2948 assert_eq!(status.status, "Filled");
2950 assert_eq!(status.filled, 100.0);
2951 assert_eq!(status.remaining, 0.0);
2952 assert_eq!(status.average_fill_price, 150.25);
2953 }
2954 }
2955 OrderUpdate::OpenOrder(order_data) => {
2956 _open_order_count += 1;
2957 assert_eq!(order_data.order_id, order_id);
2958 assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"));
2959 assert_eq!(order_data.contract.contract_id, 265598);
2960 assert_eq!(order_data.order.action, Action::Buy);
2961 assert_eq!(order_data.order.total_quantity, 100.0);
2962 assert_eq!(order_data.order.order_type, "LMT");
2963 assert_eq!(order_data.order.limit_price, Some(1.0));
2964 }
2965 OrderUpdate::ExecutionData(exec_data) => {
2966 execution_count += 1;
2967 assert_eq!(exec_data.execution.order_id, order_id);
2968 assert_eq!(exec_data.contract.symbol, Symbol::from("AAPL"));
2969 assert_eq!(exec_data.execution.shares, 100.0);
2970 assert_eq!(exec_data.execution.price, 150.25);
2971 }
2972 OrderUpdate::CommissionReport(report) => {
2973 commission_count += 1;
2974 assert_eq!(report.commission, 1.25);
2975 assert_eq!(report.currency, "USD");
2976 }
2977 OrderUpdate::Message(_) => {
2978 }
2980 }
2981 }
2982
2983 assert_eq!(order_status_count, 3, "Should receive 3 order status updates");
2985 assert_eq!(_open_order_count, 1, "Should receive 1 open order");
2986 assert_eq!(execution_count, 1, "Should receive 1 execution");
2987 assert_eq!(commission_count, 1, "Should receive 1 commission report");
2988
2989 let requests = gateway.requests();
2991 assert_eq!(requests.len(), 1, "Should have sent 1 request");
2992 assert!(requests[0].starts_with("3\0"), "Request should be a PlaceOrder message");
2994 assert!(requests[0].contains(&format!("\0{}\0", order_id)), "Request should contain order ID");
2995 }
2996
2997 #[tokio::test]
2998 async fn test_open_orders() {
2999 use crate::client::common::tests::setup_open_orders;
3000 use crate::orders::{Action, Orders};
3001
3002 let _ = env_logger::try_init();
3004
3005 let gateway = setup_open_orders();
3006 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3007
3008 let mut subscription = client.open_orders().await.expect("Failed to request open orders");
3010
3011 let mut orders = Vec::new();
3013 while let Some(result) = subscription.next().await {
3014 match result {
3015 Ok(Orders::OrderData(order_data)) => {
3016 orders.push(order_data);
3017 }
3018 Ok(Orders::OrderStatus(_)) => {
3019 }
3021 Ok(Orders::Notice(_)) => {
3022 }
3024 Err(crate::Error::EndOfStream) => break,
3025 Err(e) => panic!("Unexpected error: {:?}", e),
3026 }
3027 }
3028
3029 assert_eq!(orders.len(), 2, "Should receive 2 open orders");
3031
3032 let order1 = &orders[0];
3034 assert_eq!(order1.order_id, 1001);
3035 assert_eq!(order1.contract.symbol, Symbol::from("AAPL"));
3036 assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
3037 assert_eq!(order1.order.action, Action::Buy);
3038 assert_eq!(order1.order.total_quantity, 100.0);
3039 assert_eq!(order1.order.order_type, "MKT");
3040 assert_eq!(order1.order_state.status, "PreSubmitted");
3041
3042 let order2 = &orders[1];
3044 assert_eq!(order2.order_id, 1002);
3045 assert_eq!(order2.contract.symbol, Symbol::from("MSFT"));
3046 assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3047 assert_eq!(order2.order.action, Action::Sell);
3048 assert_eq!(order2.order.total_quantity, 50.0);
3049 assert_eq!(order2.order.order_type, "LMT");
3050 assert_eq!(order2.order.limit_price, Some(350.0));
3051 assert_eq!(order2.order_state.status, "Submitted");
3052
3053 let requests = gateway.requests();
3055 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3056 assert_eq!(requests[0], "5\01\0", "Request should be RequestOpenOrders with version 1");
3057 }
3058
3059 #[tokio::test]
3060 async fn test_all_open_orders() {
3061 use crate::client::common::tests::setup_all_open_orders;
3062 use crate::orders::{Action, Orders};
3063
3064 let _ = env_logger::try_init();
3066
3067 let gateway = setup_all_open_orders();
3068 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3069
3070 let mut subscription = client.all_open_orders().await.expect("Failed to request all open orders");
3072
3073 let mut orders = Vec::new();
3075 while let Some(result) = subscription.next().await {
3076 match result {
3077 Ok(Orders::OrderData(order_data)) => {
3078 orders.push(order_data);
3079 }
3080 Ok(Orders::OrderStatus(_)) => {
3081 }
3083 Ok(Orders::Notice(_)) => {
3084 }
3086 Err(crate::Error::EndOfStream) => break,
3087 Err(e) => panic!("Unexpected error: {:?}", e),
3088 }
3089 }
3090
3091 assert_eq!(orders.len(), 3, "Should receive 3 open orders from all accounts");
3093
3094 let order1 = &orders[0];
3096 assert_eq!(order1.order_id, 2001);
3097 assert_eq!(order1.contract.symbol, Symbol::from("TSLA"));
3098 assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Stock);
3099 assert_eq!(order1.order.action, Action::Buy);
3100 assert_eq!(order1.order.total_quantity, 10.0);
3101 assert_eq!(order1.order.order_type, "LMT");
3102 assert_eq!(order1.order.limit_price, Some(420.0));
3103 assert_eq!(order1.order.account, "DU1236110");
3104
3105 let order2 = &orders[1];
3107 assert_eq!(order2.order_id, 2002);
3108 assert_eq!(order2.contract.symbol, Symbol::from("AMZN"));
3109 assert_eq!(order2.order.action, Action::Sell);
3110 assert_eq!(order2.order.total_quantity, 5.0);
3111 assert_eq!(order2.order.order_type, "MKT");
3112 assert_eq!(order2.order.account, "DU1236111");
3113
3114 let order3 = &orders[2];
3116 assert_eq!(order3.order_id, 1003);
3117 assert_eq!(order3.contract.symbol, Symbol::from("GOOGL"));
3118 assert_eq!(order3.order.action, Action::Buy);
3119 assert_eq!(order3.order.total_quantity, 20.0);
3120 assert_eq!(order3.order.order_type, "LMT");
3121 assert_eq!(order3.order.limit_price, Some(2800.0));
3122 assert_eq!(order3.order.account, "DU1236109");
3123
3124 let requests = gateway.requests();
3126 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3127 assert_eq!(requests[0], "16\01\0", "Request should be RequestAllOpenOrders with version 1");
3128 }
3129
3130 #[tokio::test]
3131 async fn test_auto_open_orders() {
3132 use crate::client::common::tests::setup_auto_open_orders;
3133 use crate::orders::Orders;
3134
3135 let _ = env_logger::try_init();
3137
3138 let gateway = setup_auto_open_orders();
3139 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3142
3143 let mut subscription = client.auto_open_orders(true).await.expect("Failed to request auto open orders");
3145
3146 let mut order_statuses = Vec::new();
3148 let mut orders = Vec::new();
3149 while let Some(result) = subscription.next().await {
3150 match result {
3151 Ok(Orders::OrderData(order_data)) => {
3152 orders.push(order_data);
3153 }
3154 Ok(Orders::OrderStatus(status)) => {
3155 order_statuses.push(status);
3156 }
3157 Ok(Orders::Notice(_)) => {
3158 }
3160 Err(crate::Error::EndOfStream) => break,
3161 Err(e) => panic!("Unexpected error: {:?}", e),
3162 }
3163 }
3164
3165 assert_eq!(order_statuses.len(), 2, "Should receive 2 order status updates");
3167
3168 let status1 = &order_statuses[0];
3170 assert_eq!(status1.order_id, 3001);
3171 assert_eq!(status1.status, "PreSubmitted");
3172
3173 let status2 = &order_statuses[1];
3175 assert_eq!(status2.order_id, 3001);
3176 assert_eq!(status2.status, "Submitted");
3177
3178 assert_eq!(orders.len(), 1, "Should receive 1 order");
3180
3181 let order = &orders[0];
3183 assert_eq!(order.order_id, 3001);
3184 assert_eq!(order.contract.symbol, Symbol::from("FB"));
3185 assert_eq!(order.contract.security_type, crate::contracts::SecurityType::Stock);
3186 assert_eq!(order.order.action, crate::orders::Action::Buy);
3187 assert_eq!(order.order.total_quantity, 50.0);
3188 assert_eq!(order.order.order_type, "MKT");
3189 assert_eq!(order.order.account, "TWS");
3190
3191 let requests = gateway.requests();
3193 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3194 assert_eq!(
3195 requests[0], "15\01\01\0",
3196 "Request should be RequestAutoOpenOrders with version 1 and auto_bind=true"
3197 );
3198 }
3199
3200 #[tokio::test]
3201 async fn test_completed_orders() {
3202 use crate::client::common::tests::setup_completed_orders;
3203 use crate::orders::{Action, Orders};
3204
3205 let _ = env_logger::try_init();
3207
3208 let gateway = setup_completed_orders();
3209 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3210
3211 let mut subscription = client.completed_orders(false).await.expect("Failed to request completed orders");
3213
3214 let mut orders = Vec::new();
3216 while let Some(result) = subscription.next().await {
3217 match result {
3218 Ok(Orders::OrderData(order_data)) => {
3219 orders.push(order_data);
3220 }
3221 Ok(Orders::OrderStatus(_)) => {
3222 }
3224 Ok(Orders::Notice(_)) => {
3225 }
3227 Err(crate::Error::EndOfStream) => break,
3228 Err(e) => panic!("Unexpected error: {:?}", e),
3229 }
3230 }
3231
3232 assert_eq!(orders.len(), 2, "Should receive 2 completed orders");
3234
3235 let order1 = &orders[0];
3237 assert_eq!(order1.order_id, -1);
3239 assert_eq!(order1.contract.symbol, Symbol::from("ES"));
3240 assert_eq!(order1.contract.security_type, crate::contracts::SecurityType::Future);
3241 assert_eq!(order1.order.action, Action::Buy);
3242 assert_eq!(order1.order.total_quantity, 1.0);
3243 assert_eq!(order1.order.order_type, "LMT");
3244 assert_eq!(order1.order_state.status, "Cancelled");
3245 assert_eq!(order1.order.perm_id, 616088517);
3246
3247 let order2 = &orders[1];
3249 assert_eq!(order2.order_id, -1); assert_eq!(order2.contract.symbol, Symbol::from("AAPL"));
3251 assert_eq!(order2.contract.security_type, crate::contracts::SecurityType::Stock);
3252 assert_eq!(order2.order.action, Action::Buy);
3253 assert_eq!(order2.order.total_quantity, 100.0);
3254 assert_eq!(order2.order.order_type, "MKT");
3255 assert_eq!(order2.order_state.status, "Filled");
3256 assert_eq!(order2.order.perm_id, 1377295418);
3257
3258 let requests = gateway.requests();
3260 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3261 assert_eq!(requests[0], "99\00\0", "Request should be RequestCompletedOrders with api_only=false");
3262 }
3263
3264 #[tokio::test]
3265 async fn test_cancel_order() {
3266 use crate::client::common::tests::setup_cancel_order;
3267 use crate::messages::Notice;
3268 use crate::orders::CancelOrder;
3269
3270 let _ = env_logger::try_init();
3272
3273 let gateway = setup_cancel_order();
3274 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3275
3276 let order_id = 1001;
3278 let manual_order_cancel_time = "";
3279
3280 let result = client.cancel_order(order_id, manual_order_cancel_time).await;
3282
3283 match result {
3285 Ok(mut cancel_stream) => {
3286 let mut order_status_received = false;
3288 let mut notice_received = false;
3289
3290 while let Some(result) = cancel_stream.next().await {
3291 match result {
3292 Ok(CancelOrder::OrderStatus(status)) => {
3293 assert_eq!(status.order_id, order_id);
3294 assert_eq!(status.status, "Cancelled");
3295 assert_eq!(status.filled, 0.0);
3296 assert_eq!(status.remaining, 100.0);
3297 order_status_received = true;
3298 println!("Received OrderStatus: {:?}", status);
3299 }
3300 Ok(CancelOrder::Notice(Notice { code, message, .. })) => {
3301 assert_eq!(code, 202);
3304 assert!(message.contains("Order Cancelled"));
3305 notice_received = true;
3306 println!("Received Notice: code={}, message={}", code, message);
3307 }
3308 Err(e) => panic!("Error in cancel stream: {}", e),
3309 }
3310 }
3311
3312 assert!(order_status_received, "Should have received OrderStatus");
3313 assert!(notice_received, "Should have received Notice confirmation");
3314 }
3315 Err(e) => panic!("Failed to cancel order: {}", e),
3316 }
3317
3318 let requests = gateway.requests();
3320 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3321 assert!(requests[0].starts_with("4\0"), "Request should be a CancelOrder message");
3322 assert!(requests[0].contains(&format!("{}\0", order_id)), "Request should contain order ID");
3323 }
3324
3325 #[tokio::test]
3326 async fn test_global_cancel() {
3327 use crate::client::common::tests::setup_global_cancel;
3328
3329 let _ = env_logger::try_init();
3331
3332 let gateway = setup_global_cancel();
3333 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3334
3335 let result = client.global_cancel().await;
3337
3338 match result {
3340 Ok(()) => {
3341 println!("Global cancel request sent successfully");
3342 }
3343 Err(e) => panic!("Failed to send global cancel: {}", e),
3344 }
3345
3346 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
3348
3349 let requests = gateway.requests();
3351 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3352 assert_eq!(requests[0], "58\01\0", "Request should be a RequestGlobalCancel message with version 1");
3353 }
3354
3355 #[tokio::test]
3356 async fn test_executions() {
3357 use crate::client::common::tests::setup_executions;
3358 use crate::contracts::SecurityType;
3359 use crate::orders::{ExecutionFilter, Executions};
3360
3361 let _ = env_logger::try_init();
3363
3364 let gateway = setup_executions();
3365 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3366
3367 let filter = ExecutionFilter {
3369 client_id: Some(CLIENT_ID),
3370 account_code: "DU1234567".to_string(),
3371 time: "".to_string(), symbol: "".to_string(), security_type: "".to_string(), exchange: "".to_string(), side: "".to_string(), ..Default::default()
3377 };
3378
3379 let mut subscription = client.executions(filter).await.expect("Failed to request executions");
3381
3382 let mut execution_data = Vec::new();
3384 let mut commission_reports = Vec::new();
3385
3386 while let Some(result) = subscription.next().await {
3387 match result {
3388 Ok(Executions::ExecutionData(data)) => {
3389 execution_data.push(data);
3390 }
3391 Ok(Executions::CommissionReport(report)) => {
3392 commission_reports.push(report);
3393 }
3394 Ok(Executions::Notice(_)) => {
3395 }
3397 Err(crate::Error::EndOfStream) => break,
3398 Err(e) => panic!("Unexpected error: {:?}", e),
3399 }
3400 }
3401
3402 assert_eq!(execution_data.len(), 3, "Should receive 3 execution data messages");
3404 assert_eq!(commission_reports.len(), 3, "Should receive 3 commission reports");
3405
3406 let exec1 = &execution_data[0];
3408 assert_eq!(exec1.request_id, 9000);
3409 assert_eq!(exec1.execution.order_id, 1001);
3410 assert_eq!(exec1.contract.symbol, Symbol::from("AAPL"));
3411 assert_eq!(exec1.contract.security_type, SecurityType::Stock);
3412 assert_eq!(exec1.execution.execution_id, "000e1a2b.67890abc.01.01");
3413 assert_eq!(exec1.execution.side, "BOT");
3414 assert_eq!(exec1.execution.shares, 100.0);
3415 assert_eq!(exec1.execution.price, 150.25);
3416
3417 let comm1 = &commission_reports[0];
3419 assert_eq!(comm1.execution_id, "000e1a2b.67890abc.01.01");
3420 assert_eq!(comm1.commission, 1.25);
3421 assert_eq!(comm1.currency, "USD");
3422
3423 let exec2 = &execution_data[1];
3425 assert_eq!(exec2.request_id, 9000);
3426 assert_eq!(exec2.execution.order_id, 1002);
3427 assert_eq!(exec2.contract.symbol, Symbol::from("ES"));
3428 assert_eq!(exec2.contract.security_type, SecurityType::Future);
3429 assert_eq!(exec2.execution.execution_id, "000e1a2b.67890def.02.01");
3430 assert_eq!(exec2.execution.side, "SLD");
3431 assert_eq!(exec2.execution.shares, 5.0);
3432 assert_eq!(exec2.execution.price, 5050.25);
3433
3434 let comm2 = &commission_reports[1];
3436 assert_eq!(comm2.execution_id, "000e1a2b.67890def.02.01");
3437 assert_eq!(comm2.commission, 2.50);
3438 assert_eq!(comm2.realized_pnl, Some(125.50));
3439
3440 let exec3 = &execution_data[2];
3442 assert_eq!(exec3.request_id, 9000);
3443 assert_eq!(exec3.execution.order_id, 1003);
3444 assert_eq!(exec3.contract.symbol, Symbol::from("SPY"));
3445 assert_eq!(exec3.contract.security_type, SecurityType::Option);
3446 assert_eq!(exec3.execution.execution_id, "000e1a2b.67890ghi.03.01");
3447 assert_eq!(exec3.execution.side, "BOT");
3448 assert_eq!(exec3.execution.shares, 10.0);
3449 assert_eq!(exec3.execution.price, 2.50);
3450
3451 let comm3 = &commission_reports[2];
3453 assert_eq!(comm3.execution_id, "000e1a2b.67890ghi.03.01");
3454 assert_eq!(comm3.commission, 0.65);
3455 assert_eq!(comm3.realized_pnl, Some(250.00));
3456
3457 let requests = gateway.requests();
3459 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3460 assert_eq!(
3462 requests[0], "7\03\09000\0100\0DU1234567\0\0\0\0\0\0",
3463 "Request should be RequestExecutions with correct filter parameters"
3464 );
3465 }
3466
3467 #[tokio::test]
3468 async fn test_exercise_options() {
3469 use crate::client::common::tests::setup_exercise_options;
3470 use crate::contracts::{Contract, Currency, Exchange, SecurityType, Symbol};
3471 use crate::orders::{ExerciseAction, ExerciseOptions};
3472 use time::macros::datetime;
3473
3474 let _ = env_logger::try_init();
3476
3477 let gateway = setup_exercise_options();
3478 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3479
3480 let contract = Contract {
3482 contract_id: 123456789,
3483 symbol: Symbol::from("SPY"),
3484 security_type: SecurityType::Option,
3485 last_trade_date_or_contract_month: "20240126".to_string(),
3486 strike: 450.0,
3487 right: "C".to_string(), multiplier: "100".to_string(),
3489 exchange: Exchange::from("CBOE"),
3490 currency: Currency::from("USD"),
3491 local_symbol: "SPY240126C00450000".to_string(),
3492 trading_class: "SPY".to_string(),
3493 ..Default::default()
3494 };
3495
3496 let exercise_action = ExerciseAction::Exercise;
3498 let exercise_quantity = 10;
3499 let account = "DU1234567";
3500 let ovrd = false;
3501 let manual_order_time = Some(datetime!(2024-01-25 10:30:00 UTC));
3502
3503 let mut subscription = client
3504 .exercise_options(&contract, exercise_action, exercise_quantity, account, ovrd, manual_order_time)
3505 .await
3506 .expect("Failed to exercise options");
3507
3508 let mut order_statuses = Vec::new();
3510 let mut open_orders = Vec::new();
3511
3512 while let Some(result) = subscription.next().await {
3513 match result {
3514 Ok(ExerciseOptions::OrderStatus(status)) => order_statuses.push(status),
3515 Ok(ExerciseOptions::OpenOrder(order)) => open_orders.push(order),
3516 Ok(ExerciseOptions::Notice(_notice)) => {
3517 }
3520 Err(crate::Error::EndOfStream) => break,
3521 Err(e) => panic!("Unexpected error: {:?}", e),
3522 }
3523 }
3524
3525 assert_eq!(order_statuses.len(), 3, "Should have 3 order status updates");
3527 assert_eq!(open_orders.len(), 1, "Should have 1 open order");
3528
3529 assert_eq!(order_statuses[0].status, "PreSubmitted");
3531 assert_eq!(order_statuses[0].filled, 0.0);
3532 assert_eq!(order_statuses[0].remaining, 10.0);
3533
3534 assert_eq!(order_statuses[1].status, "Submitted");
3535 assert_eq!(order_statuses[2].status, "Filled");
3536 assert_eq!(order_statuses[2].filled, 10.0);
3537 assert_eq!(order_statuses[2].remaining, 0.0);
3538
3539 let open_order = &open_orders[0];
3541 assert_eq!(open_order.order.order_id, 90);
3542 assert_eq!(open_order.contract.symbol, Symbol::from("SPY"));
3543 assert_eq!(open_order.contract.security_type, SecurityType::Option);
3544 assert_eq!(open_order.order.order_type, "EXERCISE");
3545
3546 let requests = gateway.requests();
3548 assert_eq!(requests.len(), 1, "Should have sent 1 request");
3549
3550 let expected_request = format!(
3552 "21\02\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0{}\0",
3553 90, contract.contract_id,
3555 contract.symbol,
3556 contract.security_type,
3557 contract.last_trade_date_or_contract_month,
3558 contract.strike,
3559 contract.right,
3560 contract.multiplier,
3561 contract.exchange,
3562 contract.currency,
3563 contract.local_symbol,
3564 contract.trading_class,
3565 exercise_action as i32,
3566 exercise_quantity,
3567 account,
3568 if ovrd { 1 } else { 0 },
3569 "20240125 10:30:00 UTC" );
3571
3572 assert_eq!(requests[0], expected_request, "Request should be ExerciseOptions with correct parameters");
3573 }
3574
3575 #[tokio::test]
3578 async fn test_market_data() {
3579 use crate::client::common::tests::setup_market_data;
3580 use crate::contracts::tick_types::TickType;
3581 use crate::contracts::Contract;
3582 use crate::market_data::realtime::TickTypes;
3583
3584 let gateway = setup_market_data();
3585 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3586
3587 let contract = Contract::stock("AAPL").build();
3588 let generic_ticks = vec!["100", "101", "104"]; let mut subscription = client
3591 .market_data(&contract)
3592 .generic_ticks(&generic_ticks)
3593 .snapshot()
3594 .subscribe()
3595 .await
3596 .expect("Failed to request market data");
3597
3598 let mut tick_count = 0;
3599 let mut has_bid_price = false;
3600 let mut has_bid_size = false;
3601 let mut has_ask_price = false;
3602 let mut has_ask_size = false;
3603 let mut has_last_price = false;
3604 let mut has_last_size = false;
3605 let mut has_volume = false;
3606 let mut has_snapshot_end = false;
3607
3608 while let Some(tick_result) = subscription.next().await {
3609 let tick = tick_result.expect("Failed to get tick");
3610 tick_count += 1;
3611 match tick {
3612 TickTypes::PriceSize(price_size) => {
3613 match price_size.price_tick_type {
3614 TickType::Bid => {
3615 assert_eq!(price_size.price, 150.50);
3616 has_bid_price = true;
3617 }
3618 TickType::Ask => {
3619 assert_eq!(price_size.price, 151.00);
3620 has_ask_price = true;
3621 }
3622 TickType::Last => {
3623 assert_eq!(price_size.price, 150.75);
3624 has_last_price = true;
3625 }
3626 _ => {}
3627 }
3628 }
3630 TickTypes::Size(size_tick) => match size_tick.tick_type {
3631 TickType::BidSize => {
3632 assert_eq!(size_tick.size, 100.0);
3633 has_bid_size = true;
3634 }
3635 TickType::AskSize => {
3636 assert_eq!(size_tick.size, 200.0);
3637 has_ask_size = true;
3638 }
3639 TickType::LastSize => {
3640 assert_eq!(size_tick.size, 50.0);
3641 has_last_size = true;
3642 }
3643 _ => {}
3644 },
3645 TickTypes::Generic(generic_tick) => {
3646 if generic_tick.tick_type == TickType::Volume {
3647 assert_eq!(generic_tick.value, 1500000.0);
3648 has_volume = true;
3649 }
3650 }
3651 TickTypes::String(_) => {
3652 }
3654 TickTypes::SnapshotEnd => {
3655 has_snapshot_end = true;
3656 break; }
3658 _ => {}
3659 }
3660
3661 if tick_count > 20 {
3662 break; }
3664 }
3665
3666 assert!(has_bid_price, "Should receive bid price");
3667 assert!(has_bid_size, "Should receive bid size");
3668 assert!(has_ask_price, "Should receive ask price");
3669 assert!(has_ask_size, "Should receive ask size");
3670 assert!(has_last_price, "Should receive last price");
3671 assert!(has_last_size, "Should receive last size");
3672 assert!(has_volume, "Should receive volume");
3673 assert!(has_snapshot_end, "Should receive snapshot end");
3674
3675 let requests = gateway.requests();
3676 assert!(requests[0].starts_with("1\011\09000\0"), "Request should be RequestMarketData");
3677 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3678 assert!(requests[0].contains("100,101,104\0"), "Request should contain generic ticks");
3679 assert!(requests[0].contains("\01\0"), "Request should have snapshot=true");
3680 }
3681
3682 #[tokio::test]
3683 async fn test_realtime_bars() {
3684 use crate::client::common::tests::setup_realtime_bars;
3685 use crate::contracts::Contract;
3686 use crate::market_data::realtime::{BarSize, WhatToShow};
3687
3688 let gateway = setup_realtime_bars();
3689 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3690
3691 let contract = Contract::stock("AAPL").build();
3692 let bar_size = BarSize::Sec5;
3693 let what_to_show = WhatToShow::Trades;
3694 let trading_hours = TradingHours::Extended;
3695
3696 let mut subscription = client
3697 .realtime_bars(&contract, bar_size, what_to_show, trading_hours)
3698 .await
3699 .expect("Failed to request realtime bars");
3700
3701 let mut bars = Vec::new();
3702 for _ in 0..3 {
3703 if let Some(bar_result) = subscription.next().await {
3704 bars.push(bar_result.expect("Failed to get bar"));
3705 }
3706 }
3707
3708 assert_eq!(bars.len(), 3, "Should receive 3 bars");
3709
3710 let bar1 = &bars[0];
3712 assert_eq!(bar1.open, 150.25);
3713 assert_eq!(bar1.high, 150.75);
3714 assert_eq!(bar1.low, 150.00);
3715 assert_eq!(bar1.close, 150.50);
3716 assert_eq!(bar1.volume, 1000.0);
3717 assert_eq!(bar1.wap, 150.40);
3718 assert_eq!(bar1.count, 25);
3719
3720 let bar2 = &bars[1];
3722 assert_eq!(bar2.open, 150.50);
3723 assert_eq!(bar2.high, 151.00);
3724 assert_eq!(bar2.low, 150.40);
3725 assert_eq!(bar2.close, 150.90);
3726 assert_eq!(bar2.volume, 1200.0);
3727
3728 let bar3 = &bars[2];
3730 assert_eq!(bar3.open, 150.90);
3731 assert_eq!(bar3.high, 151.25);
3732 assert_eq!(bar3.low, 150.85);
3733 assert_eq!(bar3.close, 151.20);
3734
3735 let requests = gateway.requests();
3736 assert!(requests[0].starts_with("50\08\09000\0"), "Request should be RequestRealTimeBars");
3737 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3738 assert!(
3739 requests[0].contains("\00\0TRADES\00\0"),
3740 "Request should have bar_size=0 (5 sec) and TRADES"
3741 );
3742 }
3743
3744 #[tokio::test]
3745 async fn test_tick_by_tick_last() {
3746 use crate::client::common::tests::setup_tick_by_tick_last;
3747 use crate::contracts::Contract;
3748
3749 let gateway = setup_tick_by_tick_last();
3750 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3751
3752 let contract = Contract::stock("AAPL").build();
3753 let number_of_ticks = 0;
3754 let ignore_size = false;
3755
3756 let mut subscription = client
3757 .tick_by_tick_last(&contract, number_of_ticks, ignore_size)
3758 .await
3759 .expect("Failed to request tick by tick last");
3760
3761 let mut trades = Vec::new();
3762 for _ in 0..3 {
3763 if let Some(trade_result) = subscription.next().await {
3764 trades.push(trade_result.expect("Failed to get trade"));
3765 }
3766 }
3767
3768 assert_eq!(trades.len(), 3, "Should receive 3 trades");
3769
3770 let trade1 = &trades[0];
3772 assert_eq!(trade1.tick_type, "1"); assert_eq!(trade1.price, 150.75);
3774 assert_eq!(trade1.size, 100.0);
3775 assert_eq!(trade1.exchange, "NASDAQ");
3776 assert!(!trade1.trade_attribute.past_limit);
3777 assert!(!trade1.trade_attribute.unreported);
3778
3779 let trade2 = &trades[1];
3781 assert_eq!(trade2.price, 150.80);
3782 assert_eq!(trade2.size, 50.0);
3783 assert_eq!(trade2.exchange, "NYSE");
3784 assert!(trade2.trade_attribute.unreported);
3785
3786 let trade3 = &trades[2];
3788 assert_eq!(trade3.price, 150.70);
3789 assert_eq!(trade3.size, 150.0);
3790
3791 let requests = gateway.requests();
3792 assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3793 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3794 assert!(requests[0].contains("Last\0"), "Request should have Last tick type");
3795 }
3796
3797 #[tokio::test]
3798 async fn test_tick_by_tick_all_last() {
3799 use crate::client::common::tests::setup_tick_by_tick_all_last;
3800 use crate::contracts::Contract;
3801
3802 let gateway = setup_tick_by_tick_all_last();
3803 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3804
3805 let contract = Contract::stock("AAPL").build();
3806 let number_of_ticks = 0;
3807 let ignore_size = false;
3808
3809 let mut subscription = client
3810 .tick_by_tick_all_last(&contract, number_of_ticks, ignore_size)
3811 .await
3812 .expect("Failed to request tick by tick all last");
3813
3814 let mut trades = Vec::new();
3815 for _ in 0..3 {
3816 if let Some(trade_result) = subscription.next().await {
3817 trades.push(trade_result.expect("Failed to get trade"));
3818 }
3819 }
3820
3821 assert_eq!(trades.len(), 3, "Should receive 3 trades");
3822
3823 let trade1 = &trades[0];
3825 assert_eq!(trade1.tick_type, "2"); assert_eq!(trade1.price, 150.75);
3827 assert_eq!(trade1.exchange, "NASDAQ");
3828
3829 let trade2 = &trades[1];
3831 assert_eq!(trade2.price, 150.80);
3832 assert_eq!(trade2.exchange, "DARK");
3833 assert_eq!(trade2.special_conditions, "ISO");
3834 assert!(trade2.trade_attribute.unreported);
3835
3836 let trade3 = &trades[2];
3838 assert_eq!(trade3.price, 150.70);
3839 assert_eq!(trade3.exchange, "NYSE");
3840
3841 let requests = gateway.requests();
3842 assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3843 assert!(requests[0].contains("AllLast\0"), "Request should have AllLast tick type");
3844 }
3845
3846 #[tokio::test]
3847 async fn test_tick_by_tick_bid_ask() {
3848 use crate::client::common::tests::setup_tick_by_tick_bid_ask;
3849 use crate::contracts::Contract;
3850
3851 let gateway = setup_tick_by_tick_bid_ask();
3852 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3853
3854 let contract = Contract::stock("AAPL").build();
3855 let number_of_ticks = 0;
3856 let ignore_size = false;
3857
3858 let mut subscription = client
3859 .tick_by_tick_bid_ask(&contract, number_of_ticks, ignore_size)
3860 .await
3861 .expect("Failed to request tick by tick bid ask");
3862
3863 let mut bid_asks = Vec::new();
3864 for _ in 0..3 {
3865 if let Some(bid_ask_result) = subscription.next().await {
3866 bid_asks.push(bid_ask_result.expect("Failed to get bid/ask"));
3867 }
3868 }
3869
3870 assert_eq!(bid_asks.len(), 3, "Should receive 3 bid/ask updates");
3871
3872 let ba1 = &bid_asks[0];
3874 assert_eq!(ba1.bid_price, 150.50);
3875 assert_eq!(ba1.ask_price, 150.55);
3876 assert_eq!(ba1.bid_size, 100.0);
3877 assert_eq!(ba1.ask_size, 200.0);
3878 assert!(!ba1.bid_ask_attribute.bid_past_low);
3879 assert!(!ba1.bid_ask_attribute.ask_past_high);
3880
3881 let ba2 = &bid_asks[1];
3883 assert_eq!(ba2.bid_price, 150.45);
3884 assert_eq!(ba2.ask_price, 150.55);
3885 assert!(ba2.bid_ask_attribute.bid_past_low);
3886
3887 let ba3 = &bid_asks[2];
3889 assert_eq!(ba3.ask_price, 150.60);
3890 assert!(ba3.bid_ask_attribute.ask_past_high);
3891
3892 let requests = gateway.requests();
3893 assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3894 assert!(requests[0].contains("BidAsk\0"), "Request should have BidAsk tick type");
3895 }
3896
3897 #[tokio::test]
3898 async fn test_tick_by_tick_midpoint() {
3899 use crate::client::common::tests::setup_tick_by_tick_midpoint;
3900 use crate::contracts::Contract;
3901
3902 let gateway = setup_tick_by_tick_midpoint();
3903 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3904
3905 let contract = Contract::stock("AAPL").build();
3906 let number_of_ticks = 0;
3907 let ignore_size = false;
3908
3909 let mut subscription = client
3910 .tick_by_tick_midpoint(&contract, number_of_ticks, ignore_size)
3911 .await
3912 .expect("Failed to request tick by tick midpoint");
3913
3914 let mut midpoints = Vec::new();
3915 for _ in 0..3 {
3916 if let Some(midpoint_result) = subscription.next().await {
3917 midpoints.push(midpoint_result.expect("Failed to get midpoint"));
3918 }
3919 }
3920
3921 assert_eq!(midpoints.len(), 3, "Should receive 3 midpoint updates");
3922
3923 assert_eq!(midpoints[0].mid_point, 150.525);
3924 assert_eq!(midpoints[1].mid_point, 150.50);
3925 assert_eq!(midpoints[2].mid_point, 150.525);
3926
3927 let requests = gateway.requests();
3928 assert!(requests[0].starts_with("97\09000\0"), "Request should be RequestTickByTickData");
3929 assert!(requests[0].contains("MidPoint\0"), "Request should have MidPoint tick type");
3930 }
3931
3932 #[tokio::test]
3933 async fn test_market_depth() {
3934 use crate::client::common::tests::setup_market_depth;
3935 use crate::contracts::Contract;
3936 use crate::market_data::realtime::MarketDepths;
3937
3938 let gateway = setup_market_depth();
3939 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3940
3941 let contract = Contract::stock("AAPL").build();
3942 let num_rows = 5;
3943 let is_smart_depth = false;
3944
3945 let mut subscription = client
3946 .market_depth(&contract, num_rows, is_smart_depth)
3947 .await
3948 .expect("Failed to request market depth");
3949
3950 let mut updates = Vec::new();
3951 for _ in 0..4 {
3952 if let Some(update_result) = subscription.next().await {
3953 let update = update_result.expect("Failed to get depth update");
3954 if let MarketDepths::MarketDepth(depth) = update {
3955 updates.push(depth);
3956 }
3957 }
3958 }
3959
3960 assert_eq!(updates.len(), 4, "Should receive 4 depth updates");
3961
3962 let update1 = &updates[0];
3964 assert_eq!(update1.position, 0);
3965 assert_eq!(update1.operation, 0); assert_eq!(update1.side, 1); assert_eq!(update1.price, 150.50);
3969 assert_eq!(update1.size, 100.0);
3970
3971 let update2 = &updates[1];
3973 assert_eq!(update2.operation, 0); assert_eq!(update2.side, 0); assert_eq!(update2.price, 150.55);
3976 assert_eq!(update2.size, 200.0);
3977
3978 let update3 = &updates[2];
3980 assert_eq!(update3.operation, 1); assert_eq!(update3.price, 150.49);
3982
3983 let update4 = &updates[3];
3985 assert_eq!(update4.operation, 2); let requests = gateway.requests();
3988 assert!(requests[0].starts_with("10\05\09000\0"), "Request should be RequestMarketDepth");
3989 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
3990 assert!(requests[0].contains("5\00\0"), "Request should have 5 rows and smart_depth=false");
3991 }
3992
3993 #[tokio::test]
3994 async fn test_market_depth_exchanges() {
3995 use crate::client::common::tests::setup_market_depth_exchanges;
3996
3997 let gateway = setup_market_depth_exchanges();
3998 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
3999
4000 let exchanges = client.market_depth_exchanges().await.expect("Failed to get market depth exchanges");
4001
4002 assert_eq!(exchanges.len(), 3, "Should receive 3 exchange descriptions");
4003
4004 let ex1 = &exchanges[0];
4006 assert_eq!(ex1.exchange_name, "ISLAND");
4007 assert_eq!(ex1.security_type, "STK");
4008 assert_eq!(ex1.listing_exchange, "NASDAQ");
4009 assert_eq!(ex1.service_data_type, "Deep2");
4010 assert_eq!(ex1.aggregated_group, Some("1".to_string()));
4011
4012 let ex2 = &exchanges[1];
4014 assert_eq!(ex2.exchange_name, "NYSE");
4015 assert_eq!(ex2.security_type, "STK");
4016 assert_eq!(ex2.service_data_type, "Deep");
4017 assert_eq!(ex2.aggregated_group, Some("2".to_string()));
4018
4019 let ex3 = &exchanges[2];
4021 assert_eq!(ex3.exchange_name, "ARCA");
4022 assert_eq!(ex3.aggregated_group, Some("2".to_string()));
4023
4024 let requests = gateway.requests();
4025 assert_eq!(requests[0], "82\0", "Request should be RequestMktDepthExchanges");
4026 }
4027
4028 #[tokio::test]
4029 async fn test_switch_market_data_type() {
4030 use crate::client::common::tests::setup_switch_market_data_type;
4031 use crate::market_data::MarketDataType;
4032
4033 let gateway = setup_switch_market_data_type();
4034 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4035
4036 client
4038 .switch_market_data_type(MarketDataType::Delayed)
4039 .await
4040 .expect("Failed to switch market data type");
4041
4042 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
4044
4045 let requests = gateway.requests();
4046 assert_eq!(requests.len(), 1, "Should have sent 1 request");
4047 assert_eq!(requests[0], "59\01\03\0", "Request should be RequestMarketDataType with Delayed(3)");
4049 }
4050
4051 #[tokio::test]
4054 async fn test_head_timestamp() {
4055 use crate::client::common::tests::setup_head_timestamp;
4056 use crate::contracts::Contract;
4057 use crate::market_data::historical::WhatToShow;
4058
4059 let gateway = setup_head_timestamp();
4060 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4061
4062 let contract = Contract::stock("AAPL").build();
4063 let what_to_show = WhatToShow::Trades;
4064 let trading_hours = TradingHours::Regular;
4065
4066 let timestamp = client
4067 .head_timestamp(&contract, what_to_show, trading_hours)
4068 .await
4069 .expect("Failed to get head timestamp");
4070
4071 assert_eq!(timestamp.year(), 2024);
4073 assert_eq!(timestamp.month() as u8, 1);
4074 assert_eq!(timestamp.day(), 15);
4075 assert_eq!(timestamp.hour(), 9);
4076 assert_eq!(timestamp.minute(), 30);
4077
4078 let requests = gateway.requests();
4079 assert!(requests[0].starts_with("87\0"), "Request should be RequestHeadTimestamp");
4080 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4081 assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4082 }
4083
4084 #[tokio::test]
4085 async fn test_historical_data() {
4086 use crate::client::common::tests::setup_historical_data;
4087 use crate::contracts::Contract;
4088 use crate::market_data::historical::{BarSize, Duration, WhatToShow};
4089 use time::macros::datetime;
4090
4091 let gateway = setup_historical_data();
4092 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4093
4094 let contract = Contract::stock("AAPL").build();
4095 let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4096 let duration = Duration::days(1);
4097 let bar_size = BarSize::Min5;
4098 let what_to_show = WhatToShow::Trades;
4099 let trading_hours = TradingHours::Regular;
4100
4101 let historical_data = client
4102 .historical_data(&contract, Some(end_date_time), duration, bar_size, Some(what_to_show), trading_hours)
4103 .await
4104 .expect("Failed to get historical data");
4105
4106 let bars = &historical_data.bars;
4108
4109 assert_eq!(bars.len(), 3, "Should receive 3 bars");
4110
4111 assert_eq!(bars[0].open, 150.25);
4113 assert_eq!(bars[0].high, 150.75);
4114 assert_eq!(bars[0].low, 150.00);
4115 assert_eq!(bars[0].close, 150.50);
4116 assert_eq!(bars[0].volume, 1000.0);
4117 assert_eq!(bars[0].wap, 150.40);
4118 assert_eq!(bars[0].count, 25);
4119
4120 assert_eq!(bars[1].open, 150.50);
4122 assert_eq!(bars[1].high, 151.00);
4123 assert_eq!(bars[1].low, 150.40);
4124 assert_eq!(bars[1].close, 150.90);
4125 assert_eq!(bars[1].volume, 1200.0);
4126
4127 assert_eq!(bars[2].open, 150.90);
4129 assert_eq!(bars[2].high, 151.25);
4130 assert_eq!(bars[2].low, 150.85);
4131 assert_eq!(bars[2].close, 151.20);
4132
4133 let requests = gateway.requests();
4134 assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4135 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4136 }
4137
4138 #[tokio::test]
4139 async fn test_historical_schedule() {
4140 use crate::client::common::tests::setup_historical_schedules;
4141 use crate::contracts::Contract;
4142 use crate::market_data::historical::Duration;
4143 use time::macros::datetime;
4144
4145 let gateway = setup_historical_schedules();
4146 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4147
4148 let contract = Contract::stock("AAPL").build();
4149 let end_date_time = datetime!(2024-01-22 16:00:00).assume_utc();
4150 let duration = Duration::days(1);
4151
4152 let schedule = client
4153 .historical_schedule(&contract, Some(end_date_time), duration)
4154 .await
4155 .expect("Failed to get historical schedule");
4156
4157 assert_eq!(schedule.time_zone, "US/Eastern");
4159 assert!(!schedule.sessions.is_empty(), "Should have at least one session");
4160
4161 let requests = gateway.requests();
4162 assert!(requests[0].starts_with("20\0"), "Request should be RequestHistoricalData");
4163 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4164 assert!(requests[0].contains("2\0"), "Request should contain formatDate=2 for schedule");
4165 }
4166
4167 #[tokio::test]
4168 async fn test_historical_ticks_bid_ask() {
4169 use crate::client::common::tests::setup_historical_ticks_bid_ask;
4170 use crate::contracts::Contract;
4171 use time::macros::datetime;
4172
4173 let gateway = setup_historical_ticks_bid_ask();
4174 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4175
4176 let contract = Contract::stock("AAPL").build();
4177 let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4178 let number_of_ticks = 100;
4179 let trading_hours = TradingHours::Regular;
4180
4181 let mut tick_subscription = client
4182 .historical_ticks_bid_ask(&contract, Some(start_date_time), None, number_of_ticks, trading_hours, false)
4183 .await
4184 .expect("Failed to get historical ticks bid/ask");
4185
4186 let mut ticks = Vec::new();
4188 while let Some(tick) = tick_subscription.next().await {
4189 ticks.push(tick);
4190 }
4191
4192 assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4193
4194 assert_eq!(ticks[0].price_bid, 150.25);
4196 assert_eq!(ticks[0].price_ask, 150.50);
4197 assert_eq!(ticks[0].size_bid, 100);
4198 assert_eq!(ticks[0].size_ask, 200);
4199
4200 assert_eq!(ticks[1].price_bid, 150.30);
4202 assert_eq!(ticks[1].price_ask, 150.55);
4203 assert_eq!(ticks[1].size_bid, 150);
4204 assert_eq!(ticks[1].size_ask, 250);
4205
4206 assert_eq!(ticks[2].price_bid, 150.35);
4208 assert_eq!(ticks[2].price_ask, 150.60);
4209
4210 let requests = gateway.requests();
4211 assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4212 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4213 assert!(requests[0].contains("BID_ASK\0"), "Request should contain BID_ASK");
4214 }
4215
4216 #[tokio::test]
4217 async fn test_historical_ticks_mid_point() {
4218 use crate::client::common::tests::setup_historical_ticks_mid_point;
4219 use crate::contracts::Contract;
4220 use time::macros::datetime;
4221
4222 let gateway = setup_historical_ticks_mid_point();
4223 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4224
4225 let contract = Contract::stock("AAPL").build();
4226 let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4227 let number_of_ticks = 100;
4228 let trading_hours = TradingHours::Regular;
4229
4230 let mut tick_subscription = client
4231 .historical_ticks_mid_point(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4232 .await
4233 .expect("Failed to get historical ticks midpoint");
4234
4235 let mut ticks = Vec::new();
4237 while let Some(tick) = tick_subscription.next().await {
4238 ticks.push(tick);
4239 }
4240
4241 assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4242
4243 assert_eq!(ticks[0].price, 150.375);
4245 assert_eq!(ticks[0].size, 0);
4246 assert_eq!(ticks[1].price, 150.425);
4247 assert_eq!(ticks[1].size, 0);
4248 assert_eq!(ticks[2].price, 150.475);
4249 assert_eq!(ticks[2].size, 0);
4250
4251 let requests = gateway.requests();
4252 assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4253 assert!(requests[0].contains("MIDPOINT\0"), "Request should contain MIDPOINT");
4254 }
4255
4256 #[tokio::test]
4257 async fn test_historical_ticks_trade() {
4258 use crate::client::common::tests::setup_historical_ticks_trade;
4259 use crate::contracts::Contract;
4260 use time::macros::datetime;
4261
4262 let gateway = setup_historical_ticks_trade();
4263 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4264
4265 let contract = Contract::stock("AAPL").build();
4266 let start_date_time = datetime!(2024-01-22 09:30:00).assume_utc();
4267 let number_of_ticks = 100;
4268 let trading_hours = TradingHours::Regular;
4269
4270 let mut tick_subscription = client
4271 .historical_ticks_trade(&contract, Some(start_date_time), None, number_of_ticks, trading_hours)
4272 .await
4273 .expect("Failed to get historical ticks trade");
4274
4275 let mut ticks = Vec::new();
4277 while let Some(tick) = tick_subscription.next().await {
4278 ticks.push(tick);
4279 }
4280
4281 assert_eq!(ticks.len(), 3, "Should receive 3 ticks");
4282
4283 assert_eq!(ticks[0].price, 150.50);
4285 assert_eq!(ticks[0].size, 100);
4286 assert_eq!(ticks[0].exchange, "NASDAQ");
4287 assert_eq!(ticks[0].special_conditions, "T");
4288
4289 assert_eq!(ticks[1].price, 150.55);
4290 assert_eq!(ticks[1].size, 200);
4291 assert_eq!(ticks[1].exchange, "NYSE");
4292
4293 assert_eq!(ticks[2].price, 150.60);
4294 assert_eq!(ticks[2].size, 150);
4295
4296 let requests = gateway.requests();
4297 assert!(requests[0].starts_with("96\0"), "Request should be RequestHistoricalTicks");
4298 assert!(requests[0].contains("TRADES\0"), "Request should contain TRADES");
4299 }
4300
4301 #[tokio::test]
4302 async fn test_histogram_data() {
4303 use crate::client::common::tests::setup_histogram_data;
4304 use crate::contracts::Contract;
4305 use crate::market_data::historical::BarSize;
4306
4307 let gateway = setup_histogram_data();
4308 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4309
4310 let contract = Contract::stock("AAPL").build();
4311 let trading_hours = TradingHours::Regular;
4312 let period = BarSize::Day;
4313
4314 let entries = client
4315 .histogram_data(&contract, trading_hours, period)
4316 .await
4317 .expect("Failed to get histogram data");
4318
4319 assert_eq!(entries.len(), 3, "Should receive 3 entries");
4320
4321 assert_eq!(entries[0].price, 150.00);
4323 assert_eq!(entries[0].size, 1000);
4324
4325 assert_eq!(entries[1].price, 150.50);
4326 assert_eq!(entries[1].size, 1500);
4327
4328 assert_eq!(entries[2].price, 151.00);
4329 assert_eq!(entries[2].size, 800);
4330
4331 let requests = gateway.requests();
4332 assert!(requests[0].starts_with("88\0"), "Request should be RequestHistogramData");
4333 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4334 }
4335
4336 #[tokio::test]
4339 async fn test_news_providers() {
4340 use crate::client::common::tests::setup_news_providers;
4341
4342 let gateway = setup_news_providers();
4343 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4344
4345 let providers = client.news_providers().await.expect("Failed to get news providers");
4347
4348 assert_eq!(providers.len(), 3, "Should receive 3 news providers");
4350
4351 assert_eq!(providers[0].code, "BRFG");
4353 assert_eq!(providers[0].name, "Briefing.com General Market Columns");
4354
4355 assert_eq!(providers[1].code, "BRFUPDN");
4356 assert_eq!(providers[1].name, "Briefing.com Analyst Actions");
4357
4358 assert_eq!(providers[2].code, "DJ-RT");
4359 assert_eq!(providers[2].name, "Dow Jones Real-Time News");
4360
4361 let requests = gateway.requests();
4363 assert_eq!(requests.len(), 1, "Should have sent 1 request");
4364 assert_eq!(requests[0], "85\0", "Request should be RequestNewsProviders");
4365 }
4366
4367 #[tokio::test]
4368 async fn test_news_bulletins() {
4369 use crate::client::common::tests::setup_news_bulletins;
4370
4371 let gateway = setup_news_bulletins();
4372 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4373
4374 let mut subscription = client.news_bulletins(true).await.expect("Failed to get news bulletins");
4376
4377 let mut bulletins = Vec::new();
4379 while let Some(result) = subscription.next().await {
4380 match result {
4381 Ok(b) => bulletins.push(b),
4382 Err(_) => break,
4383 }
4384 if bulletins.len() >= 2 {
4385 break; }
4387 }
4388
4389 assert_eq!(bulletins.len(), 2, "Should receive 2 news bulletins");
4391
4392 assert_eq!(bulletins[0].message_id, 123);
4394 assert_eq!(bulletins[0].message_type, 1);
4395 assert_eq!(bulletins[0].message, "Important market announcement");
4396 assert_eq!(bulletins[0].exchange, "NYSE");
4397
4398 assert_eq!(bulletins[1].message_id, 124);
4399 assert_eq!(bulletins[1].message_type, 2);
4400 assert_eq!(bulletins[1].message, "Trading halt on symbol XYZ");
4401 assert_eq!(bulletins[1].exchange, "NASDAQ");
4402
4403 let requests = gateway.requests();
4405 assert!(
4406 requests[0].starts_with("12\01\01\0"),
4407 "Request should be RequestNewsBulletins with version 1 and all_messages=true"
4408 );
4409 }
4410
4411 #[tokio::test]
4412 async fn test_historical_news() {
4413 use crate::client::common::tests::setup_historical_news;
4414 use time::macros::datetime;
4415
4416 let gateway = setup_historical_news();
4417 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4418
4419 let start_time = datetime!(2024-01-15 14:00:00 UTC);
4421 let end_time = datetime!(2024-01-15 15:00:00 UTC);
4422 let mut subscription = client
4423 .historical_news(
4424 1234, &["DJ-RT", "BRFG"], start_time,
4427 end_time,
4428 10, )
4430 .await
4431 .expect("Failed to get historical news");
4432
4433 let mut articles = Vec::new();
4435 while let Some(result) = subscription.next().await {
4436 match result {
4437 Ok(a) => articles.push(a),
4438 Err(_) => break,
4439 }
4440 if articles.len() >= 2 {
4441 break; }
4443 }
4444
4445 assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4447
4448 assert_eq!(articles[0].provider_code, "DJ-RT");
4450 assert_eq!(articles[0].article_id, "DJ001234");
4451 assert_eq!(articles[0].headline, "Market hits new highs amid positive earnings");
4452
4453 assert_eq!(articles[1].provider_code, "BRFG");
4454 assert_eq!(articles[1].article_id, "BRF5678");
4455 assert_eq!(articles[1].headline, "Federal Reserve announces policy decision");
4456
4457 let requests = gateway.requests();
4459 assert!(requests[0].starts_with("86\0"), "Request should be RequestHistoricalNews");
4460 assert!(requests[0].contains("1234\0"), "Request should contain contract_id 1234");
4461 assert!(requests[0].contains("DJ-RT+BRFG\0"), "Request should contain provider codes");
4462 }
4463
4464 #[tokio::test]
4465 async fn test_news_article() {
4466 use crate::client::common::tests::setup_news_article;
4467
4468 let gateway = setup_news_article();
4469 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4470
4471 let article = client
4473 .news_article(
4474 "DJ-RT", "DJ001234", )
4477 .await
4478 .expect("Failed to get news article");
4479
4480 assert_eq!(article.article_type, crate::news::ArticleType::Text);
4482 assert_eq!(
4483 article.article_text,
4484 "This is the full text of the news article. It contains detailed information about the market event described in the headline."
4485 );
4486
4487 let requests = gateway.requests();
4489 assert!(requests[0].starts_with("84\0"), "Request should be RequestNewsArticle");
4490 assert!(requests[0].contains("DJ-RT\0"), "Request should contain provider code");
4491 assert!(requests[0].contains("DJ001234\0"), "Request should contain article ID");
4492 }
4493
4494 #[tokio::test]
4495 async fn test_scanner_parameters() {
4496 use crate::client::common::tests::setup_scanner_parameters;
4497
4498 let gateway = setup_scanner_parameters();
4499 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4500
4501 let xml = client.scanner_parameters().await.expect("Failed to get scanner parameters");
4503
4504 assert!(xml.contains("<ScanParameterResponse>"), "Should contain ScanParameterResponse");
4506 assert!(xml.contains("<Instrument>STK</Instrument>"), "Should contain STK instrument");
4507 assert!(xml.contains("<Instrument>OPT</Instrument>"), "Should contain OPT instrument");
4508 assert!(xml.contains("<Location>US</Location>"), "Should contain US location");
4509 assert!(
4510 xml.contains("<ScanType>TOP_PERC_GAIN</ScanType>"),
4511 "Should contain TOP_PERC_GAIN scan type"
4512 );
4513
4514 let requests = gateway.requests();
4516 assert_eq!(requests.len(), 1, "Should have sent 1 request");
4517 assert_eq!(requests[0], "24\01\0", "Request should be RequestScannerParameters with version 1");
4518 }
4519
4520 #[tokio::test]
4521 async fn test_scanner_subscription() {
4522 use crate::client::common::tests::setup_scanner_subscription;
4523 use crate::scanner::ScannerSubscription;
4524
4525 let gateway = setup_scanner_subscription();
4526 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4527
4528 let scanner_subscription = ScannerSubscription {
4530 instrument: Some("STK".to_string()),
4531 location_code: Some("STK.US.MAJOR".to_string()),
4532 scan_code: Some("TOP_PERC_GAIN".to_string()),
4533 number_of_rows: 10,
4534 ..Default::default()
4535 };
4536
4537 let mut subscription = client
4539 .scanner_subscription(&scanner_subscription, &vec![])
4540 .await
4541 .expect("Failed to get scanner subscription");
4542
4543 let mut scan_data_vecs = Vec::new();
4545 while let Some(result) = subscription.next().await {
4546 match result {
4547 Ok(d) => scan_data_vecs.push(d),
4548 Err(_) => break,
4549 }
4550 if !scan_data_vecs.is_empty() {
4551 break; }
4553 }
4554
4555 assert_eq!(scan_data_vecs.len(), 1, "Should receive 1 batch of scan data");
4556 let scan_data = &scan_data_vecs[0];
4557
4558 assert_eq!(scan_data.len(), 2, "Should receive 2 scan data items");
4560
4561 assert_eq!(scan_data[0].rank, 1);
4563 assert_eq!(scan_data[0].contract_details.contract.contract_id, 1234);
4564 assert_eq!(scan_data[0].contract_details.contract.symbol, Symbol::from("AAPL"));
4565
4566 assert_eq!(scan_data[1].rank, 2);
4567 assert_eq!(scan_data[1].contract_details.contract.contract_id, 5678);
4568 assert_eq!(scan_data[1].contract_details.contract.symbol, Symbol::from("GOOGL"));
4569
4570 let requests = gateway.requests();
4572 assert!(requests[0].starts_with("22\0"), "Request should be RequestScannerSubscription");
4573 }
4574
4575 #[tokio::test]
4576 async fn test_wsh_metadata() {
4577 use crate::client::common::tests::setup_wsh_metadata;
4578
4579 let gateway = setup_wsh_metadata();
4580 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4581
4582 let metadata = client.wsh_metadata().await.expect("Failed to get WSH metadata");
4584
4585 assert_eq!(metadata.data_json, "{\"dataJson\":\"sample_metadata\"}");
4587
4588 let requests = gateway.requests();
4590 assert!(requests[0].starts_with("100\0"), "Request should be RequestWshMetaData");
4591 }
4592
4593 #[tokio::test]
4594 async fn test_wsh_event_data() {
4595 use crate::client::common::tests::setup_wsh_event_data;
4596
4597 let gateway = setup_wsh_event_data();
4598 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4599
4600 let event_data = client
4602 .wsh_event_data_by_contract(1234, None, None, None, None)
4603 .await
4604 .expect("Failed to get WSH event data");
4605
4606 assert_eq!(event_data.data_json, "{\"dataJson\":\"event1\"}");
4608
4609 let requests = gateway.requests();
4611 assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4612 }
4613
4614 #[tokio::test]
4615 async fn test_contract_news() {
4616 use crate::client::common::tests::setup_contract_news;
4617 use crate::contracts::Contract;
4618
4619 let gateway = setup_contract_news();
4620 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4621
4622 let contract = Contract::stock("AAPL").build();
4624 let provider_codes = &["DJ-RT", "BRFG"];
4625
4626 let mut subscription = client
4628 .contract_news(&contract, provider_codes)
4629 .await
4630 .expect("Failed to get contract news");
4631
4632 let mut articles = Vec::new();
4634 while let Some(result) = subscription.next().await {
4635 match result {
4636 Ok(a) => articles.push(a),
4637 Err(_) => break,
4638 }
4639 if articles.len() >= 2 {
4640 break; }
4642 }
4643
4644 assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4646
4647 assert_eq!(articles[0].provider_code, "DJ-RT");
4649 assert_eq!(articles[0].article_id, "DJ001234");
4650 assert_eq!(articles[0].headline, "Stock rises on earnings beat");
4651 assert_eq!(articles[0].extra_data, "extraData1");
4652
4653 assert_eq!(articles[1].provider_code, "BRFG");
4654 assert_eq!(articles[1].article_id, "BRF5678");
4655 assert_eq!(articles[1].headline, "Company announces expansion");
4656 assert_eq!(articles[1].extra_data, "extraData2");
4657
4658 let requests = gateway.requests();
4660 assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4661 assert!(requests[0].contains("AAPL\0STK\0"), "Request should contain AAPL stock");
4662 assert!(
4663 requests[0].contains("mdoff,292:DJ-RT,292:BRFG\0"),
4664 "Request should contain news generic ticks"
4665 );
4666 }
4667
4668 #[tokio::test]
4669 async fn test_broad_tape_news() {
4670 use crate::client::common::tests::setup_broad_tape_news;
4671
4672 let gateway = setup_broad_tape_news();
4673 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4674
4675 let mut subscription = client.broad_tape_news("BRFG").await.expect("Failed to get broad tape news");
4677
4678 let mut articles = Vec::new();
4680 while let Some(result) = subscription.next().await {
4681 match result {
4682 Ok(a) => articles.push(a),
4683 Err(_) => break,
4684 }
4685 if articles.len() >= 2 {
4686 break; }
4688 }
4689
4690 assert_eq!(articles.len(), 2, "Should receive 2 news articles");
4692
4693 assert_eq!(articles[0].provider_code, "BRFG");
4695 assert_eq!(articles[0].article_id, "BRF001");
4696 assert_eq!(articles[0].headline, "Market update: Tech sector rallies");
4697 assert_eq!(articles[0].extra_data, "extraData1");
4698
4699 assert_eq!(articles[1].provider_code, "BRFG");
4700 assert_eq!(articles[1].article_id, "BRF002");
4701 assert_eq!(articles[1].headline, "Fed minutes released");
4702 assert_eq!(articles[1].extra_data, "extraData2");
4703
4704 let requests = gateway.requests();
4706 assert!(requests[0].starts_with("1\0"), "Request should be RequestMarketData");
4707
4708 if !requests[0].contains("BRFG") || !requests[0].contains("NEWS") {
4710 eprintln!("Actual request: {:?}", requests[0]);
4711 }
4712
4713 assert!(requests[0].contains("BRFG:BRFG_ALL"), "Request should contain BRFG:BRFG_ALL symbol");
4715 assert!(requests[0].contains("NEWS"), "Request should contain NEWS security type");
4716 assert!(requests[0].contains("mdoff,292\0"), "Request should contain news generic ticks");
4717 }
4718
4719 #[tokio::test]
4720 async fn test_wsh_event_data_by_filter() {
4721 use crate::client::common::tests::setup_wsh_event_data_by_filter;
4722
4723 let gateway = setup_wsh_event_data_by_filter();
4724 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4725
4726 let filter = "{\"watchlist\":[\"AAPL\"],\"country\":\"ALL\"}";
4728 let mut subscription = client
4729 .wsh_event_data_by_filter(filter, None, None)
4730 .await
4731 .expect("Failed to get WSH event data by filter");
4732
4733 let mut events = Vec::new();
4735 while let Some(result) = subscription.next().await {
4736 match result {
4737 Ok(e) => events.push(e),
4738 Err(_) => break,
4739 }
4740 if events.len() >= 2 {
4741 break; }
4743 }
4744
4745 assert_eq!(events.len(), 2, "Should receive 2 WSH events");
4747 assert_eq!(events[0].data_json, "{\"dataJson\":\"filtered_event1\"}");
4748 assert_eq!(events[1].data_json, "{\"dataJson\":\"filtered_event2\"}");
4749
4750 let requests = gateway.requests();
4752 assert!(requests[0].starts_with("102\0"), "Request should be RequestWshEventData");
4753 assert!(requests[0].contains(filter), "Request should contain the filter");
4754 }
4755
4756 #[tokio::test]
4757 async fn test_disconnect_completes() {
4758 let gateway = setup_connect();
4759 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4760
4761 tokio::time::timeout(std::time::Duration::from_secs(2), client.disconnect())
4762 .await
4763 .expect("disconnect did not complete in time");
4764
4765 assert!(!client.is_connected());
4766 }
4767
4768 #[tokio::test]
4769 async fn test_disconnect_is_idempotent() {
4770 let gateway = setup_connect();
4771 let client = Client::connect(&gateway.address(), CLIENT_ID).await.expect("Failed to connect");
4772
4773 tokio::time::timeout(std::time::Duration::from_secs(2), async {
4774 client.disconnect().await;
4775 client.disconnect().await;
4776 })
4777 .await
4778 .expect("repeated disconnect did not complete in time");
4779 }
4780}