1use 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, BybitExecClientConfig},
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 BybitExecClientConfig {
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.core.nautilus_pyo3.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, Clone)]
114#[cfg_attr(
115 feature = "python",
116 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.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 trader_id: TraderId,
124 account_id: AccountId,
125}
126
127impl BybitExecutionClientFactory {
128 #[must_use]
130 pub const fn new(trader_id: TraderId, account_id: AccountId) -> Self {
131 Self {
132 trader_id,
133 account_id,
134 }
135 }
136}
137
138impl ExecutionClientFactory for BybitExecutionClientFactory {
139 fn create(
140 &self,
141 name: &str,
142 config: &dyn ClientConfig,
143 cache: CacheView,
144 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
145 let bybit_config = config
146 .as_any()
147 .downcast_ref::<BybitExecClientConfig>()
148 .ok_or_else(|| {
149 anyhow::anyhow!(
150 "Invalid config type for BybitExecutionClientFactory. Expected BybitExecClientConfig, was {config:?}",
151 )
152 })?
153 .clone();
154
155 let product_types = if bybit_config.product_types.is_empty() {
157 vec![BybitProductType::Linear]
158 } else {
159 bybit_config.product_types.clone()
160 };
161
162 let has_derivatives = product_types.iter().any(|t| {
163 matches!(
164 t,
165 BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
166 )
167 });
168
169 let account_type = if has_derivatives {
170 AccountType::Margin
171 } else {
172 AccountType::Cash
173 };
174
175 let oms_type = if has_derivatives {
177 OmsType::Netting
178 } else {
179 OmsType::Hedging
180 };
181
182 let account_id = bybit_config.account_id.unwrap_or(self.account_id);
183
184 let core = ExecutionClientCore::new(
185 self.trader_id,
186 ClientId::from(name),
187 *BYBIT_VENUE,
188 oms_type,
189 account_id,
190 account_type,
191 None, cache,
193 );
194
195 let client = BybitExecutionClient::new(core, bybit_config)?;
196
197 Ok(Box::new(client))
198 }
199
200 fn name(&self) -> &'static str {
201 BYBIT
202 }
203
204 fn config_type(&self) -> &'static str {
205 "BybitExecClientConfig"
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use std::{cell::RefCell, rc::Rc};
212
213 use nautilus_common::{
214 cache::Cache,
215 factories::{ClientConfig, ExecutionClientFactory},
216 };
217 use nautilus_model::identifiers::{AccountId, TraderId};
218 use rstest::rstest;
219
220 use super::*;
221 use crate::{common::enums::BybitProductType, config::BybitExecClientConfig};
222
223 #[rstest]
224 fn test_bybit_execution_client_factory_creation() {
225 let factory = BybitExecutionClientFactory::new(
226 TraderId::from("TRADER-001"),
227 AccountId::from("BYBIT-001"),
228 );
229 assert_eq!(factory.name(), BYBIT);
230 assert_eq!(factory.config_type(), "BybitExecClientConfig");
231 }
232
233 #[rstest]
234 fn test_bybit_exec_client_config_implements_client_config() {
235 let config = BybitExecClientConfig::default();
236
237 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
238 let downcasted = boxed_config
239 .as_any()
240 .downcast_ref::<BybitExecClientConfig>();
241
242 assert!(downcasted.is_some());
243 }
244
245 #[rstest]
246 fn test_bybit_execution_client_factory_creates_client_for_spot() {
247 let factory = BybitExecutionClientFactory::new(
248 TraderId::from("TRADER-001"),
249 AccountId::from("BYBIT-001"),
250 );
251 let config = BybitExecClientConfig {
252 product_types: vec![BybitProductType::Spot],
253 api_key: Some("test_key".to_string()),
254 api_secret: Some("test_secret".to_string()),
255 ..Default::default()
256 };
257
258 let cache = Rc::new(RefCell::new(Cache::default()));
259
260 let result = factory.create("BYBIT-TEST", &config, cache.into());
261 assert!(result.is_ok());
262
263 let client = result.unwrap();
264 assert_eq!(client.client_id(), ClientId::from("BYBIT-TEST"));
265 }
266
267 #[rstest]
268 fn test_bybit_execution_client_factory_creates_client_for_derivatives() {
269 let factory = BybitExecutionClientFactory::new(
270 TraderId::from("TRADER-001"),
271 AccountId::from("BYBIT-001"),
272 );
273 let config = BybitExecClientConfig {
274 product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
275 api_key: Some("test_key".to_string()),
276 api_secret: Some("test_secret".to_string()),
277 ..Default::default()
278 };
279
280 let cache = Rc::new(RefCell::new(Cache::default()));
281
282 let result = factory.create("BYBIT-DERIV", &config, cache.into());
283 result.unwrap();
284 }
285
286 #[rstest]
287 fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
288 let factory = BybitExecutionClientFactory::new(
289 TraderId::from("TRADER-001"),
290 AccountId::from("BYBIT-001"),
291 );
292 let wrong_config = BybitDataClientConfig::default();
293
294 let cache = Rc::new(RefCell::new(Cache::default()));
295
296 let result = factory.create("BYBIT-TEST", &wrong_config, cache.into());
297 assert!(result.is_err());
298 assert!(
299 result
300 .err()
301 .unwrap()
302 .to_string()
303 .contains("Invalid config type")
304 );
305 }
306}