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 = "portfolio";
173    let submodule = pyo3::wrap_pymodule!(nautilus_portfolio::python::portfolio);
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 = "risk";
180    let submodule = pyo3::wrap_pymodule!(nautilus_risk::python::risk);
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 = "serialization";
187    let submodule = pyo3::wrap_pymodule!(nautilus_serialization::python::serialization);
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 = "testkit";
194    let submodule = pyo3::wrap_pymodule!(nautilus_testkit::python::testkit);
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 = "trading";
201    let submodule = pyo3::wrap_pymodule!(nautilus_trading::python::trading);
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    let n = "backtest";
208    let submodule = pyo3::wrap_pymodule!(nautilus_backtest::python::backtest);
209    m.add_wrapped(submodule)?;
210    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
211    #[cfg(feature = "cython-compat")]
212    re_export_module_attributes(m, n)?;
213
214    ////////////////////////////////////////////////////////////////////////////////
215    // Adapters
216    ////////////////////////////////////////////////////////////////////////////////
217
218    let n = "architect_ax";
219    let submodule = pyo3::wrap_pymodule!(nautilus_architect_ax::python::architect_ax);
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    #[cfg(feature = "betfair")]
226    {
227        let n = "betfair";
228        let submodule = pyo3::wrap_pymodule!(nautilus_betfair::python::betfair);
229        m.add_wrapped(submodule)?;
230        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
231        #[cfg(feature = "cython-compat")]
232        re_export_module_attributes(m, n)?;
233    }
234
235    let n = "binance";
236    let submodule = pyo3::wrap_pymodule!(nautilus_binance::python::binance);
237    m.add_wrapped(submodule)?;
238    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
239    #[cfg(feature = "cython-compat")]
240    re_export_module_attributes(m, n)?;
241
242    let n = "bitmex";
243    let submodule = pyo3::wrap_pymodule!(nautilus_bitmex::python::bitmex);
244    m.add_wrapped(submodule)?;
245    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
246    #[cfg(feature = "cython-compat")]
247    re_export_module_attributes(m, n)?;
248
249    let n = "bybit";
250    let submodule = pyo3::wrap_pymodule!(nautilus_bybit::python::bybit);
251    m.add_wrapped(submodule)?;
252    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
253    #[cfg(feature = "cython-compat")]
254    re_export_module_attributes(m, n)?;
255
256    let n = "coinbase";
257    let submodule = pyo3::wrap_pymodule!(nautilus_coinbase::python::coinbase);
258    m.add_wrapped(submodule)?;
259    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
260    #[cfg(feature = "cython-compat")]
261    re_export_module_attributes(m, n)?;
262
263    let n = "databento";
264    let submodule = pyo3::wrap_pymodule!(nautilus_databento::python::databento);
265    m.add_wrapped(submodule)?;
266    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
267    #[cfg(feature = "cython-compat")]
268    re_export_module_attributes(m, n)?;
269
270    let n = "deribit";
271    let submodule = pyo3::wrap_pymodule!(nautilus_deribit::python::deribit);
272    m.add_wrapped(submodule)?;
273    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
274    #[cfg(feature = "cython-compat")]
275    re_export_module_attributes(m, n)?;
276
277    let n = "derive";
278    let submodule = pyo3::wrap_pymodule!(nautilus_derive::python::derive);
279    m.add_wrapped(submodule)?;
280    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
281    #[cfg(feature = "cython-compat")]
282    re_export_module_attributes(m, n)?;
283
284    let n = "dydx";
285    let submodule = pyo3::wrap_pymodule!(nautilus_dydx::python::dydx);
286    m.add_wrapped(submodule)?;
287    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
288    #[cfg(feature = "cython-compat")]
289    re_export_module_attributes(m, n)?;
290
291    let n = "hyperliquid";
292    let submodule = pyo3::wrap_pymodule!(nautilus_hyperliquid::python::hyperliquid);
293    m.add_wrapped(submodule)?;
294    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
295    #[cfg(feature = "cython-compat")]
296    re_export_module_attributes(m, n)?;
297
298    let n = "kraken";
299    let submodule = pyo3::wrap_pymodule!(nautilus_kraken::python::kraken);
300    m.add_wrapped(submodule)?;
301    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
302    #[cfg(feature = "cython-compat")]
303    re_export_module_attributes(m, n)?;
304
305    let n = "lighter";
306    let submodule = pyo3::wrap_pymodule!(nautilus_lighter::python::lighter);
307    m.add_wrapped(submodule)?;
308    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
309    #[cfg(feature = "cython-compat")]
310    re_export_module_attributes(m, n)?;
311
312    let n = "interactive_brokers";
313    let submodule = pyo3::wrap_pymodule!(nautilus_interactive_brokers::python::interactive_brokers);
314    m.add_wrapped(submodule)?;
315    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
316    #[cfg(feature = "cython-compat")]
317    re_export_module_attributes(m, n)?;
318
319    let n = "okx";
320    let submodule = pyo3::wrap_pymodule!(nautilus_okx::python::okx);
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    let n = "polymarket";
327    let submodule = pyo3::wrap_pymodule!(nautilus_polymarket::python::polymarket);
328    m.add_wrapped(submodule)?;
329    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
330    #[cfg(feature = "cython-compat")]
331    re_export_module_attributes(m, n)?;
332
333    let n = "sandbox";
334    let submodule = pyo3::wrap_pymodule!(nautilus_sandbox::python::sandbox);
335    m.add_wrapped(submodule)?;
336    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
337    #[cfg(feature = "cython-compat")]
338    re_export_module_attributes(m, n)?;
339
340    let n = "tardis";
341    let submodule = pyo3::wrap_pymodule!(nautilus_tardis::python::tardis);
342    m.add_wrapped(submodule)?;
343    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
344    #[cfg(feature = "cython-compat")]
345    re_export_module_attributes(m, n)?;
346
347    #[cfg(feature = "defi")]
348    {
349        // nautilus-import-ok: wrap_pymodule! requires fully qualified paths
350        let n = "blockchain";
351        let submodule = pyo3::wrap_pymodule!(nautilus_blockchain::python::blockchain);
352        m.add_wrapped(submodule)?;
353        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
354        #[cfg(feature = "cython-compat")]
355        re_export_module_attributes(m, n)?;
356    }
357
358    // Register a lightweight shutdown hook so the interpreter waits for the Tokio
359    // runtime to yield once before `Py_Finalize` tears it down.
360    m.add_function(pyo3::wrap_pyfunction!(_shutdown_nautilus_runtime, m)?)?;
361    let shutdown_callable = m.getattr("_shutdown_nautilus_runtime")?;
362    let atexit = PyModule::import(py, "atexit")?;
363    atexit.call_method1("register", (shutdown_callable,))?;
364
365    Ok(())
366}
367
368#[cfg(feature = "cython-compat")]
369fn re_export_module_attributes(
370    parent_module: &Bound<'_, PyModule>,
371    submodule_name: &str,
372) -> PyResult<()> {
373    let submodule = parent_module.getattr(submodule_name)?;
374    for item_name in submodule.dir()? {
375        let item_name_str: &str = item_name.extract()?;
376        if let Ok(attr) = submodule.getattr(item_name_str) {
377            parent_module.add(item_name_str, attr)?;
378        }
379    }
380
381    Ok(())
382}
383
384/// Generate Python type stub info for PyO3 bindings.
385///
386/// Assumes the pyproject.toml is located in the python/ directory relative to the workspace root.
387///
388/// # Panics
389///
390/// Panics if the path locating the pyproject.toml is incorrect.
391///
392/// # Errors
393///
394/// Returns an error if stub information generation fails.
395///
396/// # Reference
397///
398/// - <https://pyo3.rs/latest/python-typing-hints>
399/// - <https://crates.io/crates/pyo3-stub-gen>
400/// - <https://github.com/Jij-Inc/pyo3-stub-gen>
401pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
402    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
403        .parent()
404        .unwrap()
405        .parent()
406        .unwrap();
407    let pyproject_path = workspace_root.join("python").join("pyproject.toml");
408
409    pyo3_stub_gen::StubInfo::from_pyproject_toml(&pyproject_path)
410}