1use nautilus_model::identifiers::AccountId;
19use nautilus_network::websocket::TransportBackend;
20use serde::{Deserialize, Serialize};
21
22use crate::common::{
23 consts::{BITMEX_HTTP_TESTNET_URL, BITMEX_HTTP_URL, BITMEX_WS_TESTNET_URL, BITMEX_WS_URL},
24 credential::credential_env_vars,
25 enums::BitmexEnvironment,
26};
27
28#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
30#[serde(default, deny_unknown_fields)]
31#[cfg_attr(
32 feature = "python",
33 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
34)]
35#[cfg_attr(
36 feature = "python",
37 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bitmex")
38)]
39pub struct BitmexDataClientConfig {
40 pub api_key: Option<String>,
42 pub api_secret: Option<String>,
44 pub base_url_http: Option<String>,
46 pub base_url_ws: Option<String>,
48 pub proxy_url: Option<String>,
50 #[builder(default = 60)]
52 pub http_timeout_secs: u64,
53 #[builder(default = 3)]
55 pub max_retries: u32,
56 #[builder(default = 1_000)]
58 pub retry_delay_initial_ms: u64,
59 #[builder(default = 10_000)]
61 pub retry_delay_max_ms: u64,
62 pub heartbeat_interval_secs: Option<u64>,
64 pub auth_timeout_secs: Option<u64>,
67 #[builder(default = 10_000)]
83 pub recv_window_ms: u64,
84 #[builder(default = true)]
86 pub active_only: bool,
87 pub update_instruments_interval_mins: Option<u64>,
89 #[builder(default)]
91 pub environment: BitmexEnvironment,
92 #[builder(default = 10)]
94 pub max_requests_per_second: u32,
95 #[builder(default = 120)]
97 pub max_requests_per_minute: u32,
98 #[builder(default)]
100 pub transport_backend: TransportBackend,
101}
102
103#[cfg(feature = "python")]
104nautilus_core::impl_pyo3_config_getters!(BitmexDataClientConfig {
105 base_url_http: Option<String>,
106 base_url_ws: Option<String>,
107 http_timeout_secs: u64,
108 max_retries: u32,
109 retry_delay_initial_ms: u64,
110 retry_delay_max_ms: u64,
111 heartbeat_interval_secs: Option<u64>,
112 auth_timeout_secs: Option<u64>,
113 recv_window_ms: u64,
114 active_only: bool,
115 update_instruments_interval_mins: Option<u64>,
116 environment: BitmexEnvironment,
117 max_requests_per_second: u32,
118 max_requests_per_minute: u32,
119 transport_backend: TransportBackend,
120});
121
122impl Default for BitmexDataClientConfig {
123 fn default() -> Self {
124 Self::builder().build()
125 }
126}
127
128impl BitmexDataClientConfig {
129 #[must_use]
131 pub fn new() -> Self {
132 Self::default()
133 }
134
135 #[must_use]
138 pub fn has_api_credentials(&self) -> bool {
139 let (key_var, secret_var) = credential_env_vars(self.environment);
140 let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
141 let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
142 has_key && has_secret
143 }
144
145 #[must_use]
147 pub fn http_base_url(&self) -> String {
148 self.base_url_http
149 .clone()
150 .unwrap_or_else(|| match self.environment {
151 BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
152 BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
153 })
154 }
155
156 #[must_use]
158 pub fn ws_url(&self) -> String {
159 self.base_url_ws
160 .clone()
161 .unwrap_or_else(|| match self.environment {
162 BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
163 BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
164 })
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
170#[serde(default, deny_unknown_fields)]
171#[cfg_attr(
172 feature = "python",
173 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
174)]
175#[cfg_attr(
176 feature = "python",
177 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bitmex")
178)]
179pub struct BitmexExecClientConfig {
180 pub api_key: Option<String>,
182 pub api_secret: Option<String>,
184 pub base_url_http: Option<String>,
186 pub base_url_ws: Option<String>,
188 pub proxy_url: Option<String>,
190 #[builder(default = 60)]
192 pub http_timeout_secs: u64,
193 #[builder(default = 3)]
195 pub max_retries: u32,
196 #[builder(default = 1_000)]
198 pub retry_delay_initial_ms: u64,
199 #[builder(default = 10_000)]
201 pub retry_delay_max_ms: u64,
202 #[builder(default = 5)]
204 pub heartbeat_interval_secs: u64,
205 pub auth_timeout_secs: Option<u64>,
208 #[builder(default = 10_000)]
224 pub recv_window_ms: u64,
225 #[builder(default = true)]
227 pub active_only: bool,
228 #[builder(default)]
230 pub environment: BitmexEnvironment,
231 pub account_id: Option<AccountId>,
233 #[builder(default = 10)]
235 pub max_requests_per_second: u32,
236 #[builder(default = 120)]
238 pub max_requests_per_minute: u32,
239 pub submitter_pool_size: Option<usize>,
241 pub canceller_pool_size: Option<usize>,
243 pub submitter_proxy_urls: Option<Vec<String>>,
245 pub canceller_proxy_urls: Option<Vec<String>>,
247 pub deadmans_switch_timeout_secs: Option<u64>,
254 #[builder(default)]
256 pub transport_backend: TransportBackend,
257}
258
259#[cfg(feature = "python")]
260nautilus_core::impl_pyo3_config_getters!(BitmexExecClientConfig {
261 base_url_http: Option<String>,
262 base_url_ws: Option<String>,
263 http_timeout_secs: u64,
264 max_retries: u32,
265 retry_delay_initial_ms: u64,
266 retry_delay_max_ms: u64,
267 heartbeat_interval_secs: u64,
268 auth_timeout_secs: Option<u64>,
269 recv_window_ms: u64,
270 active_only: bool,
271 environment: BitmexEnvironment,
272 account_id: Option<AccountId>,
273 max_requests_per_second: u32,
274 max_requests_per_minute: u32,
275 submitter_pool_size: Option<usize>,
276 canceller_pool_size: Option<usize>,
277 deadmans_switch_timeout_secs: Option<u64>,
278 transport_backend: TransportBackend,
279});
280
281impl Default for BitmexExecClientConfig {
282 fn default() -> Self {
283 Self::builder().build()
284 }
285}
286
287impl BitmexExecClientConfig {
288 #[must_use]
290 pub fn new() -> Self {
291 Self::default()
292 }
293
294 #[must_use]
297 pub fn has_api_credentials(&self) -> bool {
298 let (key_var, secret_var) = credential_env_vars(self.environment);
299 let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
300 let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
301 has_key && has_secret
302 }
303
304 #[must_use]
306 pub fn http_base_url(&self) -> String {
307 self.base_url_http
308 .clone()
309 .unwrap_or_else(|| match self.environment {
310 BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
311 BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
312 })
313 }
314
315 #[must_use]
317 pub fn ws_url(&self) -> String {
318 self.base_url_ws
319 .clone()
320 .unwrap_or_else(|| match self.environment {
321 BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
322 BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
323 })
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use rstest::rstest;
330
331 use super::*;
332
333 #[rstest]
334 fn test_data_config_toml_minimal() {
335 let config: BitmexDataClientConfig = toml::from_str(
336 r#"
337environment = "testnet"
338http_timeout_secs = 30
339active_only = false
340max_requests_per_second = 5
341"#,
342 )
343 .unwrap();
344
345 assert_eq!(config.environment, BitmexEnvironment::Testnet);
346 assert_eq!(config.http_timeout_secs, 30);
347 assert!(!config.active_only);
348 assert_eq!(config.max_requests_per_second, 5);
349 }
350
351 #[rstest]
352 fn test_exec_config_toml_empty_uses_defaults() {
353 let config: BitmexExecClientConfig = toml::from_str("").unwrap();
354 let expected = BitmexExecClientConfig::default();
355
356 assert_eq!(config.environment, expected.environment);
357 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
358 assert_eq!(
359 config.heartbeat_interval_secs,
360 expected.heartbeat_interval_secs,
361 );
362 assert_eq!(config.recv_window_ms, expected.recv_window_ms);
363 assert_eq!(config.active_only, expected.active_only);
364 assert_eq!(
365 config.max_requests_per_second,
366 expected.max_requests_per_second,
367 );
368 assert_eq!(config.transport_backend, expected.transport_backend);
369 }
370
371 #[rstest]
372 fn test_config_auth_timeout_secs() {
373 assert_eq!(BitmexDataClientConfig::default().auth_timeout_secs, None);
374 assert_eq!(BitmexExecClientConfig::default().auth_timeout_secs, None);
375
376 let data = BitmexDataClientConfig::builder()
377 .auth_timeout_secs(3)
378 .build();
379 assert_eq!(data.auth_timeout_secs, Some(3));
380
381 let exec = BitmexExecClientConfig::builder()
382 .auth_timeout_secs(4)
383 .build();
384 assert_eq!(exec.auth_timeout_secs, Some(4));
385
386 let data: BitmexDataClientConfig = toml::from_str("auth_timeout_secs = 7\n").unwrap();
387 assert_eq!(data.auth_timeout_secs, Some(7));
388
389 let exec: BitmexExecClientConfig = toml::from_str("auth_timeout_secs = 8\n").unwrap();
390 assert_eq!(exec.auth_timeout_secs, Some(8));
391 }
392}