use nautilus_model::identifiers::AccountId;
use nautilus_network::websocket::TransportBackend;
use serde::{Deserialize, Serialize};
use crate::common::{
consts::{BITMEX_HTTP_TESTNET_URL, BITMEX_HTTP_URL, BITMEX_WS_TESTNET_URL, BITMEX_WS_URL},
credential::credential_env_vars,
enums::BitmexEnvironment,
};
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bitmex")
)]
pub struct BitmexDataClientConfig {
pub api_key: Option<String>,
pub api_secret: Option<String>,
pub base_url_http: Option<String>,
pub base_url_ws: Option<String>,
pub proxy_url: Option<String>,
#[builder(default = 60)]
pub http_timeout_secs: u64,
#[builder(default = 3)]
pub max_retries: u32,
#[builder(default = 1_000)]
pub retry_delay_initial_ms: u64,
#[builder(default = 10_000)]
pub retry_delay_max_ms: u64,
pub heartbeat_interval_secs: Option<u64>,
#[builder(default = 10_000)]
pub recv_window_ms: u64,
#[builder(default = true)]
pub active_only: bool,
pub update_instruments_interval_mins: Option<u64>,
#[builder(default)]
pub environment: BitmexEnvironment,
#[builder(default = 10)]
pub max_requests_per_second: u32,
#[builder(default = 120)]
pub max_requests_per_minute: u32,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for BitmexDataClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl BitmexDataClientConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_api_credentials(&self) -> bool {
let (key_var, secret_var) = credential_env_vars(self.environment);
let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
has_key && has_secret
}
#[must_use]
pub fn http_base_url(&self) -> String {
self.base_url_http
.clone()
.unwrap_or_else(|| match self.environment {
BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
})
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| match self.environment {
BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bitmex", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.bitmex")
)]
pub struct BitmexExecClientConfig {
pub api_key: Option<String>,
pub api_secret: Option<String>,
pub base_url_http: Option<String>,
pub base_url_ws: Option<String>,
pub proxy_url: Option<String>,
#[builder(default = 60)]
pub http_timeout_secs: u64,
#[builder(default = 3)]
pub max_retries: u32,
#[builder(default = 1_000)]
pub retry_delay_initial_ms: u64,
#[builder(default = 10_000)]
pub retry_delay_max_ms: u64,
#[builder(default = 5)]
pub heartbeat_interval_secs: u64,
#[builder(default = 10_000)]
pub recv_window_ms: u64,
#[builder(default = true)]
pub active_only: bool,
#[builder(default)]
pub environment: BitmexEnvironment,
pub account_id: Option<AccountId>,
#[builder(default = 10)]
pub max_requests_per_second: u32,
#[builder(default = 120)]
pub max_requests_per_minute: u32,
pub submitter_pool_size: Option<usize>,
pub canceller_pool_size: Option<usize>,
pub submitter_proxy_urls: Option<Vec<String>>,
pub canceller_proxy_urls: Option<Vec<String>>,
pub deadmans_switch_timeout_secs: Option<u64>,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for BitmexExecClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl BitmexExecClientConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_api_credentials(&self) -> bool {
let (key_var, secret_var) = credential_env_vars(self.environment);
let has_key = self.api_key.is_some() || std::env::var(key_var).is_ok();
let has_secret = self.api_secret.is_some() || std::env::var(secret_var).is_ok();
has_key && has_secret
}
#[must_use]
pub fn http_base_url(&self) -> String {
self.base_url_http
.clone()
.unwrap_or_else(|| match self.environment {
BitmexEnvironment::Testnet => BITMEX_HTTP_TESTNET_URL.to_string(),
BitmexEnvironment::Mainnet => BITMEX_HTTP_URL.to_string(),
})
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| match self.environment {
BitmexEnvironment::Testnet => BITMEX_WS_TESTNET_URL.to_string(),
BitmexEnvironment::Mainnet => BITMEX_WS_URL.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_data_config_toml_minimal() {
let config: BitmexDataClientConfig = toml::from_str(
r#"
environment = "testnet"
http_timeout_secs = 30
active_only = false
max_requests_per_second = 5
"#,
)
.unwrap();
assert_eq!(config.environment, BitmexEnvironment::Testnet);
assert_eq!(config.http_timeout_secs, 30);
assert!(!config.active_only);
assert_eq!(config.max_requests_per_second, 5);
}
#[rstest]
fn test_exec_config_toml_empty_uses_defaults() {
let config: BitmexExecClientConfig = toml::from_str("").unwrap();
let expected = BitmexExecClientConfig::default();
assert_eq!(config.environment, expected.environment);
assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
assert_eq!(
config.heartbeat_interval_secs,
expected.heartbeat_interval_secs,
);
assert_eq!(config.recv_window_ms, expected.recv_window_ms);
assert_eq!(config.active_only, expected.active_only);
assert_eq!(
config.max_requests_per_second,
expected.max_requests_per_second,
);
assert_eq!(config.transport_backend, expected.transport_backend);
}
}