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