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        clock: Rc<RefCell<dyn Clock>>,
126    ) -> anyhow::Result<Box<dyn ExecutionClient>> {
127        let okx_config = config
128            .as_any()
129            .downcast_ref::<OKXExecClientConfig>()
130            .ok_or_else(|| {
131                anyhow::anyhow!(
132                    "Invalid config type for OKXExecutionClientFactory. Expected OKXExecClientConfig, was {config:?}",
133                )
134            })?
135            .clone();
136
137        let has_derivatives = okx_config.instrument_types.iter().any(|t| {
138            matches!(
139                t,
140                OKXInstrumentType::Swap | OKXInstrumentType::Futures | OKXInstrumentType::Option
141            )
142        });
143
144        let account_type = if okx_config.use_spot_margin || has_derivatives {
145            AccountType::Margin
146        } else {
147            AccountType::Cash
148        };
149
150        // OKX uses netting for derivatives, hedging for spot
151        let oms_type = if has_derivatives {
152            OmsType::Netting
153        } else {
154            OmsType::Hedging
155        };
156
157        let core = ExecutionClientCore::new(
158            okx_config.trader_id,
159            ClientId::from(name),
160            *OKX_VENUE,
161            oms_type,
162            okx_config.account_id,
163            account_type,
164            None, // base_currency
165            clock,
166            cache,
167        );
168
169        let client = OKXExecutionClient::new(core, okx_config)?;
170
171        Ok(Box::new(client))
172    }
173
174    fn name(&self) -> &'static str {
175        "OKX"
176    }
177
178    fn config_type(&self) -> &'static str {
179        "OKXExecClientConfig"
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use std::{cell::RefCell, rc::Rc};
186
187    use nautilus_common::{cache::Cache, clock::TestClock};
188    use nautilus_model::identifiers::{AccountId, TraderId};
189    use nautilus_system::factories::{ClientConfig, ExecutionClientFactory};
190    use rstest::rstest;
191
192    use super::*;
193    use crate::{common::enums::OKXInstrumentType, config::OKXExecClientConfig};
194
195    #[rstest]
196    fn test_okx_execution_client_factory_creation() {
197        let factory = OKXExecutionClientFactory::new();
198        assert_eq!(factory.name(), "OKX");
199        assert_eq!(factory.config_type(), "OKXExecClientConfig");
200    }
201
202    #[rstest]
203    fn test_okx_execution_client_factory_default() {
204        let factory = OKXExecutionClientFactory::new();
205        assert_eq!(factory.name(), "OKX");
206    }
207
208    #[rstest]
209    fn test_okx_exec_client_config_implements_client_config() {
210        let config = OKXExecClientConfig {
211            trader_id: TraderId::from("TRADER-001"),
212            account_id: AccountId::from("OKX-001"),
213            instrument_types: vec![OKXInstrumentType::Spot],
214            ..Default::default()
215        };
216
217        let boxed_config: Box<dyn ClientConfig> = Box::new(config);
218        let downcasted = boxed_config.as_any().downcast_ref::<OKXExecClientConfig>();
219
220        assert!(downcasted.is_some());
221    }
222
223    #[rstest]
224    fn test_okx_execution_client_factory_creates_client_for_spot() {
225        let factory = OKXExecutionClientFactory::new();
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            api_key: Some("test_key".to_string()),
231            api_secret: Some("test_secret".to_string()),
232            api_passphrase: Some("test_pass".to_string()),
233            ..Default::default()
234        };
235
236        let cache = Rc::new(RefCell::new(Cache::default()));
237        let clock = Rc::new(RefCell::new(TestClock::new()));
238
239        let result = factory.create("OKX-TEST", &config, cache, clock);
240        assert!(result.is_ok());
241
242        let client = result.unwrap();
243        assert_eq!(client.client_id(), ClientId::from("OKX-TEST"));
244    }
245
246    #[rstest]
247    fn test_okx_execution_client_factory_creates_client_for_derivatives() {
248        let factory = OKXExecutionClientFactory::new();
249        let config = OKXExecClientConfig {
250            trader_id: TraderId::from("TRADER-001"),
251            account_id: AccountId::from("OKX-001"),
252            instrument_types: vec![OKXInstrumentType::Swap, OKXInstrumentType::Futures],
253            api_key: Some("test_key".to_string()),
254            api_secret: Some("test_secret".to_string()),
255            api_passphrase: Some("test_pass".to_string()),
256            ..Default::default()
257        };
258
259        let cache = Rc::new(RefCell::new(Cache::default()));
260        let clock = Rc::new(RefCell::new(TestClock::new()));
261
262        let result = factory.create("OKX-DERIV", &config, cache, clock);
263        assert!(result.is_ok());
264    }
265
266    #[rstest]
267    fn test_okx_execution_client_factory_rejects_wrong_config_type() {
268        let factory = OKXExecutionClientFactory::new();
269        let wrong_config = OKXDataClientConfig::default();
270
271        let cache = Rc::new(RefCell::new(Cache::default()));
272        let clock = Rc::new(RefCell::new(TestClock::new()));
273
274        let result = factory.create("OKX-TEST", &wrong_config, cache, clock);
275        assert!(result.is_err());
276        assert!(
277            result
278                .err()
279                .unwrap()
280                .to_string()
281                .contains("Invalid config type")
282        );
283    }
284}