1use std::{any::Any, cell::RefCell, rc::Rc};
19
20#[cfg(test)]
21use nautilus_common::clock::VirtualClock;
22use nautilus_common::{
23 cache::CacheView,
24 clients::{DataClient, ExecutionClient},
25 clock::Clock,
26 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
27};
28use nautilus_live::ExecutionClientCore;
29use nautilus_model::{
30 enums::{AccountType, OmsType},
31 identifiers::{AccountId, ClientId, TraderId},
32};
33
34use crate::{
35 common::{
36 consts::{BYBIT, BYBIT_VENUE},
37 enums::BybitProductType,
38 },
39 config::{BybitDataClientConfig, BybitExecutionClientConfig},
40 data::BybitDataClient,
41 execution::BybitExecutionClient,
42};
43
44impl ClientConfig for BybitDataClientConfig {
45 fn as_any(&self) -> &dyn Any {
46 self
47 }
48}
49
50impl ClientConfig for BybitExecutionClientConfig {
51 fn as_any(&self) -> &dyn Any {
52 self
53 }
54}
55
56#[derive(Debug, Clone)]
58#[cfg_attr(
59 feature = "python",
60 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
61)]
62#[cfg_attr(
63 feature = "python",
64 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
65)]
66pub struct BybitDataClientFactory;
67
68impl BybitDataClientFactory {
69 #[must_use]
71 pub const fn new() -> Self {
72 Self
73 }
74}
75
76impl Default for BybitDataClientFactory {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl DataClientFactory for BybitDataClientFactory {
83 fn create(
84 &self,
85 name: &str,
86 config: &dyn ClientConfig,
87 _cache: CacheView,
88 _clock: Rc<RefCell<dyn Clock>>,
89 ) -> anyhow::Result<Box<dyn DataClient>> {
90 let bybit_config = config
91 .as_any()
92 .downcast_ref::<BybitDataClientConfig>()
93 .ok_or_else(|| {
94 anyhow::anyhow!(
95 "Invalid config type for BybitDataClientFactory. Expected BybitDataClientConfig, was {config:?}",
96 )
97 })?
98 .clone();
99
100 let client_id = ClientId::from(name);
101 let client = BybitDataClient::new(client_id, bybit_config)?;
102 Ok(Box::new(client))
103 }
104
105 fn name(&self) -> &'static str {
106 BYBIT
107 }
108
109 fn config_type(&self) -> &'static str {
110 "BybitDataClientConfig"
111 }
112}
113
114#[derive(Debug, Default, Clone)]
116#[cfg_attr(
117 feature = "python",
118 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
119)]
120#[cfg_attr(
121 feature = "python",
122 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
123)]
124pub struct BybitExecutionClientFactory;
125
126impl BybitExecutionClientFactory {
127 #[must_use]
129 pub const fn new() -> Self {
130 Self
131 }
132}
133
134impl ExecutionClientFactory for BybitExecutionClientFactory {
135 fn create(
136 &self,
137 trader_id: TraderId,
138 name: &str,
139 config: &dyn ClientConfig,
140 cache: CacheView,
141 _clock: Rc<RefCell<dyn Clock>>,
142 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
143 let bybit_config = config
144 .as_any()
145 .downcast_ref::<BybitExecutionClientConfig>()
146 .ok_or_else(|| {
147 anyhow::anyhow!(
148 "Invalid config type for BybitExecutionClientFactory. Expected BybitExecutionClientConfig, was {config:?}",
149 )
150 })?
151 .clone();
152
153 let product_types = if bybit_config.product_types.is_empty() {
155 vec![BybitProductType::Linear]
156 } else {
157 bybit_config.product_types.clone()
158 };
159
160 let has_derivatives = product_types.iter().any(|t| {
161 matches!(
162 t,
163 BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
164 )
165 });
166
167 let account_type = if has_derivatives {
168 AccountType::Margin
169 } else {
170 AccountType::Cash
171 };
172
173 let oms_type = if has_derivatives {
175 OmsType::Netting
176 } else {
177 OmsType::Hedging
178 };
179
180 let account_id = bybit_config
181 .account_id
182 .unwrap_or_else(|| AccountId::from("BYBIT-001"));
183
184 let core = ExecutionClientCore::new(
185 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 "BybitExecutionClientConfig"
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::TraderId;
218 use rstest::rstest;
219
220 use super::*;
221 use crate::{common::enums::BybitProductType, config::BybitExecutionClientConfig};
222
223 #[rstest]
224 fn test_bybit_execution_client_factory_creation() {
225 let factory = BybitExecutionClientFactory::new();
226 assert_eq!(factory.name(), BYBIT);
227 assert_eq!(factory.config_type(), "BybitExecutionClientConfig");
228 }
229
230 #[rstest]
231 fn test_bybit_exec_client_config_implements_client_config() {
232 let config = BybitExecutionClientConfig::default();
233
234 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
235 let downcasted = boxed_config
236 .as_any()
237 .downcast_ref::<BybitExecutionClientConfig>();
238
239 assert!(downcasted.is_some());
240 }
241
242 #[rstest]
243 fn test_bybit_execution_client_factory_creates_client_for_spot() {
244 let factory = BybitExecutionClientFactory::new();
245 let config = BybitExecutionClientConfig {
246 product_types: vec![BybitProductType::Spot],
247 api_key: Some("test_key".into()),
248 api_secret: Some("test_secret".into()),
249 ..Default::default()
250 };
251
252 let cache = Rc::new(RefCell::new(Cache::default()));
253
254 let result = factory.create(
255 TraderId::from("TRADER-001"),
256 "BYBIT-TEST",
257 &config,
258 cache.into(),
259 Rc::new(RefCell::new(VirtualClock::new())),
260 );
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 let config = BybitExecutionClientConfig {
271 product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
272 api_key: Some("test_key".into()),
273 api_secret: Some("test_secret".into()),
274 ..Default::default()
275 };
276
277 let cache = Rc::new(RefCell::new(Cache::default()));
278
279 let result = factory.create(
280 TraderId::from("TRADER-001"),
281 "BYBIT-DERIV",
282 &config,
283 cache.into(),
284 Rc::new(RefCell::new(VirtualClock::new())),
285 );
286 result.unwrap();
287 }
288
289 #[rstest]
290 fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
291 let factory = BybitExecutionClientFactory::new();
292 let wrong_config = BybitDataClientConfig::default();
293
294 let cache = Rc::new(RefCell::new(Cache::default()));
295
296 let result = factory.create(
297 TraderId::from("TRADER-001"),
298 "BYBIT-TEST",
299 &wrong_config,
300 cache.into(),
301 Rc::new(RefCell::new(VirtualClock::new())),
302 );
303 assert!(result.is_err());
304 assert!(
305 result
306 .err()
307 .unwrap()
308 .to_string()
309 .contains("Invalid config type")
310 );
311 }
312}