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