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