Skip to main content

rustrade_data/exchange/ibkr/
subscription.rs

1//! IBKR subscription types.
2
3use rustrade_instrument::instrument::name::InstrumentNameExchange;
4use serde::{Deserialize, Serialize};
5
6/// Subscription configuration for IBKR market data.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct IbkrSubscription<K> {
9    /// Instrument key for MarketEvent output
10    pub key: K,
11    /// Barter instrument name (must be registered in ContractRegistry)
12    pub instrument: InstrumentNameExchange,
13    /// Type of market data to subscribe to
14    pub kind: IbkrSubscriptionKind,
15}
16
17impl<K> IbkrSubscription<K> {
18    /// Create a quotes subscription.
19    pub fn quotes(key: K, instrument: InstrumentNameExchange) -> Self {
20        Self {
21            key,
22            instrument,
23            kind: IbkrSubscriptionKind::Quotes,
24        }
25    }
26
27    /// Create a depth subscription.
28    ///
29    /// # Arguments
30    ///
31    /// * `rows` - Number of order book rows. Valid range: 1-20 (IB API limit).
32    ///   Common values: 5 (shallow), 10 (medium), 20 (deep).
33    ///
34    /// # Panics
35    ///
36    /// Panics if `rows` is outside the valid range 1-20.
37    ///
38    /// # Limitations
39    ///
40    /// IB allows max 3 concurrent depth subscriptions. Exceeding this limit
41    /// causes error 309.
42    pub fn depth(key: K, instrument: InstrumentNameExchange, rows: i32) -> Self {
43        assert!(
44            (1..=20).contains(&rows),
45            "IB depth rows must be 1-20, got {rows}"
46        );
47        Self {
48            key,
49            instrument,
50            kind: IbkrSubscriptionKind::Depth { rows },
51        }
52    }
53
54    /// Create a trades subscription.
55    pub fn trades(key: K, instrument: InstrumentNameExchange) -> Self {
56        Self {
57            key,
58            instrument,
59            kind: IbkrSubscriptionKind::Trades,
60        }
61    }
62
63    /// Create an option Greeks subscription.
64    ///
65    /// Use for option contracts to receive real-time Greeks (delta, gamma, theta,
66    /// vega, implied volatility) computed from live market prices.
67    ///
68    /// # Subscription Requirements
69    ///
70    /// Requires OPRA subscription (paid) for US options.
71    pub fn option_greeks(key: K, instrument: InstrumentNameExchange) -> Self {
72        Self {
73            key,
74            instrument,
75            kind: IbkrSubscriptionKind::OptionGreeks,
76        }
77    }
78}
79
80/// Type of IBKR market data subscription.
81#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
82pub enum IbkrSubscriptionKind {
83    /// Best bid/ask quotes via market_data()
84    Quotes,
85    /// L2 order book via market_depth()
86    Depth {
87        /// Number of rows in the order book
88        rows: i32,
89    },
90    /// Tick-by-tick trades via tick_by_tick()
91    Trades,
92    /// Real-time option Greeks via market_data() with OptionComputation ticks.
93    ///
94    /// Use this for option contracts to receive live Greeks (delta, gamma, theta,
95    /// vega, IV) computed from market prices. Requires OPRA subscription for US
96    /// options.
97    OptionGreeks,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use rustrade_instrument::instrument::name::InstrumentNameExchange;
104
105    #[test]
106    #[should_panic(expected = "IB depth rows must be 1-20")]
107    fn depth_panics_on_zero_rows() {
108        let instrument = InstrumentNameExchange::new("AAPL");
109        IbkrSubscription::<()>::depth((), instrument, 0);
110    }
111
112    #[test]
113    #[should_panic(expected = "IB depth rows must be 1-20")]
114    fn depth_panics_on_rows_over_20() {
115        let instrument = InstrumentNameExchange::new("AAPL");
116        IbkrSubscription::<()>::depth((), instrument, 21);
117    }
118}