1use std::{collections::HashMap, 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},
26};
27use nautilus_portfolio::config::PortfolioConfig;
28use pyo3::{
29 IntoPyObject, Py, PyAny, PyResult, Python, pymethods,
30 types::{PyAnyMethods, PyDict, PyDictMethods},
31};
32
33use crate::config::{
34 InstrumentProviderConfig, LiveDataClientConfig, LiveDataEngineConfig, LiveExecClientConfig,
35 LiveExecEngineConfig, LiveNodeConfig, LiveRiskEngineConfig, PluginConfig, RoutingConfig,
36 duration_from_secs_f64, parse_rate_limit, validate_client_order_id_strings,
37 validate_instrument_id_strings, validate_max_notional_per_order,
38 validate_non_negative_finite_f64,
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
97fn json_value_to_py(py: Python<'_>, value: &serde_json::Value) -> PyResult<Py<PyAny>> {
99 match value {
100 serde_json::Value::Null => Ok(py.None()),
101 serde_json::Value::Bool(b) => Ok((*b).into_pyobject(py)?.to_owned().into_any().unbind()),
102 serde_json::Value::Number(n) => {
103 if let Some(i) = n.as_i64() {
104 Ok(i.into_pyobject(py)?.into_any().unbind())
105 } else if let Some(f) = n.as_f64() {
106 Ok(f.into_pyobject(py)?.into_any().unbind())
107 } else {
108 Ok(n.to_string().into_pyobject(py)?.into_any().unbind())
109 }
110 }
111 serde_json::Value::String(s) => Ok(s.into_pyobject(py)?.into_any().unbind()),
112 serde_json::Value::Array(arr) => {
113 let items: Vec<Py<PyAny>> = arr
114 .iter()
115 .map(|v| json_value_to_py(py, v))
116 .collect::<PyResult<_>>()?;
117 Ok(pyo3::types::PyList::new(py, items)?.into_any().unbind())
118 }
119 serde_json::Value::Object(obj) => {
120 let dict = pyo3::types::PyDict::new(py);
121 for (k, v) in obj {
122 dict.set_item(k, json_value_to_py(py, v)?)?;
123 }
124 Ok(dict.into_any().unbind())
125 }
126 }
127}
128
129pub(crate) fn coerce_json_config(
131 raw: HashMap<String, Py<PyAny>>,
132) -> PyResult<HashMap<String, serde_json::Value>> {
133 Python::attach(|py| -> PyResult<HashMap<String, serde_json::Value>> {
134 let mut result = HashMap::with_capacity(raw.len());
135 for (key, value) in raw {
136 let json_value = py_to_json_value(value.bind(py))?;
137 result.insert(key, json_value);
138 }
139 Ok(result)
140 })
141}
142
143fn coerce_max_notional_per_order(
147 raw: HashMap<String, Py<PyAny>>,
148) -> PyResult<HashMap<String, String>> {
149 Python::attach(|py| -> PyResult<HashMap<String, String>> {
150 let mut result = HashMap::with_capacity(raw.len());
151 for (instrument_id, value) in raw {
152 let value_str: String = value.bind(py).str()?.extract()?;
153 result.insert(instrument_id, value_str);
154 }
155 Ok(result)
156 })
157}
158
159#[pyo3_stub_gen::derive::gen_stub_pymethods]
160#[pymethods]
161impl LiveDataEngineConfig {
162 #[new]
164 #[expect(clippy::too_many_arguments)]
165 #[allow(
166 clippy::needless_pass_by_value,
167 reason = "PyO3 #[new] requires owned params"
168 )]
169 #[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))]
170 fn py_new(
171 time_bars_build_with_no_updates: Option<bool>,
172 time_bars_timestamp_on_close: Option<bool>,
173 time_bars_skip_first_non_full_bar: Option<bool>,
174 time_bars_interval_type: Option<Py<PyAny>>,
175 time_bars_build_delay: Option<u64>,
176 time_bars_origin_offset: Option<HashMap<String, u64>>,
177 validate_data_sequence: Option<bool>,
178 buffer_deltas: Option<bool>,
179 emit_quotes_from_book: Option<bool>,
180 emit_quotes_from_book_depths: Option<bool>,
181 external_clients: Option<Vec<ClientId>>,
182 debug: Option<bool>,
183 ) -> PyResult<Self> {
184 let default = Self::default();
185 let time_bars_interval_type = match time_bars_interval_type {
186 Some(ref obj) => coerce_bar_interval_type(obj)?,
187 None => default.time_bars_interval_type,
188 };
189 Ok(Self {
190 time_bars_build_with_no_updates: time_bars_build_with_no_updates
191 .unwrap_or(default.time_bars_build_with_no_updates),
192 time_bars_timestamp_on_close: time_bars_timestamp_on_close
193 .unwrap_or(default.time_bars_timestamp_on_close),
194 time_bars_skip_first_non_full_bar: time_bars_skip_first_non_full_bar
195 .unwrap_or(default.time_bars_skip_first_non_full_bar),
196 time_bars_interval_type,
197 time_bars_build_delay: time_bars_build_delay.unwrap_or(default.time_bars_build_delay),
198 time_bars_origin_offset: time_bars_origin_offset.unwrap_or_default(),
199 validate_data_sequence: validate_data_sequence
200 .unwrap_or(default.validate_data_sequence),
201 buffer_deltas: buffer_deltas.unwrap_or(default.buffer_deltas),
202 emit_quotes_from_book: emit_quotes_from_book.unwrap_or(default.emit_quotes_from_book),
203 emit_quotes_from_book_depths: emit_quotes_from_book_depths
204 .unwrap_or(default.emit_quotes_from_book_depths),
205 external_clients,
206 debug: debug.unwrap_or(default.debug),
207 qsize: default.qsize,
208 })
209 }
210
211 fn __repr__(&self) -> String {
212 format!("{self:?}")
213 }
214
215 fn __str__(&self) -> String {
216 format!("{self:?}")
217 }
218}
219
220#[pyo3_stub_gen::derive::gen_stub_pymethods]
221#[pymethods]
222impl LiveRiskEngineConfig {
223 #[new]
225 #[pyo3(signature = (bypass=None, max_order_submit_rate=None, max_order_modify_rate=None, max_notional_per_order=None, debug=None))]
226 fn py_new(
227 bypass: Option<bool>,
228 max_order_submit_rate: Option<String>,
229 max_order_modify_rate: Option<String>,
230 max_notional_per_order: Option<HashMap<String, Py<PyAny>>>,
231 debug: Option<bool>,
232 ) -> PyResult<Self> {
233 let default = Self::default();
234 let max_order_submit_rate =
235 max_order_submit_rate.unwrap_or_else(|| default.max_order_submit_rate.clone());
236 let max_order_modify_rate =
237 max_order_modify_rate.unwrap_or_else(|| default.max_order_modify_rate.clone());
238 let max_notional_per_order = match max_notional_per_order {
239 Some(raw) => coerce_max_notional_per_order(raw)?,
240 None => HashMap::new(),
241 };
242
243 parse_rate_limit(
244 "LiveRiskEngineConfig.max_order_submit_rate",
245 &max_order_submit_rate,
246 )
247 .map_err(config_error_to_pyvalue_err)?;
248 parse_rate_limit(
249 "LiveRiskEngineConfig.max_order_modify_rate",
250 &max_order_modify_rate,
251 )
252 .map_err(config_error_to_pyvalue_err)?;
253 validate_max_notional_per_order(
254 "LiveRiskEngineConfig.max_notional_per_order",
255 &max_notional_per_order,
256 )
257 .map_err(config_error_to_pyvalue_err)?;
258
259 Ok(Self {
260 bypass: bypass.unwrap_or(default.bypass),
261 max_order_submit_rate,
262 max_order_modify_rate,
263 max_notional_per_order,
264 debug: debug.unwrap_or(default.debug),
265 qsize: default.qsize,
266 })
267 }
268
269 fn __repr__(&self) -> String {
270 format!("{self:?}")
271 }
272
273 fn __str__(&self) -> String {
274 format!("{self:?}")
275 }
276}
277
278#[pyo3_stub_gen::derive::gen_stub_pymethods]
279#[pymethods]
280impl LiveExecEngineConfig {
281 #[new]
283 #[expect(clippy::too_many_arguments)]
284 #[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))]
285 fn py_new(
286 load_cache: Option<bool>,
287 manage_own_order_books: Option<bool>,
288 snapshot_positions_interval_secs: Option<f64>,
289 external_clients: Option<Vec<ClientId>>,
290 allow_overfills: Option<bool>,
291 reconciliation: Option<bool>,
292 reconciliation_startup_delay_secs: Option<f64>,
293 reconciliation_lookback_mins: Option<u32>,
294 reconciliation_instrument_ids: Option<Vec<String>>,
295 filter_unclaimed_external_orders: Option<bool>,
296 filter_position_reports: Option<bool>,
297 filtered_client_order_ids: Option<Vec<String>>,
298 generate_missing_orders: Option<bool>,
299 inflight_check_interval_ms: Option<u32>,
300 inflight_check_threshold_ms: Option<u32>,
301 inflight_check_retries: Option<u32>,
302 open_check_interval_secs: Option<f64>,
303 open_check_lookback_mins: Option<u32>,
304 open_check_threshold_ms: Option<u32>,
305 open_check_missing_retries: Option<u32>,
306 open_check_open_only: Option<bool>,
307 max_single_order_queries_per_cycle: Option<u32>,
308 single_order_query_delay_ms: Option<u32>,
309 position_check_interval_secs: Option<f64>,
310 position_check_lookback_mins: Option<u32>,
311 position_check_threshold_ms: Option<u32>,
312 position_check_retries: Option<u32>,
313 purge_closed_orders_interval_mins: Option<u32>,
314 purge_closed_orders_buffer_mins: Option<u32>,
315 purge_closed_positions_interval_mins: Option<u32>,
316 purge_closed_positions_buffer_mins: Option<u32>,
317 purge_account_events_interval_mins: Option<u32>,
318 purge_account_events_lookback_mins: Option<u32>,
319 own_books_audit_interval_secs: Option<f64>,
320 debug: Option<bool>,
321 ) -> PyResult<Self> {
322 let default = Self::default();
323
324 if let Some(delay) = reconciliation_startup_delay_secs {
325 validate_non_negative_finite_f64(
326 "LiveExecEngineConfig.reconciliation_startup_delay_secs",
327 delay,
328 )
329 .map_err(config_error_to_pyvalue_err)?;
330 }
331
332 if let Some(ids) = reconciliation_instrument_ids.as_ref() {
333 validate_instrument_id_strings(
334 "LiveExecEngineConfig.reconciliation_instrument_ids",
335 ids,
336 )
337 .map_err(config_error_to_pyvalue_err)?;
338 }
339
340 if let Some(ids) = filtered_client_order_ids.as_ref() {
341 validate_client_order_id_strings("LiveExecEngineConfig.filtered_client_order_ids", ids)
342 .map_err(config_error_to_pyvalue_err)?;
343 }
344
345 Ok(Self {
346 load_cache: load_cache.unwrap_or(default.load_cache),
347 manage_own_order_books: manage_own_order_books
348 .unwrap_or(default.manage_own_order_books),
349 snapshot_orders: default.snapshot_orders,
350 snapshot_positions: default.snapshot_positions,
351 snapshot_positions_interval_secs,
352 external_clients,
353 allow_overfills: allow_overfills.unwrap_or(default.allow_overfills),
354 reconciliation: reconciliation.unwrap_or(default.reconciliation),
355 reconciliation_startup_delay_secs: reconciliation_startup_delay_secs
356 .unwrap_or(default.reconciliation_startup_delay_secs),
357 reconciliation_lookback_mins,
358 reconciliation_instrument_ids,
359 filter_unclaimed_external_orders: filter_unclaimed_external_orders
360 .unwrap_or(default.filter_unclaimed_external_orders),
361 filter_position_reports: filter_position_reports
362 .unwrap_or(default.filter_position_reports),
363 filtered_client_order_ids,
364 generate_missing_orders: generate_missing_orders
365 .unwrap_or(default.generate_missing_orders),
366 inflight_check_interval_ms: inflight_check_interval_ms
367 .unwrap_or(default.inflight_check_interval_ms),
368 inflight_check_threshold_ms: inflight_check_threshold_ms
369 .unwrap_or(default.inflight_check_threshold_ms),
370 inflight_check_retries: inflight_check_retries
371 .unwrap_or(default.inflight_check_retries),
372 open_check_interval_secs,
373 open_check_lookback_mins: open_check_lookback_mins.or(default.open_check_lookback_mins),
374 open_check_threshold_ms: open_check_threshold_ms
375 .unwrap_or(default.open_check_threshold_ms),
376 open_check_missing_retries: open_check_missing_retries
377 .unwrap_or(default.open_check_missing_retries),
378 open_check_open_only: open_check_open_only.unwrap_or(default.open_check_open_only),
379 max_single_order_queries_per_cycle: max_single_order_queries_per_cycle
380 .unwrap_or(default.max_single_order_queries_per_cycle),
381 single_order_query_delay_ms: single_order_query_delay_ms
382 .unwrap_or(default.single_order_query_delay_ms),
383 position_check_interval_secs,
384 position_check_lookback_mins: position_check_lookback_mins
385 .unwrap_or(default.position_check_lookback_mins),
386 position_check_threshold_ms: position_check_threshold_ms
387 .unwrap_or(default.position_check_threshold_ms),
388 position_check_retries: position_check_retries
389 .unwrap_or(default.position_check_retries),
390 purge_closed_orders_interval_mins,
391 purge_closed_orders_buffer_mins,
392 purge_closed_positions_interval_mins,
393 purge_closed_positions_buffer_mins,
394 purge_account_events_interval_mins,
395 purge_account_events_lookback_mins,
396 purge_from_database: default.purge_from_database,
397 debug: debug.unwrap_or(default.debug),
398 own_books_audit_interval_secs,
399 qsize: default.qsize,
400 })
401 }
402
403 fn __repr__(&self) -> String {
404 format!("{self:?}")
405 }
406
407 fn __str__(&self) -> String {
408 format!("{self:?}")
409 }
410}
411
412#[pyo3_stub_gen::derive::gen_stub_pymethods]
413#[pymethods]
414impl RoutingConfig {
415 #[new]
417 #[pyo3(signature = (default=None, venues=None))]
418 fn py_new(default: Option<bool>, venues: Option<Vec<String>>) -> Self {
419 Self {
420 default: default.unwrap_or(false),
421 venues,
422 }
423 }
424
425 fn __repr__(&self) -> String {
426 format!("{self:?}")
427 }
428
429 fn __str__(&self) -> String {
430 format!("{self:?}")
431 }
432
433 #[getter]
434 fn default(&self) -> bool {
435 self.default
436 }
437
438 #[getter]
439 fn venues(&self) -> Option<Vec<String>> {
440 self.venues.clone()
441 }
442}
443
444#[pyo3_stub_gen::derive::gen_stub_pymethods]
445#[pymethods]
446impl InstrumentProviderConfig {
447 #[new]
449 #[allow(
450 clippy::needless_pass_by_value,
451 reason = "PyO3 #[new] requires owned params"
452 )]
453 #[pyo3(signature = (load_all=None, load_ids=None, filters=None, filter_callable=None, log_warnings=None))]
454 fn py_new(
455 load_all: Option<bool>,
456 load_ids: Option<Vec<String>>,
457 filters: Option<HashMap<String, Py<PyAny>>>,
458 filter_callable: Option<String>,
459 log_warnings: Option<bool>,
460 ) -> PyResult<Self> {
461 let default = Self::default();
462 let filters = match filters {
463 Some(raw) => coerce_json_config(raw)?,
464 None => HashMap::new(),
465 };
466 Ok(Self {
467 load_all: load_all.unwrap_or(default.load_all),
468 load_ids,
469 filters,
470 filter_callable,
471 log_warnings: log_warnings.unwrap_or(default.log_warnings),
472 })
473 }
474
475 fn __repr__(&self) -> String {
476 format!("{self:?}")
477 }
478
479 fn __str__(&self) -> String {
480 format!("{self:?}")
481 }
482
483 #[getter]
484 fn load_all(&self) -> bool {
485 self.load_all
486 }
487
488 #[getter]
489 fn load_ids(&self) -> Option<Vec<String>> {
490 self.load_ids.clone()
491 }
492
493 #[getter]
494 fn filters(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
495 let dict = pyo3::types::PyDict::new(py);
496 for (k, v) in &self.filters {
497 let py_val = json_value_to_py(py, v)?;
498 dict.set_item(k, py_val)?;
499 }
500 Ok(dict.into_any().unbind())
501 }
502
503 #[getter]
504 fn filter_callable(&self) -> Option<String> {
505 self.filter_callable.clone()
506 }
507
508 #[getter]
509 fn log_warnings(&self) -> bool {
510 self.log_warnings
511 }
512}
513
514#[pyo3_stub_gen::derive::gen_stub_pymethods]
515#[pymethods]
516impl LiveDataClientConfig {
517 #[new]
519 #[pyo3(signature = (handle_revised_bars=None, instrument_provider=None, routing=None))]
520 fn py_new(
521 handle_revised_bars: Option<bool>,
522 instrument_provider: Option<InstrumentProviderConfig>,
523 routing: Option<RoutingConfig>,
524 ) -> Self {
525 Self {
526 handle_revised_bars: handle_revised_bars.unwrap_or(false),
527 instrument_provider: instrument_provider.unwrap_or_default(),
528 routing: routing.unwrap_or_default(),
529 }
530 }
531
532 fn __repr__(&self) -> String {
533 format!("{self:?}")
534 }
535
536 fn __str__(&self) -> String {
537 format!("{self:?}")
538 }
539
540 #[getter]
541 fn handle_revised_bars(&self) -> bool {
542 self.handle_revised_bars
543 }
544
545 #[getter]
546 fn instrument_provider(&self) -> InstrumentProviderConfig {
547 self.instrument_provider.clone()
548 }
549
550 #[getter]
551 fn routing(&self) -> RoutingConfig {
552 self.routing.clone()
553 }
554}
555
556#[pyo3_stub_gen::derive::gen_stub_pymethods]
557#[pymethods]
558impl LiveExecClientConfig {
559 #[new]
561 #[pyo3(signature = (instrument_provider=None, routing=None))]
562 fn py_new(
563 instrument_provider: Option<InstrumentProviderConfig>,
564 routing: Option<RoutingConfig>,
565 ) -> Self {
566 Self {
567 instrument_provider: instrument_provider.unwrap_or_default(),
568 routing: routing.unwrap_or_default(),
569 }
570 }
571
572 fn __repr__(&self) -> String {
573 format!("{self:?}")
574 }
575
576 fn __str__(&self) -> String {
577 format!("{self:?}")
578 }
579
580 #[getter]
581 fn instrument_provider(&self) -> InstrumentProviderConfig {
582 self.instrument_provider.clone()
583 }
584
585 #[getter]
586 fn routing(&self) -> RoutingConfig {
587 self.routing.clone()
588 }
589}
590
591#[pyo3_stub_gen::derive::gen_stub_pymethods]
592#[pymethods]
593impl PluginConfig {
594 #[new]
596 #[pyo3(signature = (path, type_name, config=None, sha256=None))]
597 fn py_new(
598 path: String,
599 type_name: String,
600 config: Option<HashMap<String, Py<PyAny>>>,
601 sha256: Option<String>,
602 ) -> PyResult<Self> {
603 let config = match config {
604 Some(config) => coerce_json_config(config)?,
605 None => HashMap::new(),
606 };
607
608 Ok(Self {
609 path,
610 type_name,
611 config,
612 sha256,
613 })
614 }
615
616 #[getter]
617 fn path(&self) -> &str {
618 &self.path
619 }
620
621 #[getter]
622 fn type_name(&self) -> &str {
623 &self.type_name
624 }
625
626 #[getter]
627 fn config(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
628 let dict = PyDict::new(py);
629 for (key, value) in &self.config {
630 dict.set_item(key, json_value_to_py(py, value)?)?;
631 }
632 Ok(dict.unbind())
633 }
634
635 #[getter]
636 fn sha256(&self) -> Option<&str> {
637 self.sha256.as_deref()
638 }
639}
640
641#[pyo3_stub_gen::derive::gen_stub_pymethods]
642#[pymethods]
643impl LiveNodeConfig {
644 #[new]
646 #[expect(clippy::too_many_arguments)]
647 #[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, loop_debug=None, data_engine=None, risk_engine=None, exec_engine=None, plugins=None))]
648 fn py_new(
649 environment: Option<Environment>,
650 trader_id: Option<TraderId>,
651 load_state: Option<bool>,
652 save_state: Option<bool>,
653 shutdown_on_error: Option<bool>,
654 logging: Option<LoggerConfig>,
655 instance_id: Option<UUID4>,
656 timeout_connection_secs: Option<f64>,
657 timeout_reconciliation_secs: Option<f64>,
658 timeout_portfolio_secs: Option<f64>,
659 timeout_disconnection_secs: Option<f64>,
660 delay_post_stop_secs: Option<f64>,
661 timeout_shutdown_secs: Option<f64>,
662 cache: Option<CacheConfig>,
663 msgbus: Option<MessageBusConfig>,
664 portfolio: Option<PortfolioConfig>,
665 loop_debug: Option<bool>,
666 data_engine: Option<LiveDataEngineConfig>,
667 risk_engine: Option<LiveRiskEngineConfig>,
668 exec_engine: Option<LiveExecEngineConfig>,
669 plugins: Option<Vec<PluginConfig>>,
670 ) -> PyResult<Self> {
671 let default = Self::default();
672
673 let to_duration = |value: f64, name: &str| -> PyResult<Duration> {
674 duration_from_secs_f64(name, value).map_err(config_error_to_pyvalue_err)
675 };
676
677 Ok(Self {
678 environment: environment.unwrap_or(default.environment),
679 trader_id: trader_id.unwrap_or(default.trader_id),
680 load_state: load_state.unwrap_or(default.load_state),
681 save_state: save_state.unwrap_or(default.save_state),
682 shutdown_on_error: shutdown_on_error.unwrap_or(default.shutdown_on_error),
683 logging: logging.unwrap_or(default.logging),
684 instance_id,
685 timeout_connection: to_duration(
686 timeout_connection_secs.unwrap_or(default.timeout_connection.as_secs_f64()),
687 "timeout_connection_secs",
688 )?,
689 timeout_reconciliation: to_duration(
690 timeout_reconciliation_secs.unwrap_or(default.timeout_reconciliation.as_secs_f64()),
691 "timeout_reconciliation_secs",
692 )?,
693 timeout_portfolio: to_duration(
694 timeout_portfolio_secs.unwrap_or(default.timeout_portfolio.as_secs_f64()),
695 "timeout_portfolio_secs",
696 )?,
697 timeout_disconnection: to_duration(
698 timeout_disconnection_secs.unwrap_or(default.timeout_disconnection.as_secs_f64()),
699 "timeout_disconnection_secs",
700 )?,
701 delay_post_stop: to_duration(
702 delay_post_stop_secs.unwrap_or(default.delay_post_stop.as_secs_f64()),
703 "delay_post_stop_secs",
704 )?,
705 timeout_shutdown: to_duration(
706 timeout_shutdown_secs.unwrap_or(default.timeout_shutdown.as_secs_f64()),
707 "timeout_shutdown_secs",
708 )?,
709 cache,
710 msgbus,
711 portfolio,
712 emulator: None,
713 streaming: None,
714 event_store: None,
715 loop_debug: loop_debug.unwrap_or(false),
716 data_engine: data_engine.unwrap_or_default(),
717 risk_engine: risk_engine.unwrap_or_default(),
718 exec_engine: exec_engine.unwrap_or_default(),
719 data_clients: HashMap::new(),
720 exec_clients: HashMap::new(),
721 plugins: plugins.unwrap_or_default(),
722 })
723 }
724
725 fn __repr__(&self) -> String {
726 format!("{self:?}")
727 }
728
729 fn __str__(&self) -> String {
730 format!("{self:?}")
731 }
732
733 #[getter]
734 fn environment(&self) -> Environment {
735 self.environment
736 }
737
738 #[getter]
739 fn trader_id(&self) -> TraderId {
740 self.trader_id
741 }
742
743 #[getter]
744 fn load_state(&self) -> bool {
745 self.load_state
746 }
747
748 #[getter]
749 fn save_state(&self) -> bool {
750 self.save_state
751 }
752
753 #[getter]
754 fn shutdown_on_error(&self) -> bool {
755 self.shutdown_on_error
756 }
757
758 #[getter]
759 fn timeout_connection_secs(&self) -> f64 {
760 self.timeout_connection.as_secs_f64()
761 }
762
763 #[getter]
764 fn timeout_reconciliation_secs(&self) -> f64 {
765 self.timeout_reconciliation.as_secs_f64()
766 }
767
768 #[getter]
769 fn timeout_portfolio_secs(&self) -> f64 {
770 self.timeout_portfolio.as_secs_f64()
771 }
772
773 #[getter]
774 fn timeout_disconnection_secs(&self) -> f64 {
775 self.timeout_disconnection.as_secs_f64()
776 }
777
778 #[getter]
779 fn delay_post_stop_secs(&self) -> f64 {
780 self.delay_post_stop.as_secs_f64()
781 }
782
783 #[getter]
784 fn timeout_shutdown_secs(&self) -> f64 {
785 self.timeout_shutdown.as_secs_f64()
786 }
787
788 #[getter]
789 fn plugins(&self) -> Vec<PluginConfig> {
790 self.plugins.clone()
791 }
792}