Skip to main content

nautilus_okx/
factories.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//! Factory functions for creating OKX clients and components.
17
18use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21    cache::Cache,
22    clients::{DataClient, ExecutionClient},
23    clock::Clock,
24    factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28    enums::{AccountType, OmsType},
29    identifiers::ClientId,
30};
31
32use crate::{
33    common::{consts::OKX_VENUE, enums::OKXInstrumentType},
34    config::{OKXDataClientConfig, OKXExecClientConfig},
35    data::OKXDataClient,
36    execution::OKXExecutionClient,
37};
38
39impl ClientConfig for OKXDataClientConfig {
40    fn as_any(&self) -> &dyn Any {
41        self
42    }
43}
44
45impl ClientConfig for OKXExecClientConfig {
46    fn as_any(&self) -> &dyn Any {
47        self
48    }
49}
50
51/// Factory for creating OKX data clients.
52#[derive(Debug, Clone)]
53#[cfg_attr(
54    feature = "python",
55    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.okx", from_py_object)
56)]
57#[cfg_attr(
58    feature = "python",
59    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.okx")
60)]
61pub struct OKXDataClientFactory;
62
63impl OKXDataClientFactory {
64    /// Creates a new [`OKXDataClientFactory`] instance.
65    #[must_use]
66    pub const fn new() -> Self {
67        Self
68    }
69}
70
71impl Default for OKXDataClientFactory {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl DataClientFactory for OKXDataClientFactory {
78    fn create(
79        &self,
80        name: &str,
81        config: &dyn ClientConfig,
82        _cache: Rc<RefCell<Cache>>,
83        _clock: Rc<RefCell<dyn Clock>>,
84    ) -> anyhow::Result<Box<dyn DataClient>> {
85        let okx_config = config
86            .as_any()
87            .downcast_ref::<OKXDataClientConfig>()
88            .ok_or_else(|| {
89                anyhow::anyhow!(
90                    "Invalid config type for OKXDataClientFactory. Expected OKXDataClientConfig, was {config:?}",
91                )
92            })?
93            .clone();
94
95        let client_id = ClientId::from(name);
96        let client = OKXDataClient::new(client_id, okx_config)?;
97        Ok(Box::new(client))
98    }
99
100    fn name(&self) -> &'static str {
101        "OKX"
102    }
103
104    fn config_type(&self) -> &'static str {
105        "OKXDataClientConfig"
106    }
107}
108
109/// Factory for creating OKX execution clients.
110#[derive(Debug, Clone)]
111#[cfg_attr(
112    feature = "python",
113    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.okx", from_py_object)
114)]
115#[cfg_attr(
116    feature = "python",
117    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.okx")
118)]
119pub struct OKXExecutionClientFactory;
120
121impl OKXExecutionClientFactory {
122    /// Creates a new [`OKXExecutionClientFactory`] instance.
123    #[must_use]
124    pub const fn new() -> Self {
125        Self
126    }
127}
128
129impl Default for OKXExecutionClientFactory {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl ExecutionClientFactory for OKXExecutionClientFactory {
136    fn create(
137        &self,
138        name: &str,
139        config: &dyn ClientConfig,
140        cache: Rc<RefCell<Cache>>,
141    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
142        let okx_config = config
143            .as_any()
144            .downcast_ref::<OKXExecClientConfig>()
145            .ok_or_else(|| {
146                anyhow::anyhow!(
147                    "Invalid config type for OKXExecutionClientFactory. Expected OKXExecClientConfig, was {config:?}",
148                )
149            })?
150            .clone();
151
152        let has_derivatives = okx_config.instrument_types.iter().any(|t| {
153            matches!(
154                t,
155                OKXInstrumentType::Swap | OKXInstrumentType::Futures | OKXInstrumentType::Option
156            )
157        });
158
159        let account_type = if okx_config.use_spot_margin || has_derivatives {
160            AccountType::Margin
161        } else {
162            AccountType::Cash
163        };
164
165        // OKX uses netting for derivatives, hedging for spot
166        let oms_type = if has_derivatives {
167            OmsType::Netting
168        } else {
169            OmsType::Hedging
170        };
171
172        let core = ExecutionClientCore::new(
173            okx_config.trader_id,
174            ClientId::from(name),
175            *OKX_VENUE,
176            oms_type,
177            okx_config.account_id,
178            account_type,
179            None, // base_currency
180            cache,
181        );
182
183        let client = OKXExecutionClient::new(core, okx_config)?;
184
185        Ok(Box::new(client))
186    }
187
188    fn name(&self) -> &'static str {
189        "OKX"
190    }
191
192    fn config_type(&self) -> &'static str {
193        "OKXExecClientConfig"
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use std::{cell::RefCell, rc::Rc};
200
201    use nautilus_common::{
202        cache::Cache,
203        factories::{ClientConfig, ExecutionClientFactory},
204    };
205    use nautilus_model::identifiers::{AccountId, TraderId};
206    use rstest::rstest;
207
208    use super::*;
209    use crate::{common::enums::OKXInstrumentType, config::OKXExecClientConfig};
210
211    #[rstest]
212    fn test_okx_execution_client_factory_creation() {
213        let factory = OKXExecutionClientFactory::new();
214        assert_eq!(factory.name(), "OKX");
215        assert_eq!(factory.config_type(), "OKXExecClientConfig");
216    }
217
218    #[rstest]
219    fn test_okx_execution_client_factory_default() {
220        let factory = OKXExecutionClientFactory::new();
221        assert_eq!(factory.name(), "OKX");
222    }
223
224    #[rstest]
225    fn test_okx_exec_client_config_implements_client_config() {
226        let config = OKXExecClientConfig {
227            trader_id: TraderId::from("TRADER-001"),
228            account_id: AccountId::from("OKX-001"),
229            instrument_types: vec![OKXInstrumentType::Spot],
230            ..Default::default()
231        };
232
233        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
234        let downcasted = boxed_config.as_any().downcast_ref::<OKXExecClientConfig>();
235
236        assert!(downcasted.is_some());
237    }
238
239    #[rstest]
240    fn test_okx_execution_client_factory_creates_client_for_spot() {
241        let factory = OKXExecutionClientFactory::new();
242        let config = OKXExecClientConfig {
243            trader_id: TraderId::from("TRADER-001"),
244            account_id: AccountId::from("OKX-001"),
245            instrument_types: vec![OKXInstrumentType::Spot],
246            api_key: Some("test_key".to_string()),
247            api_secret: Some("test_secret".to_string()),
248            api_passphrase: Some("test_pass".to_string()),
249            ..Default::default()
250        };
251
252        let cache = Rc::new(RefCell::new(Cache::default()));
253
254        let result = factory.create("OKX-TEST", &config, cache);
255        assert!(result.is_ok());
256
257        let client = result.unwrap();
258        assert_eq!(client.client_id(), ClientId::from("OKX-TEST"));
259    }
260
261    #[rstest]
262    fn test_okx_execution_client_factory_creates_client_for_derivatives() {
263        let factory = OKXExecutionClientFactory::new();
264        let config = OKXExecClientConfig {
265            trader_id: TraderId::from("TRADER-001"),
266            account_id: AccountId::from("OKX-001"),
267            instrument_types: vec![OKXInstrumentType::Swap, OKXInstrumentType::Futures],
268            api_key: Some("test_key".to_string()),
269            api_secret: Some("test_secret".to_string()),
270            api_passphrase: Some("test_pass".to_string()),
271            ..Default::default()
272        };
273
274        let cache = Rc::new(RefCell::new(Cache::default()));
275
276        let result = factory.create("OKX-DERIV", &config, cache);
277        assert!(result.is_ok());
278    }
279
280    #[rstest]
281    fn test_okx_execution_client_factory_rejects_wrong_config_type() {
282        let factory = OKXExecutionClientFactory::new();
283        let wrong_config = OKXDataClientConfig::default();
284
285        let cache = Rc::new(RefCell::new(Cache::default()));
286
287        let result = factory.create("OKX-TEST", &wrong_config, cache);
288        assert!(result.is_err());
289        assert!(
290            result
291                .err()
292                .unwrap()
293                .to_string()
294                .contains("Invalid config type")
295        );
296    }
297}