Skip to main content

nautilus_pyo3/
lib.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 aggregator crate for [NautilusTrader](https://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//! # NautilusTrader
22//!
23//! [NautilusTrader](https://nautilustrader.io) is an open-source, production-grade, Rust-native
24//! engine for multi-asset, multi-venue trading systems.
25//!
26//! The system spans research, deterministic simulation, and live execution within a single
27//! event-driven architecture, providing research-to-live semantic parity.
28//!
29//! # Feature Flags
30//!
31//! This crate is primarily intended to be built for Python via
32//! [maturin](https://github.com/PyO3/maturin) and therefore provides a broad set of feature flags
33//! to toggle bindings and optional dependencies:
34//!
35//! - `extension-module`: Builds the crate as a Python extension module (automatically enabled by `maturin`).
36//! - `ffi`: Enables the C foreign function interface (FFI) support in dependent crates.
37//! - `high-precision`: Uses 128-bit value types throughout the workspace.
38//! - `cython-compat`: Adjusts the module name so it can be imported from Cython generated code.
39//! - `postgres`: Enables PostgreSQL (sqlx) back-ends in dependent crates.
40//! - `redis`: Enables Redis based infrastructure in dependent crates.
41//! - `hypersync`: Enables hypersync support (fast parallel hash maps) where available.
42//! - `tracing-bridge`: Enables the `tracing` subscriber bridge for log integration.
43//! - `defi`: Enables DeFi (Decentralized Finance) support including blockchain adapters.
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() {
63    shutdown_runtime(Duration::from_secs(RUNTIME_SHUTDOWN_TIMEOUT_SECS));
64}
65
66/// We modify sys modules so that submodule can be loaded directly as
67/// import supermodule.submodule
68///
69/// Also re-exports all submodule attributes so they can be imported directly from `nautilus_pyo3`
70/// refer: <https://github.com/PyO3/pyo3/issues/2644>
71#[pymodule] // The name of the function must match `lib.name` in `Cargo.toml`
72#[cfg_attr(feature = "cython-compat", pyo3(name = "nautilus_pyo3"))]
73fn _libnautilus(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
74    let sys = PyModule::import(py, "sys")?;
75    let modules = sys.getattr("modules")?;
76    let sys_modules: &Bound<'_, PyAny> = modules.cast()?;
77
78    #[cfg(feature = "cython-compat")]
79    let module_name = "nautilus_trader.core.nautilus_pyo3";
80
81    #[cfg(not(feature = "cython-compat"))]
82    let module_name = "nautilus_trader._libnautilus";
83
84    // Set pyo3_nautilus to be recognized as a subpackage
85    sys_modules.set_item(module_name, m)?;
86
87    // nautilus-import-ok: wrap_pymodule! requires fully qualified paths
88    let n = "analysis";
89    let submodule = pyo3::wrap_pymodule!(nautilus_analysis::python::analysis);
90    m.add_wrapped(submodule)?;
91    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
92    #[cfg(feature = "cython-compat")]
93    re_export_module_attributes(m, n)?;
94
95    let n = "core";
96    let submodule = pyo3::wrap_pymodule!(nautilus_core::python::core);
97    m.add_wrapped(submodule)?;
98    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
99    #[cfg(feature = "cython-compat")]
100    re_export_module_attributes(m, n)?;
101
102    let n = "common";
103    let submodule = pyo3::wrap_pymodule!(nautilus_common::python::common);
104    m.add_wrapped(submodule)?;
105    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
106    #[cfg(feature = "cython-compat")]
107    re_export_module_attributes(m, n)?;
108
109    let n = "cryptography";
110    let submodule = pyo3::wrap_pymodule!(nautilus_cryptography::python::cryptography);
111    m.add_wrapped(submodule)?;
112    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
113    #[cfg(feature = "cython-compat")]
114    re_export_module_attributes(m, n)?;
115
116    let n = "data";
117    let submodule = pyo3::wrap_pymodule!(nautilus_data::python::data);
118    m.add_wrapped(submodule)?;
119    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
120    #[cfg(feature = "cython-compat")]
121    re_export_module_attributes(m, n)?;
122
123    let n = "execution";
124    let submodule = pyo3::wrap_pymodule!(nautilus_execution::python::execution);
125    m.add_wrapped(submodule)?;
126    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
127    #[cfg(feature = "cython-compat")]
128    re_export_module_attributes(m, n)?;
129
130    let n = "indicators";
131    let submodule = pyo3::wrap_pymodule!(nautilus_indicators::python::indicators);
132    m.add_wrapped(submodule)?;
133    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
134    #[cfg(feature = "cython-compat")]
135    re_export_module_attributes(m, n)?;
136
137    let n = "infrastructure";
138    let submodule = pyo3::wrap_pymodule!(nautilus_infrastructure::python::infrastructure);
139    m.add_wrapped(submodule)?;
140    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
141    #[cfg(feature = "cython-compat")]
142    re_export_module_attributes(m, n)?;
143
144    let n = "live";
145    let submodule = pyo3::wrap_pymodule!(nautilus_live::python::live);
146    m.add_wrapped(submodule)?;
147    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
148    #[cfg(feature = "cython-compat")]
149    re_export_module_attributes(m, n)?;
150
151    let n = "model";
152    let submodule = pyo3::wrap_pymodule!(nautilus_model::python::model);
153    m.add_wrapped(submodule)?;
154    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
155    #[cfg(feature = "cython-compat")]
156    re_export_module_attributes(m, n)?;
157
158    let n = "network";
159    let submodule = pyo3::wrap_pymodule!(nautilus_network::python::network);
160    m.add_wrapped(submodule)?;
161    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
162    #[cfg(feature = "cython-compat")]
163    re_export_module_attributes(m, n)?;
164
165    let n = "persistence";
166    let submodule = pyo3::wrap_pymodule!(nautilus_persistence::python::persistence);
167    m.add_wrapped(submodule)?;
168    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
169    #[cfg(feature = "cython-compat")]
170    re_export_module_attributes(m, n)?;
171
172    let n = "risk";
173    let submodule = pyo3::wrap_pymodule!(nautilus_risk::python::risk);
174    m.add_wrapped(submodule)?;
175    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
176    #[cfg(feature = "cython-compat")]
177    re_export_module_attributes(m, n)?;
178
179    let n = "serialization";
180    let submodule = pyo3::wrap_pymodule!(nautilus_serialization::python::serialization);
181    m.add_wrapped(submodule)?;
182    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
183    #[cfg(feature = "cython-compat")]
184    re_export_module_attributes(m, n)?;
185
186    let n = "testkit";
187    let submodule = pyo3::wrap_pymodule!(nautilus_testkit::python::testkit);
188    m.add_wrapped(submodule)?;
189    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
190    #[cfg(feature = "cython-compat")]
191    re_export_module_attributes(m, n)?;
192
193    let n = "trading";
194    let submodule = pyo3::wrap_pymodule!(nautilus_trading::python::trading);
195    m.add_wrapped(submodule)?;
196    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
197    #[cfg(feature = "cython-compat")]
198    re_export_module_attributes(m, n)?;
199
200    let n = "backtest";
201    let submodule = pyo3::wrap_pymodule!(nautilus_backtest::python::backtest);
202    m.add_wrapped(submodule)?;
203    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
204    #[cfg(feature = "cython-compat")]
205    re_export_module_attributes(m, n)?;
206
207    ////////////////////////////////////////////////////////////////////////////////
208    // Adapters
209    ////////////////////////////////////////////////////////////////////////////////
210
211    let n = "architect";
212    let submodule = pyo3::wrap_pymodule!(nautilus_architect_ax::python::architect);
213    m.add_wrapped(submodule)?;
214    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
215    #[cfg(feature = "cython-compat")]
216    re_export_module_attributes(m, n)?;
217
218    let n = "binance";
219    let submodule = pyo3::wrap_pymodule!(nautilus_binance::python::binance);
220    m.add_wrapped(submodule)?;
221    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
222    #[cfg(feature = "cython-compat")]
223    re_export_module_attributes(m, n)?;
224
225    let n = "bitmex";
226    let submodule = pyo3::wrap_pymodule!(nautilus_bitmex::python::bitmex);
227    m.add_wrapped(submodule)?;
228    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
229    #[cfg(feature = "cython-compat")]
230    re_export_module_attributes(m, n)?;
231
232    let n = "bybit";
233    let submodule = pyo3::wrap_pymodule!(nautilus_bybit::python::bybit);
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    let n = "coinbase";
240    let submodule = pyo3::wrap_pymodule!(nautilus_coinbase::python::coinbase);
241    m.add_wrapped(submodule)?;
242    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
243    #[cfg(feature = "cython-compat")]
244    re_export_module_attributes(m, n)?;
245
246    let n = "databento";
247    let submodule = pyo3::wrap_pymodule!(nautilus_databento::python::databento);
248    m.add_wrapped(submodule)?;
249    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
250    #[cfg(feature = "cython-compat")]
251    re_export_module_attributes(m, n)?;
252
253    let n = "deribit";
254    let submodule = pyo3::wrap_pymodule!(nautilus_deribit::python::deribit);
255    m.add_wrapped(submodule)?;
256    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
257    #[cfg(feature = "cython-compat")]
258    re_export_module_attributes(m, n)?;
259
260    let n = "dydx";
261    let submodule = pyo3::wrap_pymodule!(nautilus_dydx::python::dydx);
262    m.add_wrapped(submodule)?;
263    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
264    #[cfg(feature = "cython-compat")]
265    re_export_module_attributes(m, n)?;
266
267    let n = "hyperliquid";
268    let submodule = pyo3::wrap_pymodule!(nautilus_hyperliquid::python::hyperliquid);
269    m.add_wrapped(submodule)?;
270    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
271    #[cfg(feature = "cython-compat")]
272    re_export_module_attributes(m, n)?;
273
274    let n = "kraken";
275    let submodule = pyo3::wrap_pymodule!(nautilus_kraken::python::kraken);
276    m.add_wrapped(submodule)?;
277    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
278    #[cfg(feature = "cython-compat")]
279    re_export_module_attributes(m, n)?;
280
281    let n = "interactive_brokers";
282    let submodule = pyo3::wrap_pymodule!(nautilus_interactive_brokers::python::interactive_brokers);
283    m.add_wrapped(submodule)?;
284    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
285    #[cfg(feature = "cython-compat")]
286    re_export_module_attributes(m, n)?;
287
288    let n = "okx";
289    let submodule = pyo3::wrap_pymodule!(nautilus_okx::python::okx);
290    m.add_wrapped(submodule)?;
291    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
292    #[cfg(feature = "cython-compat")]
293    re_export_module_attributes(m, n)?;
294
295    let n = "polymarket";
296    let submodule = pyo3::wrap_pymodule!(nautilus_polymarket::python::polymarket);
297    m.add_wrapped(submodule)?;
298    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
299    #[cfg(feature = "cython-compat")]
300    re_export_module_attributes(m, n)?;
301
302    let n = "sandbox";
303    let submodule = pyo3::wrap_pymodule!(nautilus_sandbox::python::sandbox);
304    m.add_wrapped(submodule)?;
305    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
306    #[cfg(feature = "cython-compat")]
307    re_export_module_attributes(m, n)?;
308
309    let n = "tardis";
310    let submodule = pyo3::wrap_pymodule!(nautilus_tardis::python::tardis);
311    m.add_wrapped(submodule)?;
312    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
313    #[cfg(feature = "cython-compat")]
314    re_export_module_attributes(m, n)?;
315
316    #[cfg(feature = "defi")]
317    {
318        // nautilus-import-ok: wrap_pymodule! requires fully qualified paths
319        let n = "blockchain";
320        let submodule = pyo3::wrap_pymodule!(nautilus_blockchain::python::blockchain);
321        m.add_wrapped(submodule)?;
322        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
323        #[cfg(feature = "cython-compat")]
324        re_export_module_attributes(m, n)?;
325    }
326
327    // Register a lightweight shutdown hook so the interpreter waits for the Tokio
328    // runtime to yield once before `Py_Finalize` tears it down.
329    m.add_function(pyo3::wrap_pyfunction!(_shutdown_nautilus_runtime, m)?)?;
330    let shutdown_callable = m.getattr("_shutdown_nautilus_runtime")?;
331    let atexit = PyModule::import(py, "atexit")?;
332    atexit.call_method1("register", (shutdown_callable,))?;
333
334    Ok(())
335}
336
337#[cfg(feature = "cython-compat")]
338fn re_export_module_attributes(
339    parent_module: &Bound<'_, PyModule>,
340    submodule_name: &str,
341) -> PyResult<()> {
342    let submodule = parent_module.getattr(submodule_name)?;
343    for item_name in submodule.dir()? {
344        let item_name_str: &str = item_name.extract()?;
345        if let Ok(attr) = submodule.getattr(item_name_str) {
346            parent_module.add(item_name_str, attr)?;
347        }
348    }
349
350    Ok(())
351}
352
353/// Generate Python type stub info for PyO3 bindings.
354///
355/// Assumes the pyproject.toml is located in the python/ directory relative to the workspace root.
356///
357/// # Panics
358///
359/// Panics if the path locating the pyproject.toml is incorrect.
360///
361/// # Errors
362///
363/// Returns an error if stub information generation fails.
364///
365/// # Reference
366///
367/// - <https://pyo3.rs/latest/python-typing-hints>
368/// - <https://crates.io/crates/pyo3-stub-gen>
369/// - <https://github.com/Jij-Inc/pyo3-stub-gen>
370pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
371    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
372        .parent()
373        .unwrap()
374        .parent()
375        .unwrap();
376    let pyproject_path = workspace_root.join("python").join("pyproject.toml");
377
378    pyo3_stub_gen::StubInfo::from_pyproject_toml(&pyproject_path)
379}