Skip to main content

nautilus_bitmex/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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 types for the BitMEX adapter clients.
17
18use nautilus_model::identifiers::AccountId;
19use nautilus_network::websocket::TransportBackend;
20
21use crate::common::{
22    consts::{BITMEX_HTTP_TESTNET_URL, BITMEX_HTTP_URL, BITMEX_WS_TESTNET_URL, BITMEX_WS_URL},
23    credential::credential_env_vars,
24    enums::BitmexEnvironment,
25};
26
27/// Configuration for the BitMEX live data client.
28#[derive(Clone, Debug, bon::Builder)]
29#[cfg_attr(
30    feature = "python",
31    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
32)]
33#[cfg_attr(
34    feature = "python",
35    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bitmex")
36)]
37pub struct BitmexDataClientConfig {
38    /// Optional API key used for authenticated REST/WebSocket requests.
39    pub api_key: Option<String>,
40    /// Optional API secret used for authenticated REST/WebSocket requests.
41    pub api_secret: Option<String>,
42    /// Optional override for the REST base URL.
43    pub base_url_http: Option<String>,
44    /// Optional override for the WebSocket URL.
45    pub base_url_ws: Option<String>,
46    /// Optional proxy URL for HTTP and WebSocket transports.
47    pub proxy_url: Option<String>,
48    /// REST timeout in seconds.
49    #[builder(default = 60)]
50    pub http_timeout_secs: u64,
51    /// Maximum retry attempts for REST requests.
52    #[builder(default = 3)]
53    pub max_retries: u32,
54    /// Initial retry backoff in milliseconds.
55    #[builder(default = 1_000)]
56    pub retry_delay_initial_ms: u64,
57    /// Maximum retry backoff in milliseconds.
58    #[builder(default = 10_000)]
59    pub retry_delay_max_ms: u64,
60    /// Optional heartbeat interval (seconds) for the WebSocket client.
61    pub heartbeat_interval_secs: Option<u64>,
62    /// Receive window in milliseconds for signed requests.
63    ///
64    /// This value determines how far in the future the `api-expires` timestamp will be set
65    /// for signed REST requests. BitMEX uses seconds-granularity Unix timestamps in the
66    /// `api-expires` header, calculated as: `current_timestamp + (recv_window_ms / 1000)`.
67    ///
68    /// **Note**: This parameter is specified in milliseconds for consistency with other
69    /// adapter configurations (e.g., Bybit's `recv_window_ms`), but BitMEX only supports
70    /// seconds-granularity timestamps. The value is converted via integer division, so
71    /// 10000ms becomes 10 seconds, 15500ms becomes 15 seconds, etc.
72    ///
73    /// A larger window provides more tolerance for clock skew and network latency, but
74    /// increases the replay attack window. The default of 10 seconds should be sufficient
75    /// for most deployments. Consider increasing this value (e.g., to 30_000ms = 30s) if you
76    /// experience request expiration errors due to clock drift or high network latency.
77    #[builder(default = 10_000)]
78    pub recv_window_ms: u64,
79    /// When `true`, only active instruments are requested during bootstrap.
80    #[builder(default = true)]
81    pub active_only: bool,
82    /// Optional interval (minutes) for instrument refresh from REST.
83    pub update_instruments_interval_mins: Option<u64>,
84    /// BitMEX environment (mainnet or testnet).
85    #[builder(default)]
86    pub environment: BitmexEnvironment,
87    /// Maximum number of requests per second (burst limit).
88    #[builder(default = 10)]
89    pub max_requests_per_second: u32,
90    /// Maximum number of requests per minute (rolling window).
91    #[builder(default = 120)]
92    pub max_requests_per_minute: u32,
93    /// WebSocket transport backend (defaults to `Tungstenite`).
94    #[builder(default)]
95    pub transport_backend: TransportBackend,
96}
97
98impl Default for BitmexDataClientConfig {
99    fn default() -> Self {
100        Self::builder().build()
101    }
102}
103
104impl BitmexDataClientConfig {
105    /// Creates a configuration with default values.
106    #[must_use]
107    pub fn new() -> Self {
108        Self::default()
109    }
110
111    /// Returns `true` if both API key and secret are available
112    /// (either explicitly set or resolvable from environment variables).
113    #[must_use]
114    pub fn has_api_credentials(&self) -> bool {
115        let (key_var, secret_var) = credential_env_vars(self.environment);
116        let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
117        let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
118        has_key && has_secret
119    }
120
121    /// Returns the REST base URL, considering overrides and the environment.
122    #[must_use]
123    pub fn http_base_url(&self) -> String {
124        self.base_url_http
125            .clone()
126            .unwrap_or_else(|| match self.environment {
127                BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
128                BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
129            })
130    }
131
132    /// Returns the WebSocket URL, considering overrides and the environment.
133    #[must_use]
134    pub fn ws_url(&self) -> String {
135        self.base_url_ws
136            .clone()
137            .unwrap_or_else(|| match self.environment {
138                BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
139                BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
140            })
141    }
142}
143
144/// Configuration for the BitMEX live execution client.
145#[derive(Clone, Debug, bon::Builder)]
146#[cfg_attr(
147    feature = "python",
148    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
149)]
150#[cfg_attr(
151    feature = "python",
152    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bitmex")
153)]
154pub struct BitmexExecClientConfig {
155    /// API key used for authenticated requests.
156    pub api_key: Option<String>,
157    /// API secret used for authenticated requests.
158    pub api_secret: Option<String>,
159    /// Optional override for the REST base URL.
160    pub base_url_http: Option<String>,
161    /// Optional override for the WebSocket URL.
162    pub base_url_ws: Option<String>,
163    /// Optional proxy URL for HTTP and WebSocket transports.
164    pub proxy_url: Option<String>,
165    /// REST timeout in seconds.
166    #[builder(default = 60)]
167    pub http_timeout_secs: u64,
168    /// Maximum retry attempts for REST requests.
169    #[builder(default = 3)]
170    pub max_retries: u32,
171    /// Initial retry backoff in milliseconds.
172    #[builder(default = 1_000)]
173    pub retry_delay_initial_ms: u64,
174    /// Maximum retry backoff in milliseconds.
175    #[builder(default = 10_000)]
176    pub retry_delay_max_ms: u64,
177    /// Heartbeat interval (seconds) for the WebSocket client.
178    #[builder(default = 5)]
179    pub heartbeat_interval_secs: u64,
180    /// Receive window in milliseconds for signed requests.
181    ///
182    /// This value determines how far in the future the `api-expires` timestamp will be set
183    /// for signed REST requests. BitMEX uses seconds-granularity Unix timestamps in the
184    /// `api-expires` header, calculated as: `current_timestamp + (recv_window_ms / 1000)`.
185    ///
186    /// **Note**: This parameter is specified in milliseconds for consistency with other
187    /// adapter configurations (e.g., Bybit's `recv_window_ms`), but BitMEX only supports
188    /// seconds-granularity timestamps. The value is converted via integer division, so
189    /// 10000ms becomes 10 seconds, 15500ms becomes 15 seconds, etc.
190    ///
191    /// A larger window provides more tolerance for clock skew and network latency, but
192    /// increases the replay attack window. The default of 10 seconds should be sufficient
193    /// for most deployments. Consider increasing this value (e.g., to 30000ms = 30s) if you
194    /// experience request expiration errors due to clock drift or high network latency.
195    #[builder(default = 10_000)]
196    pub recv_window_ms: u64,
197    /// When `true`, only active instruments are requested during bootstrap.
198    #[builder(default = true)]
199    pub active_only: bool,
200    /// BitMEX environment (mainnet or testnet).
201    #[builder(default)]
202    pub environment: BitmexEnvironment,
203    /// Optional account identifier to associate with the execution client.
204    pub account_id: Option<AccountId>,
205    /// Maximum number of requests per second (burst limit).
206    #[builder(default = 10)]
207    pub max_requests_per_second: u32,
208    /// Maximum number of requests per minute (rolling window).
209    #[builder(default = 120)]
210    pub max_requests_per_minute: u32,
211    /// Number of HTTP clients in the submit broadcaster pool (defaults to 1).
212    pub submitter_pool_size: Option<usize>,
213    /// Number of HTTP clients in the cancel broadcaster pool (defaults to 1).
214    pub canceller_pool_size: Option<usize>,
215    /// Optional list of proxy URLs for submit broadcaster pool (path diversity).
216    pub submitter_proxy_urls: Option<Vec<String>>,
217    /// Optional list of proxy URLs for cancel broadcaster pool (path diversity).
218    pub canceller_proxy_urls: Option<Vec<String>>,
219    /// Optional dead man's switch timeout in seconds.
220    ///
221    /// When set, a background task periodically calls the BitMEX `cancelAllAfter` endpoint
222    /// to keep a server-side timer alive. If the client loses connectivity the timer expires
223    /// and BitMEX cancels all open orders. Calling with `timeout=0` disarms the switch.
224    /// The refresh interval is derived as `timeout / 4` (minimum 1 second).
225    pub deadmans_switch_timeout_secs: Option<u64>,
226    /// WebSocket transport backend (defaults to `Tungstenite`).
227    #[builder(default)]
228    pub transport_backend: TransportBackend,
229}
230
231impl Default for BitmexExecClientConfig {
232    fn default() -> Self {
233        Self::builder().build()
234    }
235}
236
237impl BitmexExecClientConfig {
238    /// Creates a configuration with default values.
239    #[must_use]
240    pub fn new() -> Self {
241        Self::default()
242    }
243
244    /// Returns `true` if both API key and secret are available
245    /// (either explicitly set or resolvable from environment variables).
246    #[must_use]
247    pub fn has_api_credentials(&self) -> bool {
248        let (key_var, secret_var) = credential_env_vars(self.environment);
249        let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
250        let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
251        has_key && has_secret
252    }
253
254    /// Returns the REST base URL, considering overrides and the environment.
255    #[must_use]
256    pub fn http_base_url(&self) -> String {
257        self.base_url_http
258            .clone()
259            .unwrap_or_else(|| match self.environment {
260                BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
261                BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
262            })
263    }
264
265    /// Returns the WebSocket URL, considering overrides and the environment.
266    #[must_use]
267    pub fn ws_url(&self) -> String {
268        self.base_url_ws
269            .clone()
270            .unwrap_or_else(|| match self.environment {
271                BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
272                BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
273            })
274    }
275}