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_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    ))]
74    fn py_new(
75        time_bars_build_with_no_updates: Option<bool>,
76        time_bars_timestamp_on_close: Option<bool>,
77        time_bars_skip_first_non_full_bar: Option<bool>,
78        time_bars_interval_type: Option<Py<PyAny>>,
79        time_bars_build_delay: Option<u64>,
80        time_bars_origin_offset: Option<HashMap<BarAggregation, u64>>,
81        validate_data_sequence: Option<bool>,
82        buffer_deltas: Option<bool>,
83        emit_quotes_from_book: Option<bool>,
84        emit_quotes_from_book_depths: Option<bool>,
85        external_clients: Option<Vec<ClientId>>,
86        debug: Option<bool>,
87        disable_historical_cache: Option<bool>,
88    ) -> PyResult<Self> {
89        let time_bars_interval_type = match time_bars_interval_type {
90            Some(value) => Some(coerce_bar_interval_type(&value)?),
91            None => None,
92        };
93        let time_bars_origin_offset = time_bars_origin_offset.map(|map| {
94            map.into_iter()
95                .map(|(agg, nanos)| (agg, Duration::from_nanos(nanos)))
96                .collect()
97        });
98        Ok(Self::builder()
99            .maybe_time_bars_build_with_no_updates(time_bars_build_with_no_updates)
100            .maybe_time_bars_timestamp_on_close(time_bars_timestamp_on_close)
101            .maybe_time_bars_skip_first_non_full_bar(time_bars_skip_first_non_full_bar)
102            .maybe_time_bars_interval_type(time_bars_interval_type)
103            .maybe_time_bars_build_delay(time_bars_build_delay)
104            .maybe_time_bars_origin_offset(time_bars_origin_offset)
105            .maybe_validate_data_sequence(validate_data_sequence)
106            .maybe_buffer_deltas(buffer_deltas)
107            .maybe_emit_quotes_from_book(emit_quotes_from_book)
108            .maybe_emit_quotes_from_book_depths(emit_quotes_from_book_depths)
109            .maybe_disable_historical_cache(disable_historical_cache)
110            .maybe_external_clients(external_clients)
111            .maybe_debug(debug)
112            .build())
113    }
114
115    #[getter]
116    #[pyo3(name = "time_bars_build_with_no_updates")]
117    const fn py_time_bars_build_with_no_updates(&self) -> bool {
118        self.time_bars_build_with_no_updates
119    }
120
121    #[getter]
122    #[pyo3(name = "time_bars_timestamp_on_close")]
123    const fn py_time_bars_timestamp_on_close(&self) -> bool {
124        self.time_bars_timestamp_on_close
125    }
126
127    #[getter]
128    #[pyo3(name = "time_bars_skip_first_non_full_bar")]
129    const fn py_time_bars_skip_first_non_full_bar(&self) -> bool {
130        self.time_bars_skip_first_non_full_bar
131    }
132
133    #[getter]
134    #[pyo3(name = "time_bars_interval_type")]
135    const fn py_time_bars_interval_type(&self) -> BarIntervalType {
136        self.time_bars_interval_type
137    }
138
139    #[getter]
140    #[pyo3(name = "time_bars_build_delay")]
141    const fn py_time_bars_build_delay(&self) -> u64 {
142        self.time_bars_build_delay
143    }
144
145    #[getter]
146    #[pyo3(name = "validate_data_sequence")]
147    const fn py_validate_data_sequence(&self) -> bool {
148        self.validate_data_sequence
149    }
150
151    #[getter]
152    #[pyo3(name = "buffer_deltas")]
153    const fn py_buffer_deltas(&self) -> bool {
154        self.buffer_deltas
155    }
156
157    #[getter]
158    #[pyo3(name = "emit_quotes_from_book")]
159    const fn py_emit_quotes_from_book(&self) -> bool {
160        self.emit_quotes_from_book
161    }
162
163    #[getter]
164    #[pyo3(name = "emit_quotes_from_book_depths")]
165    const fn py_emit_quotes_from_book_depths(&self) -> bool {
166        self.emit_quotes_from_book_depths
167    }
168
169    #[getter]
170    #[pyo3(name = "disable_historical_cache")]
171    const fn py_disable_historical_cache(&self) -> bool {
172        self.disable_historical_cache
173    }
174
175    #[getter]
176    #[pyo3(name = "debug")]
177    const fn py_debug(&self) -> bool {
178        self.debug
179    }
180
181    fn __repr__(&self) -> String {
182        format!("{self:?}")
183    }
184
185    fn __str__(&self) -> String {
186        format!("{self:?}")
187    }
188}