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