use nautilus_model::enums::AccountType;
use nautilus_network::websocket::TransportBackend;
use crate::common::{
enums::{CoinbaseEnvironment, CoinbaseMarginType},
urls,
};
#[derive(Clone, Debug, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.coinbase")
)]
pub struct CoinbaseDataClientConfig {
pub api_key: Option<String>,
pub api_secret: Option<String>,
pub base_url_rest: Option<String>,
pub base_url_ws: Option<String>,
pub proxy_url: Option<String>,
#[builder(default)]
pub environment: CoinbaseEnvironment,
#[builder(default = 10)]
pub http_timeout_secs: u64,
#[builder(default = 30)]
pub ws_timeout_secs: u64,
#[builder(default = 60)]
pub update_instruments_interval_mins: u64,
#[builder(default = 15)]
pub derivatives_poll_interval_secs: u64,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for CoinbaseDataClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl CoinbaseDataClientConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_credentials(&self) -> bool {
self.api_key
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
&& self
.api_secret
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
}
#[must_use]
pub fn rest_url(&self) -> String {
self.base_url_rest
.clone()
.unwrap_or_else(|| urls::rest_url(self.environment).to_string())
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| urls::ws_url(self.environment).to_string())
}
}
#[derive(Clone, Debug, bon::Builder)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.coinbase")
)]
pub struct CoinbaseExecClientConfig {
pub api_key: Option<String>,
pub api_secret: Option<String>,
pub base_url_rest: Option<String>,
pub base_url_ws: Option<String>,
pub proxy_url: Option<String>,
#[builder(default)]
pub environment: CoinbaseEnvironment,
#[builder(default = 10)]
pub http_timeout_secs: u64,
#[builder(default = 3)]
pub max_retries: u32,
#[builder(default = 100)]
pub retry_delay_initial_ms: u64,
#[builder(default = 5000)]
pub retry_delay_max_ms: u64,
#[builder(default = AccountType::Cash)]
pub account_type: AccountType,
pub default_margin_type: Option<CoinbaseMarginType>,
pub default_leverage: Option<rust_decimal::Decimal>,
pub retail_portfolio_id: Option<String>,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for CoinbaseExecClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl CoinbaseExecClientConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_credentials(&self) -> bool {
self.api_key
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
&& self
.api_secret
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
}
#[must_use]
pub fn rest_url(&self) -> String {
self.base_url_rest
.clone()
.unwrap_or_else(|| urls::rest_url(self.environment).to_string())
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| urls::ws_user_url(self.environment).to_string())
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_data_config_defaults() {
let config = CoinbaseDataClientConfig::default();
assert_eq!(config.environment, CoinbaseEnvironment::Live);
assert_eq!(config.http_timeout_secs, 10);
assert_eq!(config.ws_timeout_secs, 30);
assert_eq!(config.update_instruments_interval_mins, 60);
assert!(!config.has_credentials());
}
#[rstest]
fn test_data_config_has_credentials() {
let config = CoinbaseDataClientConfig {
api_key: Some("key".to_string()),
api_secret: Some("secret".to_string()),
..CoinbaseDataClientConfig::default()
};
assert!(config.has_credentials());
}
#[rstest]
fn test_data_config_empty_credentials() {
let config = CoinbaseDataClientConfig {
api_key: Some(" ".to_string()),
api_secret: Some("secret".to_string()),
..CoinbaseDataClientConfig::default()
};
assert!(!config.has_credentials());
}
#[rstest]
fn test_data_config_urls_live() {
let config = CoinbaseDataClientConfig::default();
assert!(config.rest_url().contains("api.coinbase.com"));
assert!(config.ws_url().contains("advanced-trade-ws.coinbase.com"));
}
#[rstest]
fn test_data_config_urls_sandbox() {
let config = CoinbaseDataClientConfig {
environment: CoinbaseEnvironment::Sandbox,
..CoinbaseDataClientConfig::default()
};
assert!(config.rest_url().contains("sandbox"));
assert!(config.ws_url().contains("sandbox"));
}
#[rstest]
fn test_exec_config_defaults() {
let config = CoinbaseExecClientConfig::default();
assert_eq!(config.environment, CoinbaseEnvironment::Live);
assert_eq!(config.http_timeout_secs, 10);
assert_eq!(config.max_retries, 3);
}
#[rstest]
fn test_exec_config_ws_url_uses_user_endpoint() {
let config = CoinbaseExecClientConfig::default();
assert!(config.ws_url().contains("user"));
}
}