1use 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::{AccountId, ClientId, TraderId},
30};
31
32use crate::{
33 common::{consts::BYBIT_VENUE, enums::BybitProductType},
34 config::{BybitDataClientConfig, BybitExecClientConfig},
35 data::BybitDataClient,
36 execution::BybitExecutionClient,
37};
38
39impl ClientConfig for BybitDataClientConfig {
40 fn as_any(&self) -> &dyn Any {
41 self
42 }
43}
44
45impl ClientConfig for BybitExecClientConfig {
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.bybit", from_py_object)
56)]
57#[cfg_attr(
58 feature = "python",
59 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
60)]
61pub struct BybitDataClientFactory;
62
63impl BybitDataClientFactory {
64 #[must_use]
66 pub const fn new() -> Self {
67 Self
68 }
69}
70
71impl Default for BybitDataClientFactory {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl DataClientFactory for BybitDataClientFactory {
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 bybit_config = config
86 .as_any()
87 .downcast_ref::<BybitDataClientConfig>()
88 .ok_or_else(|| {
89 anyhow::anyhow!(
90 "Invalid config type for BybitDataClientFactory. Expected BybitDataClientConfig, was {config:?}",
91 )
92 })?
93 .clone();
94
95 let client_id = ClientId::from(name);
96 let client = BybitDataClient::new(client_id, bybit_config)?;
97 Ok(Box::new(client))
98 }
99
100 fn name(&self) -> &'static str {
101 "BYBIT"
102 }
103
104 fn config_type(&self) -> &'static str {
105 "BybitDataClientConfig"
106 }
107}
108
109#[derive(Debug, Clone)]
111#[cfg_attr(
112 feature = "python",
113 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
114)]
115#[cfg_attr(
116 feature = "python",
117 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bybit")
118)]
119pub struct BybitExecutionClientFactory {
120 trader_id: TraderId,
121 account_id: AccountId,
122}
123
124impl BybitExecutionClientFactory {
125 #[must_use]
127 pub const fn new(trader_id: TraderId, account_id: AccountId) -> Self {
128 Self {
129 trader_id,
130 account_id,
131 }
132 }
133}
134
135impl ExecutionClientFactory for BybitExecutionClientFactory {
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 bybit_config = config
143 .as_any()
144 .downcast_ref::<BybitExecClientConfig>()
145 .ok_or_else(|| {
146 anyhow::anyhow!(
147 "Invalid config type for BybitExecutionClientFactory. Expected BybitExecClientConfig, was {config:?}",
148 )
149 })?
150 .clone();
151
152 let product_types = if bybit_config.product_types.is_empty() {
154 vec![BybitProductType::Linear]
155 } else {
156 bybit_config.product_types.clone()
157 };
158
159 let has_derivatives = product_types.iter().any(|t| {
160 matches!(
161 t,
162 BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
163 )
164 });
165
166 let account_type = if has_derivatives {
167 AccountType::Margin
168 } else {
169 AccountType::Cash
170 };
171
172 let oms_type = if has_derivatives {
174 OmsType::Netting
175 } else {
176 OmsType::Hedging
177 };
178
179 let account_id = bybit_config.account_id.unwrap_or(self.account_id);
180
181 let core = ExecutionClientCore::new(
182 self.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 "BybitExecClientConfig"
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::{AccountId, TraderId};
215 use rstest::rstest;
216
217 use super::*;
218 use crate::{common::enums::BybitProductType, config::BybitExecClientConfig};
219
220 #[rstest]
221 fn test_bybit_execution_client_factory_creation() {
222 let factory = BybitExecutionClientFactory::new(
223 TraderId::from("TRADER-001"),
224 AccountId::from("BYBIT-001"),
225 );
226 assert_eq!(factory.name(), "BYBIT");
227 assert_eq!(factory.config_type(), "BybitExecClientConfig");
228 }
229
230 #[rstest]
231 fn test_bybit_exec_client_config_implements_client_config() {
232 let config = BybitExecClientConfig::default();
233
234 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
235 let downcasted = boxed_config
236 .as_any()
237 .downcast_ref::<BybitExecClientConfig>();
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 TraderId::from("TRADER-001"),
246 AccountId::from("BYBIT-001"),
247 );
248 let config = BybitExecClientConfig {
249 product_types: vec![BybitProductType::Spot],
250 api_key: Some("test_key".to_string()),
251 api_secret: Some("test_secret".to_string()),
252 ..Default::default()
253 };
254
255 let cache = Rc::new(RefCell::new(Cache::default()));
256
257 let result = factory.create("BYBIT-TEST", &config, cache);
258 assert!(result.is_ok());
259
260 let client = result.unwrap();
261 assert_eq!(client.client_id(), ClientId::from("BYBIT-TEST"));
262 }
263
264 #[rstest]
265 fn test_bybit_execution_client_factory_creates_client_for_derivatives() {
266 let factory = BybitExecutionClientFactory::new(
267 TraderId::from("TRADER-001"),
268 AccountId::from("BYBIT-001"),
269 );
270 let config = BybitExecClientConfig {
271 product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
272 api_key: Some("test_key".to_string()),
273 api_secret: Some("test_secret".to_string()),
274 ..Default::default()
275 };
276
277 let cache = Rc::new(RefCell::new(Cache::default()));
278
279 let result = factory.create("BYBIT-DERIV", &config, cache);
280 assert!(result.is_ok());
281 }
282
283 #[rstest]
284 fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
285 let factory = BybitExecutionClientFactory::new(
286 TraderId::from("TRADER-001"),
287 AccountId::from("BYBIT-001"),
288 );
289 let wrong_config = BybitDataClientConfig::default();
290
291 let cache = Rc::new(RefCell::new(Cache::default()));
292
293 let result = factory.create("BYBIT-TEST", &wrong_config, cache);
294 assert!(result.is_err());
295 assert!(
296 result
297 .err()
298 .unwrap()
299 .to_string()
300 .contains("Invalid config type")
301 );
302 }
303}