nautilus_coinbase/
config.rs1use nautilus_model::enums::AccountType;
19use nautilus_network::websocket::TransportBackend;
20use serde::{Deserialize, Serialize};
21
22use crate::common::{
23 enums::{CoinbaseEnvironment, CoinbaseMarginType},
24 urls,
25};
26
27#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
29#[serde(default, deny_unknown_fields)]
30#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
33)]
34#[cfg_attr(
35 feature = "python",
36 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.coinbase")
37)]
38pub struct CoinbaseDataClientConfig {
39 pub api_key: Option<String>,
41 pub api_secret: Option<String>,
43 pub base_url_rest: Option<String>,
45 pub base_url_ws: Option<String>,
47 pub proxy_url: Option<String>,
49 #[builder(default)]
51 pub environment: CoinbaseEnvironment,
52 #[builder(default = 10)]
54 pub http_timeout_secs: u64,
55 #[builder(default = 30)]
57 pub ws_timeout_secs: u64,
58 #[builder(default = 60)]
60 pub update_instruments_interval_mins: u64,
61 #[builder(default = 15)]
66 pub derivatives_poll_interval_secs: u64,
67 #[builder(default)]
69 pub transport_backend: TransportBackend,
70}
71
72impl Default for CoinbaseDataClientConfig {
73 fn default() -> Self {
74 Self::builder().build()
75 }
76}
77
78impl CoinbaseDataClientConfig {
79 #[must_use]
81 pub fn new() -> Self {
82 Self::default()
83 }
84
85 #[must_use]
87 pub fn has_credentials(&self) -> bool {
88 self.api_key
89 .as_deref()
90 .is_some_and(|s| !s.trim().is_empty())
91 && self
92 .api_secret
93 .as_deref()
94 .is_some_and(|s| !s.trim().is_empty())
95 }
96
97 #[must_use]
99 pub fn rest_url(&self) -> String {
100 self.base_url_rest
101 .clone()
102 .unwrap_or_else(|| urls::rest_url(self.environment).to_string())
103 }
104
105 #[must_use]
107 pub fn ws_url(&self) -> String {
108 self.base_url_ws
109 .clone()
110 .unwrap_or_else(|| urls::ws_url(self.environment).to_string())
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
116#[serde(default, deny_unknown_fields)]
117#[cfg_attr(
118 feature = "python",
119 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
120)]
121#[cfg_attr(
122 feature = "python",
123 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.coinbase")
124)]
125pub struct CoinbaseExecClientConfig {
126 pub api_key: Option<String>,
128 pub api_secret: Option<String>,
130 pub base_url_rest: Option<String>,
132 pub base_url_ws: Option<String>,
134 pub proxy_url: Option<String>,
136 #[builder(default)]
138 pub environment: CoinbaseEnvironment,
139 #[builder(default = 10)]
141 pub http_timeout_secs: u64,
142 #[builder(default = 3)]
144 pub max_retries: u32,
145 #[builder(default = 100)]
147 pub retry_delay_initial_ms: u64,
148 #[builder(default = 5000)]
150 pub retry_delay_max_ms: u64,
151 #[builder(default = AccountType::Cash)]
154 pub account_type: AccountType,
155 pub default_margin_type: Option<CoinbaseMarginType>,
158 pub default_leverage: Option<rust_decimal::Decimal>,
161 pub retail_portfolio_id: Option<String>,
166 #[builder(default)]
168 pub transport_backend: TransportBackend,
169}
170
171impl Default for CoinbaseExecClientConfig {
172 fn default() -> Self {
173 Self::builder().build()
174 }
175}
176
177impl CoinbaseExecClientConfig {
178 #[must_use]
180 pub fn new() -> Self {
181 Self::default()
182 }
183
184 #[must_use]
186 pub fn has_credentials(&self) -> bool {
187 self.api_key
188 .as_deref()
189 .is_some_and(|s| !s.trim().is_empty())
190 && self
191 .api_secret
192 .as_deref()
193 .is_some_and(|s| !s.trim().is_empty())
194 }
195
196 #[must_use]
198 pub fn rest_url(&self) -> String {
199 self.base_url_rest
200 .clone()
201 .unwrap_or_else(|| urls::rest_url(self.environment).to_string())
202 }
203
204 #[must_use]
206 pub fn ws_url(&self) -> String {
207 self.base_url_ws
208 .clone()
209 .unwrap_or_else(|| urls::ws_user_url(self.environment).to_string())
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use rstest::rstest;
216
217 use super::*;
218
219 #[rstest]
220 fn test_data_config_defaults() {
221 let config = CoinbaseDataClientConfig::default();
222 assert_eq!(config.environment, CoinbaseEnvironment::Live);
223 assert_eq!(config.http_timeout_secs, 10);
224 assert_eq!(config.ws_timeout_secs, 30);
225 assert_eq!(config.update_instruments_interval_mins, 60);
226 assert!(!config.has_credentials());
227 }
228
229 #[rstest]
230 fn test_data_config_has_credentials() {
231 let config = CoinbaseDataClientConfig {
232 api_key: Some("key".to_string()),
233 api_secret: Some("secret".to_string()),
234 ..CoinbaseDataClientConfig::default()
235 };
236 assert!(config.has_credentials());
237 }
238
239 #[rstest]
240 fn test_data_config_empty_credentials() {
241 let config = CoinbaseDataClientConfig {
242 api_key: Some(" ".to_string()),
243 api_secret: Some("secret".to_string()),
244 ..CoinbaseDataClientConfig::default()
245 };
246 assert!(!config.has_credentials());
247 }
248
249 #[rstest]
250 fn test_data_config_urls_live() {
251 let config = CoinbaseDataClientConfig::default();
252 assert!(config.rest_url().contains("api.coinbase.com"));
253 assert!(config.ws_url().contains("advanced-trade-ws.coinbase.com"));
254 }
255
256 #[rstest]
257 fn test_data_config_urls_sandbox() {
258 let config = CoinbaseDataClientConfig {
259 environment: CoinbaseEnvironment::Sandbox,
260 ..CoinbaseDataClientConfig::default()
261 };
262 assert!(config.rest_url().contains("sandbox"));
263 assert!(config.ws_url().contains("sandbox"));
264 }
265
266 #[rstest]
267 fn test_exec_config_defaults() {
268 let config = CoinbaseExecClientConfig::default();
269 assert_eq!(config.environment, CoinbaseEnvironment::Live);
270 assert_eq!(config.http_timeout_secs, 10);
271 assert_eq!(config.max_retries, 3);
272 }
273
274 #[rstest]
275 fn test_exec_config_ws_url_uses_user_endpoint() {
276 let config = CoinbaseExecClientConfig::default();
277 assert!(config.ws_url().contains("user"));
278 }
279
280 #[rstest]
281 fn test_data_config_toml_minimal() {
282 let config: CoinbaseDataClientConfig = toml::from_str(
283 r#"
284environment = "Sandbox"
285http_timeout_secs = 5
286update_instruments_interval_mins = 30
287derivatives_poll_interval_secs = 60
288"#,
289 )
290 .unwrap();
291
292 assert_eq!(config.environment, CoinbaseEnvironment::Sandbox);
293 assert_eq!(config.http_timeout_secs, 5);
294 assert_eq!(config.update_instruments_interval_mins, 30);
295 assert_eq!(config.derivatives_poll_interval_secs, 60);
296 }
297
298 #[rstest]
299 fn test_exec_config_toml_empty_uses_defaults() {
300 let config: CoinbaseExecClientConfig = toml::from_str("").unwrap();
301 let expected = CoinbaseExecClientConfig::default();
302
303 assert_eq!(config.environment, expected.environment);
304 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
305 assert_eq!(config.max_retries, expected.max_retries);
306 assert_eq!(config.account_type, expected.account_type);
307 assert_eq!(config.transport_backend, expected.transport_backend);
308 }
309}