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 = okx_config.oms_type.unwrap_or(if has_derivatives {
173 OmsType::Netting
174 } else {
175 OmsType::Hedging
176 });
177
178 let core = ExecutionClientCore::new(
179 trader_id,
180 ClientId::from(name),
181 *OKX_VENUE,
182 oms_type,
183 okx_config.account_id,
184 account_type,
185 None, cache,
187 );
188
189 let client = OKXExecutionClient::new(core, okx_config)?;
190
191 Ok(Box::new(client))
192 }
193
194 fn name(&self) -> &'static str {
195 OKX
196 }
197
198 fn config_type(&self) -> &'static str {
199 "OKXExecutionClientConfig"
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::{cell::RefCell, rc::Rc};
206
207 use nautilus_common::{
208 cache::Cache,
209 factories::{ClientConfig, ExecutionClientFactory},
210 };
211 use nautilus_model::identifiers::{AccountId, TraderId};
212 use rstest::rstest;
213
214 use super::*;
215 use crate::{common::enums::OKXInstrumentType, config::OKXExecutionClientConfig};
216
217 #[rstest]
218 fn test_okx_execution_client_factory_creation() {
219 let factory = OKXExecutionClientFactory::new();
220 assert_eq!(factory.name(), OKX);
221 assert_eq!(factory.config_type(), "OKXExecutionClientConfig");
222 }
223
224 #[rstest]
225 fn test_okx_execution_client_factory_default() {
226 let factory = OKXExecutionClientFactory::new();
227 assert_eq!(factory.name(), OKX);
228 }
229
230 #[rstest]
231 fn test_okx_exec_client_config_implements_client_config() {
232 let config = OKXExecutionClientConfig {
233 account_id: AccountId::from("OKX-001"),
234 instrument_types: vec![OKXInstrumentType::Spot],
235 ..Default::default()
236 };
237
238 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
239 let downcasted = boxed_config
240 .as_any()
241 .downcast_ref::<OKXExecutionClientConfig>();
242
243 assert!(downcasted.is_some());
244 }
245
246 #[rstest]
247 fn test_okx_execution_client_factory_creates_client_for_spot() {
248 let factory = OKXExecutionClientFactory::new();
249 let config = OKXExecutionClientConfig {
250 account_id: AccountId::from("OKX-001"),
251 instrument_types: vec![OKXInstrumentType::Spot],
252 api_key: Some("test_key".into()),
253 api_secret: Some("test_secret".into()),
254 api_passphrase: Some("test_pass".into()),
255 ..Default::default()
256 };
257
258 let cache = Rc::new(RefCell::new(Cache::default()));
259
260 let result = factory.create(
261 TraderId::from("TRADER-001"),
262 "OKX-TEST",
263 &config,
264 cache.into(),
265 Rc::new(RefCell::new(VirtualClock::new())),
266 );
267 assert!(result.is_ok());
268
269 let client = result.unwrap();
270 assert_eq!(client.client_id(), ClientId::from("OKX-TEST"));
271 }
272
273 #[rstest]
274 fn test_okx_execution_client_factory_creates_client_for_derivatives() {
275 let factory = OKXExecutionClientFactory::new();
276 let config = OKXExecutionClientConfig {
277 account_id: AccountId::from("OKX-001"),
278 instrument_types: vec![OKXInstrumentType::Swap, OKXInstrumentType::Futures],
279 api_key: Some("test_key".into()),
280 api_secret: Some("test_secret".into()),
281 api_passphrase: Some("test_pass".into()),
282 ..Default::default()
283 };
284
285 let cache = Rc::new(RefCell::new(Cache::default()));
286
287 let result = factory.create(
288 TraderId::from("TRADER-001"),
289 "OKX-DERIV",
290 &config,
291 cache.into(),
292 Rc::new(RefCell::new(VirtualClock::new())),
293 );
294 result.unwrap();
295 }
296
297 #[rstest]
298 fn test_okx_execution_client_factory_rejects_wrong_config_type() {
299 let factory = OKXExecutionClientFactory::new();
300 let wrong_config = OKXDataClientConfig::default();
301
302 let cache = Rc::new(RefCell::new(Cache::default()));
303
304 let result = factory.create(
305 TraderId::from("TRADER-001"),
306 "OKX-TEST",
307 &wrong_config,
308 cache.into(),
309 Rc::new(RefCell::new(VirtualClock::new())),
310 );
311 assert!(result.is_err());
312 assert!(
313 result
314 .err()
315 .unwrap()
316 .to_string()
317 .contains("Invalid config type")
318 );
319 }
320
321 #[rstest]
322 #[case(OKXInstrumentType::Swap, None, OmsType::Netting)]
323 #[case(OKXInstrumentType::Futures, Some(OmsType::Hedging), OmsType::Hedging)]
324 #[case(OKXInstrumentType::Spot, None, OmsType::Hedging)]
325 fn test_execution_oms_type(
326 #[case] instrument_type: OKXInstrumentType,
327 #[case] oms_type: Option<OmsType>,
328 #[case] expected: OmsType,
329 ) {
330 let config = OKXExecutionClientConfig {
331 instrument_types: vec![instrument_type],
332 oms_type,
333 api_key: Some("key".into()),
334 api_secret: Some("secret".into()),
335 api_passphrase: Some("pass".into()),
336 ..Default::default()
337 };
338
339 let cache = Rc::new(RefCell::new(Cache::default()));
340 let client = OKXExecutionClientFactory::new()
341 .create(
342 TraderId::from("TRADER-001"),
343 "OKX-TEST",
344 &config,
345 cache.into(),
346 Rc::new(RefCell::new(VirtualClock::new())),
347 )
348 .unwrap();
349 assert_eq!(client.oms_type(), expected);
350 }
351}