1use std::collections::HashMap;
19
20use nautilus_model::identifiers::AccountId;
21
22use crate::common::{
23 enums::{BybitEnvironment, BybitMarginMode, BybitPositionMode, BybitProductType},
24 urls::{bybit_http_base_url, bybit_ws_private_url, bybit_ws_public_url, bybit_ws_trade_url},
25};
26
27#[derive(Clone, Debug)]
29pub struct BybitDataClientConfig {
30 pub api_key: Option<String>,
32 pub api_secret: Option<String>,
34 pub product_types: Vec<BybitProductType>,
36 pub environment: BybitEnvironment,
38 pub base_url_http: Option<String>,
40 pub base_url_ws_public: Option<String>,
42 pub base_url_ws_private: Option<String>,
44 pub http_proxy_url: Option<String>,
46 pub ws_proxy_url: Option<String>,
51 pub http_timeout_secs: Option<u64>,
53 pub max_retries: Option<u32>,
55 pub retry_delay_initial_ms: Option<u64>,
57 pub retry_delay_max_ms: Option<u64>,
59 pub heartbeat_interval_secs: Option<u64>,
61 pub recv_window_ms: Option<u64>,
63 pub update_instruments_interval_mins: Option<u64>,
65}
66
67impl Default for BybitDataClientConfig {
68 fn default() -> Self {
69 Self {
70 api_key: None,
71 api_secret: None,
72 product_types: vec![BybitProductType::Linear],
73 environment: BybitEnvironment::Mainnet,
74 base_url_http: None,
75 base_url_ws_public: None,
76 base_url_ws_private: None,
77 http_proxy_url: None,
78 ws_proxy_url: None,
79 http_timeout_secs: Some(60),
80 max_retries: Some(3),
81 retry_delay_initial_ms: Some(1_000),
82 retry_delay_max_ms: Some(10_000),
83 heartbeat_interval_secs: Some(20),
84 recv_window_ms: Some(5_000),
85 update_instruments_interval_mins: Some(60),
86 }
87 }
88}
89
90impl BybitDataClientConfig {
91 #[must_use]
93 pub fn new() -> Self {
94 Self::default()
95 }
96
97 #[must_use]
99 pub fn has_api_credentials(&self) -> bool {
100 self.api_key.is_some() && self.api_secret.is_some()
101 }
102
103 #[must_use]
105 pub fn http_base_url(&self) -> String {
106 self.base_url_http
107 .clone()
108 .unwrap_or_else(|| bybit_http_base_url(self.environment).to_string())
109 }
110
111 #[must_use]
115 pub fn ws_public_url(&self) -> String {
116 self.base_url_ws_public.clone().unwrap_or_else(|| {
117 let product_type = self
118 .product_types
119 .first()
120 .copied()
121 .unwrap_or(BybitProductType::Linear);
122 bybit_ws_public_url(product_type, self.environment)
123 })
124 }
125
126 #[must_use]
128 pub fn ws_public_url_for(&self, product_type: BybitProductType) -> String {
129 self.base_url_ws_public
130 .clone()
131 .unwrap_or_else(|| bybit_ws_public_url(product_type, self.environment))
132 }
133
134 #[must_use]
136 pub fn ws_private_url(&self) -> String {
137 self.base_url_ws_private
138 .clone()
139 .unwrap_or_else(|| bybit_ws_private_url(self.environment).to_string())
140 }
141
142 #[must_use]
144 pub fn requires_private_ws(&self) -> bool {
145 self.has_api_credentials()
146 }
147}
148
149#[derive(Clone, Debug)]
151pub struct BybitExecClientConfig {
152 pub api_key: Option<String>,
154 pub api_secret: Option<String>,
156 pub product_types: Vec<BybitProductType>,
158 pub environment: BybitEnvironment,
160 pub base_url_http: Option<String>,
162 pub base_url_ws_private: Option<String>,
164 pub base_url_ws_trade: Option<String>,
166 pub http_proxy_url: Option<String>,
168 pub ws_proxy_url: Option<String>,
173 pub http_timeout_secs: Option<u64>,
175 pub max_retries: Option<u32>,
177 pub retry_delay_initial_ms: Option<u64>,
179 pub retry_delay_max_ms: Option<u64>,
181 pub heartbeat_interval_secs: Option<u64>,
183 pub recv_window_ms: Option<u64>,
185 pub account_id: Option<AccountId>,
187 pub use_spot_position_reports: bool,
189 pub futures_leverages: Option<HashMap<String, u32>>,
191 pub position_mode: Option<HashMap<String, BybitPositionMode>>,
193 pub margin_mode: Option<BybitMarginMode>,
195}
196
197impl Default for BybitExecClientConfig {
198 fn default() -> Self {
199 Self {
200 api_key: None,
201 api_secret: None,
202 product_types: vec![BybitProductType::Linear],
203 environment: BybitEnvironment::Mainnet,
204 base_url_http: None,
205 base_url_ws_private: None,
206 base_url_ws_trade: None,
207 http_proxy_url: None,
208 ws_proxy_url: None,
209 http_timeout_secs: Some(60),
210 max_retries: Some(3),
211 retry_delay_initial_ms: Some(1_000),
212 retry_delay_max_ms: Some(10_000),
213 heartbeat_interval_secs: Some(5),
214 recv_window_ms: Some(5_000),
215 account_id: None,
216 use_spot_position_reports: false,
217 futures_leverages: None,
218 position_mode: None,
219 margin_mode: None,
220 }
221 }
222}
223
224impl BybitExecClientConfig {
225 #[must_use]
227 pub fn new() -> Self {
228 Self::default()
229 }
230
231 #[must_use]
233 pub fn has_api_credentials(&self) -> bool {
234 self.api_key.is_some() && self.api_secret.is_some()
235 }
236
237 #[must_use]
239 pub fn http_base_url(&self) -> String {
240 self.base_url_http
241 .clone()
242 .unwrap_or_else(|| bybit_http_base_url(self.environment).to_string())
243 }
244
245 #[must_use]
247 pub fn ws_private_url(&self) -> String {
248 self.base_url_ws_private
249 .clone()
250 .unwrap_or_else(|| bybit_ws_private_url(self.environment).to_string())
251 }
252
253 #[must_use]
255 pub fn ws_trade_url(&self) -> String {
256 self.base_url_ws_trade
257 .clone()
258 .unwrap_or_else(|| bybit_ws_trade_url(self.environment).to_string())
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use rstest::rstest;
265
266 use super::*;
267
268 #[rstest]
269 fn test_data_config_default() {
270 let config = BybitDataClientConfig::default();
271
272 assert!(!config.has_api_credentials());
273 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
274 assert_eq!(config.http_timeout_secs, Some(60));
275 assert_eq!(config.heartbeat_interval_secs, Some(20));
276 }
277
278 #[rstest]
279 fn test_data_config_with_credentials() {
280 let config = BybitDataClientConfig {
281 api_key: Some("test_key".to_string()),
282 api_secret: Some("test_secret".to_string()),
283 ..Default::default()
284 };
285
286 assert!(config.has_api_credentials());
287 assert!(config.requires_private_ws());
288 }
289
290 #[rstest]
291 fn test_data_config_http_url_mainnet() {
292 let config = BybitDataClientConfig {
293 environment: BybitEnvironment::Mainnet,
294 ..Default::default()
295 };
296
297 assert_eq!(config.http_base_url(), "https://api.bybit.com");
298 }
299
300 #[rstest]
301 fn test_data_config_http_url_testnet() {
302 let config = BybitDataClientConfig {
303 environment: BybitEnvironment::Testnet,
304 ..Default::default()
305 };
306
307 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
308 }
309
310 #[rstest]
311 fn test_data_config_http_url_demo() {
312 let config = BybitDataClientConfig {
313 environment: BybitEnvironment::Demo,
314 ..Default::default()
315 };
316
317 assert_eq!(config.http_base_url(), "https://api-demo.bybit.com");
318 }
319
320 #[rstest]
321 fn test_data_config_http_url_override() {
322 let custom_url = "https://custom.bybit.com";
323 let config = BybitDataClientConfig {
324 base_url_http: Some(custom_url.to_string()),
325 ..Default::default()
326 };
327
328 assert_eq!(config.http_base_url(), custom_url);
329 }
330
331 #[rstest]
332 fn test_data_config_ws_public_url() {
333 let config = BybitDataClientConfig {
334 environment: BybitEnvironment::Mainnet,
335 ..Default::default()
336 };
337
338 assert_eq!(
339 config.ws_public_url(),
340 "wss://stream.bybit.com/v5/public/linear"
341 );
342 }
343
344 #[rstest]
345 fn test_data_config_ws_public_url_for_spot() {
346 let config = BybitDataClientConfig {
347 environment: BybitEnvironment::Mainnet,
348 ..Default::default()
349 };
350
351 assert_eq!(
352 config.ws_public_url_for(BybitProductType::Spot),
353 "wss://stream.bybit.com/v5/public/spot"
354 );
355 }
356
357 #[rstest]
358 fn test_data_config_ws_private_url() {
359 let config = BybitDataClientConfig {
360 environment: BybitEnvironment::Mainnet,
361 ..Default::default()
362 };
363
364 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
365 }
366
367 #[rstest]
368 fn test_data_config_ws_private_url_testnet() {
369 let config = BybitDataClientConfig {
370 environment: BybitEnvironment::Testnet,
371 ..Default::default()
372 };
373
374 assert_eq!(
375 config.ws_private_url(),
376 "wss://stream-testnet.bybit.com/v5/private"
377 );
378 }
379
380 #[rstest]
381 fn test_exec_config_default() {
382 let config = BybitExecClientConfig::default();
383
384 assert!(!config.has_api_credentials());
385 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
386 assert_eq!(config.http_timeout_secs, Some(60));
387 assert_eq!(config.heartbeat_interval_secs, Some(5));
388 }
389
390 #[rstest]
391 fn test_exec_config_with_credentials() {
392 let config = BybitExecClientConfig {
393 api_key: Some("test_key".to_string()),
394 api_secret: Some("test_secret".to_string()),
395 ..Default::default()
396 };
397
398 assert!(config.has_api_credentials());
399 }
400
401 #[rstest]
402 fn test_exec_config_urls() {
403 let config = BybitExecClientConfig {
404 environment: BybitEnvironment::Mainnet,
405 ..Default::default()
406 };
407
408 assert_eq!(config.http_base_url(), "https://api.bybit.com");
409 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
410 assert_eq!(config.ws_trade_url(), "wss://stream.bybit.com/v5/trade");
411 }
412
413 #[rstest]
414 fn test_exec_config_urls_testnet() {
415 let config = BybitExecClientConfig {
416 environment: BybitEnvironment::Testnet,
417 ..Default::default()
418 };
419
420 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
421 assert_eq!(
422 config.ws_private_url(),
423 "wss://stream-testnet.bybit.com/v5/private"
424 );
425 assert_eq!(
426 config.ws_trade_url(),
427 "wss://stream-testnet.bybit.com/v5/trade"
428 );
429 }
430
431 #[rstest]
432 fn test_exec_config_custom_urls() {
433 let config = BybitExecClientConfig {
434 base_url_http: Some("https://custom-http.bybit.com".to_string()),
435 base_url_ws_private: Some("wss://custom-private.bybit.com".to_string()),
436 base_url_ws_trade: Some("wss://custom-trade.bybit.com".to_string()),
437 ..Default::default()
438 };
439
440 assert_eq!(config.http_base_url(), "https://custom-http.bybit.com");
441 assert_eq!(config.ws_private_url(), "wss://custom-private.bybit.com");
442 assert_eq!(config.ws_trade_url(), "wss://custom-trade.bybit.com");
443 }
444}