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)]
267mod tests {
268 use rstest::rstest;
269
270 use super::*;
271
272 #[rstest]
273 fn test_data_config_default() {
274 let config = BybitDataClientConfig::default();
275
276 assert!(!config.has_api_credentials());
277 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
278 assert_eq!(config.http_timeout_secs, Some(60));
279 assert_eq!(config.heartbeat_interval_secs, Some(20));
280 }
281
282 #[rstest]
283 fn test_data_config_with_credentials() {
284 let config = BybitDataClientConfig {
285 api_key: Some("test_key".to_string()),
286 api_secret: Some("test_secret".to_string()),
287 ..Default::default()
288 };
289
290 assert!(config.has_api_credentials());
291 assert!(config.requires_private_ws());
292 }
293
294 #[rstest]
295 fn test_data_config_http_url_mainnet() {
296 let config = BybitDataClientConfig {
297 environment: BybitEnvironment::Mainnet,
298 ..Default::default()
299 };
300
301 assert_eq!(config.http_base_url(), "https://api.bybit.com");
302 }
303
304 #[rstest]
305 fn test_data_config_http_url_testnet() {
306 let config = BybitDataClientConfig {
307 environment: BybitEnvironment::Testnet,
308 ..Default::default()
309 };
310
311 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
312 }
313
314 #[rstest]
315 fn test_data_config_http_url_demo() {
316 let config = BybitDataClientConfig {
317 environment: BybitEnvironment::Demo,
318 ..Default::default()
319 };
320
321 assert_eq!(config.http_base_url(), "https://api-demo.bybit.com");
322 }
323
324 #[rstest]
325 fn test_data_config_http_url_override() {
326 let custom_url = "https://custom.bybit.com";
327 let config = BybitDataClientConfig {
328 base_url_http: Some(custom_url.to_string()),
329 ..Default::default()
330 };
331
332 assert_eq!(config.http_base_url(), custom_url);
333 }
334
335 #[rstest]
336 fn test_data_config_ws_public_url() {
337 let config = BybitDataClientConfig {
338 environment: BybitEnvironment::Mainnet,
339 ..Default::default()
340 };
341
342 assert_eq!(
343 config.ws_public_url(),
344 "wss://stream.bybit.com/v5/public/linear"
345 );
346 }
347
348 #[rstest]
349 fn test_data_config_ws_public_url_for_spot() {
350 let config = BybitDataClientConfig {
351 environment: BybitEnvironment::Mainnet,
352 ..Default::default()
353 };
354
355 assert_eq!(
356 config.ws_public_url_for(BybitProductType::Spot),
357 "wss://stream.bybit.com/v5/public/spot"
358 );
359 }
360
361 #[rstest]
362 fn test_data_config_ws_private_url() {
363 let config = BybitDataClientConfig {
364 environment: BybitEnvironment::Mainnet,
365 ..Default::default()
366 };
367
368 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
369 }
370
371 #[rstest]
372 fn test_data_config_ws_private_url_testnet() {
373 let config = BybitDataClientConfig {
374 environment: BybitEnvironment::Testnet,
375 ..Default::default()
376 };
377
378 assert_eq!(
379 config.ws_private_url(),
380 "wss://stream-testnet.bybit.com/v5/private"
381 );
382 }
383
384 #[rstest]
385 fn test_exec_config_default() {
386 let config = BybitExecClientConfig::default();
387
388 assert!(!config.has_api_credentials());
389 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
390 assert_eq!(config.http_timeout_secs, Some(60));
391 assert_eq!(config.heartbeat_interval_secs, Some(5));
392 }
393
394 #[rstest]
395 fn test_exec_config_with_credentials() {
396 let config = BybitExecClientConfig {
397 api_key: Some("test_key".to_string()),
398 api_secret: Some("test_secret".to_string()),
399 ..Default::default()
400 };
401
402 assert!(config.has_api_credentials());
403 }
404
405 #[rstest]
406 fn test_exec_config_urls() {
407 let config = BybitExecClientConfig {
408 environment: BybitEnvironment::Mainnet,
409 ..Default::default()
410 };
411
412 assert_eq!(config.http_base_url(), "https://api.bybit.com");
413 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
414 assert_eq!(config.ws_trade_url(), "wss://stream.bybit.com/v5/trade");
415 }
416
417 #[rstest]
418 fn test_exec_config_urls_testnet() {
419 let config = BybitExecClientConfig {
420 environment: BybitEnvironment::Testnet,
421 ..Default::default()
422 };
423
424 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
425 assert_eq!(
426 config.ws_private_url(),
427 "wss://stream-testnet.bybit.com/v5/private"
428 );
429 assert_eq!(
430 config.ws_trade_url(),
431 "wss://stream-testnet.bybit.com/v5/trade"
432 );
433 }
434
435 #[rstest]
436 fn test_exec_config_custom_urls() {
437 let config = BybitExecClientConfig {
438 base_url_http: Some("https://custom-http.bybit.com".to_string()),
439 base_url_ws_private: Some("wss://custom-private.bybit.com".to_string()),
440 base_url_ws_trade: Some("wss://custom-trade.bybit.com".to_string()),
441 ..Default::default()
442 };
443
444 assert_eq!(config.http_base_url(), "https://custom-http.bybit.com");
445 assert_eq!(config.ws_private_url(), "wss://custom-private.bybit.com");
446 assert_eq!(config.ws_trade_url(), "wss://custom-trade.bybit.com");
447 }
448}