Skip to main content

nautilus_interactive_brokers/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration types for the Interactive Brokers adapter.
17
18use std::collections::{HashMap, HashSet};
19
20use nautilus_model::identifiers::InstrumentId;
21use serde::{Deserialize, Serialize};
22
23use crate::common::consts::{DEFAULT_CLIENT_ID, DEFAULT_HOST, DEFAULT_PORT};
24
25/// Market data type for switching between real-time and frozen/delayed.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[cfg_attr(
28    feature = "python",
29    pyo3::pyclass(
30        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
31        from_py_object
32    )
33)]
34#[derive(Default)]
35pub enum MarketDataType {
36    /// Live market data
37    #[default]
38    Realtime = 1,
39    /// Frozen market data (for when market is closed)
40    Frozen = 2,
41    /// Delayed market data (usually 15-20 minutes)
42    Delayed = 3,
43    /// Delayed frozen market data
44    DelayedFrozen = 4,
45}
46
47impl From<MarketDataType> for ibapi::market_data::MarketDataType {
48    fn from(data_type: MarketDataType) -> Self {
49        match data_type {
50            MarketDataType::Realtime => Self::Realtime,
51            MarketDataType::Frozen => Self::Frozen,
52            MarketDataType::Delayed => Self::Delayed,
53            MarketDataType::DelayedFrozen => Self::DelayedFrozen,
54        }
55    }
56}
57
58/// Configuration for Interactive Brokers data client.
59#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
60#[serde(default)]
61#[cfg_attr(
62    feature = "python",
63    pyo3::pyclass(
64        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
65        subclass,
66        from_py_object
67    )
68)]
69pub struct InteractiveBrokersDataClientConfig {
70    /// Host for IB Gateway/TWS.
71    #[builder(default = DEFAULT_HOST.to_string())]
72    pub host: String,
73    /// Port for IB Gateway/TWS.
74    #[builder(default = DEFAULT_PORT)]
75    pub port: u16,
76    /// Client ID.
77    #[builder(default = DEFAULT_CLIENT_ID)]
78    pub client_id: i32,
79    /// Whether to use regular trading hours only (RTH filtering).
80    #[builder(default = true)]
81    pub use_regular_trading_hours: bool,
82    /// Market data type (realtime, delayed, frozen).
83    #[builder(default)]
84    pub market_data_type: MarketDataType,
85    /// Whether to ignore quote tick size updates (filters size-only updates).
86    #[builder(default)]
87    pub ignore_quote_tick_size_updates: bool,
88    /// Connection timeout in seconds.
89    #[builder(default = 300)]
90    pub connection_timeout: u64,
91    /// Request timeout in seconds. Applied to IB API requests (open orders, executions, positions,
92    /// account summary, order update stream, next order id). See execution/core.rs and
93    /// execution/account.rs for call sites.
94    #[builder(default = 60)]
95    pub request_timeout: u64,
96    /// Whether to handle revised bars.
97    #[builder(default)]
98    pub handle_revised_bars: bool,
99    /// Whether to use batch quotes (reqMktData) by default instead of tick-by-tick.
100    #[builder(default = true)]
101    pub batch_quotes: bool,
102    /// Instrument provider configuration.
103    #[builder(default)]
104    pub instrument_provider: InteractiveBrokersInstrumentProviderConfig,
105}
106
107impl Default for InteractiveBrokersDataClientConfig {
108    fn default() -> Self {
109        Self::builder().build()
110    }
111}
112
113/// Configuration for Interactive Brokers execution client.
114#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
115#[serde(default)]
116#[cfg_attr(
117    feature = "python",
118    pyo3::pyclass(
119        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
120        subclass,
121        from_py_object
122    )
123)]
124pub struct InteractiveBrokersExecClientConfig {
125    /// Host for IB Gateway/TWS.
126    #[builder(default = DEFAULT_HOST.to_string())]
127    pub host: String,
128    /// Port for IB Gateway/TWS.
129    #[builder(default = DEFAULT_PORT)]
130    pub port: u16,
131    /// Client ID.
132    #[builder(default = DEFAULT_CLIENT_ID)]
133    pub client_id: i32,
134    /// Account ID.
135    pub account_id: Option<String>,
136    /// Connection timeout in seconds.
137    #[builder(default = 300)]
138    pub connection_timeout: u64,
139    /// Request timeout in seconds for IB API requests (open orders, executions, positions, etc.).
140    #[builder(default = 60)]
141    pub request_timeout: u64,
142    /// Whether to fetch all open orders (reqAllOpenOrders vs reqOpenOrders).
143    #[builder(default)]
144    pub fetch_all_open_orders: bool,
145    /// Whether to track option exercise from position updates.
146    #[builder(default)]
147    pub track_option_exercise_from_position_update: bool,
148    /// Instrument provider configuration.
149    #[builder(default)]
150    pub instrument_provider: InteractiveBrokersInstrumentProviderConfig,
151}
152
153impl Default for InteractiveBrokersExecClientConfig {
154    fn default() -> Self {
155        Self::builder().build()
156    }
157}
158
159/// Symbology method for converting between IB contracts and Nautilus instrument IDs.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
161#[cfg_attr(
162    feature = "python",
163    pyo3::pyclass(
164        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
165        from_py_object
166    )
167)]
168#[derive(Default)]
169pub enum SymbologyMethod {
170    /// Simplified symbology: clean, readable symbols (e.g., "EUR/USD", "ESM23")
171    #[serde(rename = "simplified")]
172    #[default]
173    Simplified,
174    /// Raw symbology: preserves IB raw format with security type suffix (e.g., "EUR.USD=CASH", "AAPL=STK")
175    #[serde(rename = "raw")]
176    Raw,
177}
178
179/// Configuration for Interactive Brokers instrument provider.
180#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
181#[serde(default)]
182#[cfg_attr(
183    feature = "python",
184    pyo3::pyclass(
185        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
186        subclass,
187        from_py_object
188    )
189)]
190pub struct InteractiveBrokersInstrumentProviderConfig {
191    /// Symbology method to use for instrument ID conversion.
192    #[builder(default)]
193    pub symbology_method: SymbologyMethod,
194    /// Instrument IDs to load on startup.
195    #[builder(default)]
196    pub load_ids: HashSet<InstrumentId>,
197    /// IB contracts to load on startup.
198    #[builder(default)]
199    pub load_contracts: Vec<serde_json::Value>,
200    /// Minimum expiry days for options and futures chains.
201    pub min_expiry_days: Option<u32>,
202    /// Maximum expiry days for options and futures chains.
203    pub max_expiry_days: Option<u32>,
204    /// Whether to build full options chain.
205    pub build_options_chain: Option<bool>,
206    /// Whether to build full futures chain.
207    pub build_futures_chain: Option<bool>,
208    /// Cache validity in days (None means no caching).
209    pub cache_validity_days: Option<u32>,
210    /// Whether to convert IB exchanges to MIC venues.
211    #[builder(default)]
212    pub convert_exchange_to_mic_venue: bool,
213    /// Symbol to MIC venue mapping override.
214    #[builder(default)]
215    pub symbol_to_mic_venue: HashMap<String, String>,
216    /// Security types to filter out.
217    #[builder(default)]
218    pub filter_sec_types: HashSet<String>,
219    /// Fully-qualified Python callable path for custom instrument filtering.
220    ///
221    /// Configuring this without the Python feature enabled is an error.
222    pub filter_callable: Option<String>,
223    /// Path to cache file for persistent instrument caching (equivalent to pickle_path in Python).
224    /// If provided, instruments will be cached to disk and loaded from cache if still valid.
225    pub cache_path: Option<String>,
226}
227
228impl Default for InteractiveBrokersInstrumentProviderConfig {
229    fn default() -> Self {
230        Self::builder().build()
231    }
232}
233
234/// Trading mode for Dockerized IB Gateway.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
236#[cfg_attr(
237    feature = "python",
238    pyo3::pyclass(
239        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
240        from_py_object
241    )
242)]
243#[derive(Default)]
244pub enum TradingMode {
245    /// Paper trading mode.
246    #[serde(rename = "paper")]
247    #[default]
248    Paper,
249    /// Live trading mode.
250    #[serde(rename = "live")]
251    Live,
252}
253
254/// Configuration for Dockerized IB Gateway.
255///
256/// This configuration is for managing containerized IB Gateway instances.
257/// It supports environment variable loading and sensitive data masking.
258#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
259#[serde(default)]
260#[cfg_attr(
261    feature = "python",
262    pyo3::pyclass(
263        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
264        subclass,
265        from_py_object
266    )
267)]
268pub struct DockerizedIBGatewayConfig {
269    /// Username for IB account (falls back to `TWS_USERNAME` env var via [`Default`]).
270    pub username: Option<String>,
271    /// Password for IB account (falls back to `TWS_PASSWORD` env var via [`Default`]).
272    pub password: Option<String>,
273    /// Trading mode (paper or live).
274    #[builder(default)]
275    pub trading_mode: TradingMode,
276    /// Whether to enable read-only API mode.
277    #[builder(default = true)]
278    pub read_only_api: bool,
279    /// Timeout in seconds for container startup.
280    #[builder(default = 300)]
281    pub timeout: u64,
282    /// Container image reference.
283    #[builder(default = "ghcr.io/gnzsnz/ib-gateway:stable".to_string())]
284    pub container_image: String,
285    /// VNC port for remote desktop access (None to disable).
286    pub vnc_port: Option<u16>,
287}
288
289impl DockerizedIBGatewayConfig {
290    /// Mask sensitive information for display.
291    pub fn mask_sensitive_info(value: &str) -> String {
292        if value.len() <= 2 {
293            "*".repeat(value.len())
294        } else {
295            format!(
296                "{}{}{}",
297                &value[0..1],
298                "*".repeat(value.len() - 2),
299                &value[value.len() - 1..]
300            )
301        }
302    }
303
304    /// Validate configuration.
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if validation fails.
309    pub fn validate(&self) -> anyhow::Result<()> {
310        if self.timeout == 0 {
311            anyhow::bail!("Timeout must be greater than 0");
312        }
313
314        if self.timeout > 3600 {
315            anyhow::bail!("Timeout must be less than 3600 seconds");
316        }
317
318        if let Some(port) = self.vnc_port
319            && (!(5900..=5999).contains(&port))
320        {
321            anyhow::bail!("VNC port must be between 5900 and 5999");
322        }
323
324        Ok(())
325    }
326}
327
328impl Default for DockerizedIBGatewayConfig {
329    fn default() -> Self {
330        Self::builder()
331            .maybe_username(std::env::var("TWS_USERNAME").ok())
332            .maybe_password(std::env::var("TWS_PASSWORD").ok())
333            .build()
334    }
335}