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