diff --git a/src/client/async.rs b/src/client/async.rs index 486d0764..a498cb4a 100644 --- a/src/client/async.rs +++ b/src/client/async.rs @@ -614,6 +614,40 @@ impl Client { display_groups::r#async::subscribe_to_group_events(self, group_id).await } + /// Updates the contract displayed in a TWS display group. + /// + /// This function changes the contract shown in the specified display group within TWS. + /// You must first subscribe to the group using [`subscribe_to_group_events`](Self::subscribe_to_group_events) + /// before calling this function. + /// + /// # Arguments + /// * `request_id` - The request ID from the subscription (use `subscription.request_id()`) + /// * `contract_info` - Contract to display: + /// - `"contractID@exchange"` for individual contracts (e.g., "265598@SMART") + /// - `"none"` for empty selection + /// - `"combo"` for combination contracts + /// + /// # Examples + /// + /// ```no_run + /// use ibapi::Client; + /// + /// #[tokio::main] + /// async fn main() { + /// let client = Client::connect("127.0.0.1:7497", 100).await.expect("connection failed"); + /// + /// // First subscribe to the display group + /// let subscription = client.subscribe_to_group_events(1).await.expect("subscription failed"); + /// let request_id = subscription.request_id().expect("no request ID"); + /// + /// // Update the display group to show AAPL + /// client.update_display_group(request_id, "265598@SMART").await.expect("update failed"); + /// } + /// ``` + pub async fn update_display_group(&self, request_id: i32, contract_info: &str) -> Result<(), Error> { + display_groups::r#async::update_display_group(self, request_id, contract_info).await + } + // === Market Data === /// Creates a market data subscription builder with a fluent interface. diff --git a/src/client/sync.rs b/src/client/sync.rs index 3fabfba3..b9cb7f44 100644 --- a/src/client/sync.rs +++ b/src/client/sync.rs @@ -520,6 +520,37 @@ impl Client { display_groups::sync::subscribe_to_group_events(self, group_id) } + /// Updates the contract displayed in a TWS display group. + /// + /// This function changes the contract shown in the specified display group within TWS. + /// You must first subscribe to the group using [`subscribe_to_group_events`](Self::subscribe_to_group_events) + /// before calling this function. + /// + /// # Arguments + /// * `request_id` - The request ID from the subscription (use `subscription.request_id()`) + /// * `contract_info` - Contract to display: + /// - `"contractID@exchange"` for individual contracts (e.g., "265598@SMART") + /// - `"none"` for empty selection + /// - `"combo"` for combination contracts + /// + /// # Examples + /// + /// ```no_run + /// use ibapi::client::blocking::Client; + /// + /// let client = Client::connect("127.0.0.1:7497", 100).expect("connection failed"); + /// + /// // First subscribe to the display group + /// let subscription = client.subscribe_to_group_events(1).expect("subscription failed"); + /// let request_id = subscription.request_id().expect("no request ID"); + /// + /// // Update the display group to show AAPL + /// client.update_display_group(request_id, "265598@SMART").expect("update failed"); + /// ``` + pub fn update_display_group(&self, request_id: i32, contract_info: &str) -> Result<(), Error> { + display_groups::sync::update_display_group(self, request_id, contract_info) + } + // === Contracts === /// Requests contract information. diff --git a/src/display_groups/async.rs b/src/display_groups/async.rs index 93d0c49e..f6ebd9e6 100644 --- a/src/display_groups/async.rs +++ b/src/display_groups/async.rs @@ -22,6 +22,25 @@ pub async fn subscribe_to_group_events(client: &Client, group_id: i32) -> Result builder.send::(request).await } +/// Updates the contract displayed in a TWS display group. +/// +/// This function changes the contract shown in the specified display group within TWS. +/// You must first subscribe to the group using [`subscribe_to_group_events`] before +/// calling this function. The update will trigger a `DisplayGroupUpdated` callback +/// on the existing subscription. +/// +/// # Arguments +/// * `client` - The connected client +/// * `request_id` - The request ID from the subscription (use `subscription.request_id()`) +/// * `contract_info` - Contract to display: +/// - `"contractID@exchange"` for individual contracts (e.g., "265598@SMART") +/// - `"none"` for empty selection +/// - `"combo"` for combination contracts +pub async fn update_display_group(client: &Client, request_id: i32, contract_info: &str) -> Result<(), Error> { + let request = encoders::encode_update_display_group(request_id, contract_info)?; + client.send_message(request).await +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/display_groups/common/encoders.rs b/src/display_groups/common/encoders.rs index 5a39c9ed..bb098ac5 100644 --- a/src/display_groups/common/encoders.rs +++ b/src/display_groups/common/encoders.rs @@ -24,6 +24,21 @@ pub(crate) fn encode_unsubscribe_from_group_events(request_id: i32) -> Result Result { + let mut message = RequestMessage::new(); + message.push_field(&OutgoingMessages::UpdateDisplayGroup); + message.push_field(&VERSION); + message.push_field(&request_id); + message.push_field(&contract_info); + Ok(message) +} + #[cfg(test)] mod tests { use super::*; @@ -52,4 +67,28 @@ mod tests { assert_eq!(message[1], "1"); // version assert_eq!(message[2], request_id.to_field()); } + + #[test] + fn test_encode_update_display_group() { + let request_id = 9000; + let contract_info = "265598@SMART"; + + let message = encode_update_display_group(request_id, contract_info).expect("encoding failed"); + + assert_eq!(message[0], OutgoingMessages::UpdateDisplayGroup.to_field()); + assert_eq!(message[1], "1"); // version + assert_eq!(message[2], request_id.to_field()); + assert_eq!(message[3], contract_info); + } + + #[test] + fn test_encode_update_display_group_none() { + let request_id = 9000; + let contract_info = "none"; + + let message = encode_update_display_group(request_id, contract_info).expect("encoding failed"); + + assert_eq!(message[0], OutgoingMessages::UpdateDisplayGroup.to_field()); + assert_eq!(message[3], "none"); + } } diff --git a/src/display_groups/sync.rs b/src/display_groups/sync.rs index a99fb352..80c38e1f 100644 --- a/src/display_groups/sync.rs +++ b/src/display_groups/sync.rs @@ -21,3 +21,22 @@ pub fn subscribe_to_group_events(client: &Client, group_id: i32) -> Result Result<(), Error> { + let request = encoders::encode_update_display_group(request_id, contract_info)?; + client.send_message(request) +} diff --git a/src/subscriptions/async.rs b/src/subscriptions/async.rs index d59a3fa2..f3a49125 100644 --- a/src/subscriptions/async.rs +++ b/src/subscriptions/async.rs @@ -227,6 +227,11 @@ impl Subscription { SubscriptionInner::PreDecoded { receiver } => receiver.recv().await, } } + + /// Get the request ID associated with this subscription + pub fn request_id(&self) -> Option { + self.request_id + } } impl Subscription { diff --git a/src/subscriptions/sync.rs b/src/subscriptions/sync.rs index a8a3c5b7..3476de31 100644 --- a/src/subscriptions/sync.rs +++ b/src/subscriptions/sync.rs @@ -95,6 +95,11 @@ impl> Subscription { } } + /// Returns the request ID associated with this subscription. + pub fn request_id(&self) -> Option { + self.request_id + } + /// Returns the next available value, blocking if necessary until a value becomes available. /// /// # Examples