nautilus_okx/python/
mod.rs1#![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#[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}