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