ibapi 5.4.0

An unofficial Rust client for the Interactive Brokers TWS API: market data, orders, and account data, with async (Tokio) and blocking clients for TWS and IB Gateway.
Documentation
//! Asynchronous implementation of display groups functionality

use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use crate::client::ClientRequestBuilders;
use crate::common::request_helpers::{self, expect_proto};
use crate::subscriptions::Subscription;
use crate::transport::AsyncMessageBus;
use crate::{Client, Error};

use super::common::decoders;
use super::common::stream_decoders::DisplayGroupUpdate;
use super::encoders;

/// A subscription to display group events with the ability to update the displayed contract.
///
/// Created by [`Client::subscribe_to_group_events`](crate::Client::subscribe_to_group_events).
/// Derefs to `Subscription<DisplayGroupUpdate>` for `next()`, `cancel()`, etc.
///
/// The canonical pattern-match form works through `Deref` with no extra ceremony:
///
/// ```ignore
/// while let Some(item) = subscription.next().await { /* match on SubscriptionItem */ }
/// ```
///
/// # `filter_data` and the reborrow gotcha
///
/// `Subscription::filter_data` (from `SubscriptionItemStreamExt`) takes `self`,
/// and method resolution through `DerefMut` is not allowed to *move* the
/// dereferenced value. So `subscription.filter_data()` on a
/// `DisplayGroupSubscription` fails with `cannot move out of dereference`.
///
/// Reborrow first:
///
/// ```ignore
/// let inner = &mut *subscription;            // `&mut Subscription<_>`
/// while let Some(item) = inner.filter_data().next().await { /* ... */ }
/// ```
///
/// This is a Rust language quirk, not a subscription-shape issue. The same
/// reborrow applies to any Deref-wrapping subscription type.
#[must_use = "DisplayGroupSubscription must be polled (deref to Subscription, then .next().await) to receive updates; dropping it releases the subscription"]
pub struct DisplayGroupSubscription {
    inner: Subscription<DisplayGroupUpdate>,
    // The id the subscription was opened with; `update` must reuse it.
    request_id: i32,
    message_bus: Arc<dyn AsyncMessageBus>,
}

impl DisplayGroupSubscription {
    /// Updates the contract displayed in the TWS display group.
    ///
    /// # Arguments
    /// * `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::prelude::*;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let client = Client::connect("127.0.0.1:7497", 100).await.expect("connection failed");
    ///     let subscription = client.subscribe_to_group_events(1).await.expect("subscription failed");
    ///
    ///     subscription.update("265598@SMART").await.expect("update failed");
    /// }
    /// ```
    pub async fn update(&self, contract_info: &str) -> Result<(), Error> {
        let request = encoders::encode_update_display_group(self.request_id, contract_info)?;
        self.message_bus.send_message(request).await
    }
}

impl Deref for DisplayGroupSubscription {
    type Target = Subscription<DisplayGroupUpdate>;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl DerefMut for DisplayGroupSubscription {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl Client {
    /// Returns the ids of the display groups visible in TWS, most used first.
    ///
    /// Display Groups are a TWS-only feature (not available in IB Gateway).
    /// Pass an id to [`subscribe_to_group_events`](Self::subscribe_to_group_events).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::prelude::*;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), ibapi::Error> {
    ///     let client = Client::connect("127.0.0.1:7497", 100).await?;
    ///
    ///     let groups = client.display_groups().await?;
    ///     println!("visible display groups: {groups:?}");
    ///     Ok(())
    /// }
    /// ```
    pub async fn display_groups(&self) -> Result<Vec<i32>, Error> {
        request_helpers::one_shot_by_request_id(
            self,
            encoders::encode_query_display_groups,
            expect_proto(decoders::decode_display_group_list_proto),
        )
        .await
    }

    /// Subscribes to display group events for the specified group.
    ///
    /// Display Groups are a TWS-only feature (not available in IB Gateway).
    /// They allow organizing contracts into color-coded groups in the TWS UI.
    /// When subscribed, you receive updates whenever the user changes the contract
    /// displayed in that group within TWS.
    ///
    /// # Arguments
    /// * `group_id` - The ID of the group to subscribe to (1-9)
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ibapi::prelude::*;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let client = Client::connect("127.0.0.1:7497", 100).await.expect("connection failed");
    ///
    ///     let mut subscription = client.subscribe_to_group_events(1).await.expect("subscription failed");
    ///
    ///     // Update the displayed contract
    ///     subscription.update("265598@SMART").await.expect("update failed");
    ///
    ///     // Consume the subscription so display-group events surface.
    ///     while let Some(event) = subscription.next().await {
    ///         println!("group event: {event:?}");
    ///     }
    /// }
    /// ```
    pub async fn subscribe_to_group_events(&self, group_id: i32) -> Result<DisplayGroupSubscription, Error> {
        let builder = self.request();
        let request_id = builder.request_id();
        let request = encoders::encode_subscribe_to_group_events(request_id, group_id)?;
        let inner = builder.send::<DisplayGroupUpdate>(request).await?;
        Ok(DisplayGroupSubscription {
            inner,
            request_id,
            message_bus: self.message_bus.clone(),
        })
    }
}

#[cfg(test)]
#[path = "async_tests.rs"]
mod tests;