nautilus_okx/
factories.rs1use 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,
30};
31
32use crate::{
33 common::{
34 consts::{OKX, OKX_VENUE},
35 enums::OKXInstrumentType,
36 },
37 config::{OKXDataClientConfig, OKXExecClientConfig},
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 OKXExecClientConfig {
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.core.nautilus_pyo3.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.core.nautilus_pyo3.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 name: &str,
142 config: &dyn ClientConfig,
143 cache: CacheView,
144 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
145 let okx_config = config
146 .as_any()
147 .downcast_ref::<OKXExecClientConfig>()
148 .ok_or_else(|| {
149 anyhow::anyhow!(
150 "Invalid config type for OKXExecutionClientFactory. Expected OKXExecClientConfig, was {config:?}",
151 )
152 })?
153 .clone();
154
155 let has_derivatives = okx_config.instrument_types.iter().any(|t| {
156 matches!(
157 t,
158 OKXInstrumentType::Swap | OKXInstrumentType::Futures | OKXInstrumentType::Option
159 )
160 });
161
162 let account_type = if okx_config.use_spot_margin || has_derivatives {
163 AccountType::Margin
164 } else {
165 AccountType::Cash
166 };
167
168 let oms_type = if has_derivatives {
170 OmsType::Netting
171 } else {
172 OmsType::Hedging
173 };
174
175 let core = ExecutionClientCore::new(
176 okx_config.trader_id,
177 ClientId::from(name),
178 *OKX_VENUE,
179 oms_type,
180 okx_config.account_id,
181 account_type,
182 None, cache,
184 );
185
186 let client = OKXExecutionClient::new(core, okx_config)?;
187
188 Ok(Box::new(client))
189 }
190
191 fn name(&self) -> &'static str {
192 OKX
193 }
194
195 fn config_type(&self) -> &'static str {
196 "OKXExecClientConfig"
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use std::{cell::RefCell, rc::Rc};
203
204 use nautilus_common::{
205 cache::Cache,
206 factories::{ClientConfig, ExecutionClientFactory},
207 };
208 use nautilus_model::identifiers::{AccountId, TraderId};
209 use rstest::rstest;
210
211 use super::*;
212 use crate::{common::enums::OKXInstrumentType, config::OKXExecClientConfig};
213
214 #[rstest]
215 fn test_okx_execution_client_factory_creation() {
216 let factory = OKXExecutionClientFactory::new();
217 assert_eq!(factory.name(), OKX);
218 assert_eq!(factory.config_type(), "OKXExecClientConfig");
219 }
220
221 #[rstest]
222 fn test_okx_execution_client_factory_default() {
223 let factory = OKXExecutionClientFactory::new();
224 assert_eq!(factory.name(), OKX);
225 }
226
227 #[rstest]
228 fn test_okx_exec_client_config_implements_client_config() {
229 let config = OKXExecClientConfig {
230 trader_id: TraderId::from("TRADER-001"),
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.as_any().downcast_ref::<OKXExecClientConfig>();
238
239 assert!(downcasted.is_some());
240 }
241
242 #[rstest]
243 fn test_okx_execution_client_factory_creates_client_for_spot() {
244 let factory = OKXExecutionClientFactory::new();
245 let config = OKXExecClientConfig {
246 trader_id: TraderId::from("TRADER-001"),
247 account_id: AccountId::from("OKX-001"),
248 instrument_types: vec![OKXInstrumentType::Spot],
249 api_key: Some("test_key".to_string()),
250 api_secret: Some("test_secret".to_string()),
251 api_passphrase: Some("test_pass".to_string()),
252 ..Default::default()
253 };
254
255 let cache = Rc::new(RefCell::new(Cache::default()));
256
257 let result = factory.create("OKX-TEST", &config, cache.into());
258 assert!(result.is_ok());
259
260 let client = result.unwrap();
261 assert_eq!(client.client_id(), ClientId::from("OKX-TEST"));
262 }
263
264 #[rstest]
265 fn test_okx_execution_client_factory_creates_client_for_derivatives() {
266 let factory = OKXExecutionClientFactory::new();
267 let config = OKXExecClientConfig {
268 trader_id: TraderId::from("TRADER-001"),
269 account_id: AccountId::from("OKX-001"),
270 instrument_types: vec![OKXInstrumentType::Swap, OKXInstrumentType::Futures],
271 api_key: Some("test_key".to_string()),
272 api_secret: Some("test_secret".to_string()),
273 api_passphrase: Some("test_pass".to_string()),
274 ..Default::default()
275 };
276
277 let cache = Rc::new(RefCell::new(Cache::default()));
278
279 let result = factory.create("OKX-DERIV", &config, cache.into());
280 result.unwrap();
281 }
282
283 #[rstest]
284 fn test_okx_execution_client_factory_rejects_wrong_config_type() {
285 let factory = OKXExecutionClientFactory::new();
286 let wrong_config = OKXDataClientConfig::default();
287
288 let cache = Rc::new(RefCell::new(Cache::default()));
289
290 let result = factory.create("OKX-TEST", &wrong_config, cache.into());
291 assert!(result.is_err());
292 assert!(
293 result
294 .err()
295 .unwrap()
296 .to_string()
297 .contains("Invalid config type")
298 );
299 }
300}