Skip to main content

nautilus_okx/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 from `pyo3`.
17
18#![expect(
19    clippy::missing_errors_doc,
20    reason = "errors documented on underlying Rust methods"
21)]
22
23pub mod config;
24pub mod enums;
25pub mod factories;
26pub mod http;
27pub mod models;
28pub mod urls;
29pub mod websocket;
30
31use std::str::FromStr;
32
33use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
34use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
35use nautilus_system::get_global_pyo3_registry;
36use pyo3::{prelude::*, types::PyDict};
37
38use crate::{
39    common::enums::OKXTriggerType,
40    config::{OKXDataClientConfig, OKXExecClientConfig},
41    factories::{OKXDataClientFactory, OKXExecutionClientFactory},
42};
43
44pub(super) fn extract_optional_string(
45    dict: &Bound<'_, PyDict>,
46    key: &str,
47) -> PyResult<Option<String>> {
48    dict.get_item(key)?
49        .map(|value| value.extract::<String>())
50        .transpose()
51}
52
53pub(super) fn extract_optional_trigger_type(
54    dict: &Bound<'_, PyDict>,
55    key: &str,
56) -> PyResult<Option<OKXTriggerType>> {
57    extract_optional_string(dict, key)?
58        .map(|value| {
59            OKXTriggerType::from_str(&value).map_err(|_| {
60                to_pyvalue_err(format!("Invalid OKX trigger type {value:?} for {key}"))
61            })
62        })
63        .transpose()
64}
65
66#[expect(clippy::needless_pass_by_value)]
67fn extract_okx_data_factory(
68    py: Python<'_>,
69    factory: Py<PyAny>,
70) -> PyResult<Box<dyn DataClientFactory>> {
71    match factory.extract::<OKXDataClientFactory>(py) {
72        Ok(f) => Ok(Box::new(f)),
73        Err(e) => Err(to_pyvalue_err(format!(
74            "Failed to extract OKXDataClientFactory: {e}"
75        ))),
76    }
77}
78
79#[expect(clippy::needless_pass_by_value)]
80fn extract_okx_exec_factory(
81    py: Python<'_>,
82    factory: Py<PyAny>,
83) -> PyResult<Box<dyn ExecutionClientFactory>> {
84    match factory.extract::<OKXExecutionClientFactory>(py) {
85        Ok(f) => Ok(Box::new(f)),
86        Err(e) => Err(to_pyvalue_err(format!(
87            "Failed to extract OKXExecutionClientFactory: {e}"
88        ))),
89    }
90}
91
92#[expect(clippy::needless_pass_by_value)]
93fn extract_okx_data_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
94    match config.extract::<OKXDataClientConfig>(py) {
95        Ok(c) => Ok(Box::new(c)),
96        Err(e) => Err(to_pyvalue_err(format!(
97            "Failed to extract OKXDataClientConfig: {e}"
98        ))),
99    }
100}
101
102#[expect(clippy::needless_pass_by_value)]
103fn extract_okx_exec_config(py: Python<'_>, config: Py<PyAny>) -> PyResult<Box<dyn ClientConfig>> {
104    match config.extract::<OKXExecClientConfig>(py) {
105        Ok(c) => Ok(Box::new(c)),
106        Err(e) => Err(to_pyvalue_err(format!(
107            "Failed to extract OKXExecClientConfig: {e}"
108        ))),
109    }
110}
111
112/// Loaded as `nautilus_pyo3.okx`.
113///
114/// # Errors
115///
116/// Returns an error if any bindings fail to register with the Python module.
117#[pymodule]
118pub fn okx(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
119    m.add_class::<super::websocket::OKXWebSocketClient>()?;
120    m.add_class::<super::websocket::messages::OKXWebSocketError>()?;
121    m.add_class::<super::http::OKXHttpClient>()?;
122    m.add_class::<crate::http::models::OKXBalanceDetail>()?;
123    m.add_class::<crate::common::enums::OKXInstrumentType>()?;
124    m.add_class::<crate::common::enums::OKXContractType>()?;
125    m.add_class::<crate::common::enums::OKXGreeksType>()?;
126    m.add_class::<crate::common::enums::OKXMarginMode>()?;
127    m.add_class::<crate::common::enums::OKXTradeMode>()?;
128    m.add_class::<crate::common::enums::OKXOrderStatus>()?;
129    m.add_class::<crate::common::enums::OKXPositionMode>()?;
130    m.add_class::<crate::common::enums::OKXVipLevel>()?;
131    m.add_class::<crate::common::enums::OKXEnvironment>()?;
132    m.add_class::<crate::common::urls::OKXEndpointType>()?;
133    m.add_class::<OKXDataClientConfig>()?;
134    m.add_class::<OKXExecClientConfig>()?;
135    m.add_class::<OKXDataClientFactory>()?;
136    m.add_class::<OKXExecutionClientFactory>()?;
137    m.add_function(wrap_pyfunction!(urls::get_okx_http_base_url, m)?)?;
138    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_public, m)?)?;
139    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_private, m)?)?;
140    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_business, m)?)?;
141    m.add_function(wrap_pyfunction!(urls::derive_okx_ws_url, m)?)?;
142    m.add_function(wrap_pyfunction!(urls::okx_requires_authentication, m)?)?;
143
144    let registry = get_global_pyo3_registry();
145
146    if let Err(e) = registry.register_factory_extractor("OKX".to_string(), extract_okx_data_factory)
147    {
148        return Err(to_pyruntime_err(format!(
149            "Failed to register OKX data factory extractor: {e}"
150        )));
151    }
152
153    if let Err(e) =
154        registry.register_exec_factory_extractor("OKX".to_string(), extract_okx_exec_factory)
155    {
156        return Err(to_pyruntime_err(format!(
157            "Failed to register OKX exec factory extractor: {e}"
158        )));
159    }
160
161    if let Err(e) = registry
162        .register_config_extractor("OKXDataClientConfig".to_string(), extract_okx_data_config)
163    {
164        return Err(to_pyruntime_err(format!(
165            "Failed to register OKX data config extractor: {e}"
166        )));
167    }
168
169    if let Err(e) = registry
170        .register_config_extractor("OKXExecClientConfig".to_string(), extract_okx_exec_config)
171    {
172        return Err(to_pyruntime_err(format!(
173            "Failed to register OKX exec config extractor: {e}"
174        )));
175    }
176
177    Ok(())
178}