nautilus_data/python/
config.rs1use std::{collections::HashMap, time::Duration};
19
20use nautilus_core::{datetime::get_timezone, python::to_pyvalue_err};
21use nautilus_model::{
22 enums::{BarAggregation, BarIntervalType},
23 identifiers::ClientId,
24};
25use pyo3::{Py, PyAny, PyResult, Python, prelude::PyAnyMethods, pymethods};
26
27use crate::engine::config::DataEngineConfig;
28
29fn coerce_bar_interval_type(value: &Py<PyAny>) -> PyResult<BarIntervalType> {
33 Python::attach(|py| {
34 let bound = value.bind(py);
35 if let Ok(variant) = bound.extract::<BarIntervalType>() {
36 return Ok(variant);
37 }
38
39 let raw = bound.extract::<String>().map_err(|_| {
40 to_pyvalue_err("`time_bars_interval_type` must be a string or BarIntervalType")
41 })?;
42
43 match raw.to_ascii_uppercase().replace('-', "_").as_str() {
44 "LEFT_OPEN" => Ok(BarIntervalType::LeftOpen),
45 "RIGHT_OPEN" => Ok(BarIntervalType::RightOpen),
46 _ => Err(to_pyvalue_err(format!(
47 "invalid `time_bars_interval_type`: {raw:?} (expected 'left-open' or 'right-open')"
48 ))),
49 }
50 })
51}
52
53#[pyo3_stub_gen::derive::gen_stub_pymethods]
54#[pymethods]
55impl DataEngineConfig {
56 #[new]
58 #[expect(clippy::too_many_arguments)]
59 #[pyo3(signature = (
60 time_bars_build_with_no_updates = None,
61 time_bars_timestamp_on_close = None,
62 time_bars_skip_first_non_full_bar = None,
63 time_bars_interval_type = None,
64 time_bars_build_delay = None,
65 time_bars_origin_offset = None,
66 validate_data_sequence = None,
67 buffer_deltas = None,
68 emit_quotes_from_book = None,
69 emit_quotes_from_book_depths = None,
70 external_clients = None,
71 debug = None,
72 disable_historical_cache = None,
73 time_bars_time_zone = None,
74 ))]
75 fn py_new(
76 time_bars_build_with_no_updates: Option<bool>,
77 time_bars_timestamp_on_close: Option<bool>,
78 time_bars_skip_first_non_full_bar: Option<bool>,
79 time_bars_interval_type: Option<Py<PyAny>>,
80 time_bars_build_delay: Option<u64>,
81 time_bars_origin_offset: Option<HashMap<BarAggregation, u64>>,
82 validate_data_sequence: Option<bool>,
83 buffer_deltas: Option<bool>,
84 emit_quotes_from_book: Option<bool>,
85 emit_quotes_from_book_depths: Option<bool>,
86 external_clients: Option<Vec<ClientId>>,
87 debug: Option<bool>,
88 disable_historical_cache: Option<bool>,
89 time_bars_time_zone: Option<String>,
90 ) -> PyResult<Self> {
91 if let Some(name) = &time_bars_time_zone {
92 get_timezone(name).map_err(to_pyvalue_err)?;
93 }
94
95 let time_bars_interval_type = match time_bars_interval_type {
96 Some(value) => Some(coerce_bar_interval_type(&value)?),
97 None => None,
98 };
99 let time_bars_origin_offset = time_bars_origin_offset.map(|map| {
100 map.into_iter()
101 .map(|(agg, nanos)| (agg, Duration::from_nanos(nanos)))
102 .collect()
103 });
104 Ok(Self::builder()
105 .maybe_time_bars_build_with_no_updates(time_bars_build_with_no_updates)
106 .maybe_time_bars_timestamp_on_close(time_bars_timestamp_on_close)
107 .maybe_time_bars_skip_first_non_full_bar(time_bars_skip_first_non_full_bar)
108 .maybe_time_bars_interval_type(time_bars_interval_type)
109 .maybe_time_bars_build_delay(time_bars_build_delay)
110 .maybe_time_bars_origin_offset(time_bars_origin_offset)
111 .maybe_validate_data_sequence(validate_data_sequence)
112 .maybe_buffer_deltas(buffer_deltas)
113 .maybe_emit_quotes_from_book(emit_quotes_from_book)
114 .maybe_emit_quotes_from_book_depths(emit_quotes_from_book_depths)
115 .maybe_disable_historical_cache(disable_historical_cache)
116 .maybe_time_bars_time_zone(time_bars_time_zone)
117 .maybe_external_clients(external_clients)
118 .maybe_debug(debug)
119 .build())
120 }
121
122 #[getter]
123 #[pyo3(name = "time_bars_build_with_no_updates")]
124 const fn py_time_bars_build_with_no_updates(&self) -> bool {
125 self.time_bars_build_with_no_updates
126 }
127
128 #[getter]
129 #[pyo3(name = "time_bars_timestamp_on_close")]
130 const fn py_time_bars_timestamp_on_close(&self) -> bool {
131 self.time_bars_timestamp_on_close
132 }
133
134 #[getter]
135 #[pyo3(name = "time_bars_skip_first_non_full_bar")]
136 const fn py_time_bars_skip_first_non_full_bar(&self) -> bool {
137 self.time_bars_skip_first_non_full_bar
138 }
139
140 #[getter]
141 #[pyo3(name = "time_bars_interval_type")]
142 const fn py_time_bars_interval_type(&self) -> BarIntervalType {
143 self.time_bars_interval_type
144 }
145
146 #[getter]
147 #[pyo3(name = "time_bars_build_delay")]
148 const fn py_time_bars_build_delay(&self) -> u64 {
149 self.time_bars_build_delay
150 }
151
152 #[getter]
153 #[pyo3(name = "time_bars_origin_offset")]
154 fn py_time_bars_origin_offset(&self) -> HashMap<BarAggregation, u64> {
155 self.time_bars_origin_offset
156 .iter()
157 .map(|(aggregation, offset)| (*aggregation, offset.as_nanos() as u64))
158 .collect()
159 }
160
161 #[getter]
162 #[pyo3(name = "validate_data_sequence")]
163 const fn py_validate_data_sequence(&self) -> bool {
164 self.validate_data_sequence
165 }
166
167 #[getter]
168 #[pyo3(name = "buffer_deltas")]
169 const fn py_buffer_deltas(&self) -> bool {
170 self.buffer_deltas
171 }
172
173 #[getter]
174 #[pyo3(name = "emit_quotes_from_book")]
175 const fn py_emit_quotes_from_book(&self) -> bool {
176 self.emit_quotes_from_book
177 }
178
179 #[getter]
180 #[pyo3(name = "emit_quotes_from_book_depths")]
181 const fn py_emit_quotes_from_book_depths(&self) -> bool {
182 self.emit_quotes_from_book_depths
183 }
184
185 #[getter]
186 #[pyo3(name = "disable_historical_cache")]
187 const fn py_disable_historical_cache(&self) -> bool {
188 self.disable_historical_cache
189 }
190
191 #[getter]
192 #[pyo3(name = "external_clients")]
193 fn py_external_clients(&self) -> Option<Vec<ClientId>> {
194 self.external_clients.clone()
195 }
196
197 #[getter]
198 #[pyo3(name = "debug")]
199 const fn py_debug(&self) -> bool {
200 self.debug
201 }
202
203 fn __repr__(&self) -> String {
204 format!("{self:?}")
205 }
206
207 fn __str__(&self) -> String {
208 format!("{self:?}")
209 }
210 #[getter]
211 #[pyo3(name = "time_bars_time_zone")]
212 fn py_time_bars_time_zone(&self) -> Option<String> {
213 self.time_bars_time_zone.clone()
214 }
215}