1use nautilus_model::identifiers::AccountId;
19
20use crate::common::{
21 enums::{BybitEnvironment, BybitProductType},
22 urls::{bybit_http_base_url, bybit_ws_private_url, bybit_ws_public_url, bybit_ws_trade_url},
23};
24
25#[derive(Clone, Debug)]
27pub struct BybitDataClientConfig {
28 pub api_key: Option<String>,
30 pub api_secret: Option<String>,
32 pub product_types: Vec<BybitProductType>,
34 pub environment: BybitEnvironment,
36 pub base_url_http: Option<String>,
38 pub base_url_ws_public: Option<String>,
40 pub base_url_ws_private: Option<String>,
42 pub http_timeout_secs: Option<u64>,
44 pub max_retries: Option<u32>,
46 pub retry_delay_initial_ms: Option<u64>,
48 pub retry_delay_max_ms: Option<u64>,
50 pub heartbeat_interval_secs: Option<u64>,
52 pub recv_window_ms: Option<u64>,
54 pub update_instruments_interval_mins: Option<u64>,
56}
57
58impl Default for BybitDataClientConfig {
59 fn default() -> Self {
60 Self {
61 api_key: None,
62 api_secret: None,
63 product_types: vec![BybitProductType::Linear],
64 environment: BybitEnvironment::Mainnet,
65 base_url_http: None,
66 base_url_ws_public: None,
67 base_url_ws_private: None,
68 http_timeout_secs: Some(60),
69 max_retries: Some(3),
70 retry_delay_initial_ms: Some(1_000),
71 retry_delay_max_ms: Some(10_000),
72 heartbeat_interval_secs: Some(20),
73 recv_window_ms: Some(5_000),
74 update_instruments_interval_mins: Some(60),
75 }
76 }
77}
78
79impl BybitDataClientConfig {
80 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 #[must_use]
88 pub fn has_api_credentials(&self) -> bool {
89 self.api_key.is_some() && self.api_secret.is_some()
90 }
91
92 #[must_use]
94 pub fn http_base_url(&self) -> String {
95 self.base_url_http
96 .clone()
97 .unwrap_or_else(|| bybit_http_base_url(self.environment).to_string())
98 }
99
100 #[must_use]
104 pub fn ws_public_url(&self) -> String {
105 self.base_url_ws_public.clone().unwrap_or_else(|| {
106 let product_type = self
107 .product_types
108 .first()
109 .copied()
110 .unwrap_or(BybitProductType::Linear);
111 bybit_ws_public_url(product_type, self.environment)
112 })
113 }
114
115 #[must_use]
117 pub fn ws_public_url_for(&self, product_type: BybitProductType) -> String {
118 self.base_url_ws_public
119 .clone()
120 .unwrap_or_else(|| bybit_ws_public_url(product_type, self.environment))
121 }
122
123 #[must_use]
125 pub fn ws_private_url(&self) -> String {
126 self.base_url_ws_private
127 .clone()
128 .unwrap_or_else(|| bybit_ws_private_url(self.environment).to_string())
129 }
130
131 #[must_use]
133 pub fn requires_private_ws(&self) -> bool {
134 self.has_api_credentials()
135 }
136}
137
138#[derive(Clone, Debug)]
140pub struct BybitExecClientConfig {
141 pub api_key: Option<String>,
143 pub api_secret: Option<String>,
145 pub product_types: Vec<BybitProductType>,
147 pub environment: BybitEnvironment,
149 pub base_url_http: Option<String>,
151 pub base_url_ws_private: Option<String>,
153 pub base_url_ws_trade: Option<String>,
155 pub http_timeout_secs: Option<u64>,
157 pub max_retries: Option<u32>,
159 pub retry_delay_initial_ms: Option<u64>,
161 pub retry_delay_max_ms: Option<u64>,
163 pub heartbeat_interval_secs: Option<u64>,
165 pub recv_window_ms: Option<u64>,
167 pub account_id: Option<AccountId>,
169}
170
171impl Default for BybitExecClientConfig {
172 fn default() -> Self {
173 Self {
174 api_key: None,
175 api_secret: None,
176 product_types: vec![BybitProductType::Linear],
177 environment: BybitEnvironment::Mainnet,
178 base_url_http: None,
179 base_url_ws_private: None,
180 base_url_ws_trade: None,
181 http_timeout_secs: Some(60),
182 max_retries: Some(3),
183 retry_delay_initial_ms: Some(1_000),
184 retry_delay_max_ms: Some(10_000),
185 heartbeat_interval_secs: Some(5),
186 recv_window_ms: Some(5_000),
187 account_id: None,
188 }
189 }
190}
191
192impl BybitExecClientConfig {
193 #[must_use]
195 pub fn new() -> Self {
196 Self::default()
197 }
198
199 #[must_use]
201 pub fn has_api_credentials(&self) -> bool {
202 self.api_key.is_some() && self.api_secret.is_some()
203 }
204
205 #[must_use]
207 pub fn http_base_url(&self) -> String {
208 self.base_url_http
209 .clone()
210 .unwrap_or_else(|| bybit_http_base_url(self.environment).to_string())
211 }
212
213 #[must_use]
215 pub fn ws_private_url(&self) -> String {
216 self.base_url_ws_private
217 .clone()
218 .unwrap_or_else(|| bybit_ws_private_url(self.environment).to_string())
219 }
220
221 #[must_use]
223 pub fn ws_trade_url(&self) -> String {
224 self.base_url_ws_trade
225 .clone()
226 .unwrap_or_else(|| bybit_ws_trade_url(self.environment).to_string())
227 }
228}
229
230#[cfg(test)]
235mod tests {
236 use rstest::rstest;
237
238 use super::*;
239
240 #[rstest]
241 fn test_data_config_default() {
242 let config = BybitDataClientConfig::default();
243
244 assert!(!config.has_api_credentials());
245 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
246 assert_eq!(config.http_timeout_secs, Some(60));
247 assert_eq!(config.heartbeat_interval_secs, Some(20));
248 }
249
250 #[rstest]
251 fn test_data_config_with_credentials() {
252 let config = BybitDataClientConfig {
253 api_key: Some("test_key".to_string()),
254 api_secret: Some("test_secret".to_string()),
255 ..Default::default()
256 };
257
258 assert!(config.has_api_credentials());
259 assert!(config.requires_private_ws());
260 }
261
262 #[rstest]
263 fn test_data_config_http_url_mainnet() {
264 let config = BybitDataClientConfig {
265 environment: BybitEnvironment::Mainnet,
266 ..Default::default()
267 };
268
269 assert_eq!(config.http_base_url(), "https://api.bybit.com");
270 }
271
272 #[rstest]
273 fn test_data_config_http_url_testnet() {
274 let config = BybitDataClientConfig {
275 environment: BybitEnvironment::Testnet,
276 ..Default::default()
277 };
278
279 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
280 }
281
282 #[rstest]
283 fn test_data_config_http_url_demo() {
284 let config = BybitDataClientConfig {
285 environment: BybitEnvironment::Demo,
286 ..Default::default()
287 };
288
289 assert_eq!(config.http_base_url(), "https://api-demo.bybit.com");
290 }
291
292 #[rstest]
293 fn test_data_config_http_url_override() {
294 let custom_url = "https://custom.bybit.com";
295 let config = BybitDataClientConfig {
296 base_url_http: Some(custom_url.to_string()),
297 ..Default::default()
298 };
299
300 assert_eq!(config.http_base_url(), custom_url);
301 }
302
303 #[rstest]
304 fn test_data_config_ws_public_url() {
305 let config = BybitDataClientConfig {
306 environment: BybitEnvironment::Mainnet,
307 ..Default::default()
308 };
309
310 assert_eq!(
311 config.ws_public_url(),
312 "wss://stream.bybit.com/v5/public/linear"
313 );
314 }
315
316 #[rstest]
317 fn test_data_config_ws_public_url_for_spot() {
318 let config = BybitDataClientConfig {
319 environment: BybitEnvironment::Mainnet,
320 ..Default::default()
321 };
322
323 assert_eq!(
324 config.ws_public_url_for(BybitProductType::Spot),
325 "wss://stream.bybit.com/v5/public/spot"
326 );
327 }
328
329 #[rstest]
330 fn test_data_config_ws_private_url() {
331 let config = BybitDataClientConfig {
332 environment: BybitEnvironment::Mainnet,
333 ..Default::default()
334 };
335
336 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
337 }
338
339 #[rstest]
340 fn test_data_config_ws_private_url_testnet() {
341 let config = BybitDataClientConfig {
342 environment: BybitEnvironment::Testnet,
343 ..Default::default()
344 };
345
346 assert_eq!(
347 config.ws_private_url(),
348 "wss://stream-testnet.bybit.com/v5/private"
349 );
350 }
351
352 #[rstest]
353 fn test_exec_config_default() {
354 let config = BybitExecClientConfig::default();
355
356 assert!(!config.has_api_credentials());
357 assert_eq!(config.product_types, vec![BybitProductType::Linear]);
358 assert_eq!(config.http_timeout_secs, Some(60));
359 assert_eq!(config.heartbeat_interval_secs, Some(5));
360 }
361
362 #[rstest]
363 fn test_exec_config_with_credentials() {
364 let config = BybitExecClientConfig {
365 api_key: Some("test_key".to_string()),
366 api_secret: Some("test_secret".to_string()),
367 ..Default::default()
368 };
369
370 assert!(config.has_api_credentials());
371 }
372
373 #[rstest]
374 fn test_exec_config_urls() {
375 let config = BybitExecClientConfig {
376 environment: BybitEnvironment::Mainnet,
377 ..Default::default()
378 };
379
380 assert_eq!(config.http_base_url(), "https://api.bybit.com");
381 assert_eq!(config.ws_private_url(), "wss://stream.bybit.com/v5/private");
382 assert_eq!(config.ws_trade_url(), "wss://stream.bybit.com/v5/trade");
383 }
384
385 #[rstest]
386 fn test_exec_config_urls_testnet() {
387 let config = BybitExecClientConfig {
388 environment: BybitEnvironment::Testnet,
389 ..Default::default()
390 };
391
392 assert_eq!(config.http_base_url(), "https://api-testnet.bybit.com");
393 assert_eq!(
394 config.ws_private_url(),
395 "wss://stream-testnet.bybit.com/v5/private"
396 );
397 assert_eq!(
398 config.ws_trade_url(),
399 "wss://stream-testnet.bybit.com/v5/trade"
400 );
401 }
402
403 #[rstest]
404 fn test_exec_config_custom_urls() {
405 let config = BybitExecClientConfig {
406 base_url_http: Some("https://custom-http.bybit.com".to_string()),
407 base_url_ws_private: Some("wss://custom-private.bybit.com".to_string()),
408 base_url_ws_trade: Some("wss://custom-trade.bybit.com".to_string()),
409 ..Default::default()
410 };
411
412 assert_eq!(config.http_base_url(), "https://custom-http.bybit.com");
413 assert_eq!(config.ws_private_url(), "wss://custom-private.bybit.com");
414 assert_eq!(config.ws_trade_url(), "wss://custom-trade.bybit.com");
415 }
416}