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//! - `arrow`: Enables Apache Arrow support in dependent crates.
36//! - `betfair`: Enables the Betfair adapter and its Python bindings.
37//! - `defi`: Enables DeFi (Decentralized Finance) support, including blockchain adapters.
38//! - `extension-module`: Builds as a Python extension module and is automatically enabled by
39//!   `maturin`.
40//! - `high-precision`: Uses 128-bit value types throughout the workspace.
41//! - `hypersync`: Enables [`hypersync-client`](https://crates.io/crates/hypersync-client)
42//!   support for the blockchain adapter.
43//! - `mimalloc`: Sets [mimalloc](https://crates.io/crates/mimalloc) as Rust's global allocator.
44//! - `postgres`: Enables PostgreSQL (sqlx) back-ends in dependent crates.
45//! - `redis`: Enables Redis based infrastructure in dependent crates.
46//! - `tracing-bridge`: Enables the `tracing` subscriber bridge for log integration.
47
48#![warn(rustc::all)]
49#![deny(unsafe_code)]
50#![deny(unsafe_op_in_unsafe_fn)]
51#![deny(nonstandard_style)]
52#![deny(missing_debug_implementations)]
53#![deny(clippy::missing_errors_doc)]
54#![deny(clippy::missing_panics_doc)]
55#![deny(rustdoc::broken_intra_doc_links)]
56
57use std::{path::Path, time::Duration};
58
59#[cfg(feature = "mimalloc")]
60use mimalloc::MiMalloc;
61use nautilus_common::live::runtime::shutdown_runtime;
62use nautilus_system::python::controller::PyController;
63use pyo3::{prelude::*, pyfunction};
64
65#[cfg(feature = "mimalloc")]
66#[global_allocator]
67static GLOBAL: MiMalloc = MiMalloc;
68
69const RUNTIME_SHUTDOWN_TIMEOUT_SECS: u64 = 10;
70
71#[pyfunction]
72fn _shutdown_nautilus_runtime() {
73    shutdown_runtime(Duration::from_secs(RUNTIME_SHUTDOWN_TIMEOUT_SECS));
74}
75
76/// Adds each wrapped module to `sys.modules` so Python can import it as a submodule.
77///
78/// See <https://github.com/PyO3/pyo3/issues/2644>.
79#[pymodule] // The name of the function must match `lib.name` in `Cargo.toml`
80fn _libnautilus(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
81    #[cfg(feature = "mimalloc")]
82    nautilus_common::logging::headers::register_allocator_mimalloc();
83
84    let sys = PyModule::import(py, "sys")?;
85    let modules = sys.getattr("modules")?;
86    let sys_modules: &Bound<'_, PyAny> = modules.cast()?;
87
88    let module_name = "nautilus_trader._libnautilus";
89
90    // Set pyo3_nautilus to be recognized as a subpackage
91    sys_modules.set_item(module_name, m)?;
92
93    // nautilus-import-ok: wrap_pymodule! requires fully qualified paths
94    let n = "analysis";
95    let submodule = pyo3::wrap_pymodule!(nautilus_analysis::python::analysis);
96    m.add_wrapped(submodule)?;
97    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
98
99    let n = "core";
100    let submodule = pyo3::wrap_pymodule!(nautilus_core::python::core);
101    m.add_wrapped(submodule)?;
102    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
103
104    let n = "common";
105    let submodule = pyo3::wrap_pymodule!(nautilus_common::python::common);
106    m.add_wrapped(submodule)?;
107    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(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
114    let n = "data";
115    let submodule = pyo3::wrap_pymodule!(nautilus_data::python::data);
116    m.add_wrapped(submodule)?;
117    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
118
119    let n = "execution";
120    let submodule = pyo3::wrap_pymodule!(nautilus_execution::python::execution);
121    m.add_wrapped(submodule)?;
122    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
123
124    let n = "indicators";
125    let submodule = pyo3::wrap_pymodule!(nautilus_indicators::python::indicators);
126    m.add_wrapped(submodule)?;
127    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
128
129    let n = "infrastructure";
130    let submodule = pyo3::wrap_pymodule!(nautilus_infrastructure::python::infrastructure);
131    m.add_wrapped(submodule)?;
132    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
133
134    let n = "live";
135    let submodule = pyo3::wrap_pymodule!(nautilus_live::python::live);
136    m.add_wrapped(submodule)?;
137    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
138
139    let n = "model";
140    let submodule = pyo3::wrap_pymodule!(nautilus_model::python::model);
141    m.add_wrapped(submodule)?;
142    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
143
144    let n = "network";
145    let submodule = pyo3::wrap_pymodule!(nautilus_network::python::network);
146    m.add_wrapped(submodule)?;
147    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(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
154    let n = "portfolio";
155    let submodule = pyo3::wrap_pymodule!(nautilus_portfolio::python::portfolio);
156    m.add_wrapped(submodule)?;
157    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
158
159    let n = "risk";
160    let submodule = pyo3::wrap_pymodule!(nautilus_risk::python::risk);
161    m.add_wrapped(submodule)?;
162    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
163
164    let n = "serialization";
165    let submodule = pyo3::wrap_pymodule!(nautilus_serialization::python::serialization);
166    m.add_wrapped(submodule)?;
167    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
168
169    let n = "testkit";
170    let submodule = pyo3::wrap_pymodule!(nautilus_testkit::python::testkit);
171    m.add_wrapped(submodule)?;
172    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
173
174    let n = "trading";
175    let submodule = pyo3::wrap_pymodule!(nautilus_trading::python::trading);
176    m.add_wrapped(submodule)?;
177
178    // `Controller` drives the trader, so it lives in nautilus-system which depends on
179    // nautilus-trading and therefore cannot register itself from the trading module
180    m.getattr(n)?
181        .cast::<PyModule>()?
182        .add_class::<PyController>()?;
183    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
184
185    let n = "backtest";
186    let submodule = pyo3::wrap_pymodule!(nautilus_backtest::python::backtest);
187    m.add_wrapped(submodule)?;
188    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
189
190    ////////////////////////////////////////////////////////////////////////////////
191    // Adapters
192    ////////////////////////////////////////////////////////////////////////////////
193
194    let n = "architect_ax";
195    let submodule = pyo3::wrap_pymodule!(nautilus_architect_ax::python::architect_ax);
196    m.add_wrapped(submodule)?;
197    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
198
199    #[cfg(feature = "betfair")]
200    {
201        let n = "betfair";
202        let submodule = pyo3::wrap_pymodule!(nautilus_betfair::python::betfair);
203        m.add_wrapped(submodule)?;
204        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
205    }
206
207    let n = "binance";
208    let submodule = pyo3::wrap_pymodule!(nautilus_binance::python::binance);
209    m.add_wrapped(submodule)?;
210    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
211
212    let n = "bitmex";
213    let submodule = pyo3::wrap_pymodule!(nautilus_bitmex::python::bitmex);
214    m.add_wrapped(submodule)?;
215    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
216
217    let n = "bybit";
218    let submodule = pyo3::wrap_pymodule!(nautilus_bybit::python::bybit);
219    m.add_wrapped(submodule)?;
220    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
221
222    let n = "coinbase";
223    let submodule = pyo3::wrap_pymodule!(nautilus_coinbase::python::coinbase);
224    m.add_wrapped(submodule)?;
225    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
226
227    let n = "databento";
228    let submodule = pyo3::wrap_pymodule!(nautilus_databento::python::databento);
229    m.add_wrapped(submodule)?;
230    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
231
232    let n = "deribit";
233    let submodule = pyo3::wrap_pymodule!(nautilus_deribit::python::deribit);
234    m.add_wrapped(submodule)?;
235    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
236
237    let n = "derive";
238    let submodule = pyo3::wrap_pymodule!(nautilus_derive::python::derive);
239    m.add_wrapped(submodule)?;
240    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
241
242    let n = "dydx";
243    let submodule = pyo3::wrap_pymodule!(nautilus_dydx::python::dydx);
244    m.add_wrapped(submodule)?;
245    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
246
247    let n = "hyperliquid";
248    let submodule = pyo3::wrap_pymodule!(nautilus_hyperliquid::python::hyperliquid);
249    m.add_wrapped(submodule)?;
250    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
251
252    let n = "kraken";
253    let submodule = pyo3::wrap_pymodule!(nautilus_kraken::python::kraken);
254    m.add_wrapped(submodule)?;
255    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
256
257    let n = "lighter";
258    let submodule = pyo3::wrap_pymodule!(nautilus_lighter::python::lighter);
259    m.add_wrapped(submodule)?;
260    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
261
262    let n = "interactive_brokers";
263    let submodule = pyo3::wrap_pymodule!(nautilus_interactive_brokers::python::interactive_brokers);
264    m.add_wrapped(submodule)?;
265    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
266
267    let n = "okx";
268    let submodule = pyo3::wrap_pymodule!(nautilus_okx::python::okx);
269    m.add_wrapped(submodule)?;
270    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
271
272    let n = "polymarket";
273    let submodule = pyo3::wrap_pymodule!(nautilus_polymarket::python::polymarket);
274    m.add_wrapped(submodule)?;
275    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
276
277    let n = "sandbox";
278    let submodule = pyo3::wrap_pymodule!(nautilus_sandbox::python::sandbox);
279    m.add_wrapped(submodule)?;
280    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
281
282    let n = "tardis";
283    let submodule = pyo3::wrap_pymodule!(nautilus_tardis::python::tardis);
284    m.add_wrapped(submodule)?;
285    sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
286
287    #[cfg(feature = "defi")]
288    {
289        // nautilus-import-ok: wrap_pymodule! requires fully qualified paths
290        let n = "blockchain";
291        let submodule = pyo3::wrap_pymodule!(nautilus_blockchain::python::blockchain);
292        m.add_wrapped(submodule)?;
293        sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?;
294    }
295
296    // Register a lightweight shutdown hook so the interpreter waits for the Tokio
297    // runtime to yield once before `Py_Finalize` tears it down.
298    m.add_function(pyo3::wrap_pyfunction!(_shutdown_nautilus_runtime, m)?)?;
299    let shutdown_callable = m.getattr("_shutdown_nautilus_runtime")?;
300    let atexit = PyModule::import(py, "atexit")?;
301    atexit.call_method1("register", (shutdown_callable,))?;
302
303    Ok(())
304}
305
306/// Generate Python type stub info for PyO3 bindings.
307///
308/// Assumes the pyproject.toml is located in the python/ directory relative to the workspace root.
309///
310/// # Panics
311///
312/// Panics if the path locating the pyproject.toml is incorrect.
313///
314/// # Errors
315///
316/// Returns an error if stub information generation fails.
317///
318/// # Reference
319///
320/// - <https://pyo3.rs/latest/python-typing-hints>
321/// - <https://crates.io/crates/pyo3-stub-gen>
322/// - <https://github.com/Jij-Inc/pyo3-stub-gen>
323pub fn stub_info() -> pyo3_stub_gen::Result<pyo3_stub_gen::StubInfo> {
324    let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
325        .parent()
326        .unwrap()
327        .parent()
328        .unwrap();
329    let pyproject_path = workspace_root.join("python").join("pyproject.toml");
330
331    pyo3_stub_gen::StubInfo::from_pyproject_toml(&pyproject_path)
332}