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