1use std::{collections::HashMap, hash::BuildHasher, time::Duration};
17
18use nautilus_common::{
19 cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
20 msgbus::MessageBusConfig, python::config_error_to_pyvalue_err,
21};
22use nautilus_core::{UUID4, python::to_pyvalue_err};
23use nautilus_model::{
24 enums::BarIntervalType,
25 identifiers::{ClientId, TraderId, Venue},
26};
27use nautilus_portfolio::config::PortfolioConfig;
28use nautilus_trading::ImportableControllerConfig;
29use pyo3::{
30 IntoPyObject, Py, PyAny, PyResult, Python, pymethods,
31 types::{PyAnyMethods, PyDict, PyDictMethods},
32};
33
34use crate::config::{
35 DataClientConfig, ExecutionClientConfig, InstrumentProviderConfig, LiveDataEngineConfig,
36 LiveExecutionEngineConfig, LiveNodeConfig, LiveRiskEngineConfig, PluginConfig,
37 QueueMonitorConfig, RoutingConfig, duration_from_secs_f64, parse_rate_limit,
38 validate_max_notional_per_order,
39};
40
41fn coerce_bar_interval_type(value: &Py<PyAny>) -> PyResult<BarIntervalType> {
44 Python::attach(|py| {
45 let bound = value.bind(py);
46 if let Ok(variant) = bound.extract::<BarIntervalType>() {
47 return Ok(variant);
48 }
49
50 let raw = bound.extract::<String>().map_err(|_| {
51 to_pyvalue_err("`time_bars_interval_type` must be a string or BarIntervalType")
52 })?;
53
54 match raw.to_ascii_uppercase().replace('-', "_").as_str() {
55 "LEFT_OPEN" => Ok(BarIntervalType::LeftOpen),
56 "RIGHT_OPEN" => Ok(BarIntervalType::RightOpen),
57 _ => Err(to_pyvalue_err(format!(
58 "invalid `time_bars_interval_type`: {raw:?} (expected 'left-open' or 'right-open')"
59 ))),
60 }
61 })
62}
63
64fn py_to_json_value(bound: &pyo3::Bound<'_, PyAny>) -> PyResult<serde_json::Value> {
66 if let Ok(b) = bound.extract::<bool>() {
68 Ok(serde_json::Value::Bool(b))
69 } else if let Ok(s) = bound.extract::<String>() {
70 Ok(serde_json::Value::String(s))
71 } else if let Ok(i) = bound.extract::<i64>() {
72 Ok(serde_json::Value::Number(serde_json::Number::from(i)))
73 } else if let Ok(f) = bound.extract::<f64>() {
74 Ok(serde_json::Number::from_f64(f)
75 .map_or(serde_json::Value::Null, serde_json::Value::Number))
76 } else if let Ok(dict) = bound.cast::<PyDict>() {
77 let mut obj = serde_json::Map::with_capacity(dict.len());
78 for (key, value) in dict.iter() {
79 obj.insert(key.extract::<String>()?, py_to_json_value(&value)?);
80 }
81 Ok(serde_json::Value::Object(obj))
82 } else if let Ok(items) = bound.extract::<Vec<Py<PyAny>>>() {
83 let py = bound.py();
85 let arr: Vec<serde_json::Value> = items
86 .iter()
87 .map(|item| py_to_json_value(item.bind(py)))
88 .collect::<PyResult<_>>()?;
89 Ok(serde_json::Value::Array(arr))
90 } else {
91 let s: String = bound.str()?.extract()?;
93 Ok(serde_json::Value::String(s))
94 }
95}
96
97pub fn json_value_to_py(py: Python<'_>, value: &serde_json::Value) -> PyResult<Py<PyAny>> {
103 match value {
104 serde_json::Value::Null => Ok(py.None()),
105 serde_json::Value::Bool(b) => Ok((*b).into_pyobject(py)?.to_owned().into_any().unbind()),
106 serde_json::Value::Number(n) => {
107 if let Some(i) = n.as_i64() {
108 Ok(i.into_pyobject(py)?.into_any().unbind())
109 } else if let Some(f) = n.as_f64() {
110 Ok(f.into_pyobject(py)?.into_any().unbind())
111 } else {
112 Ok(n.to_string().into_pyobject(py)?.into_any().unbind())
113 }
114 }
115 serde_json::Value::String(s) => Ok(s.into_pyobject(py)?.into_any().unbind()),
116 serde_json::Value::Array(arr) => {
117 let items: Vec<Py<PyAny>> = arr
118 .iter()
119 .map(|v| json_value_to_py(py, v))
120 .collect::<PyResult<_>>()?;
121 Ok(pyo3::types::PyList::new(py, items)?.into_any().unbind())
122 }
123 serde_json::Value::Object(obj) => {
124 let dict = pyo3::types::PyDict::new(py);
125 for (k, v) in obj {
126 dict.set_item(k, json_value_to_py(py, v)?)?;
127 }
128 Ok(dict.into_any().unbind())
129 }
130 }
131}
132
133pub fn coerce_json_config<S: BuildHasher>(
139 raw: HashMap<String, Py<PyAny>, S>,
140) -> PyResult<HashMap<String, serde_json::Value>> {
141 Python::attach(|py| -> PyResult<HashMap<String, serde_json::Value>> {
142 let mut result = HashMap::with_capacity(raw.len());
143 for (key, value) in raw {
144 let json_value = py_to_json_value(value.bind(py))?;
145 result.insert(key, json_value);
146 }
147 Ok(result)
148 })
149}
150
151fn coerce_max_notional_per_order(
155 raw: HashMap<String, Py<PyAny>>,
156) -> PyResult<HashMap<String, String>> {
157 Python::attach(|py| -> PyResult<HashMap<String, String>> {
158 let mut result = HashMap::with_capacity(raw.len());
159 for (instrument_id, value) in raw {
160 let value_str: String = value.bind(py).str()?.extract()?;
161 result.insert(instrument_id, value_str);
162 }
163 Ok(result)
164 })
165}
166
167#[pyo3_stub_gen::derive::gen_stub_pymethods]
168#[pymethods]
169impl LiveDataEngineConfig {
170 #[new]
172 #[expect(clippy::too_many_arguments)]
173 #[allow(
174 clippy::needless_pass_by_value,
175 reason = "PyO3 #[new] requires owned params"
176 )]
177 #[pyo3(signature = (time_bars_build_with_no_updates=None, time_bars_timestamp_on_close=None, time_bars_skip_first_non_full_bar=None, time_bars_interval_type=None, time_bars_build_delay=None, time_bars_origin_offset=None, validate_data_sequence=None, buffer_deltas=None, emit_quotes_from_book=None, emit_quotes_from_book_depths=None, external_clients=None, debug=None))]
178 fn py_new(
179 time_bars_build_with_no_updates: Option<bool>,
180 time_bars_timestamp_on_close: Option<bool>,
181 time_bars_skip_first_non_full_bar: Option<bool>,
182 time_bars_interval_type: Option<Py<PyAny>>,
183 time_bars_build_delay: Option<u64>,
184 time_bars_origin_offset: Option<HashMap<String, u64>>,
185 validate_data_sequence: Option<bool>,
186 buffer_deltas: Option<bool>,
187 emit_quotes_from_book: Option<bool>,
188 emit_quotes_from_book_depths: Option<bool>,
189 external_clients: Option<Vec<ClientId>>,
190 debug: Option<bool>,
191 ) -> PyResult<Self> {
192 let default = Self::default();
193 let time_bars_interval_type = match time_bars_interval_type {
194 Some(ref obj) => coerce_bar_interval_type(obj)?,
195 None => default.time_bars_interval_type,
196 };
197 Ok(Self {
198 time_bars_build_with_no_updates: time_bars_build_with_no_updates
199 .unwrap_or(default.time_bars_build_with_no_updates),
200 time_bars_timestamp_on_close: time_bars_timestamp_on_close
201 .unwrap_or(default.time_bars_timestamp_on_close),
202 time_bars_skip_first_non_full_bar: time_bars_skip_first_non_full_bar
203 .unwrap_or(default.time_bars_skip_first_non_full_bar),
204 time_bars_interval_type,
205 time_bars_build_delay: time_bars_build_delay.unwrap_or(default.time_bars_build_delay),
206 time_bars_origin_offset: time_bars_origin_offset.unwrap_or_default(),
207 validate_data_sequence: validate_data_sequence
208 .unwrap_or(default.validate_data_sequence),
209 buffer_deltas: buffer_deltas.unwrap_or(default.buffer_deltas),
210 emit_quotes_from_book: emit_quotes_from_book.unwrap_or(default.emit_quotes_from_book),
211 emit_quotes_from_book_depths: emit_quotes_from_book_depths
212 .unwrap_or(default.emit_quotes_from_book_depths),
213 external_clients,
214 debug: debug.unwrap_or(default.debug),
215 qsize: default.qsize,
216 })
217 }
218
219 #[getter]
220 #[pyo3(name = "time_bars_build_with_no_updates")]
221 const fn py_time_bars_build_with_no_updates(&self) -> bool {
222 self.time_bars_build_with_no_updates
223 }
224
225 #[getter]
226 #[pyo3(name = "time_bars_timestamp_on_close")]
227 const fn py_time_bars_timestamp_on_close(&self) -> bool {
228 self.time_bars_timestamp_on_close
229 }
230
231 #[getter]
232 #[pyo3(name = "time_bars_skip_first_non_full_bar")]
233 const fn py_time_bars_skip_first_non_full_bar(&self) -> bool {
234 self.time_bars_skip_first_non_full_bar
235 }
236
237 #[getter]
238 #[pyo3(name = "time_bars_interval_type")]
239 const fn py_time_bars_interval_type(&self) -> BarIntervalType {
240 self.time_bars_interval_type
241 }
242
243 #[getter]
244 #[pyo3(name = "time_bars_build_delay")]
245 const fn py_time_bars_build_delay(&self) -> u64 {
246 self.time_bars_build_delay
247 }
248
249 #[getter]
250 #[pyo3(name = "time_bars_origin_offset")]
251 fn py_time_bars_origin_offset(&self) -> HashMap<String, u64> {
252 self.time_bars_origin_offset.clone()
253 }
254
255 #[getter]
256 #[pyo3(name = "validate_data_sequence")]
257 const fn py_validate_data_sequence(&self) -> bool {
258 self.validate_data_sequence
259 }
260
261 #[getter]
262 #[pyo3(name = "buffer_deltas")]
263 const fn py_buffer_deltas(&self) -> bool {
264 self.buffer_deltas
265 }
266
267 #[getter]
268 #[pyo3(name = "emit_quotes_from_book")]
269 const fn py_emit_quotes_from_book(&self) -> bool {
270 self.emit_quotes_from_book
271 }
272
273 #[getter]
274 #[pyo3(name = "emit_quotes_from_book_depths")]
275 const fn py_emit_quotes_from_book_depths(&self) -> bool {
276 self.emit_quotes_from_book_depths
277 }
278
279 #[getter]
280 #[pyo3(name = "external_clients")]
281 fn py_external_clients(&self) -> Option<Vec<ClientId>> {
282 self.external_clients.clone()
283 }
284
285 #[getter]
286 #[pyo3(name = "debug")]
287 const fn py_debug(&self) -> bool {
288 self.debug
289 }
290
291 fn __repr__(&self) -> String {
292 format!("{self:?}")
293 }
294
295 fn __str__(&self) -> String {
296 format!("{self:?}")
297 }
298}
299
300#[pyo3_stub_gen::derive::gen_stub_pymethods]
301#[pymethods]
302impl LiveRiskEngineConfig {
303 #[new]
305 #[pyo3(signature = (bypass=None, max_order_submit_rate=None, max_order_modify_rate=None, max_notional_per_order=None, full_position_exit_venues=None, debug=None))]
306 fn py_new(
307 bypass: Option<bool>,
308 max_order_submit_rate: Option<String>,
309 max_order_modify_rate: Option<String>,
310 max_notional_per_order: Option<HashMap<String, Py<PyAny>>>,
311 full_position_exit_venues: Option<Vec<Venue>>,
312 debug: Option<bool>,
313 ) -> PyResult<Self> {
314 let default = Self::default();
315 let max_order_submit_rate =
316 max_order_submit_rate.unwrap_or_else(|| default.max_order_submit_rate.clone());
317 let max_order_modify_rate =
318 max_order_modify_rate.unwrap_or_else(|| default.max_order_modify_rate.clone());
319 let max_notional_per_order = match max_notional_per_order {
320 Some(raw) => coerce_max_notional_per_order(raw)?,
321 None => HashMap::new(),
322 };
323 let full_position_exit_venues = full_position_exit_venues.unwrap_or_default();
324
325 parse_rate_limit(
326 "LiveRiskEngineConfig.max_order_submit_rate",
327 &max_order_submit_rate,
328 )
329 .map_err(config_error_to_pyvalue_err)?;
330 parse_rate_limit(
331 "LiveRiskEngineConfig.max_order_modify_rate",
332 &max_order_modify_rate,
333 )
334 .map_err(config_error_to_pyvalue_err)?;
335 validate_max_notional_per_order(
336 "LiveRiskEngineConfig.max_notional_per_order",
337 &max_notional_per_order,
338 )
339 .map_err(config_error_to_pyvalue_err)?;
340
341 Ok(Self {
342 bypass: bypass.unwrap_or(default.bypass),
343 max_order_submit_rate,
344 max_order_modify_rate,
345 max_notional_per_order,
346 full_position_exit_venues,
347 debug: debug.unwrap_or(default.debug),
348 qsize: default.qsize,
349 })
350 }
351
352 #[getter]
353 #[pyo3(name = "bypass")]
354 const fn py_bypass(&self) -> bool {
355 self.bypass
356 }
357
358 #[getter]
359 #[pyo3(name = "max_order_submit_rate")]
360 fn py_max_order_submit_rate(&self) -> &str {
361 &self.max_order_submit_rate
362 }
363
364 #[getter]
365 #[pyo3(name = "max_order_modify_rate")]
366 fn py_max_order_modify_rate(&self) -> &str {
367 &self.max_order_modify_rate
368 }
369
370 #[getter]
371 #[pyo3(name = "max_notional_per_order")]
372 fn py_max_notional_per_order(&self) -> HashMap<String, String> {
373 self.max_notional_per_order.clone()
374 }
375
376 #[getter]
377 #[pyo3(name = "full_position_exit_venues")]
378 fn py_full_position_exit_venues(&self) -> Vec<Venue> {
379 self.full_position_exit_venues.clone()
380 }
381
382 #[getter]
383 #[pyo3(name = "debug")]
384 const fn py_debug(&self) -> bool {
385 self.debug
386 }
387
388 fn __repr__(&self) -> String {
389 format!("{self:?}")
390 }
391
392 fn __str__(&self) -> String {
393 format!("{self:?}")
394 }
395}
396
397#[pyo3_stub_gen::derive::gen_stub_pymethods]
398#[pymethods]
399impl LiveExecutionEngineConfig {
400 #[new]
402 #[expect(clippy::too_many_arguments)]
403 #[pyo3(signature = (load_cache=None, manage_own_order_books=None, snapshot_positions_interval_secs=None, external_clients=None, allow_overfills=None, reconciliation=None, reconciliation_startup_delay_secs=None, reconciliation_lookback_mins=None, reconciliation_instrument_ids=None, filter_unclaimed_external_orders=None, filter_position_reports=None, filtered_client_order_ids=None, generate_missing_orders=None, inflight_check_interval_ms=None, inflight_check_threshold_ms=None, inflight_check_retries=None, open_check_interval_secs=None, open_check_lookback_mins=None, open_check_threshold_ms=None, open_check_missing_retries=None, open_check_open_only=None, max_single_order_queries_per_cycle=None, single_order_query_delay_ms=None, position_check_interval_secs=None, position_check_lookback_mins=None, position_check_threshold_ms=None, position_check_retries=None, purge_closed_orders_interval_mins=None, purge_closed_orders_buffer_mins=None, purge_closed_positions_interval_mins=None, purge_closed_positions_buffer_mins=None, purge_account_events_interval_mins=None, purge_account_events_lookback_mins=None, own_books_audit_interval_secs=None, debug=None, snapshot_orders=None, snapshot_positions=None))]
404 fn py_new(
405 load_cache: Option<bool>,
406 manage_own_order_books: Option<bool>,
407 snapshot_positions_interval_secs: Option<f64>,
408 external_clients: Option<Vec<ClientId>>,
409 allow_overfills: Option<bool>,
410 reconciliation: Option<bool>,
411 reconciliation_startup_delay_secs: Option<f64>,
412 reconciliation_lookback_mins: Option<u32>,
413 reconciliation_instrument_ids: Option<Vec<String>>,
414 filter_unclaimed_external_orders: Option<bool>,
415 filter_position_reports: Option<bool>,
416 filtered_client_order_ids: Option<Vec<String>>,
417 generate_missing_orders: Option<bool>,
418 inflight_check_interval_ms: Option<u32>,
419 inflight_check_threshold_ms: Option<u32>,
420 inflight_check_retries: Option<u32>,
421 open_check_interval_secs: Option<f64>,
422 open_check_lookback_mins: Option<u32>,
423 open_check_threshold_ms: Option<u32>,
424 open_check_missing_retries: Option<u32>,
425 open_check_open_only: Option<bool>,
426 max_single_order_queries_per_cycle: Option<u32>,
427 single_order_query_delay_ms: Option<u32>,
428 position_check_interval_secs: Option<f64>,
429 position_check_lookback_mins: Option<u32>,
430 position_check_threshold_ms: Option<u32>,
431 position_check_retries: Option<u32>,
432 purge_closed_orders_interval_mins: Option<u32>,
433 purge_closed_orders_buffer_mins: Option<u32>,
434 purge_closed_positions_interval_mins: Option<u32>,
435 purge_closed_positions_buffer_mins: Option<u32>,
436 purge_account_events_interval_mins: Option<u32>,
437 purge_account_events_lookback_mins: Option<u32>,
438 own_books_audit_interval_secs: Option<f64>,
439 debug: Option<bool>,
440 snapshot_orders: Option<bool>,
441 snapshot_positions: Option<bool>,
442 ) -> PyResult<Self> {
443 let default = Self::default();
444
445 let config = Self {
446 load_cache: load_cache.unwrap_or(default.load_cache),
447 manage_own_order_books: manage_own_order_books
448 .unwrap_or(default.manage_own_order_books),
449 snapshot_orders: snapshot_orders.unwrap_or(default.snapshot_orders),
450 snapshot_positions: snapshot_positions.unwrap_or(default.snapshot_positions),
451 snapshot_positions_interval_secs,
452 external_clients,
453 allow_overfills: allow_overfills.unwrap_or(default.allow_overfills),
454 reconciliation: reconciliation.unwrap_or(default.reconciliation),
455 reconciliation_startup_delay_secs: reconciliation_startup_delay_secs
456 .unwrap_or(default.reconciliation_startup_delay_secs),
457 reconciliation_lookback_mins,
458 reconciliation_instrument_ids,
459 filter_unclaimed_external_orders: filter_unclaimed_external_orders
460 .unwrap_or(default.filter_unclaimed_external_orders),
461 filter_position_reports: filter_position_reports
462 .unwrap_or(default.filter_position_reports),
463 filtered_client_order_ids,
464 generate_missing_orders: generate_missing_orders
465 .unwrap_or(default.generate_missing_orders),
466 inflight_check_interval_ms: inflight_check_interval_ms
467 .unwrap_or(default.inflight_check_interval_ms),
468 inflight_check_threshold_ms: inflight_check_threshold_ms
469 .unwrap_or(default.inflight_check_threshold_ms),
470 inflight_check_retries: inflight_check_retries
471 .unwrap_or(default.inflight_check_retries),
472 open_check_interval_secs,
473 open_check_lookback_mins: open_check_lookback_mins.or(default.open_check_lookback_mins),
474 open_check_threshold_ms: open_check_threshold_ms
475 .unwrap_or(default.open_check_threshold_ms),
476 open_check_missing_retries: open_check_missing_retries
477 .unwrap_or(default.open_check_missing_retries),
478 open_check_open_only: open_check_open_only.unwrap_or(default.open_check_open_only),
479 max_single_order_queries_per_cycle: max_single_order_queries_per_cycle
480 .unwrap_or(default.max_single_order_queries_per_cycle),
481 single_order_query_delay_ms: single_order_query_delay_ms
482 .unwrap_or(default.single_order_query_delay_ms),
483 position_check_interval_secs,
484 position_check_lookback_mins: position_check_lookback_mins
485 .unwrap_or(default.position_check_lookback_mins),
486 position_check_threshold_ms: position_check_threshold_ms
487 .unwrap_or(default.position_check_threshold_ms),
488 position_check_retries: position_check_retries
489 .unwrap_or(default.position_check_retries),
490 purge_closed_orders_interval_mins,
491 purge_closed_orders_buffer_mins,
492 purge_closed_positions_interval_mins,
493 purge_closed_positions_buffer_mins,
494 purge_account_events_interval_mins,
495 purge_account_events_lookback_mins,
496 purge_from_database: default.purge_from_database,
497 debug: debug.unwrap_or(default.debug),
498 own_books_audit_interval_secs,
499 qsize: default.qsize,
500 };
501 config
502 .validate_runtime_support()
503 .map_err(config_error_to_pyvalue_err)?;
504 Ok(config)
505 }
506
507 #[getter]
508 #[pyo3(name = "load_cache")]
509 const fn py_load_cache(&self) -> bool {
510 self.load_cache
511 }
512
513 #[getter]
514 #[pyo3(name = "manage_own_order_books")]
515 const fn py_manage_own_order_books(&self) -> bool {
516 self.manage_own_order_books
517 }
518
519 #[getter]
520 #[pyo3(name = "snapshot_orders")]
521 const fn py_snapshot_orders(&self) -> bool {
522 self.snapshot_orders
523 }
524
525 #[getter]
526 #[pyo3(name = "snapshot_positions")]
527 const fn py_snapshot_positions(&self) -> bool {
528 self.snapshot_positions
529 }
530
531 #[getter]
532 #[pyo3(name = "snapshot_positions_interval_secs")]
533 const fn py_snapshot_positions_interval_secs(&self) -> Option<f64> {
534 self.snapshot_positions_interval_secs
535 }
536
537 #[getter]
538 #[pyo3(name = "external_clients")]
539 fn py_external_clients(&self) -> Option<Vec<ClientId>> {
540 self.external_clients.clone()
541 }
542
543 #[getter]
544 #[pyo3(name = "allow_overfills")]
545 const fn py_allow_overfills(&self) -> bool {
546 self.allow_overfills
547 }
548
549 #[getter]
550 #[pyo3(name = "reconciliation")]
551 const fn py_reconciliation(&self) -> bool {
552 self.reconciliation
553 }
554
555 #[getter]
556 #[pyo3(name = "reconciliation_startup_delay_secs")]
557 const fn py_reconciliation_startup_delay_secs(&self) -> f64 {
558 self.reconciliation_startup_delay_secs
559 }
560
561 #[getter]
562 #[pyo3(name = "reconciliation_lookback_mins")]
563 const fn py_reconciliation_lookback_mins(&self) -> Option<u32> {
564 self.reconciliation_lookback_mins
565 }
566
567 #[getter]
568 #[pyo3(name = "reconciliation_instrument_ids")]
569 fn py_reconciliation_instrument_ids(&self) -> Option<Vec<String>> {
570 self.reconciliation_instrument_ids.clone()
571 }
572
573 #[getter]
574 #[pyo3(name = "filter_unclaimed_external_orders")]
575 const fn py_filter_unclaimed_external_orders(&self) -> bool {
576 self.filter_unclaimed_external_orders
577 }
578
579 #[getter]
580 #[pyo3(name = "filter_position_reports")]
581 const fn py_filter_position_reports(&self) -> bool {
582 self.filter_position_reports
583 }
584
585 #[getter]
586 #[pyo3(name = "filtered_client_order_ids")]
587 fn py_filtered_client_order_ids(&self) -> Option<Vec<String>> {
588 self.filtered_client_order_ids.clone()
589 }
590
591 #[getter]
592 #[pyo3(name = "generate_missing_orders")]
593 const fn py_generate_missing_orders(&self) -> bool {
594 self.generate_missing_orders
595 }
596
597 #[getter]
598 #[pyo3(name = "inflight_check_interval_ms")]
599 const fn py_inflight_check_interval_ms(&self) -> u32 {
600 self.inflight_check_interval_ms
601 }
602
603 #[getter]
604 #[pyo3(name = "inflight_check_threshold_ms")]
605 const fn py_inflight_check_threshold_ms(&self) -> u32 {
606 self.inflight_check_threshold_ms
607 }
608
609 #[getter]
610 #[pyo3(name = "inflight_check_retries")]
611 const fn py_inflight_check_retries(&self) -> u32 {
612 self.inflight_check_retries
613 }
614
615 #[getter]
616 #[pyo3(name = "open_check_interval_secs")]
617 const fn py_open_check_interval_secs(&self) -> Option<f64> {
618 self.open_check_interval_secs
619 }
620
621 #[getter]
622 #[pyo3(name = "open_check_lookback_mins")]
623 const fn py_open_check_lookback_mins(&self) -> Option<u32> {
624 self.open_check_lookback_mins
625 }
626
627 #[getter]
628 #[pyo3(name = "open_check_threshold_ms")]
629 const fn py_open_check_threshold_ms(&self) -> u32 {
630 self.open_check_threshold_ms
631 }
632
633 #[getter]
634 #[pyo3(name = "open_check_missing_retries")]
635 const fn py_open_check_missing_retries(&self) -> u32 {
636 self.open_check_missing_retries
637 }
638
639 #[getter]
640 #[pyo3(name = "open_check_open_only")]
641 const fn py_open_check_open_only(&self) -> bool {
642 self.open_check_open_only
643 }
644
645 #[getter]
646 #[pyo3(name = "max_single_order_queries_per_cycle")]
647 const fn py_max_single_order_queries_per_cycle(&self) -> u32 {
648 self.max_single_order_queries_per_cycle
649 }
650
651 #[getter]
652 #[pyo3(name = "single_order_query_delay_ms")]
653 const fn py_single_order_query_delay_ms(&self) -> u32 {
654 self.single_order_query_delay_ms
655 }
656
657 #[getter]
658 #[pyo3(name = "position_check_interval_secs")]
659 const fn py_position_check_interval_secs(&self) -> Option<f64> {
660 self.position_check_interval_secs
661 }
662
663 #[getter]
664 #[pyo3(name = "position_check_lookback_mins")]
665 const fn py_position_check_lookback_mins(&self) -> u32 {
666 self.position_check_lookback_mins
667 }
668
669 #[getter]
670 #[pyo3(name = "position_check_threshold_ms")]
671 const fn py_position_check_threshold_ms(&self) -> u32 {
672 self.position_check_threshold_ms
673 }
674
675 #[getter]
676 #[pyo3(name = "position_check_retries")]
677 const fn py_position_check_retries(&self) -> u32 {
678 self.position_check_retries
679 }
680
681 #[getter]
682 #[pyo3(name = "purge_closed_orders_interval_mins")]
683 const fn py_purge_closed_orders_interval_mins(&self) -> Option<u32> {
684 self.purge_closed_orders_interval_mins
685 }
686
687 #[getter]
688 #[pyo3(name = "purge_closed_orders_buffer_mins")]
689 const fn py_purge_closed_orders_buffer_mins(&self) -> Option<u32> {
690 self.purge_closed_orders_buffer_mins
691 }
692
693 #[getter]
694 #[pyo3(name = "purge_closed_positions_interval_mins")]
695 const fn py_purge_closed_positions_interval_mins(&self) -> Option<u32> {
696 self.purge_closed_positions_interval_mins
697 }
698
699 #[getter]
700 #[pyo3(name = "purge_closed_positions_buffer_mins")]
701 const fn py_purge_closed_positions_buffer_mins(&self) -> Option<u32> {
702 self.purge_closed_positions_buffer_mins
703 }
704
705 #[getter]
706 #[pyo3(name = "purge_account_events_interval_mins")]
707 const fn py_purge_account_events_interval_mins(&self) -> Option<u32> {
708 self.purge_account_events_interval_mins
709 }
710
711 #[getter]
712 #[pyo3(name = "purge_account_events_lookback_mins")]
713 const fn py_purge_account_events_lookback_mins(&self) -> Option<u32> {
714 self.purge_account_events_lookback_mins
715 }
716
717 #[getter]
718 #[pyo3(name = "own_books_audit_interval_secs")]
719 const fn py_own_books_audit_interval_secs(&self) -> Option<f64> {
720 self.own_books_audit_interval_secs
721 }
722
723 #[getter]
724 #[pyo3(name = "debug")]
725 const fn py_debug(&self) -> bool {
726 self.debug
727 }
728
729 fn __repr__(&self) -> String {
730 format!("{self:?}")
731 }
732
733 fn __str__(&self) -> String {
734 format!("{self:?}")
735 }
736}
737
738#[pyo3_stub_gen::derive::gen_stub_pymethods]
739#[pymethods]
740impl RoutingConfig {
741 #[new]
743 #[pyo3(signature = (default=None, venues=None))]
744 fn py_new(default: Option<bool>, venues: Option<Vec<String>>) -> Self {
745 Self {
746 default: default.unwrap_or(false),
747 venues,
748 }
749 }
750
751 fn __repr__(&self) -> String {
752 format!("{self:?}")
753 }
754
755 fn __str__(&self) -> String {
756 format!("{self:?}")
757 }
758
759 #[getter]
760 fn default(&self) -> bool {
761 self.default
762 }
763
764 #[getter]
765 fn venues(&self) -> Option<Vec<String>> {
766 self.venues.clone()
767 }
768}
769
770#[pyo3_stub_gen::derive::gen_stub_pymethods]
771#[pymethods]
772impl InstrumentProviderConfig {
773 #[new]
775 #[allow(
776 clippy::needless_pass_by_value,
777 reason = "PyO3 #[new] requires owned params"
778 )]
779 #[pyo3(signature = (load_all=None, load_ids=None, filters=None, filter_callable=None, log_warnings=None))]
780 fn py_new(
781 load_all: Option<bool>,
782 load_ids: Option<Vec<String>>,
783 filters: Option<HashMap<String, Py<PyAny>>>,
784 filter_callable: Option<String>,
785 log_warnings: Option<bool>,
786 ) -> PyResult<Self> {
787 let default = Self::default();
788 let filters = match filters {
789 Some(raw) => coerce_json_config(raw)?,
790 None => HashMap::new(),
791 };
792 Ok(Self {
793 load_all: load_all.unwrap_or(default.load_all),
794 load_ids,
795 filters,
796 filter_callable,
797 log_warnings: log_warnings.unwrap_or(default.log_warnings),
798 })
799 }
800
801 fn __repr__(&self) -> String {
802 format!("{self:?}")
803 }
804
805 fn __str__(&self) -> String {
806 format!("{self:?}")
807 }
808
809 #[getter]
810 fn load_all(&self) -> bool {
811 self.load_all
812 }
813
814 #[getter]
815 fn load_ids(&self) -> Option<Vec<String>> {
816 self.load_ids.clone()
817 }
818
819 #[getter]
820 fn filters(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
821 let dict = pyo3::types::PyDict::new(py);
822 for (k, v) in &self.filters {
823 let py_val = json_value_to_py(py, v)?;
824 dict.set_item(k, py_val)?;
825 }
826 Ok(dict.into_any().unbind())
827 }
828
829 #[getter]
830 fn filter_callable(&self) -> Option<String> {
831 self.filter_callable.clone()
832 }
833
834 #[getter]
835 fn log_warnings(&self) -> bool {
836 self.log_warnings
837 }
838}
839
840#[pyo3_stub_gen::derive::gen_stub_pymethods]
841#[pymethods]
842impl DataClientConfig {
843 #[new]
845 #[pyo3(signature = (handle_revised_bars=None, instrument_provider=None, routing=None))]
846 fn py_new(
847 handle_revised_bars: Option<bool>,
848 instrument_provider: Option<InstrumentProviderConfig>,
849 routing: Option<RoutingConfig>,
850 ) -> Self {
851 Self {
852 handle_revised_bars: handle_revised_bars.unwrap_or(false),
853 instrument_provider: instrument_provider.unwrap_or_default(),
854 routing: routing.unwrap_or_default(),
855 }
856 }
857
858 fn __repr__(&self) -> String {
859 format!("{self:?}")
860 }
861
862 fn __str__(&self) -> String {
863 format!("{self:?}")
864 }
865
866 #[getter]
867 fn handle_revised_bars(&self) -> bool {
868 self.handle_revised_bars
869 }
870
871 #[getter]
872 fn instrument_provider(&self) -> InstrumentProviderConfig {
873 self.instrument_provider.clone()
874 }
875
876 #[getter]
877 fn routing(&self) -> RoutingConfig {
878 self.routing.clone()
879 }
880}
881
882#[pyo3_stub_gen::derive::gen_stub_pymethods]
883#[pymethods]
884impl ExecutionClientConfig {
885 #[new]
887 #[pyo3(signature = (instrument_provider=None, routing=None))]
888 fn py_new(
889 instrument_provider: Option<InstrumentProviderConfig>,
890 routing: Option<RoutingConfig>,
891 ) -> Self {
892 Self {
893 instrument_provider: instrument_provider.unwrap_or_default(),
894 routing: routing.unwrap_or_default(),
895 }
896 }
897
898 fn __repr__(&self) -> String {
899 format!("{self:?}")
900 }
901
902 fn __str__(&self) -> String {
903 format!("{self:?}")
904 }
905
906 #[getter]
907 fn instrument_provider(&self) -> InstrumentProviderConfig {
908 self.instrument_provider.clone()
909 }
910
911 #[getter]
912 fn routing(&self) -> RoutingConfig {
913 self.routing.clone()
914 }
915}
916
917#[pyo3_stub_gen::derive::gen_stub_pymethods]
918#[pymethods]
919impl PluginConfig {
920 #[new]
922 #[pyo3(signature = (path, type_name, config=None, sha256=None))]
923 fn py_new(
924 path: String,
925 type_name: String,
926 config: Option<HashMap<String, Py<PyAny>>>,
927 sha256: Option<String>,
928 ) -> PyResult<Self> {
929 let config = match config {
930 Some(config) => coerce_json_config(config)?,
931 None => HashMap::new(),
932 };
933
934 Ok(Self {
935 path,
936 type_name,
937 config,
938 sha256,
939 })
940 }
941
942 #[getter]
943 fn path(&self) -> &str {
944 &self.path
945 }
946
947 #[getter]
948 fn type_name(&self) -> &str {
949 &self.type_name
950 }
951
952 #[getter]
953 fn config(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
954 let dict = PyDict::new(py);
955 for (key, value) in &self.config {
956 dict.set_item(key, json_value_to_py(py, value)?)?;
957 }
958 Ok(dict.unbind())
959 }
960
961 #[getter]
962 fn sha256(&self) -> Option<&str> {
963 self.sha256.as_deref()
964 }
965}
966
967#[pyo3_stub_gen::derive::gen_stub_pymethods]
968#[pymethods]
969impl QueueMonitorConfig {
970 #[new]
972 const fn py_new(
973 queue_depth_trigger: usize,
974 queue_depth_clear: usize,
975 mean_dispatch_ns_trigger: u64,
976 mean_dispatch_ns_clear: u64,
977 ) -> Self {
978 Self {
979 queue_depth_trigger,
980 queue_depth_clear,
981 mean_dispatch_ns_trigger,
982 mean_dispatch_ns_clear,
983 }
984 }
985
986 fn __repr__(&self) -> String {
987 format!("{self:?}")
988 }
989
990 fn __str__(&self) -> String {
991 format!("{self:?}")
992 }
993
994 #[getter]
995 const fn queue_depth_trigger(&self) -> usize {
996 self.queue_depth_trigger
997 }
998
999 #[getter]
1000 const fn queue_depth_clear(&self) -> usize {
1001 self.queue_depth_clear
1002 }
1003
1004 #[getter]
1005 const fn mean_dispatch_ns_trigger(&self) -> u64 {
1006 self.mean_dispatch_ns_trigger
1007 }
1008
1009 #[getter]
1010 const fn mean_dispatch_ns_clear(&self) -> u64 {
1011 self.mean_dispatch_ns_clear
1012 }
1013}
1014
1015#[pyo3_stub_gen::derive::gen_stub_pymethods]
1016#[pymethods]
1017impl LiveNodeConfig {
1018 #[new]
1020 #[expect(clippy::too_many_arguments)]
1021 #[pyo3(signature = (environment=None, trader_id=None, load_state=None, save_state=None, shutdown_on_error=None, logging=None, instance_id=None, timeout_connection_secs=None, timeout_reconciliation_secs=None, timeout_portfolio_secs=None, timeout_disconnection_secs=None, delay_post_stop_secs=None, timeout_shutdown_secs=None, cache=None, msgbus=None, portfolio=None, queue_monitor=None, loop_debug=None, data_engine=None, risk_engine=None, exec_engine=None, controller=None, plugins=None))]
1022 fn py_new(
1023 environment: Option<Environment>,
1024 trader_id: Option<TraderId>,
1025 load_state: Option<bool>,
1026 save_state: Option<bool>,
1027 shutdown_on_error: Option<bool>,
1028 logging: Option<LoggerConfig>,
1029 instance_id: Option<UUID4>,
1030 timeout_connection_secs: Option<f64>,
1031 timeout_reconciliation_secs: Option<f64>,
1032 timeout_portfolio_secs: Option<f64>,
1033 timeout_disconnection_secs: Option<f64>,
1034 delay_post_stop_secs: Option<f64>,
1035 timeout_shutdown_secs: Option<f64>,
1036 cache: Option<CacheConfig>,
1037 msgbus: Option<MessageBusConfig>,
1038 portfolio: Option<PortfolioConfig>,
1039 queue_monitor: Option<QueueMonitorConfig>,
1040 loop_debug: Option<bool>,
1041 data_engine: Option<LiveDataEngineConfig>,
1042 risk_engine: Option<LiveRiskEngineConfig>,
1043 exec_engine: Option<LiveExecutionEngineConfig>,
1044 controller: Option<ImportableControllerConfig>,
1045 plugins: Option<Vec<PluginConfig>>,
1046 ) -> PyResult<Self> {
1047 let default = Self::default();
1048
1049 let to_duration = |value: f64, name: &str| -> PyResult<Duration> {
1050 duration_from_secs_f64(name, value).map_err(config_error_to_pyvalue_err)
1051 };
1052
1053 Ok(Self {
1054 environment: environment.unwrap_or(default.environment),
1055 trader_id: trader_id.unwrap_or(default.trader_id),
1056 load_state: load_state.unwrap_or(default.load_state),
1057 save_state: save_state.unwrap_or(default.save_state),
1058 shutdown_on_error: shutdown_on_error.unwrap_or(default.shutdown_on_error),
1059 logging: logging.unwrap_or(default.logging),
1060 instance_id,
1061 timeout_connection: to_duration(
1062 timeout_connection_secs.unwrap_or(default.timeout_connection.as_secs_f64()),
1063 "timeout_connection_secs",
1064 )?,
1065 timeout_reconciliation: to_duration(
1066 timeout_reconciliation_secs.unwrap_or(default.timeout_reconciliation.as_secs_f64()),
1067 "timeout_reconciliation_secs",
1068 )?,
1069 timeout_portfolio: to_duration(
1070 timeout_portfolio_secs.unwrap_or(default.timeout_portfolio.as_secs_f64()),
1071 "timeout_portfolio_secs",
1072 )?,
1073 timeout_disconnection: to_duration(
1074 timeout_disconnection_secs.unwrap_or(default.timeout_disconnection.as_secs_f64()),
1075 "timeout_disconnection_secs",
1076 )?,
1077 delay_post_stop: to_duration(
1078 delay_post_stop_secs.unwrap_or(default.delay_post_stop.as_secs_f64()),
1079 "delay_post_stop_secs",
1080 )?,
1081 timeout_shutdown: to_duration(
1082 timeout_shutdown_secs.unwrap_or(default.timeout_shutdown.as_secs_f64()),
1083 "timeout_shutdown_secs",
1084 )?,
1085 cache,
1086 msgbus,
1087 portfolio,
1088 emulator: None,
1089 streaming: None,
1090 queue_monitor,
1091 event_store: None,
1092 loop_debug: loop_debug.unwrap_or(false),
1093 data_engine: data_engine.unwrap_or_default(),
1094 risk_engine: risk_engine.unwrap_or_default(),
1095 exec_engine: exec_engine.unwrap_or_default(),
1096 data_clients: HashMap::new(),
1097 exec_clients: HashMap::new(),
1098 controller,
1099 plugins: plugins.unwrap_or_default(),
1100 })
1101 }
1102
1103 fn __repr__(&self) -> String {
1104 format!("{self:?}")
1105 }
1106
1107 fn __str__(&self) -> String {
1108 format!("{self:?}")
1109 }
1110
1111 #[getter]
1112 fn environment(&self) -> Environment {
1113 self.environment
1114 }
1115
1116 #[getter]
1117 fn trader_id(&self) -> TraderId {
1118 self.trader_id
1119 }
1120
1121 #[getter]
1122 fn load_state(&self) -> bool {
1123 self.load_state
1124 }
1125
1126 #[getter]
1127 fn save_state(&self) -> bool {
1128 self.save_state
1129 }
1130
1131 #[getter]
1132 fn shutdown_on_error(&self) -> bool {
1133 self.shutdown_on_error
1134 }
1135
1136 #[getter]
1137 fn timeout_connection_secs(&self) -> f64 {
1138 self.timeout_connection.as_secs_f64()
1139 }
1140
1141 #[getter]
1142 fn timeout_reconciliation_secs(&self) -> f64 {
1143 self.timeout_reconciliation.as_secs_f64()
1144 }
1145
1146 #[getter]
1147 fn timeout_portfolio_secs(&self) -> f64 {
1148 self.timeout_portfolio.as_secs_f64()
1149 }
1150
1151 #[getter]
1152 fn timeout_disconnection_secs(&self) -> f64 {
1153 self.timeout_disconnection.as_secs_f64()
1154 }
1155
1156 #[getter]
1157 fn delay_post_stop_secs(&self) -> f64 {
1158 self.delay_post_stop.as_secs_f64()
1159 }
1160
1161 #[getter]
1162 fn timeout_shutdown_secs(&self) -> f64 {
1163 self.timeout_shutdown.as_secs_f64()
1164 }
1165
1166 #[getter]
1167 #[pyo3(name = "logging")]
1168 fn py_logging(&self) -> LoggerConfig {
1169 self.logging.clone()
1170 }
1171
1172 #[getter]
1173 #[pyo3(name = "instance_id")]
1174 const fn py_instance_id(&self) -> Option<UUID4> {
1175 self.instance_id
1176 }
1177
1178 #[getter]
1179 #[pyo3(name = "cache")]
1180 fn py_cache(&self) -> Option<CacheConfig> {
1181 self.cache.clone()
1182 }
1183
1184 #[getter]
1185 #[pyo3(name = "msgbus")]
1186 fn py_msgbus(&self) -> Option<MessageBusConfig> {
1187 self.msgbus.clone()
1188 }
1189
1190 #[getter]
1191 #[pyo3(name = "portfolio")]
1192 fn py_portfolio(&self) -> Option<PortfolioConfig> {
1193 self.portfolio
1194 }
1195
1196 #[getter]
1197 #[pyo3(name = "queue_monitor")]
1198 fn py_queue_monitor(&self) -> Option<QueueMonitorConfig> {
1199 self.queue_monitor.clone()
1200 }
1201
1202 #[getter]
1203 #[pyo3(name = "loop_debug")]
1204 const fn py_loop_debug(&self) -> bool {
1205 self.loop_debug
1206 }
1207
1208 #[getter]
1209 #[pyo3(name = "data_engine")]
1210 fn py_data_engine(&self) -> LiveDataEngineConfig {
1211 self.data_engine.clone()
1212 }
1213
1214 #[getter]
1215 #[pyo3(name = "risk_engine")]
1216 fn py_risk_engine(&self) -> LiveRiskEngineConfig {
1217 self.risk_engine.clone()
1218 }
1219
1220 #[getter]
1221 #[pyo3(name = "exec_engine")]
1222 fn py_exec_engine(&self) -> LiveExecutionEngineConfig {
1223 self.exec_engine.clone()
1224 }
1225
1226 #[getter]
1227 fn plugins(&self) -> Vec<PluginConfig> {
1228 self.plugins.clone()
1229 }
1230
1231 #[getter]
1232 fn controller(&self) -> Option<ImportableControllerConfig> {
1233 self.controller.clone()
1234 }
1235}