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