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