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