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