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