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}
103
104impl Default for InteractiveBrokersDataClientConfig {
105    fn default() -> Self {
106        Self::builder().build()
107    }
108}
109
110/// Configuration for Interactive Brokers execution client.
111#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
112#[serde(default)]
113#[cfg_attr(
114    feature = "python",
115    pyo3::pyclass(
116        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
117        subclass,
118        from_py_object
119    )
120)]
121pub struct InteractiveBrokersExecClientConfig {
122    /// Host for IB Gateway/TWS.
123    #[builder(default = DEFAULT_HOST.to_string())]
124    pub host: String,
125    /// Port for IB Gateway/TWS.
126    #[builder(default = DEFAULT_PORT)]
127    pub port: u16,
128    /// Client ID.
129    #[builder(default = DEFAULT_CLIENT_ID)]
130    pub client_id: i32,
131    /// Account ID.
132    pub account_id: Option<String>,
133    /// Connection timeout in seconds.
134    #[builder(default = 300)]
135    pub connection_timeout: u64,
136    /// Request timeout in seconds for IB API requests (open orders, executions, positions, etc.).
137    #[builder(default = 60)]
138    pub request_timeout: u64,
139    /// Whether to fetch all open orders (reqAllOpenOrders vs reqOpenOrders).
140    #[builder(default)]
141    pub fetch_all_open_orders: bool,
142    /// Whether to track option exercise from position updates.
143    #[builder(default)]
144    pub track_option_exercise_from_position_update: bool,
145}
146
147impl Default for InteractiveBrokersExecClientConfig {
148    fn default() -> Self {
149        Self::builder().build()
150    }
151}
152
153/// Symbology method for converting between IB contracts and Nautilus instrument IDs.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
155#[cfg_attr(
156    feature = "python",
157    pyo3::pyclass(
158        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
159        from_py_object
160    )
161)]
162#[derive(Default)]
163pub enum SymbologyMethod {
164    /// Simplified symbology: clean, readable symbols (e.g., "EUR/USD", "ESM23")
165    #[serde(rename = "simplified")]
166    #[default]
167    Simplified,
168    /// Raw symbology: preserves IB raw format with security type suffix (e.g., "EUR.USD=CASH", "AAPL=STK")
169    #[serde(rename = "raw")]
170    Raw,
171}
172
173/// Configuration for Interactive Brokers instrument provider.
174#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
175#[serde(default)]
176#[cfg_attr(
177    feature = "python",
178    pyo3::pyclass(
179        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
180        subclass,
181        from_py_object
182    )
183)]
184pub struct InteractiveBrokersInstrumentProviderConfig {
185    /// Symbology method to use for instrument ID conversion.
186    #[builder(default)]
187    pub symbology_method: SymbologyMethod,
188    /// Instrument IDs to load on startup.
189    #[builder(default)]
190    pub load_ids: HashSet<InstrumentId>,
191    /// IB contracts to load on startup.
192    #[builder(default)]
193    pub load_contracts: Vec<serde_json::Value>,
194    /// Minimum expiry days for options and futures chains.
195    pub min_expiry_days: Option<u32>,
196    /// Maximum expiry days for options and futures chains.
197    pub max_expiry_days: Option<u32>,
198    /// Whether to build full options chain.
199    pub build_options_chain: Option<bool>,
200    /// Whether to build full futures chain.
201    pub build_futures_chain: Option<bool>,
202    /// Cache validity in days (None means no caching).
203    pub cache_validity_days: Option<u32>,
204    /// Whether to convert IB exchanges to MIC venues.
205    #[builder(default)]
206    pub convert_exchange_to_mic_venue: bool,
207    /// Symbol to MIC venue mapping override.
208    #[builder(default)]
209    pub symbol_to_mic_venue: HashMap<String, String>,
210    /// Security types to filter out.
211    #[builder(default)]
212    pub filter_sec_types: HashSet<String>,
213    /// Path to cache file for persistent instrument caching (equivalent to pickle_path in Python).
214    /// If provided, instruments will be cached to disk and loaded from cache if still valid.
215    pub cache_path: Option<String>,
216}
217
218impl Default for InteractiveBrokersInstrumentProviderConfig {
219    fn default() -> Self {
220        Self::builder().build()
221    }
222}
223
224/// Trading mode for Dockerized IB Gateway.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
226#[cfg_attr(
227    feature = "python",
228    pyo3::pyclass(
229        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
230        from_py_object
231    )
232)]
233#[derive(Default)]
234pub enum TradingMode {
235    /// Paper trading mode.
236    #[serde(rename = "paper")]
237    #[default]
238    Paper,
239    /// Live trading mode.
240    #[serde(rename = "live")]
241    Live,
242}
243
244/// Configuration for Dockerized IB Gateway.
245///
246/// This configuration is for managing containerized IB Gateway instances.
247/// It supports environment variable loading and sensitive data masking.
248#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
249#[serde(default)]
250#[cfg_attr(
251    feature = "python",
252    pyo3::pyclass(
253        module = "nautilus_trader.core.nautilus_pyo3.interactive_brokers",
254        subclass,
255        from_py_object
256    )
257)]
258pub struct DockerizedIBGatewayConfig {
259    /// Username for IB account (falls back to `TWS_USERNAME` env var via [`Default`]).
260    pub username: Option<String>,
261    /// Password for IB account (falls back to `TWS_PASSWORD` env var via [`Default`]).
262    pub password: Option<String>,
263    /// Trading mode (paper or live).
264    #[builder(default)]
265    pub trading_mode: TradingMode,
266    /// Whether to enable read-only API mode.
267    #[builder(default = true)]
268    pub read_only_api: bool,
269    /// Timeout in seconds for container startup.
270    #[builder(default = 300)]
271    pub timeout: u64,
272    /// Container image reference.
273    #[builder(default = "ghcr.io/gnzsnz/ib-gateway:stable".to_string())]
274    pub container_image: String,
275    /// VNC port for remote desktop access (None to disable).
276    pub vnc_port: Option<u16>,
277}
278
279impl DockerizedIBGatewayConfig {
280    /// Mask sensitive information for display.
281    pub fn mask_sensitive_info(value: &str) -> String {
282        if value.len() <= 2 {
283            "*".repeat(value.len())
284        } else {
285            format!(
286                "{}{}{}",
287                &value[0..1],
288                "*".repeat(value.len() - 2),
289                &value[value.len() - 1..]
290            )
291        }
292    }
293
294    /// Validate configuration.
295    ///
296    /// # Errors
297    ///
298    /// Returns an error if validation fails.
299    pub fn validate(&self) -> anyhow::Result<()> {
300        if self.timeout == 0 {
301            anyhow::bail!("Timeout must be greater than 0");
302        }
303
304        if self.timeout > 3600 {
305            anyhow::bail!("Timeout must be less than 3600 seconds");
306        }
307
308        if let Some(port) = self.vnc_port
309            && (!(5900..=5999).contains(&port))
310        {
311            anyhow::bail!("VNC port must be between 5900 and 5999");
312        }
313
314        Ok(())
315    }
316}
317
318impl Default for DockerizedIBGatewayConfig {
319    fn default() -> Self {
320        Self::builder()
321            .maybe_username(std::env::var("TWS_USERNAME").ok())
322            .maybe_password(std::env::var("TWS_PASSWORD").ok())
323            .build()
324    }
325}