Skip to main content

nautilus_tardis/python/
mod.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 from [PyO3](https://pyo3.rs).
17
18pub mod config;
19pub mod csv;
20pub mod enums;
21pub mod factories;
22pub mod http;
23pub mod machine;
24
25use nautilus_common::factories::{ClientConfig, DataClientFactory};
26use nautilus_core::python::{enums::parse_enum, to_pyruntime_err, to_pyvalue_err};
27use nautilus_system::get_global_pyo3_registry;
28use pyo3::prelude::*;
29use ustr::Ustr;
30
31use crate::{
32    common::{
33        consts::TARDIS,
34        enums::{TardisExchange, TardisInstrumentType},
35        parse::normalize_symbol_str,
36    },
37    config::TardisDataClientConfig,
38    factories::TardisDataClientFactory,
39};
40
41/// Normalize a symbol string for Tardis, returning a suffix-modified symbol.
42///
43/// # Errors
44///
45/// Returns a `PyErr` if the `exchange` or `instrument_type` cannot be parsed.
46#[pyfunction(name = "tardis_normalize_symbol_str")]
47#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
48#[pyo3(signature = (symbol, exchange, instrument_type, is_inverse=None))]
49pub fn py_tardis_normalize_symbol_str(
50    symbol: &str,
51    exchange: &str,
52    instrument_type: &str,
53    is_inverse: Option<bool>,
54) -> PyResult<String> {
55    let symbol = Ustr::from(symbol);
56    let exchange: TardisExchange = parse_enum(exchange, stringify!(exchange))?;
57    let instrument_type: TardisInstrumentType =
58        parse_enum(instrument_type, stringify!(instrument_type))?;
59
60    Ok(normalize_symbol_str(symbol, &exchange, &instrument_type, is_inverse).to_string())
61}
62
63#[expect(clippy::needless_pass_by_value)]
64fn extract_tardis_data_factory(
65    py: Python<'_>,
66    factory: Py<PyAny>,
67) -> PyResult<Box<dyn DataClientFactory>> {
68    match factory.extract::<TardisDataClientFactory>(py) {
69        Ok(f) => Ok(Box::new(f)),
70        Err(e) => Err(to_pyvalue_err(format!(
71            "Failed to extract TardisDataClientFactory: {e}"
72        ))),
73    }
74}
75
76#[expect(clippy::needless_pass_by_value)]
77fn extract_tardis_data_config(
78    py: Python<'_>,
79    config: Py<PyAny>,
80) -> PyResult<Box<dyn ClientConfig>> {
81    match config.extract::<TardisDataClientConfig>(py) {
82        Ok(c) => Ok(Box::new(c)),
83        Err(e) => Err(to_pyvalue_err(format!(
84            "Failed to extract TardisDataClientConfig: {e}"
85        ))),
86    }
87}
88
89/// Loaded as `nautilus_pyo3.tardis`.
90///
91/// # Errors
92///
93/// Returns a `PyErr` if registering any module components fails.
94#[pymodule]
95pub fn tardis(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
96    m.add_class::<super::machine::types::TardisInstrumentMiniInfo>()?;
97    m.add_class::<super::machine::types::ReplayNormalizedRequestOptions>()?;
98    m.add_class::<super::machine::types::StreamNormalizedRequestOptions>()?;
99    m.add_class::<super::machine::TardisMachineClient>()?;
100    m.add_class::<super::http::client::TardisHttpClient>()?;
101    m.add_class::<TardisDataClientConfig>()?;
102    m.add_class::<TardisDataClientFactory>()?;
103    m.add_function(wrap_pyfunction!(py_tardis_normalize_symbol_str, m)?)?;
104    m.add_function(wrap_pyfunction!(
105        enums::py_tardis_exchange_from_venue_str,
106        m
107    )?)?;
108    m.add_function(wrap_pyfunction!(enums::py_tardis_exchange_to_venue_str, m)?)?;
109    m.add_function(wrap_pyfunction!(
110        enums::py_tardis_exchange_is_option_exchange,
111        m
112    )?)?;
113    m.add_function(wrap_pyfunction!(enums::py_tardis_exchanges, m)?)?;
114    m.add_function(wrap_pyfunction!(
115        config::py_bar_spec_to_tardis_trade_bar_string,
116        m
117    )?)?;
118    m.add_function(wrap_pyfunction!(machine::py_run_tardis_machine_replay, m)?)?;
119    m.add_function(wrap_pyfunction!(csv::py_load_tardis_deltas, m)?)?;
120    m.add_function(wrap_pyfunction!(
121        csv::py_load_tardis_depth10_from_snapshot5,
122        m
123    )?)?;
124    m.add_function(wrap_pyfunction!(
125        csv::py_load_tardis_depth10_from_snapshot25,
126        m
127    )?)?;
128    m.add_function(wrap_pyfunction!(csv::py_load_tardis_quotes, m)?)?;
129    m.add_function(wrap_pyfunction!(csv::py_load_tardis_trades, m)?)?;
130    m.add_function(wrap_pyfunction!(csv::py_load_tardis_options_chain, m)?)?;
131    m.add_function(wrap_pyfunction!(
132        csv::py_convert_tardis_options_chain_csv,
133        m
134    )?)?;
135    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_deltas, m)?)?;
136    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_batched_deltas, m)?)?;
137    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_quotes, m)?)?;
138    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_options_chain, m)?)?;
139    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_trades, m)?)?;
140    m.add_function(wrap_pyfunction!(
141        csv::py_stream_tardis_depth10_from_snapshot5,
142        m
143    )?)?;
144    m.add_function(wrap_pyfunction!(
145        csv::py_stream_tardis_depth10_from_snapshot25,
146        m
147    )?)?;
148    m.add_function(wrap_pyfunction!(csv::py_load_tardis_funding_rates, m)?)?;
149    m.add_function(wrap_pyfunction!(csv::py_stream_tardis_funding_rates, m)?)?;
150
151    let registry = get_global_pyo3_registry();
152
153    if let Err(e) =
154        registry.register_factory_extractor(TARDIS.to_string(), extract_tardis_data_factory)
155    {
156        return Err(to_pyruntime_err(format!(
157            "Failed to register Tardis data factory extractor: {e}"
158        )));
159    }
160
161    if let Err(e) = registry.register_config_extractor(
162        "TardisDataClientConfig".to_string(),
163        extract_tardis_data_config,
164    ) {
165        return Err(to_pyruntime_err(format!(
166            "Failed to register Tardis data config extractor: {e}"
167        )));
168    }
169
170    Ok(())
171}