nautilus_interactive_brokers/python/
config.rs1use nautilus_core::python::to_pyvalue_err;
19use nautilus_model::identifiers::InstrumentId;
20use pyo3::prelude::*;
21
22use crate::config::{
23 DockerizedIBGatewayConfig, InteractiveBrokersDataClientConfig,
24 InteractiveBrokersExecClientConfig, InteractiveBrokersInstrumentProviderConfig, MarketDataType,
25 SymbologyMethod, TradingMode,
26};
27
28fn validate_order_id_client_slot(client_id: i32) -> PyResult<()> {
29 if client_id.unsigned_abs().is_multiple_of(1000) {
30 return Err(to_pyvalue_err(format!(
31 "`client_id` must not be a multiple of 1000 for the Rust/PyO3 IB execution client because order ID partitioning uses client_id % 1000; got {client_id}"
32 )));
33 }
34
35 Ok(())
36}
37
38#[pymethods]
39impl MarketDataType {
40 #[classattr]
41 const REALTIME: Self = Self::Realtime;
42
43 #[classattr]
44 const FROZEN: Self = Self::Frozen;
45
46 #[classattr]
47 const DELAYED: Self = Self::Delayed;
48
49 #[classattr]
50 const DELAYED_FROZEN: Self = Self::DelayedFrozen;
51}
52
53#[pymethods]
54impl SymbologyMethod {
55 #[classattr]
56 const SIMPLIFIED: Self = Self::Simplified;
57
58 #[classattr]
59 const RAW: Self = Self::Raw;
60}
61
62#[pymethods]
63impl TradingMode {
64 #[classattr]
65 const PAPER: Self = Self::Paper;
66
67 #[classattr]
68 const LIVE: Self = Self::Live;
69}
70
71#[pymethods]
72impl InteractiveBrokersDataClientConfig {
73 #[new]
75 #[pyo3(signature = (host=None, port=None, client_id=None, use_regular_trading_hours=None, market_data_type=None, ignore_quote_tick_size_updates=None, connection_timeout=None, request_timeout=None, handle_revised_bars=None, batch_quotes=None, instrument_provider=None, dockerized_gateway=None))]
76 #[allow(clippy::too_many_arguments)]
77 fn py_new(
78 host: Option<String>,
79 port: Option<u16>,
80 client_id: Option<i32>,
81 use_regular_trading_hours: Option<bool>,
82 market_data_type: Option<MarketDataType>,
83 ignore_quote_tick_size_updates: Option<bool>,
84 connection_timeout: Option<u64>,
85 request_timeout: Option<u64>,
86 handle_revised_bars: Option<bool>,
87 batch_quotes: Option<bool>,
88 instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
89 dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
90 ) -> PyResult<Self> {
91 if dockerized_gateway.is_some() {
92 return Err(to_pyvalue_err(
93 "`dockerized_gateway` is not wired into the Rust/PyO3 IB data client; start `DockerizedIBGateway` separately and pass `host`/`port`",
94 ));
95 }
96
97 let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
98 let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
99 let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
100 let request_timeout = request_timeout.unwrap_or(60);
101
102 Ok(Self {
103 host,
104 port,
105 client_id,
106 use_regular_trading_hours: use_regular_trading_hours.unwrap_or(true),
107 market_data_type: market_data_type.unwrap_or_default(),
108 ignore_quote_tick_size_updates: ignore_quote_tick_size_updates.unwrap_or(false),
109 connection_timeout: connection_timeout.unwrap_or(300),
110 request_timeout,
111 handle_revised_bars: handle_revised_bars.unwrap_or(false),
112 batch_quotes: batch_quotes.unwrap_or(true),
113 instrument_provider: instrument_provider.unwrap_or_default(),
114 })
115 }
116
117 #[getter]
119 fn host(&self) -> &str {
120 &self.host
121 }
122
123 #[getter]
125 fn port(&self) -> u16 {
126 self.port
127 }
128
129 #[getter]
131 fn client_id(&self) -> i32 {
132 self.client_id
133 }
134
135 #[getter]
137 fn use_regular_trading_hours(&self) -> bool {
138 self.use_regular_trading_hours
139 }
140
141 #[getter]
143 fn market_data_type(&self) -> MarketDataType {
144 self.market_data_type
145 }
146
147 #[getter]
149 fn ignore_quote_tick_size_updates(&self) -> bool {
150 self.ignore_quote_tick_size_updates
151 }
152
153 #[getter]
155 fn connection_timeout(&self) -> u64 {
156 self.connection_timeout
157 }
158
159 #[getter]
161 fn request_timeout(&self) -> u64 {
162 self.request_timeout
163 }
164
165 #[getter]
167 fn handle_revised_bars(&self) -> bool {
168 self.handle_revised_bars
169 }
170
171 #[getter]
173 fn batch_quotes(&self) -> bool {
174 self.batch_quotes
175 }
176
177 #[getter]
179 fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
180 self.instrument_provider.clone()
181 }
182
183 #[setter]
185 fn set_instrument_provider(
186 &mut self,
187 instrument_provider: InteractiveBrokersInstrumentProviderConfig,
188 ) {
189 self.instrument_provider = instrument_provider;
190 }
191}
192
193#[pymethods]
194impl InteractiveBrokersExecClientConfig {
195 #[new]
197 #[pyo3(signature = (host=None, port=None, client_id=None, account_id=None, connection_timeout=None, request_timeout=None, fetch_all_open_orders=None, track_option_exercise_from_position_update=None, instrument_provider=None, dockerized_gateway=None))]
198 #[allow(clippy::too_many_arguments)]
199 fn py_new(
200 host: Option<String>,
201 port: Option<u16>,
202 client_id: Option<i32>,
203 account_id: Option<String>,
204 connection_timeout: Option<u64>,
205 request_timeout: Option<u64>,
206 fetch_all_open_orders: Option<bool>,
207 track_option_exercise_from_position_update: Option<bool>,
208 instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
209 dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
210 ) -> PyResult<Self> {
211 if dockerized_gateway.is_some() {
212 return Err(to_pyvalue_err(
213 "`dockerized_gateway` is not wired into the Rust/PyO3 IB execution client; start `DockerizedIBGateway` separately and pass `host`/`port`",
214 ));
215 }
216
217 let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
218 let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
219 let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
220 validate_order_id_client_slot(client_id)?;
221 let request_timeout = request_timeout.unwrap_or(60);
222
223 Ok(Self {
224 host,
225 port,
226 client_id,
227 account_id,
228 connection_timeout: connection_timeout.unwrap_or(300),
229 request_timeout,
230 fetch_all_open_orders: fetch_all_open_orders.unwrap_or(false),
231 track_option_exercise_from_position_update: track_option_exercise_from_position_update
232 .unwrap_or(false),
233 instrument_provider: instrument_provider.unwrap_or_default(),
234 })
235 }
236
237 #[getter]
239 fn host(&self) -> &str {
240 &self.host
241 }
242
243 #[getter]
245 fn port(&self) -> u16 {
246 self.port
247 }
248
249 #[getter]
251 fn client_id(&self) -> i32 {
252 self.client_id
253 }
254
255 #[getter]
257 fn account_id(&self) -> Option<String> {
258 self.account_id.clone()
259 }
260
261 #[getter]
263 fn connection_timeout(&self) -> u64 {
264 self.connection_timeout
265 }
266
267 #[getter]
269 fn request_timeout(&self) -> u64 {
270 self.request_timeout
271 }
272
273 #[getter]
275 fn fetch_all_open_orders(&self) -> bool {
276 self.fetch_all_open_orders
277 }
278
279 #[getter]
281 fn track_option_exercise_from_position_update(&self) -> bool {
282 self.track_option_exercise_from_position_update
283 }
284
285 #[getter]
287 fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
288 self.instrument_provider.clone()
289 }
290
291 #[setter]
293 fn set_instrument_provider(
294 &mut self,
295 instrument_provider: InteractiveBrokersInstrumentProviderConfig,
296 ) {
297 self.instrument_provider = instrument_provider;
298 }
299}
300
301#[pymethods]
302impl InteractiveBrokersInstrumentProviderConfig {
303 #[new]
305 #[pyo3(signature = (symbology_method=None, load_ids=None, load_contracts=None, min_expiry_days=None, max_expiry_days=None, build_options_chain=None, build_futures_chain=None, cache_validity_days=None, convert_exchange_to_mic_venue=None, symbol_to_mic_venue=None, filter_sec_types=None, filter_callable=None, cache_path=None))]
306 #[allow(clippy::too_many_arguments)]
307 fn py_new(
308 py: Python<'_>,
309 symbology_method: Option<crate::config::SymbologyMethod>,
310 load_ids: Option<std::collections::HashSet<InstrumentId>>,
311 load_contracts: Option<Py<pyo3::types::PyList>>,
312 min_expiry_days: Option<u32>,
313 max_expiry_days: Option<u32>,
314 build_options_chain: Option<bool>,
315 build_futures_chain: Option<bool>,
316 cache_validity_days: Option<u32>,
317 convert_exchange_to_mic_venue: Option<bool>,
318 symbol_to_mic_venue: Option<std::collections::HashMap<String, String>>,
319 filter_sec_types: Option<std::collections::HashSet<String>>,
320 filter_callable: Option<String>,
321 cache_path: Option<String>,
322 ) -> PyResult<Self> {
323 Ok(Self {
324 symbology_method: symbology_method.unwrap_or_default(),
325 load_ids: load_ids.unwrap_or_default(),
326 load_contracts: if let Some(c) = load_contracts {
327 crate::python::conversion::py_list_to_json_values(c.bind(py))?
328 } else {
329 Vec::new()
330 },
331 min_expiry_days,
332 max_expiry_days,
333 build_options_chain,
334 build_futures_chain,
335 cache_validity_days,
336 convert_exchange_to_mic_venue: convert_exchange_to_mic_venue.unwrap_or(false),
337 symbol_to_mic_venue: symbol_to_mic_venue.unwrap_or_default(),
338 filter_sec_types: filter_sec_types.unwrap_or_default(),
339 filter_callable,
340 cache_path,
341 })
342 }
343
344 #[getter]
346 fn symbology_method(&self) -> crate::config::SymbologyMethod {
347 self.symbology_method
348 }
349
350 #[getter]
352 fn load_ids(&self) -> std::collections::HashSet<InstrumentId> {
353 self.load_ids.clone()
354 }
355
356 #[getter]
358 fn load_contracts(&self, py: Python<'_>) -> PyResult<Py<pyo3::types::PyList>> {
359 let json_mod = py.import("json")?;
360 let list = pyo3::types::PyList::empty(py);
361
362 for value in &self.load_contracts {
363 let json_str = value.to_string();
364 let dict = json_mod.call_method1("loads", (json_str,))?;
365 list.append(dict)?;
366 }
367 Ok(list.unbind())
368 }
369
370 #[getter]
372 fn min_expiry_days(&self) -> Option<u32> {
373 self.min_expiry_days
374 }
375
376 #[getter]
378 fn max_expiry_days(&self) -> Option<u32> {
379 self.max_expiry_days
380 }
381
382 #[getter]
384 fn build_options_chain(&self) -> Option<bool> {
385 self.build_options_chain
386 }
387
388 #[getter]
390 fn build_futures_chain(&self) -> Option<bool> {
391 self.build_futures_chain
392 }
393
394 #[getter]
396 fn cache_validity_days(&self) -> Option<u32> {
397 self.cache_validity_days
398 }
399
400 #[getter]
402 fn convert_exchange_to_mic_venue(&self) -> bool {
403 self.convert_exchange_to_mic_venue
404 }
405
406 #[getter]
408 fn symbol_to_mic_venue(&self) -> std::collections::HashMap<String, String> {
409 self.symbol_to_mic_venue.clone()
410 }
411
412 #[getter]
414 fn filter_sec_types(&self) -> Vec<String> {
415 self.filter_sec_types.iter().cloned().collect()
416 }
417
418 #[getter]
420 fn filter_callable(&self) -> Option<String> {
421 self.filter_callable.clone()
422 }
423
424 #[getter]
426 fn cache_path(&self) -> Option<String> {
427 self.cache_path.clone()
428 }
429
430 #[setter]
432 fn set_cache_path(&mut self, cache_path: Option<String>) {
433 self.cache_path = cache_path;
434 }
435}
436
437#[pymethods]
438impl DockerizedIBGatewayConfig {
439 #[new]
441 #[pyo3(signature = (username=None, password=None, trading_mode=None, read_only_api=None, timeout=None, container_image=None, vnc_port=None))]
442 fn py_new(
443 username: Option<String>,
444 password: Option<String>,
445 trading_mode: Option<TradingMode>,
446 read_only_api: Option<bool>,
447 timeout: Option<u64>,
448 container_image: Option<String>,
449 vnc_port: Option<u16>,
450 ) -> Self {
451 Self {
452 username,
453 password,
454 trading_mode: trading_mode.unwrap_or_default(),
455 read_only_api: read_only_api.unwrap_or(true),
456 timeout: timeout.unwrap_or(300),
457 container_image: container_image
458 .unwrap_or_else(|| "ghcr.io/gnzsnz/ib-gateway:stable".to_string()),
459 vnc_port,
460 }
461 }
462
463 #[getter]
465 fn username(&self) -> Option<String> {
466 self.username.clone()
467 }
468
469 #[getter]
471 fn password(&self) -> Option<String> {
472 self.password.as_ref().map(|_| "********".to_string())
473 }
474
475 #[getter]
477 fn trading_mode(&self) -> TradingMode {
478 self.trading_mode
479 }
480
481 #[getter]
483 fn read_only_api(&self) -> bool {
484 self.read_only_api
485 }
486
487 #[getter]
489 fn timeout(&self) -> u64 {
490 self.timeout
491 }
492
493 #[getter]
495 fn container_image(&self) -> &str {
496 &self.container_image
497 }
498
499 #[getter]
501 fn vnc_port(&self) -> Option<u16> {
502 self.vnc_port
503 }
504}