Skip to main content

nautilus_architect_ax/python/
mod.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 for the Ax adapter.
17
18#![expect(
19    clippy::missing_errors_doc,
20    reason = "errors documented on underlying Rust methods"
21)]
22
23pub mod config;
24pub mod factories;
25pub mod http;
26
27use std::str::FromStr;
28
29use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
30use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
31use nautilus_system::get_global_pyo3_registry;
32use pyo3::{prelude::*, types::PyType};
33
34use crate::{
35    common::{
36        consts::{AX, AX_CLIENT_ID, AX_VENUE},
37        enums::{AxEnvironment, AxMarketDataLevel},
38    },
39    config::{AxDataClientConfig, AxExecutionClientConfig},
40    factories::{AxDataClientFactory, AxExecutionClientFactory},
41    http::client::AxHttpClient,
42};
43
44#[expect(clippy::needless_pass_by_value)]
45fn extract_ax_data_factory(
46    py: Python<'_>,
47    factory: Py<PyAny>,
48) -> PyResult<Box<dyn DataClientFactory>> {
49    match factory.extract::<AxDataClientFactory>(py) {
50        Ok(f) => Ok(Box::new(f)),
51        Err(e) => Err(to_pyvalue_err(format!(
52            "Failed to extract AxDataClientFactory: {e}"
53        ))),
54    }
55}
56
57#[expect(clippy::needless_pass_by_value)]
58fn extract_ax_exec_factory(
59    py: Python<'_>,
60    factory: Py<PyAny>,
61) -> PyResult<Box<dyn ExecutionClientFactory>> {
62    match factory.extract::<AxExecutionClientFactory>(py) {
63        Ok(f) => Ok(Box::new(f)),
64        Err(e) => Err(to_pyvalue_err(format!(
65            "Failed to extract AxExecutionClientFactory: {e}"
66        ))),
67    }
68}
69
70#[expect(clippy::needless_pass_by_value)]
71fn extract_ax_data_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
72    match config.extract::<AxDataClientConfig>(py) {
73        Ok(c) => Ok(Box::new(c)),
74        Err(e) => Err(to_pyvalue_err(format!(
75            "Failed to extract AxDataClientConfig: {e}"
76        ))),
77    }
78}
79
80#[expect(clippy::needless_pass_by_value)]
81fn extract_ax_exec_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
82    match config.extract::<AxExecutionClientConfig>(py) {
83        Ok(c) => Ok(Box::new(c)),
84        Err(e) => Err(to_pyvalue_err(format!(
85            "Failed to extract AxExecutionClientConfig: {e}"
86        ))),
87    }
88}
89
90#[pymethods]
91#[pyo3_stub_gen::derive::gen_stub_pymethods]
92impl AxEnvironment {
93    fn __repr__(&self) -> String {
94        format!(
95            "<{}.{}: '{}'>",
96            stringify!(AxEnvironment),
97            self.name(),
98            self.value(),
99        )
100    }
101
102    fn __str__(&self) -> String {
103        self.to_string()
104    }
105
106    #[getter]
107    #[must_use]
108    pub fn name(&self) -> String {
109        self.to_string()
110    }
111
112    #[getter]
113    #[must_use]
114    pub fn value(&self) -> u8 {
115        *self as u8
116    }
117
118    #[classmethod]
119    #[pyo3(name = "from_str")]
120    fn py_from_str(_: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
121        let data_str: &str = data.extract()?;
122        let tokenized = data_str.to_uppercase();
123        Self::from_str(&tokenized).map_err(to_pyvalue_err)
124    }
125}
126
127#[pymethods]
128#[pyo3_stub_gen::derive::gen_stub_pymethods]
129impl AxMarketDataLevel {
130    fn __repr__(&self) -> String {
131        format!(
132            "<{}.{}: '{}'>",
133            stringify!(AxMarketDataLevel),
134            self.name(),
135            self.value(),
136        )
137    }
138
139    fn __str__(&self) -> String {
140        self.to_string()
141    }
142
143    #[getter]
144    #[must_use]
145    pub fn name(&self) -> String {
146        self.to_string()
147    }
148
149    #[getter]
150    #[must_use]
151    pub fn value(&self) -> u8 {
152        *self as u8
153    }
154
155    #[classmethod]
156    #[pyo3(name = "from_str")]
157    fn py_from_str(_: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
158        let data_str: &str = data.extract()?;
159        let tokenized = data_str.to_uppercase();
160        Self::from_str(&tokenized).map_err(to_pyvalue_err)
161    }
162}
163
164/// Exposed through `nautilus_trader.adapters.architect_ax`.
165///
166/// # Errors
167///
168/// Returns a `PyErr` if registering any module components fails.
169#[pymodule]
170pub fn architect_ax(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
171    m.add(stringify!(AX), AX)?;
172    m.add(stringify!(AX_CLIENT_ID), *AX_CLIENT_ID)?;
173    m.add(stringify!(AX_VENUE), *AX_VENUE)?;
174    m.add_class::<AxEnvironment>()?;
175    m.add_class::<AxMarketDataLevel>()?;
176    m.add_class::<AxDataClientConfig>()?;
177    m.add_class::<AxDataClientFactory>()?;
178    m.add_class::<AxExecutionClientConfig>()?;
179    m.add_class::<AxExecutionClientFactory>()?;
180    m.add_class::<AxHttpClient>()?;
181
182    let registry = get_global_pyo3_registry();
183
184    if let Err(e) = registry.register_factory_extractor(AX.to_string(), extract_ax_data_factory) {
185        return Err(to_pyruntime_err(format!(
186            "Failed to register Ax data factory extractor: {e}"
187        )));
188    }
189
190    if let Err(e) =
191        registry.register_exec_factory_extractor(AX.to_string(), extract_ax_exec_factory)
192    {
193        return Err(to_pyruntime_err(format!(
194            "Failed to register Ax exec factory extractor: {e}"
195        )));
196    }
197
198    if let Err(e) =
199        registry.register_config_extractor("AxDataClientConfig".to_string(), extract_ax_data_config)
200    {
201        return Err(to_pyruntime_err(format!(
202            "Failed to register Ax data config extractor: {e}"
203        )));
204    }
205
206    if let Err(e) = registry.register_config_extractor(
207        "AxExecutionClientConfig".to_string(),
208        extract_ax_exec_config,
209    ) {
210        return Err(to_pyruntime_err(format!(
211            "Failed to register Ax exec config extractor: {e}"
212        )));
213    }
214
215    Ok(())
216}