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 InteractiveBrokersExecutionClientConfig, InteractiveBrokersInstrumentProviderConfig,
25 MarketDataType, 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]
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40impl InteractiveBrokersDataClientConfig {
41 #[new]
43 #[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))]
44 #[allow(clippy::too_many_arguments)]
45 fn py_new(
46 host: Option<String>,
47 port: Option<u16>,
48 client_id: Option<i32>,
49 use_regular_trading_hours: Option<bool>,
50 market_data_type: Option<MarketDataType>,
51 ignore_quote_tick_size_updates: Option<bool>,
52 connection_timeout: Option<u64>,
53 request_timeout: Option<u64>,
54 handle_revised_bars: Option<bool>,
55 batch_quotes: Option<bool>,
56 instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
57 dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
58 ) -> PyResult<Self> {
59 if dockerized_gateway.is_some() {
60 return Err(to_pyvalue_err(
61 "`dockerized_gateway` is not wired into the Rust/PyO3 IB data client; start `DockerizedIBGateway` separately and pass `host`/`port`",
62 ));
63 }
64
65 let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
66 let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
67 let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
68 let request_timeout = request_timeout.unwrap_or(60);
69
70 Ok(Self {
71 host,
72 port,
73 client_id,
74 use_regular_trading_hours: use_regular_trading_hours.unwrap_or(true),
75 market_data_type: market_data_type.unwrap_or_default(),
76 ignore_quote_tick_size_updates: ignore_quote_tick_size_updates.unwrap_or(false),
77 connection_timeout: connection_timeout.unwrap_or(300),
78 request_timeout,
79 handle_revised_bars: handle_revised_bars.unwrap_or(false),
80 batch_quotes: batch_quotes.unwrap_or(true),
81 instrument_provider: instrument_provider.unwrap_or_default(),
82 })
83 }
84
85 #[getter]
87 fn host(&self) -> &str {
88 &self.host
89 }
90
91 #[getter]
93 fn port(&self) -> u16 {
94 self.port
95 }
96
97 #[getter]
99 fn client_id(&self) -> i32 {
100 self.client_id
101 }
102
103 #[getter]
105 fn use_regular_trading_hours(&self) -> bool {
106 self.use_regular_trading_hours
107 }
108
109 #[getter]
111 fn market_data_type(&self) -> MarketDataType {
112 self.market_data_type
113 }
114
115 #[getter]
117 fn ignore_quote_tick_size_updates(&self) -> bool {
118 self.ignore_quote_tick_size_updates
119 }
120
121 #[getter]
123 fn connection_timeout(&self) -> u64 {
124 self.connection_timeout
125 }
126
127 #[getter]
129 fn request_timeout(&self) -> u64 {
130 self.request_timeout
131 }
132
133 #[getter]
135 fn handle_revised_bars(&self) -> bool {
136 self.handle_revised_bars
137 }
138
139 #[getter]
141 fn batch_quotes(&self) -> bool {
142 self.batch_quotes
143 }
144
145 #[getter]
147 fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
148 self.instrument_provider.clone()
149 }
150
151 #[setter]
153 fn set_instrument_provider(
154 &mut self,
155 instrument_provider: InteractiveBrokersInstrumentProviderConfig,
156 ) {
157 self.instrument_provider = instrument_provider;
158 }
159}
160
161#[pymethods]
162#[pyo3_stub_gen::derive::gen_stub_pymethods]
163impl InteractiveBrokersExecutionClientConfig {
164 #[new]
166 #[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))]
167 #[allow(clippy::too_many_arguments)]
168 fn py_new(
169 host: Option<String>,
170 port: Option<u16>,
171 client_id: Option<i32>,
172 account_id: Option<String>,
173 connection_timeout: Option<u64>,
174 request_timeout: Option<u64>,
175 fetch_all_open_orders: Option<bool>,
176 track_option_exercise_from_position_update: Option<bool>,
177 instrument_provider: Option<InteractiveBrokersInstrumentProviderConfig>,
178 dockerized_gateway: Option<&DockerizedIBGatewayConfig>,
179 ) -> PyResult<Self> {
180 if dockerized_gateway.is_some() {
181 return Err(to_pyvalue_err(
182 "`dockerized_gateway` is not wired into the Rust/PyO3 IB execution client; start `DockerizedIBGateway` separately and pass `host`/`port`",
183 ));
184 }
185
186 let host = host.unwrap_or_else(|| crate::common::consts::DEFAULT_HOST.to_string());
187 let port = port.unwrap_or(crate::common::consts::DEFAULT_PORT);
188 let client_id = client_id.unwrap_or(crate::common::consts::DEFAULT_CLIENT_ID);
189 validate_order_id_client_slot(client_id)?;
190 let request_timeout = request_timeout.unwrap_or(60);
191
192 Ok(Self {
193 host,
194 port,
195 client_id,
196 account_id,
197 connection_timeout: connection_timeout.unwrap_or(300),
198 request_timeout,
199 fetch_all_open_orders: fetch_all_open_orders.unwrap_or(false),
200 track_option_exercise_from_position_update: track_option_exercise_from_position_update
201 .unwrap_or(false),
202 instrument_provider: instrument_provider.unwrap_or_default(),
203 })
204 }
205
206 #[getter]
208 fn host(&self) -> &str {
209 &self.host
210 }
211
212 #[getter]
214 fn port(&self) -> u16 {
215 self.port
216 }
217
218 #[getter]
220 fn client_id(&self) -> i32 {
221 self.client_id
222 }
223
224 #[getter]
226 fn account_id(&self) -> Option<String> {
227 self.account_id.clone()
228 }
229
230 #[getter]
232 fn connection_timeout(&self) -> u64 {
233 self.connection_timeout
234 }
235
236 #[getter]
238 fn request_timeout(&self) -> u64 {
239 self.request_timeout
240 }
241
242 #[getter]
244 fn fetch_all_open_orders(&self) -> bool {
245 self.fetch_all_open_orders
246 }
247
248 #[getter]
250 fn track_option_exercise_from_position_update(&self) -> bool {
251 self.track_option_exercise_from_position_update
252 }
253
254 #[getter]
256 fn instrument_provider(&self) -> InteractiveBrokersInstrumentProviderConfig {
257 self.instrument_provider.clone()
258 }
259
260 #[setter]
262 fn set_instrument_provider(
263 &mut self,
264 instrument_provider: InteractiveBrokersInstrumentProviderConfig,
265 ) {
266 self.instrument_provider = instrument_provider;
267 }
268}
269
270#[pymethods]
271#[pyo3_stub_gen::derive::gen_stub_pymethods]
272impl InteractiveBrokersInstrumentProviderConfig {
273 #[new]
275 #[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))]
276 #[allow(clippy::too_many_arguments)]
277 fn py_new(
278 py: Python<'_>,
279 symbology_method: Option<crate::config::SymbologyMethod>,
280 load_ids: Option<std::collections::HashSet<InstrumentId>>,
281 load_contracts: Option<Py<pyo3::types::PyList>>,
282 min_expiry_days: Option<u32>,
283 max_expiry_days: Option<u32>,
284 build_options_chain: Option<bool>,
285 build_futures_chain: Option<bool>,
286 cache_validity_days: Option<u32>,
287 convert_exchange_to_mic_venue: Option<bool>,
288 symbol_to_mic_venue: Option<std::collections::HashMap<String, String>>,
289 filter_sec_types: Option<std::collections::HashSet<String>>,
290 filter_callable: Option<String>,
291 cache_path: Option<String>,
292 ) -> PyResult<Self> {
293 Ok(Self {
294 symbology_method: symbology_method.unwrap_or_default(),
295 load_ids: load_ids.unwrap_or_default(),
296 load_contracts: if let Some(c) = load_contracts {
297 crate::python::conversion::py_list_to_json_values(c.bind(py))?
298 } else {
299 Vec::new()
300 },
301 min_expiry_days,
302 max_expiry_days,
303 build_options_chain,
304 build_futures_chain,
305 cache_validity_days,
306 convert_exchange_to_mic_venue: convert_exchange_to_mic_venue.unwrap_or(false),
307 symbol_to_mic_venue: symbol_to_mic_venue.unwrap_or_default(),
308 filter_sec_types: filter_sec_types.unwrap_or_default(),
309 filter_callable,
310 cache_path,
311 })
312 }
313
314 #[getter]
316 fn symbology_method(&self) -> crate::config::SymbologyMethod {
317 self.symbology_method
318 }
319
320 #[getter]
322 fn load_ids(&self) -> std::collections::HashSet<InstrumentId> {
323 self.load_ids.clone()
324 }
325
326 #[getter]
328 fn load_contracts(&self, py: Python<'_>) -> PyResult<Py<pyo3::types::PyList>> {
329 let json_mod = py.import("json")?;
330 let list = pyo3::types::PyList::empty(py);
331
332 for value in &self.load_contracts {
333 let json_str = value.to_string();
334 let dict = json_mod.call_method1("loads", (json_str,))?;
335 list.append(dict)?;
336 }
337 Ok(list.unbind())
338 }
339
340 #[getter]
342 fn min_expiry_days(&self) -> Option<u32> {
343 self.min_expiry_days
344 }
345
346 #[getter]
348 fn max_expiry_days(&self) -> Option<u32> {
349 self.max_expiry_days
350 }
351
352 #[getter]
354 fn build_options_chain(&self) -> Option<bool> {
355 self.build_options_chain
356 }
357
358 #[getter]
360 fn build_futures_chain(&self) -> Option<bool> {
361 self.build_futures_chain
362 }
363
364 #[getter]
366 fn cache_validity_days(&self) -> Option<u32> {
367 self.cache_validity_days
368 }
369
370 #[getter]
372 fn convert_exchange_to_mic_venue(&self) -> bool {
373 self.convert_exchange_to_mic_venue
374 }
375
376 #[getter]
378 fn symbol_to_mic_venue(&self) -> std::collections::HashMap<String, String> {
379 self.symbol_to_mic_venue.clone()
380 }
381
382 #[getter]
384 fn filter_sec_types(&self) -> Vec<String> {
385 self.filter_sec_types.iter().cloned().collect()
386 }
387
388 #[getter]
390 fn filter_callable(&self) -> Option<String> {
391 self.filter_callable.clone()
392 }
393
394 #[getter]
396 fn cache_path(&self) -> Option<String> {
397 self.cache_path.clone()
398 }
399
400 #[setter]
402 fn set_cache_path(&mut self, cache_path: Option<String>) {
403 self.cache_path = cache_path;
404 }
405}
406
407#[pymethods]
408#[pyo3_stub_gen::derive::gen_stub_pymethods]
409impl DockerizedIBGatewayConfig {
410 #[new]
412 #[pyo3(signature = (username=None, password=None, trading_mode=None, read_only_api=None, timeout=None, container_image=None, vnc_port=None))]
413 fn py_new(
414 username: Option<String>,
415 password: Option<String>,
416 trading_mode: Option<TradingMode>,
417 read_only_api: Option<bool>,
418 timeout: Option<u64>,
419 container_image: Option<String>,
420 vnc_port: Option<u16>,
421 ) -> Self {
422 Self {
423 username,
424 password,
425 trading_mode: trading_mode.unwrap_or_default(),
426 read_only_api: read_only_api.unwrap_or(true),
427 timeout: timeout.unwrap_or(300),
428 container_image: container_image
429 .unwrap_or_else(|| "ghcr.io/gnzsnz/ib-gateway:stable".to_string()),
430 vnc_port,
431 }
432 }
433
434 #[getter]
436 fn username(&self) -> Option<String> {
437 self.username.clone()
438 }
439
440 #[getter]
441 const fn has_password(&self) -> bool {
442 self.password.is_some()
443 }
444
445 #[getter]
447 fn trading_mode(&self) -> TradingMode {
448 self.trading_mode
449 }
450
451 #[getter]
453 fn read_only_api(&self) -> bool {
454 self.read_only_api
455 }
456
457 #[getter]
459 fn timeout(&self) -> u64 {
460 self.timeout
461 }
462
463 #[getter]
465 fn container_image(&self) -> &str {
466 &self.container_image
467 }
468
469 #[getter]
471 fn vnc_port(&self) -> Option<u16> {
472 self.vnc_port
473 }
474}