nautilus_okx/
factories.rs

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