nautilus_bybit/
factories.rs1use std::{any::Any, cell::RefCell, rc::Rc};
19
20use nautilus_common::{
21 cache::Cache,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24};
25use nautilus_live::ExecutionClientCore;
26use nautilus_model::{
27 enums::{AccountType, OmsType},
28 identifiers::{AccountId, ClientId, TraderId},
29};
30use nautilus_system::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
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)]
53pub struct BybitDataClientFactory;
54
55impl BybitDataClientFactory {
56 #[must_use]
58 pub const fn new() -> Self {
59 Self
60 }
61}
62
63impl Default for BybitDataClientFactory {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl DataClientFactory for BybitDataClientFactory {
70 fn create(
71 &self,
72 name: &str,
73 config: &dyn ClientConfig,
74 _cache: Rc<RefCell<Cache>>,
75 _clock: Rc<RefCell<dyn Clock>>,
76 ) -> anyhow::Result<Box<dyn DataClient>> {
77 let bybit_config = config
78 .as_any()
79 .downcast_ref::<BybitDataClientConfig>()
80 .ok_or_else(|| {
81 anyhow::anyhow!(
82 "Invalid config type for BybitDataClientFactory. Expected BybitDataClientConfig, was {config:?}",
83 )
84 })?
85 .clone();
86
87 let client_id = ClientId::from(name);
88 let client = BybitDataClient::new(client_id, bybit_config)?;
89 Ok(Box::new(client))
90 }
91
92 fn name(&self) -> &'static str {
93 "BYBIT"
94 }
95
96 fn config_type(&self) -> &'static str {
97 "BybitDataClientConfig"
98 }
99}
100
101#[derive(Debug)]
103pub struct BybitExecutionClientFactory {
104 trader_id: TraderId,
105 account_id: AccountId,
106}
107
108impl BybitExecutionClientFactory {
109 #[must_use]
111 pub const fn new(trader_id: TraderId, account_id: AccountId) -> Self {
112 Self {
113 trader_id,
114 account_id,
115 }
116 }
117}
118
119impl ExecutionClientFactory for BybitExecutionClientFactory {
120 fn create(
121 &self,
122 name: &str,
123 config: &dyn ClientConfig,
124 cache: Rc<RefCell<Cache>>,
125 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
126 let bybit_config = config
127 .as_any()
128 .downcast_ref::<BybitExecClientConfig>()
129 .ok_or_else(|| {
130 anyhow::anyhow!(
131 "Invalid config type for BybitExecutionClientFactory. Expected BybitExecClientConfig, was {config:?}",
132 )
133 })?
134 .clone();
135
136 let product_types = if bybit_config.product_types.is_empty() {
138 vec![BybitProductType::Linear]
139 } else {
140 bybit_config.product_types.clone()
141 };
142
143 let has_derivatives = product_types.iter().any(|t| {
144 matches!(
145 t,
146 BybitProductType::Linear | BybitProductType::Inverse | BybitProductType::Option
147 )
148 });
149
150 let account_type = if has_derivatives {
151 AccountType::Margin
152 } else {
153 AccountType::Cash
154 };
155
156 let oms_type = if has_derivatives {
158 OmsType::Netting
159 } else {
160 OmsType::Hedging
161 };
162
163 let account_id = bybit_config.account_id.unwrap_or(self.account_id);
164
165 let core = ExecutionClientCore::new(
166 self.trader_id,
167 ClientId::from(name),
168 *BYBIT_VENUE,
169 oms_type,
170 account_id,
171 account_type,
172 None, cache,
174 );
175
176 let client = BybitExecutionClient::new(core, bybit_config)?;
177
178 Ok(Box::new(client))
179 }
180
181 fn name(&self) -> &'static str {
182 "BYBIT"
183 }
184
185 fn config_type(&self) -> &'static str {
186 "BybitExecClientConfig"
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use std::{cell::RefCell, rc::Rc};
193
194 use nautilus_common::cache::Cache;
195 use nautilus_model::identifiers::{AccountId, TraderId};
196 use nautilus_system::factories::{ClientConfig, ExecutionClientFactory};
197 use rstest::rstest;
198
199 use super::*;
200 use crate::{common::enums::BybitProductType, config::BybitExecClientConfig};
201
202 #[rstest]
203 fn test_bybit_execution_client_factory_creation() {
204 let factory = BybitExecutionClientFactory::new(
205 TraderId::from("TRADER-001"),
206 AccountId::from("BYBIT-001"),
207 );
208 assert_eq!(factory.name(), "BYBIT");
209 assert_eq!(factory.config_type(), "BybitExecClientConfig");
210 }
211
212 #[rstest]
213 fn test_bybit_exec_client_config_implements_client_config() {
214 let config = BybitExecClientConfig::default();
215
216 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
217 let downcasted = boxed_config
218 .as_any()
219 .downcast_ref::<BybitExecClientConfig>();
220
221 assert!(downcasted.is_some());
222 }
223
224 #[rstest]
225 fn test_bybit_execution_client_factory_creates_client_for_spot() {
226 let factory = BybitExecutionClientFactory::new(
227 TraderId::from("TRADER-001"),
228 AccountId::from("BYBIT-001"),
229 );
230 let config = BybitExecClientConfig {
231 product_types: vec![BybitProductType::Spot],
232 api_key: Some("test_key".to_string()),
233 api_secret: Some("test_secret".to_string()),
234 ..Default::default()
235 };
236
237 let cache = Rc::new(RefCell::new(Cache::default()));
238
239 let result = factory.create("BYBIT-TEST", &config, cache);
240 assert!(result.is_ok());
241
242 let client = result.unwrap();
243 assert_eq!(client.client_id(), ClientId::from("BYBIT-TEST"));
244 }
245
246 #[rstest]
247 fn test_bybit_execution_client_factory_creates_client_for_derivatives() {
248 let factory = BybitExecutionClientFactory::new(
249 TraderId::from("TRADER-001"),
250 AccountId::from("BYBIT-001"),
251 );
252 let config = BybitExecClientConfig {
253 product_types: vec![BybitProductType::Linear, BybitProductType::Inverse],
254 api_key: Some("test_key".to_string()),
255 api_secret: Some("test_secret".to_string()),
256 ..Default::default()
257 };
258
259 let cache = Rc::new(RefCell::new(Cache::default()));
260
261 let result = factory.create("BYBIT-DERIV", &config, cache);
262 assert!(result.is_ok());
263 }
264
265 #[rstest]
266 fn test_bybit_execution_client_factory_rejects_wrong_config_type() {
267 let factory = BybitExecutionClientFactory::new(
268 TraderId::from("TRADER-001"),
269 AccountId::from("BYBIT-001"),
270 );
271 let wrong_config = BybitDataClientConfig::default();
272
273 let cache = Rc::new(RefCell::new(Cache::default()));
274
275 let result = factory.create("BYBIT-TEST", &wrong_config, cache);
276 assert!(result.is_err());
277 assert!(
278 result
279 .err()
280 .unwrap()
281 .to_string()
282 .contains("Invalid config type")
283 );
284 }
285}