nautilus_bybit/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration structures for the Bybit adapter.
17
18use 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/// Configuration for the Bybit live data client.
28#[derive(Clone, Debug)]
29pub struct BybitDataClientConfig {
30    /// Optional API key for authenticated REST/WebSocket requests.
31    pub api_key: Option<String>,
32    /// Optional API secret for authenticated REST/WebSocket requests.
33    pub api_secret: Option<String>,
34    /// Product types to subscribe to (e.g., Linear, Spot, Inverse, Option).
35    pub product_types: Vec<BybitProductType>,
36    /// Environment selection (Mainnet, Testnet, Demo).
37    pub environment: BybitEnvironment,
38    /// Optional override for the REST base URL.
39    pub base_url_http: Option<String>,
40    /// Optional override for the public WebSocket URL.
41    pub base_url_ws_public: Option<String>,
42    /// Optional override for the private WebSocket URL.
43    pub base_url_ws_private: Option<String>,
44    /// Optional HTTP proxy URL.
45    pub http_proxy_url: Option<String>,
46    /// Optional WebSocket proxy URL.
47    ///
48    /// Note: WebSocket proxy support is not yet implemented. This field is reserved
49    /// for future functionality. Use `http_proxy_url` for REST API proxy support.
50    pub ws_proxy_url: Option<String>,
51    /// Optional REST timeout in seconds.
52    pub http_timeout_secs: Option<u64>,
53    /// Optional maximum retry attempts for REST requests.
54    pub max_retries: Option<u32>,
55    /// Optional initial retry backoff in milliseconds.
56    pub retry_delay_initial_ms: Option<u64>,
57    /// Optional maximum retry backoff in milliseconds.
58    pub retry_delay_max_ms: Option<u64>,
59    /// Optional heartbeat interval (seconds) for WebSocket clients.
60    pub heartbeat_interval_secs: Option<u64>,
61    /// Optional receive window in milliseconds for signed requests.
62    pub recv_window_ms: Option<u64>,
63    /// Optional interval (minutes) for instrument refresh from REST.
64    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    /// Creates a configuration with default values.
92    #[must_use]
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    /// Returns `true` if both API key and secret are available.
98    #[must_use]
99    pub fn has_api_credentials(&self) -> bool {
100        self.api_key.is_some() && self.api_secret.is_some()
101    }
102
103    /// Returns the REST base URL, considering overrides and environment.
104    #[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    /// Returns the public WebSocket URL for the given product type.
112    ///
113    /// Falls back to the first product type in the config if multiple are configured.
114    #[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    /// Returns the public WebSocket URL for a specific product type.
127    #[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    /// Returns the private WebSocket URL, considering overrides and environment.
135    #[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    /// Returns `true` when private WebSocket connection is required.
143    #[must_use]
144    pub fn requires_private_ws(&self) -> bool {
145        self.has_api_credentials()
146    }
147}
148
149/// Configuration for the Bybit live execution client.
150#[derive(Clone, Debug)]
151pub struct BybitExecClientConfig {
152    /// API key for authenticated requests.
153    pub api_key: Option<String>,
154    /// API secret for authenticated requests.
155    pub api_secret: Option<String>,
156    /// Product types to support (e.g., Linear, Spot, Inverse, Option).
157    pub product_types: Vec<BybitProductType>,
158    /// Environment selection (Mainnet, Testnet, Demo).
159    pub environment: BybitEnvironment,
160    /// Optional override for the REST base URL.
161    pub base_url_http: Option<String>,
162    /// Optional override for the private WebSocket URL.
163    pub base_url_ws_private: Option<String>,
164    /// Optional override for the trade WebSocket URL.
165    pub base_url_ws_trade: Option<String>,
166    /// Optional HTTP proxy URL.
167    pub http_proxy_url: Option<String>,
168    /// Optional WebSocket proxy URL.
169    ///
170    /// Note: WebSocket proxy support is not yet implemented. This field is reserved
171    /// for future functionality. Use `http_proxy_url` for REST API proxy support.
172    pub ws_proxy_url: Option<String>,
173    /// Optional REST timeout in seconds.
174    pub http_timeout_secs: Option<u64>,
175    /// Optional maximum retry attempts for REST requests.
176    pub max_retries: Option<u32>,
177    /// Optional initial retry backoff in milliseconds.
178    pub retry_delay_initial_ms: Option<u64>,
179    /// Optional maximum retry backoff in milliseconds.
180    pub retry_delay_max_ms: Option<u64>,
181    /// Optional heartbeat interval (seconds) for WebSocket clients.
182    pub heartbeat_interval_secs: Option<u64>,
183    /// Optional receive window in milliseconds for signed requests.
184    pub recv_window_ms: Option<u64>,
185    /// Optional account identifier to associate with the execution client.
186    pub account_id: Option<AccountId>,
187    /// Whether to generate position reports from wallet balances for SPOT positions.
188    pub use_spot_position_reports: bool,
189    /// Leverage configuration for futures (symbol -> leverage).
190    pub futures_leverages: Option<HashMap<String, u32>>,
191    /// Position mode configuration for symbols (symbol -> mode).
192    pub position_mode: Option<HashMap<String, BybitPositionMode>>,
193    /// Unified margin mode setting.
194    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    /// Creates a configuration with default values.
226    #[must_use]
227    pub fn new() -> Self {
228        Self::default()
229    }
230
231    /// Returns `true` if both API key and secret are available.
232    #[must_use]
233    pub fn has_api_credentials(&self) -> bool {
234        self.api_key.is_some() && self.api_secret.is_some()
235    }
236
237    /// Returns the REST base URL, considering overrides and environment.
238    #[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    /// Returns the private WebSocket URL, considering overrides and environment.
246    #[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    /// Returns the trade WebSocket URL, considering overrides and environment.
254    #[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////////////////////////////////////////////////////////////////////////////////
263// Tests
264////////////////////////////////////////////////////////////////////////////////
265
266#[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}