Skip to main content

nautilus_data/python/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for data engine configuration.
17
18use std::{collections::HashMap, time::Duration};
19
20use nautilus_core::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
29// Coerces a PyO3 input into `BarIntervalType`, accepting both the enum (modern
30// Rust surface) and the legacy Python v1 string form
31// (`"left-open"` / `"right-open"`), matching `LiveDataEngineConfig`.
32fn 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#[pymethods]
54#[pyo3_stub_gen::derive::gen_stub_pymethods]
55impl DataEngineConfig {
56    /// Configuration for `DataEngine` instances.
57    #[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_origins = 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    ))]
73    fn py_new(
74        time_bars_build_with_no_updates: Option<bool>,
75        time_bars_timestamp_on_close: Option<bool>,
76        time_bars_skip_first_non_full_bar: Option<bool>,
77        time_bars_interval_type: Option<Py<PyAny>>,
78        time_bars_build_delay: Option<u64>,
79        time_bars_origins: Option<HashMap<BarAggregation, u64>>,
80        validate_data_sequence: Option<bool>,
81        buffer_deltas: Option<bool>,
82        emit_quotes_from_book: Option<bool>,
83        emit_quotes_from_book_depths: Option<bool>,
84        external_clients: Option<Vec<ClientId>>,
85        debug: Option<bool>,
86    ) -> PyResult<Self> {
87        let time_bars_interval_type = match time_bars_interval_type {
88            Some(value) => Some(coerce_bar_interval_type(&value)?),
89            None => None,
90        };
91        let time_bars_origins = time_bars_origins.map(|map| {
92            map.into_iter()
93                .map(|(agg, nanos)| (agg, Duration::from_nanos(nanos)))
94                .collect()
95        });
96        Ok(Self::builder()
97            .maybe_time_bars_build_with_no_updates(time_bars_build_with_no_updates)
98            .maybe_time_bars_timestamp_on_close(time_bars_timestamp_on_close)
99            .maybe_time_bars_skip_first_non_full_bar(time_bars_skip_first_non_full_bar)
100            .maybe_time_bars_interval_type(time_bars_interval_type)
101            .maybe_time_bars_build_delay(time_bars_build_delay)
102            .maybe_time_bars_origins(time_bars_origins)
103            .maybe_validate_data_sequence(validate_data_sequence)
104            .maybe_buffer_deltas(buffer_deltas)
105            .maybe_emit_quotes_from_book(emit_quotes_from_book)
106            .maybe_emit_quotes_from_book_depths(emit_quotes_from_book_depths)
107            .maybe_external_clients(external_clients)
108            .maybe_debug(debug)
109            .build())
110    }
111
112    #[getter]
113    #[pyo3(name = "time_bars_build_with_no_updates")]
114    const fn py_time_bars_build_with_no_updates(&self) -> bool {
115        self.time_bars_build_with_no_updates
116    }
117
118    #[getter]
119    #[pyo3(name = "time_bars_timestamp_on_close")]
120    const fn py_time_bars_timestamp_on_close(&self) -> bool {
121        self.time_bars_timestamp_on_close
122    }
123
124    #[getter]
125    #[pyo3(name = "time_bars_skip_first_non_full_bar")]
126    const fn py_time_bars_skip_first_non_full_bar(&self) -> bool {
127        self.time_bars_skip_first_non_full_bar
128    }
129
130    #[getter]
131    #[pyo3(name = "time_bars_interval_type")]
132    const fn py_time_bars_interval_type(&self) -> BarIntervalType {
133        self.time_bars_interval_type
134    }
135
136    #[getter]
137    #[pyo3(name = "time_bars_build_delay")]
138    const fn py_time_bars_build_delay(&self) -> u64 {
139        self.time_bars_build_delay
140    }
141
142    #[getter]
143    #[pyo3(name = "validate_data_sequence")]
144    const fn py_validate_data_sequence(&self) -> bool {
145        self.validate_data_sequence
146    }
147
148    #[getter]
149    #[pyo3(name = "buffer_deltas")]
150    const fn py_buffer_deltas(&self) -> bool {
151        self.buffer_deltas
152    }
153
154    #[getter]
155    #[pyo3(name = "emit_quotes_from_book")]
156    const fn py_emit_quotes_from_book(&self) -> bool {
157        self.emit_quotes_from_book
158    }
159
160    #[getter]
161    #[pyo3(name = "emit_quotes_from_book_depths")]
162    const fn py_emit_quotes_from_book_depths(&self) -> bool {
163        self.emit_quotes_from_book_depths
164    }
165
166    #[getter]
167    #[pyo3(name = "debug")]
168    const fn py_debug(&self) -> bool {
169        self.debug
170    }
171
172    fn __repr__(&self) -> String {
173        format!("{self:?}")
174    }
175
176    fn __str__(&self) -> String {
177        format!("{self:?}")
178    }
179}