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