nautilus_pyo3/
lib.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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 aggregator crate for [NautilusTrader](http://nautilustrader.io).
17//!
18//! The `nautilus-pyo3` crate collects the Python bindings generated across the NautilusTrader workspace
19//! and re-exports them through a single shared library that can be included in binary wheels.
20//!
21//! # Platform
22//!
23//! [NautilusTrader](http://nautilustrader.io) is an open-source, high-performance, production-grade
24//! algorithmic trading platform, providing quantitative traders with the ability to backtest
25//! portfolios of automated trading strategies on historical data with an event-driven engine,
26//! and also deploy those same strategies live, with no code changes.
27//!
28//! NautilusTrader's design, architecture, and implementation philosophy prioritizes software correctness and safety at the
29//! highest level, with the aim of supporting mission-critical, trading system backtesting and live deployment workloads.
30//!
31//! # Feature flags
32//!
33//! This crate is primarily intended to be built for Python via
34//! [maturin](https://github.com/PyO3/maturin) and therefore provides a broad set of feature flags
35//! to toggle bindings and optional dependencies:
36//!
37//! - `extension-module`: Builds the crate as a Python extension module (automatically enabled by `maturin`).
38//! - `ffi`: Enables the C foreign function interface (FFI) support in dependent crates.
39//! - `high-precision`: Uses 128-bit value types throughout the workspace.
40//! - `cython-compat`: Adjusts the module name so it can be imported from Cython generated code.
41//! - `postgres`: Enables PostgreSQL (sqlx) back-ends in dependent crates.
42//! - `redis`: Enables Redis based infrastructure in dependent crates.
43//! - `hypersync`: Enables hypersync support (fast parallel hash maps) where available.
44
45#![warn(rustc::all)]
46#![deny(unsafe_code)]
47#![deny(nonstandard_style)]
48#![deny(missing_debug_implementations)]
49#![deny(clippy::missing_errors_doc)]
50#![deny(clippy::missing_panics_doc)]
51#![deny(rustdoc::broken_intra_doc_links)]
52
53use std::path::Path;
54
55use pyo3::prelude::*;
56
57/// We modify sys modules so that submodule can be loaded directly as
58/// import supermodule.submodule
59///
60/// Also re-exports all submodule attributes so they can be imported directly from `nautilus_pyo3`
61/// refer: <https://github.com/PyO3/pyo3/issues/2644>
62#[pymodule] // The name of the function must match `lib.name` in `Cargo.toml`
63#[cfg_attr(feature = "cython-compat", pyo3(name = "nautilus_pyo3"))]
64fn _libnautilus(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
65    let sys = PyModule::import(py, "sys")?;
66    let modules = sys.getattr("modules")?;
67    let sys_modules: &Bound<'_, PyAny> = modules.downcast()?;
68
69    #[cfg(feature = "cython-compat")]
70    let module_name = "nautilus_trader.core.nautilus_pyo3";
71
72    #[cfg(not(feature = "cython-compat"))]
73    let module_name = "nautilus_trader._libnautilus";
74
75    // Set pyo3_nautilus to be recognized as a subpackage
76    sys_modules.set_item(module_name, m)?;
77
78    let n = "core";
79    let submodule = pyo3::wrap_pymodule!(nautilus_core::python::core);
80    m.add_wrapped(submodule)?;
81    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
82    #[cfg(feature = "cython-compat")]
83    re_export_module_attributes(m, n)?;
84
85    let n = "common";
86    let submodule = pyo3::wrap_pymodule!(nautilus_common::python::common);
87    m.add_wrapped(submodule)?;
88    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
89    #[cfg(feature = "cython-compat")]
90    re_export_module_attributes(m, n)?;
91
92    let n = "cryptography";
93    let submodule = pyo3::wrap_pymodule!(nautilus_cryptography::python::cryptography);
94    m.add_wrapped(submodule)?;
95    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
96    #[cfg(feature = "cython-compat")]
97    re_export_module_attributes(m, n)?;
98
99    let n = "indicators";
100    let submodule = pyo3::wrap_pymodule!(nautilus_indicators::python::indicators);
101    m.add_wrapped(submodule)?;
102    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
103    #[cfg(feature = "cython-compat")]
104    re_export_module_attributes(m, n)?;
105
106    let n = "infrastructure";
107    let submodule = pyo3::wrap_pymodule!(nautilus_infrastructure::python::infrastructure);
108    m.add_wrapped(submodule)?;
109    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
110    #[cfg(feature = "cython-compat")]
111    re_export_module_attributes(m, n)?;
112
113    let n = "live";
114    let submodule = pyo3::wrap_pymodule!(nautilus_live::python::live);
115    m.add_wrapped(submodule)?;
116    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
117    #[cfg(feature = "cython-compat")]
118    re_export_module_attributes(m, n)?;
119
120    let n = "model";
121    let submodule = pyo3::wrap_pymodule!(nautilus_model::python::model);
122    m.add_wrapped(submodule)?;
123    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
124    #[cfg(feature = "cython-compat")]
125    re_export_module_attributes(m, n)?;
126
127    let n = "network";
128    let submodule = pyo3::wrap_pymodule!(nautilus_network::python::network);
129    m.add_wrapped(submodule)?;
130    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
131    #[cfg(feature = "cython-compat")]
132    re_export_module_attributes(m, n)?;
133
134    let n = "persistence";
135    let submodule = pyo3::wrap_pymodule!(nautilus_persistence::python::persistence);
136    m.add_wrapped(submodule)?;
137    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
138    #[cfg(feature = "cython-compat")]
139    re_export_module_attributes(m, n)?;
140
141    let n = "serialization";
142    let submodule = pyo3::wrap_pymodule!(nautilus_serialization::python::serialization);
143    m.add_wrapped(submodule)?;
144    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
145    #[cfg(feature = "cython-compat")]
146    re_export_module_attributes(m, n)?;
147
148    let n = "testkit";
149    let submodule = pyo3::wrap_pymodule!(nautilus_testkit::python::testkit);
150    m.add_wrapped(submodule)?;
151    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
152    #[cfg(feature = "cython-compat")]
153    re_export_module_attributes(m, n)?;
154
155    let n = "trading";
156    let submodule = pyo3::wrap_pymodule!(nautilus_trading::python::trading);
157    m.add_wrapped(submodule)?;
158    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
159    #[cfg(feature = "cython-compat")]
160    re_export_module_attributes(m, n)?;
161
162    // Adapters
163
164    let n = "bitmex";
165    let submodule = pyo3::wrap_pymodule!(nautilus_bitmex::python::bitmex);
166    m.add_wrapped(submodule)?;
167    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
168    #[cfg(feature = "cython-compat")]
169    re_export_module_attributes(m, n)?;
170
171    let n = "coinbase_intx";
172    let submodule = pyo3::wrap_pymodule!(nautilus_coinbase_intx::python::coinbase_intx);
173    m.add_wrapped(submodule)?;
174    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
175    #[cfg(feature = "cython-compat")]
176    re_export_module_attributes(m, n)?;
177
178    let n = "databento";
179    let submodule = pyo3::wrap_pymodule!(nautilus_databento::python::databento);
180    m.add_wrapped(submodule)?;
181    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
182    #[cfg(feature = "cython-compat")]
183    re_export_module_attributes(m, n)?;
184
185    let n = "hyperliquid";
186    let submodule = pyo3::wrap_pymodule!(nautilus_hyperliquid::python::hyperliquid);
187    m.add_wrapped(submodule)?;
188    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
189    #[cfg(feature = "cython-compat")]
190    re_export_module_attributes(m, n)?;
191
192    let n = "okx";
193    let submodule = pyo3::wrap_pymodule!(nautilus_okx::python::okx);
194    m.add_wrapped(submodule)?;
195    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
196    #[cfg(feature = "cython-compat")]
197    re_export_module_attributes(m, n)?;
198
199    let n = "tardis";
200    let submodule = pyo3::wrap_pymodule!(nautilus_tardis::python::tardis);
201    m.add_wrapped(submodule)?;
202    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
203    #[cfg(feature = "cython-compat")]
204    re_export_module_attributes(m, n)?;
205
206    #[cfg(feature = "defi")]
207    {
208        let n = "blockchain";
209        let submodule = pyo3::wrap_pymodule!(nautilus_blockchain::python::blockchain);
210        m.add_wrapped(submodule)?;
211        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
212        #[cfg(feature = "cython-compat")]
213        re_export_module_attributes(m, n)?;
214    }
215
216    Ok(())
217}
218
219#[cfg(feature = "cython-compat")]
220fn re_export_module_attributes(
221    parent_module: &Bound<'_, PyModule>,
222    submodule_name: &str,
223) -> PyResult<()> {
224    let submodule = parent_module.getattr(submodule_name)?;
225    for item_name in submodule.dir()? {
226        let item_name_str: &str = item_name.extract()?;
227        if let Ok(attr) = submodule.getattr(item_name_str) {
228            parent_module.add(item_name_str, attr)?;
229        }
230    }
231
232    Ok(())
233}
234
235/// Generate Python type stub info for PyO3 bindings.
236///
237/// Assumes the pyproject.toml is located in the python/ directory relative to the workspace root.
238///
239/// # Panics
240///
241/// Panics if the path locating the pyproject.toml is incorrect.
242///
243/// # Errors
244///
245/// Returns an error if stub information generation fails.
246///
247/// # Reference
248///
249/// - <https://pyo3.rs/latest/python-typing-hints>
250/// - <https://crates.io/crates/pyo3-stub-gen>
251/// - <https://github.com/Jij-Inc/pyo3-stub-gen>
252pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
253    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
254        .parent()
255        .unwrap()
256        .parent()
257        .unwrap();
258    let pyproject_path = workspace_root.join("python").join("pyproject.toml");
259
260    pyo3_stub_gen::StubInfo::from_pyproject_toml(&pyproject_path)
261}