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, time::Duration};
54
55use pyo3::{prelude::*, pyfunction};
56
57const RUNTIME_SHUTDOWN_TIMEOUT_SECS: u64 = 10;
58
59#[pyfunction]
60fn _shutdown_nautilus_runtime() -> PyResult<()> {
61    nautilus_common::runtime::shutdown_runtime(Duration::from_secs(RUNTIME_SHUTDOWN_TIMEOUT_SECS));
62    Ok(())
63}
64
65/// We modify sys modules so that submodule can be loaded directly as
66/// import supermodule.submodule
67///
68/// Also re-exports all submodule attributes so they can be imported directly from `nautilus_pyo3`
69/// refer: <https://github.com/PyO3/pyo3/issues/2644>
70#[pymodule] // The name of the function must match `lib.name` in `Cargo.toml`
71#[cfg_attr(feature = "cython-compat", pyo3(name = "nautilus_pyo3"))]
72fn _libnautilus(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
73    let sys = PyModule::import(py, "sys")?;
74    let modules = sys.getattr("modules")?;
75    let sys_modules: &Bound<'_, PyAny> = modules.downcast()?;
76
77    #[cfg(feature = "cython-compat")]
78    let module_name = "nautilus_trader.core.nautilus_pyo3";
79
80    #[cfg(not(feature = "cython-compat"))]
81    let module_name = "nautilus_trader._libnautilus";
82
83    // Set pyo3_nautilus to be recognized as a subpackage
84    sys_modules.set_item(module_name, m)?;
85
86    let n = "analysis";
87    let submodule = pyo3::wrap_pymodule!(nautilus_analysis::python::analysis);
88    m.add_wrapped(submodule)?;
89    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
90    #[cfg(feature = "cython-compat")]
91    re_export_module_attributes(m, n)?;
92
93    let n = "core";
94    let submodule = pyo3::wrap_pymodule!(nautilus_core::python::core);
95    m.add_wrapped(submodule)?;
96    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
97    #[cfg(feature = "cython-compat")]
98    re_export_module_attributes(m, n)?;
99
100    let n = "common";
101    let submodule = pyo3::wrap_pymodule!(nautilus_common::python::common);
102    m.add_wrapped(submodule)?;
103    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
104    #[cfg(feature = "cython-compat")]
105    re_export_module_attributes(m, n)?;
106
107    let n = "cryptography";
108    let submodule = pyo3::wrap_pymodule!(nautilus_cryptography::python::cryptography);
109    m.add_wrapped(submodule)?;
110    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
111    #[cfg(feature = "cython-compat")]
112    re_export_module_attributes(m, n)?;
113
114    let n = "indicators";
115    let submodule = pyo3::wrap_pymodule!(nautilus_indicators::python::indicators);
116    m.add_wrapped(submodule)?;
117    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
118    #[cfg(feature = "cython-compat")]
119    re_export_module_attributes(m, n)?;
120
121    let n = "infrastructure";
122    let submodule = pyo3::wrap_pymodule!(nautilus_infrastructure::python::infrastructure);
123    m.add_wrapped(submodule)?;
124    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
125    #[cfg(feature = "cython-compat")]
126    re_export_module_attributes(m, n)?;
127
128    let n = "live";
129    let submodule = pyo3::wrap_pymodule!(nautilus_live::python::live);
130    m.add_wrapped(submodule)?;
131    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
132    #[cfg(feature = "cython-compat")]
133    re_export_module_attributes(m, n)?;
134
135    let n = "model";
136    let submodule = pyo3::wrap_pymodule!(nautilus_model::python::model);
137    m.add_wrapped(submodule)?;
138    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
139    #[cfg(feature = "cython-compat")]
140    re_export_module_attributes(m, n)?;
141
142    let n = "network";
143    let submodule = pyo3::wrap_pymodule!(nautilus_network::python::network);
144    m.add_wrapped(submodule)?;
145    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
146    #[cfg(feature = "cython-compat")]
147    re_export_module_attributes(m, n)?;
148
149    let n = "persistence";
150    let submodule = pyo3::wrap_pymodule!(nautilus_persistence::python::persistence);
151    m.add_wrapped(submodule)?;
152    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
153    #[cfg(feature = "cython-compat")]
154    re_export_module_attributes(m, n)?;
155
156    let n = "serialization";
157    let submodule = pyo3::wrap_pymodule!(nautilus_serialization::python::serialization);
158    m.add_wrapped(submodule)?;
159    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
160    #[cfg(feature = "cython-compat")]
161    re_export_module_attributes(m, n)?;
162
163    let n = "testkit";
164    let submodule = pyo3::wrap_pymodule!(nautilus_testkit::python::testkit);
165    m.add_wrapped(submodule)?;
166    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
167    #[cfg(feature = "cython-compat")]
168    re_export_module_attributes(m, n)?;
169
170    let n = "trading";
171    let submodule = pyo3::wrap_pymodule!(nautilus_trading::python::trading);
172    m.add_wrapped(submodule)?;
173    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
174    #[cfg(feature = "cython-compat")]
175    re_export_module_attributes(m, n)?;
176
177    ////////////////////////////////////////////////////////////////////////////////
178    // Adapters
179    ////////////////////////////////////////////////////////////////////////////////
180
181    let n = "bitmex";
182    let submodule = pyo3::wrap_pymodule!(nautilus_bitmex::python::bitmex);
183    m.add_wrapped(submodule)?;
184    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
185    #[cfg(feature = "cython-compat")]
186    re_export_module_attributes(m, n)?;
187
188    let n = "bybit";
189    let submodule = pyo3::wrap_pymodule!(nautilus_bybit::python::bybit);
190    m.add_wrapped(submodule)?;
191    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
192    #[cfg(feature = "cython-compat")]
193    re_export_module_attributes(m, n)?;
194
195    let n = "coinbase_intx";
196    let submodule = pyo3::wrap_pymodule!(nautilus_coinbase_intx::python::coinbase_intx);
197    m.add_wrapped(submodule)?;
198    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
199    #[cfg(feature = "cython-compat")]
200    re_export_module_attributes(m, n)?;
201
202    let n = "databento";
203    let submodule = pyo3::wrap_pymodule!(nautilus_databento::python::databento);
204    m.add_wrapped(submodule)?;
205    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
206    #[cfg(feature = "cython-compat")]
207    re_export_module_attributes(m, n)?;
208
209    let n = "hyperliquid";
210    let submodule = pyo3::wrap_pymodule!(nautilus_hyperliquid::python::hyperliquid);
211    m.add_wrapped(submodule)?;
212    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
213    #[cfg(feature = "cython-compat")]
214    re_export_module_attributes(m, n)?;
215
216    let n = "okx";
217    let submodule = pyo3::wrap_pymodule!(nautilus_okx::python::okx);
218    m.add_wrapped(submodule)?;
219    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
220    #[cfg(feature = "cython-compat")]
221    re_export_module_attributes(m, n)?;
222
223    let n = "tardis";
224    let submodule = pyo3::wrap_pymodule!(nautilus_tardis::python::tardis);
225    m.add_wrapped(submodule)?;
226    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
227    #[cfg(feature = "cython-compat")]
228    re_export_module_attributes(m, n)?;
229
230    #[cfg(feature = "defi")]
231    {
232        let n = "blockchain";
233        let submodule = pyo3::wrap_pymodule!(nautilus_blockchain::python::blockchain);
234        m.add_wrapped(submodule)?;
235        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
236        #[cfg(feature = "cython-compat")]
237        re_export_module_attributes(m, n)?;
238    }
239
240    // Register a lightweight shutdown hook so the interpreter waits for the Tokio
241    // runtime to yield once before `Py_Finalize` tears it down.
242    m.add_function(pyo3::wrap_pyfunction!(_shutdown_nautilus_runtime, m)?)?;
243    let shutdown_callable = m.getattr("_shutdown_nautilus_runtime")?;
244    let atexit = PyModule::import(py, "atexit")?;
245    atexit.call_method1("register", (shutdown_callable,))?;
246
247    Ok(())
248}
249
250#[cfg(feature = "cython-compat")]
251fn re_export_module_attributes(
252    parent_module: &Bound<'_, PyModule>,
253    submodule_name: &str,
254) -> PyResult<()> {
255    let submodule = parent_module.getattr(submodule_name)?;
256    for item_name in submodule.dir()? {
257        let item_name_str: &str = item_name.extract()?;
258        if let Ok(attr) = submodule.getattr(item_name_str) {
259            parent_module.add(item_name_str, attr)?;
260        }
261    }
262
263    Ok(())
264}
265
266/// Generate Python type stub info for PyO3 bindings.
267///
268/// Assumes the pyproject.toml is located in the python/ directory relative to the workspace root.
269///
270/// # Panics
271///
272/// Panics if the path locating the pyproject.toml is incorrect.
273///
274/// # Errors
275///
276/// Returns an error if stub information generation fails.
277///
278/// # Reference
279///
280/// - <https://pyo3.rs/latest/python-typing-hints>
281/// - <https://crates.io/crates/pyo3-stub-gen>
282/// - <https://github.com/Jij-Inc/pyo3-stub-gen>
283pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
284    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
285        .parent()
286        .unwrap()
287        .parent()
288        .unwrap();
289    let pyproject_path = workspace_root.join("python").join("pyproject.toml");
290
291    pyo3_stub_gen::StubInfo::from_pyproject_toml(&pyproject_path)
292}