From 2a6140c7e62797553e5b36c4993feb300dfc1855 Mon Sep 17 00:00:00 2001 From: t00ts Date: Sun, 1 Feb 2026 15:45:04 +0400 Subject: [PATCH 1/5] feat(orders): add `last_n_days` and `specific_dates` to `ExecutionFilter` --- src/client/async.rs | 1 + src/client/sync.rs | 1 + src/orders/common/encoders.rs | 8 ++++++++ src/orders/mod.rs | 4 ++++ src/orders/sync.rs | 1 + 5 files changed, 15 insertions(+) diff --git a/src/client/async.rs b/src/client/async.rs index 77ff380c..486d0764 100644 --- a/src/client/async.rs +++ b/src/client/async.rs @@ -3290,6 +3290,7 @@ mod tests { security_type: "".to_string(), // Empty means all types exchange: "".to_string(), // Empty means all exchanges side: "".to_string(), // Empty means all sides + ..Default::default() }; // Request executions diff --git a/src/client/sync.rs b/src/client/sync.rs index 239940a6..3fabfba3 100644 --- a/src/client/sync.rs +++ b/src/client/sync.rs @@ -3426,6 +3426,7 @@ mod tests { security_type: "".to_string(), // Empty means all types exchange: "".to_string(), // Empty means all exchanges side: "".to_string(), // Empty means all sides + ..Default::default() }; // Request executions diff --git a/src/orders/common/encoders.rs b/src/orders/common/encoders.rs index 3e712a8d..32ea21b0 100644 --- a/src/orders/common/encoders.rs +++ b/src/orders/common/encoders.rs @@ -488,6 +488,14 @@ pub(crate) fn encode_executions(server_version: i32, request_id: i32, filter: &E message.push_field(&filter.exchange); message.push_field(&filter.side); + if server_version >= server_versions::PARAMETRIZED_DAYS_OF_EXECUTIONS { + message.push_field(&filter.last_n_days); + message.push_field(&(filter.specific_dates.len() as i32)); + for date in &filter.specific_dates { + message.push_field(date); + } + } + Ok(message) } diff --git a/src/orders/mod.rs b/src/orders/mod.rs index 4234559d..168014f8 100644 --- a/src/orders/mod.rs +++ b/src/orders/mod.rs @@ -1446,6 +1446,10 @@ pub struct ExecutionFilter { pub exchange: String, /// The Contract's side (BUY or SELL) pub side: String, + /// Filter executions from the last N days (0 = no filter). + pub last_n_days: i32, + /// Filter executions for specific dates (format: yyyymmdd). + pub specific_dates: Vec, } /// Enumerates possible results from querying an [Execution]. diff --git a/src/orders/sync.rs b/src/orders/sync.rs index b42f0aa4..e970de70 100644 --- a/src/orders/sync.rs +++ b/src/orders/sync.rs @@ -954,6 +954,7 @@ mod tests { security_type: "STK".to_owned(), exchange: "ISLAND".to_owned(), side: "BUY".to_owned(), + ..Default::default() }; let results = client.executions(filter); From 7f21c4ed4e54103f54fbae92cb40cbaa6b14b52a Mon Sep 17 00:00:00 2001 From: t00ts Date: Sun, 1 Feb 2026 16:24:41 +0400 Subject: [PATCH 2/5] feat(connection): bump server `max_version` to `200` (PARAMETRIZED_DAYS_OF_EXECUTIONS) --- src/connection/common.rs | 2 +- src/transport/sync.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/connection/common.rs b/src/connection/common.rs index 3cc38385..77bc02cc 100644 --- a/src/connection/common.rs +++ b/src/connection/common.rs @@ -65,7 +65,7 @@ impl Default for ConnectionHandler { fn default() -> Self { Self { min_version: 100, - max_version: server_versions::WSH_EVENT_DATA_FILTERS_DATE, + max_version: server_versions::PARAMETRIZED_DAYS_OF_EXECUTIONS, } } } diff --git a/src/transport/sync.rs b/src/transport/sync.rs index d7185fdd..7a88594f 100644 --- a/src/transport/sync.rs +++ b/src/transport/sync.rs @@ -1047,7 +1047,7 @@ mod tests { let request = encode_place_order(176, 5, contract, &order)?; let events = vec![ - Exchange::simple("v100..173", &["173|20250415 19:38:30 British Summer Time|"]), + Exchange::simple("v100..200", &["173|20250415 19:38:30 British Summer Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|5|"]), Exchange::request(request.clone(), &[ @@ -1080,7 +1080,7 @@ mod tests { #[test] fn test_connection_establish_connection() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple( "71|2|28||", &[ @@ -1102,7 +1102,7 @@ mod tests { #[test] fn test_reconnect_failed() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART ]; let socket = MockSocket::new(events, MAX_RECONNECT_ATTEMPTS as usize + 1); @@ -1122,9 +1122,9 @@ mod tests { #[test] fn test_reconnect_success() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; let socket = MockSocket::new(events, MAX_RECONNECT_ATTEMPTS as usize - 1); @@ -1141,10 +1141,10 @@ mod tests { #[test] fn test_client_reconnect() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::simple("17|1|", &["\0"]), // ManagedAccounts RESTART - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::simple("17|1|", &["15|1|DU1234567|"]), // ManagedAccounts ]; @@ -1170,9 +1170,9 @@ mod tests { let expected_response = &format!("10|9000|{AAPL_CONTRACT_RESPONSE}"); let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &[expected_response, "52|1|9001|"]), ]; @@ -1204,10 +1204,10 @@ mod tests { let packet = encode_request_contract_data(173, 9000, &Contract::stock("AAPL").build())?; let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &["\0"]), // RESTART - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; @@ -1236,9 +1236,9 @@ mod tests { let packet = encode_request_contract_data(173, 9000, &Contract::stock("AAPL").build())?; let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::request(packet.clone(), &[]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; @@ -1268,10 +1268,10 @@ mod tests { let packet = encode_request_contract_data(173, 9000, contract)?; let events = vec![ - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &["\0"]), - Exchange::simple("v100..173", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; From 14ffb87f0f393ffb5e087cd96f35f2f3870493cc Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sun, 1 Feb 2026 12:16:46 -0800 Subject: [PATCH 3/5] Add tests for encode_executions version-gated date fields Tests verify: - Version < 200: date fields not encoded - Version >= 200: last_n_days and specific_dates encoded --- src/orders/common/encoders.rs | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/orders/common/encoders.rs b/src/orders/common/encoders.rs index 32ea21b0..aec918e5 100644 --- a/src/orders/common/encoders.rs +++ b/src/orders/common/encoders.rs @@ -825,4 +825,71 @@ pub(crate) mod tests { assert_eq!(field_vec[4], "0"); // is_more (false) assert_eq!(field_vec[5], "5.5"); // percent } + + #[test] + fn test_encode_executions_without_date_filter() { + let filter = ExecutionFilter { + client_id: Some(1), + account_code: "DU123456".to_string(), + time: "20260101 09:30:00".to_string(), + symbol: "AAPL".to_string(), + security_type: "STK".to_string(), + exchange: "SMART".to_string(), + side: "BUY".to_string(), + ..Default::default() + }; + + // Version below PARAMETRIZED_DAYS_OF_EXECUTIONS should not include date fields + let result = encode_executions(server_versions::WSH_EVENT_DATA_FILTERS_DATE, 9000, &filter).unwrap(); + let fields = result.encode(); + let field_vec: Vec<&str> = fields.split('\0').collect(); + + assert_eq!(field_vec[0], "7"); // RequestExecutions + assert_eq!(field_vec[1], "3"); // VERSION + assert_eq!(field_vec[2], "9000"); // request_id + assert_eq!(field_vec[3], "1"); // client_id + assert_eq!(field_vec[4], "DU123456"); // account_code + assert_eq!(field_vec[5], "20260101 09:30:00"); // time + assert_eq!(field_vec[6], "AAPL"); // symbol + assert_eq!(field_vec[7], "STK"); // security_type + assert_eq!(field_vec[8], "SMART"); // exchange + assert_eq!(field_vec[9], "BUY"); // side + assert_eq!(field_vec.len(), 11); // 10 fields + trailing empty + } + + #[test] + fn test_encode_executions_with_date_filter() { + let filter = ExecutionFilter { + client_id: Some(1), + account_code: "DU123456".to_string(), + time: "".to_string(), + symbol: "".to_string(), + security_type: "".to_string(), + exchange: "".to_string(), + side: "".to_string(), + last_n_days: 7, + specific_dates: vec!["20260125".to_string(), "20260126".to_string()], + }; + + // Version at PARAMETRIZED_DAYS_OF_EXECUTIONS should include date fields + let result = encode_executions(server_versions::PARAMETRIZED_DAYS_OF_EXECUTIONS, 9000, &filter).unwrap(); + let fields = result.encode(); + let field_vec: Vec<&str> = fields.split('\0').collect(); + + assert_eq!(field_vec[0], "7"); // RequestExecutions + assert_eq!(field_vec[1], "3"); // VERSION + assert_eq!(field_vec[2], "9000"); // request_id + assert_eq!(field_vec[3], "1"); // client_id + assert_eq!(field_vec[4], "DU123456"); // account_code + assert_eq!(field_vec[5], ""); // time + assert_eq!(field_vec[6], ""); // symbol + assert_eq!(field_vec[7], ""); // security_type + assert_eq!(field_vec[8], ""); // exchange + assert_eq!(field_vec[9], ""); // side + assert_eq!(field_vec[10], "7"); // last_n_days + assert_eq!(field_vec[11], "2"); // specific_dates count + assert_eq!(field_vec[12], "20260125"); // specific_dates[0] + assert_eq!(field_vec[13], "20260126"); // specific_dates[1] + assert_eq!(field_vec.len(), 15); // 14 fields + trailing empty + } } From 9d26b152fd8ffe4b757b0b0e994a8daed8552108 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sun, 1 Feb 2026 12:32:49 -0800 Subject: [PATCH 4/5] Update mock version responses to 200, improve doc comment - Mock server responses now return v200 to match advertised range - Add format spec and example to specific_dates doc --- src/orders/mod.rs | 2 +- src/transport/sync.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/orders/mod.rs b/src/orders/mod.rs index 168014f8..0ffa43cd 100644 --- a/src/orders/mod.rs +++ b/src/orders/mod.rs @@ -1448,7 +1448,7 @@ pub struct ExecutionFilter { pub side: String, /// Filter executions from the last N days (0 = no filter). pub last_n_days: i32, - /// Filter executions for specific dates (format: yyyymmdd). + /// Filter executions for specific dates (format: yyyymmdd, e.g., "20260130"). pub specific_dates: Vec, } diff --git a/src/transport/sync.rs b/src/transport/sync.rs index 7a88594f..428b28bd 100644 --- a/src/transport/sync.rs +++ b/src/transport/sync.rs @@ -1047,7 +1047,7 @@ mod tests { let request = encode_place_order(176, 5, contract, &order)?; let events = vec![ - Exchange::simple("v100..200", &["173|20250415 19:38:30 British Summer Time|"]), + Exchange::simple("v100..200", &["200|20250415 19:38:30 British Summer Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|5|"]), Exchange::request(request.clone(), &[ @@ -1080,7 +1080,7 @@ mod tests { #[test] fn test_connection_establish_connection() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple( "71|2|28||", &[ @@ -1102,7 +1102,7 @@ mod tests { #[test] fn test_reconnect_failed() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART ]; let socket = MockSocket::new(events, MAX_RECONNECT_ATTEMPTS as usize + 1); @@ -1122,9 +1122,9 @@ mod tests { #[test] fn test_reconnect_success() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; let socket = MockSocket::new(events, MAX_RECONNECT_ATTEMPTS as usize - 1); @@ -1141,10 +1141,10 @@ mod tests { #[test] fn test_client_reconnect() -> Result<(), Error> { let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::simple("17|1|", &["\0"]), // ManagedAccounts RESTART - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::simple("17|1|", &["15|1|DU1234567|"]), // ManagedAccounts ]; @@ -1170,9 +1170,9 @@ mod tests { let expected_response = &format!("10|9000|{AAPL_CONTRACT_RESPONSE}"); let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &[expected_response, "52|1|9001|"]), ]; @@ -1204,10 +1204,10 @@ mod tests { let packet = encode_request_contract_data(173, 9000, &Contract::stock("AAPL").build())?; let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &["\0"]), // RESTART - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; @@ -1236,9 +1236,9 @@ mod tests { let packet = encode_request_contract_data(173, 9000, &Contract::stock("AAPL").build())?; let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|", "\0"]), // RESTART - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::request(packet.clone(), &[]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; @@ -1268,10 +1268,10 @@ mod tests { let packet = encode_request_contract_data(173, 9000, contract)?; let events = vec![ - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), Exchange::request(packet.clone(), &["\0"]), - Exchange::simple("v100..200", &["173|20250323 22:21:01 Greenwich Mean Time|"]), + Exchange::simple("v100..200", &["200|20250323 22:21:01 Greenwich Mean Time|"]), Exchange::simple("71|2|28||", &["15|1|DU1234567|", "9|1|1|"]), ]; From a9c4047b217f02a2829b51475bde42b63d7217d6 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sun, 1 Feb 2026 13:36:25 -0800 Subject: [PATCH 5/5] Fix test helper to include issuer_id field for v200 Server version 200 >= BOND_ISSUERID (176), so encoder adds issuer_id. Test helper was missing this field, causing packet mismatch with mock. --- src/transport/sync.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/transport/sync.rs b/src/transport/sync.rs index 428b28bd..d0f26ec8 100644 --- a/src/transport/sync.rs +++ b/src/transport/sync.rs @@ -822,6 +822,9 @@ mod tests { packet.push_field(&contract.security_id_type); packet.push_field(&contract.security_id); + // Server version 200 includes issuer_id (>= 176) + packet.push_field(&contract.issuer_id); + Ok(packet) }