nautilus_bybit/
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::{AccountId, ClientId, TraderId},
30};
31
32use crate::{
33 common::{
34 consts::{BYBIT, BYBIT_VENUE},
35 enums::BybitProductType,
36 },
37 config::{BybitDataClientConfig, BybitExecutionClientConfig},
38 data::BybitDataClient,
39 execution::BybitExecutionClient,
40};
41
42impl ClientConfig for BybitDataClientConfig {
43 fn as_any(&self) -> &dyn Any {
44 self
45 }
46}
47
48impl ClientConfig for BybitExecutionClientConfig {
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.adapters.bybit", from_py_object)
59)]
60#[cfg_attr(
61 feature = "python",
62 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
63)]
64pub struct BybitDataClientFactory;
65
66impl BybitDataClientFactory {
67 #[must_use]
69 pub const fn new() -> Self {
70 Self
71 }
72}
73
74impl Default for BybitDataClientFactory {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl DataClientFactory for BybitDataClientFactory {
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 bybit_config = config
89 .as_any()
90 .downcast_ref::<BybitDataClientConfig>()
91 .ok_or_else(|| {
92 anyhow::anyhow!(
93 "Invalid config type for BybitDataClientFactory. Expected BybitDataClientConfig, was {config:?}",
94 )
95 })?
96 .clone();
97
98 let client_id = ClientId::from(name);
99 let client = BybitDataClient::new(client_id, bybit_config)?;
100 Ok(Box::new(client))
101 }
102
103 fn name(&self) -> &'static str {
104 BYBIT
105 }
106
107 fn config_type(&self) -> &'static str {
108 "BybitDataClientConfig"
109 }
110}
111
112#[derive(Debug, Default, Clone)]
114#[cfg_attr(
115 feature = "python",
116 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
117)]
118#[cfg_attr(
119 feature = "python",
120 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
121)]
122pub struct BybitExecutionClientFactory;
123
124impl BybitExecutionClientFactory {
125 #[must_use]
127 pub const fn new() -> Self {
128 Self
129 }
130}
131
132impl ExecutionClientFactory for BybitExecutionClientFactory {
133 fn create(
134 &self,
135 trader_id: TraderId,
136 name: &str,
137 config: &dyn ClientConfig,
138 cache: CacheView,
139 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
140 let bybit_config = config
141 .as_any()
142 .downcast_ref::<BybitExecutionClientConfig>()
143 .ok_or_else(|| {
144 anyhow::anyhow!(
145 "Invalid config type for BybitExecutionClientFactory. Expected BybitExecutionClientConfig, was {config:?}",
146 )
147 })?
148 .clone();
149
150 let product_types = if bybit_config.product_types.is_empty() {
152 vec![BybitProductType::Linear]
153 } else {
154 bybit_config.product_types.clone()
155 };
156
157 let has_derivatives = product_types.iter().any(|t| {
158 matches!(
159 t,
160 BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
161 )
162 });
163
164 let account_type = if has_derivatives {
165 AccountType::Margin
166 } else {
167 AccountType::Cash
168 };
169
170 let oms_type = if has_derivatives {
172 OmsType::Netting
173 } else {
174 OmsType::Hedging
175 };
176
177 let account_id = bybit_config
178 .account_id
179 .unwrap_or_else(|| AccountId::from("BYBIT-001"));
180
181 let core = ExecutionClientCore::new(
182 trader_id,
183 ClientId::from(name),
184 *BYBIT_VENUE,
185 oms_type,
186 account_id,
187 account_type,
188 None, cache,
190 );
191
192 let client = BybitExecutionClient::new(core, bybit_config)?;
193
194 Ok(Box::new(client))
195 }
196
197 fn name(&self) -> &'static str {
198 BYBIT
199 }
200
201 fn config_type(&self) -> &'static str {
202 "BybitExecutionClientConfig"
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use std::{cell::RefCell, rc::Rc};
209
210 use nautilus_common::{
211 cache::Cache,
212 factories::{ClientConfig, ExecutionClientFactory},
213 };
214 use nautilus_model::identifiers::TraderId;
215 use rstest::rstest;
216
217 use super::*;
218 use crate::{common::enums::BybitProductType, config::BybitExecutionClientConfig};
219
220 #[rstest]
221 fn test_bybit_execution_client_factory_creation() {
222 let factory = BybitExecutionClientFactory::new();
223 assert_eq!(factory.name(), BYBIT);
224 assert_eq!(factory.config_type(), "BybitExecutionClientConfig");
225 }
226
227 #[rstest]
228 fn test_bybit_exec_client_config_implements_client_config() {
229 let config = BybitExecutionClientConfig::default();
230
231 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
232 let downcasted = boxed_config
233 .as_any()
234 .downcast_ref::<BybitExecutionClientConfig>();
235
236 assert!(downcasted.is_some());
237 }
238
239 #[rstest]
240 fn test_bybit_execution_client_factory_creates_client_for_spot() {
241 let factory = BybitExecutionClientFactory::new();
242 let config = BybitExecutionClientConfig {
243 product_types: vec![BybitProductType::Spot],
244 api_key: Some("test_key".to_string()),
245 api_secret: Some("test_secret".to_string()),
246 ..Default::default()
247 };
248
249 let cache = Rc::new(RefCell::new(Cache::default()));
250
251 let result = factory.create(
252 TraderId::from("TRADER-001"),
253 "BYBIT-TEST",
254 &config,
255 cache.into(),
256 );
257 assert!(result.is_ok());
258
259 let client = result.unwrap();
260 assert_eq!(client.client_id(), ClientId::from("BYBIT-TEST"));
261 }
262
263 #[rstest]
264 fn test_bybit_execution_client_factory_creates_client_for_derivatives() {
265 let factory = BybitExecutionClientFactory::new();
266 let config = BybitExecutionClientConfig {
267 product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
268 api_key: Some("test_key".to_string()),
269 api_secret: Some("test_secret".to_string()),
270 ..Default::default()
271 };
272
273 let cache = Rc::new(RefCell::new(Cache::default()));
274
275 let result = factory.create(
276 TraderId::from("TRADER-001"),
277 "BYBIT-DERIV",
278 &config,
279 cache.into(),
280 );
281 result.unwrap();
282 }
283
284 #[rstest]
285 fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
286 let factory = BybitExecutionClientFactory::new();
287 let wrong_config = BybitDataClientConfig::default();
288
289 let cache = Rc::new(RefCell::new(Cache::default()));
290
291 let result = factory.create(
292 TraderId::from("TRADER-001"),
293 "BYBIT-TEST",
294 &wrong_config,
295 cache.into(),
296 );
297 assert!(result.is_err());
298 assert!(
299 result
300 .err()
301 .unwrap()
302 .to_string()
303 .contains("Invalid config type")
304 );
305 }
306}