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