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::{consts::OKX, 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(|e| {
60                to_pyvalue_err(format!("Invalid OKX trigger type {value:?} for {key}: {e}"))
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(stringify!(OKX), OKX)?;
120    m.add_class::<super::websocket::OKXWebSocketClient>()?;
121    m.add_class::<super::websocket::messages::OKXWebSocketError>()?;
122    m.add_class::<super::http::OKXHttpClient>()?;
123    m.add_class::<crate::http::models::OKXBalanceDetail>()?;
124    m.add_class::<crate::common::enums::OKXInstrumentType>()?;
125    m.add_class::<crate::common::enums::OKXContractType>()?;
126    m.add_class::<crate::common::enums::OKXGreeksType>()?;
127    m.add_class::<crate::common::enums::OKXMarginMode>()?;
128    m.add_class::<crate::common::enums::OKXTradeMode>()?;
129    m.add_class::<crate::common::enums::OKXOrderStatus>()?;
130    m.add_class::<crate::common::enums::OKXPositionMode>()?;
131    m.add_class::<crate::common::enums::OKXVipLevel>()?;
132    m.add_class::<crate::common::enums::OKXEnvironment>()?;
133    m.add_class::<crate::common::enums::OKXRegion>()?;
134    m.add_class::<crate::common::urls::OKXEndpointType>()?;
135    m.add_class::<OKXDataClientConfig>()?;
136    m.add_class::<OKXExecClientConfig>()?;
137    m.add_class::<OKXDataClientFactory>()?;
138    m.add_class::<OKXExecutionClientFactory>()?;
139    m.add_function(wrap_pyfunction!(urls::get_okx_http_base_url, m)?)?;
140    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_public, m)?)?;
141    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_private, m)?)?;
142    m.add_function(wrap_pyfunction!(urls::get_okx_ws_url_business, m)?)?;
143    m.add_function(wrap_pyfunction!(urls::derive_okx_ws_url, m)?)?;
144    m.add_function(wrap_pyfunction!(urls::okx_requires_authentication, m)?)?;
145
146    let registry = get_global_pyo3_registry();
147
148    if let Err(e) = registry.register_factory_extractor(OKX.to_string(), extract_okx_data_factory) {
149        return Err(to_pyruntime_err(format!(
150            "Failed to register OKX data factory extractor: {e}"
151        )));
152    }
153
154    if let Err(e) =
155        registry.register_exec_factory_extractor(OKX.to_string(), extract_okx_exec_factory)
156    {
157        return Err(to_pyruntime_err(format!(
158            "Failed to register OKX exec factory extractor: {e}"
159        )));
160    }
161
162    if let Err(e) = registry
163        .register_config_extractor("OKXDataClientConfig".to_string(), extract_okx_data_config)
164    {
165        return Err(to_pyruntime_err(format!(
166            "Failed to register OKX data config extractor: {e}"
167        )));
168    }
169
170    if let Err(e) = registry
171        .register_config_extractor("OKXExecClientConfig".to_string(), extract_okx_exec_config)
172    {
173        return Err(to_pyruntime_err(format!(
174            "Failed to register OKX exec config extractor: {e}"
175        )));
176    }
177
178    Ok(())
179}