nautilus_coinbase/
config.rs1use nautilus_model::enums::AccountType;
19use nautilus_network::websocket::TransportBackend;
20
21use crate::common::{
22 enums::{CoinbaseEnvironment, CoinbaseMarginType},
23 urls,
24};
25
26#[derive(Clone, Debug, bon::Builder)]
28#[cfg_attr(
29 feature = "python",
30 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
31)]
32#[cfg_attr(
33 feature = "python",
34 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.coinbase")
35)]
36pub struct CoinbaseDataClientConfig {
37 pub api_key: Option<String>,
39 pub api_secret: Option<String>,
41 pub base_url_rest: Option<String>,
43 pub base_url_ws: Option<String>,
45 pub proxy_url: Option<String>,
47 #[builder(default)]
49 pub environment: CoinbaseEnvironment,
50 #[builder(default = 10)]
52 pub http_timeout_secs: u64,
53 #[builder(default = 30)]
55 pub ws_timeout_secs: u64,
56 #[builder(default = 60)]
58 pub update_instruments_interval_mins: u64,
59 #[builder(default = 15)]
64 pub derivatives_poll_interval_secs: u64,
65 #[builder(default)]
67 pub transport_backend: TransportBackend,
68}
69
70impl Default for CoinbaseDataClientConfig {
71 fn default() -> Self {
72 Self::builder().build()
73 }
74}
75
76impl CoinbaseDataClientConfig {
77 #[must_use]
79 pub fn new() -> Self {
80 Self::default()
81 }
82
83 #[must_use]
85 pub fn has_credentials(&self) -> bool {
86 self.api_key
87 .as_deref()
88 .is_some_and(|s| !s.trim().is_empty())
89 && self
90 .api_secret
91 .as_deref()
92 .is_some_and(|s| !s.trim().is_empty())
93 }
94
95 #[must_use]
97 pub fn rest_url(&self) -> String {
98 self.base_url_rest
99 .clone()
100 .unwrap_or_else(|| urls::rest_url(self.environment).to_string())
101 }
102
103 #[must_use]
105 pub fn ws_url(&self) -> String {
106 self.base_url_ws
107 .clone()
108 .unwrap_or_else(|| urls::ws_url(self.environment).to_string())
109 }
110}
111
112#[derive(Clone, Debug, bon::Builder)]
114#[cfg_attr(
115 feature = "python",
116 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.coinbase", from_py_object)
117)]
118#[cfg_attr(
119 feature = "python",
120 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.coinbase")
121)]
122pub struct CoinbaseExecClientConfig {
123 pub api_key: Option<String>,
125 pub api_secret: Option<String>,
127 pub base_url_rest: Option<String>,
129 pub base_url_ws: Option<String>,
131 pub proxy_url: Option<String>,
133 #[builder(default)]
135 pub environment: CoinbaseEnvironment,
136 #[builder(default = 10)]
138 pub http_timeout_secs: u64,
139 #[builder(default = 3)]
141 pub max_retries: u32,
142 #[builder(default = 100)]
144 pub retry_delay_initial_ms: u64,
145 #[builder(default = 5000)]
147 pub retry_delay_max_ms: u64,
148 #[builder(default = AccountType::Cash)]
151 pub account_type: AccountType,
152 pub default_margin_type: Option<CoinbaseMarginType>,
155 pub default_leverage: Option<rust_decimal::Decimal>,
158 pub retail_portfolio_id: Option<String>,
163 #[builder(default)]
165 pub transport_backend: TransportBackend,
166}
167
168impl Default for CoinbaseExecClientConfig {
169 fn default() -> Self {
170 Self::builder().build()
171 }
172}
173
174impl CoinbaseExecClientConfig {
175 #[must_use]
177 pub fn new() -> Self {
178 Self::default()
179 }
180
181 #[must_use]
183 pub fn has_credentials(&self) -> bool {
184 self.api_key
185 .as_deref()
186 .is_some_and(|s| !s.trim().is_empty())
187 && self
188 .api_secret
189 .as_deref()
190 .is_some_and(|s| !s.trim().is_empty())
191 }
192
193 #[must_use]
195 pub fn rest_url(&self) -> String {
196 self.base_url_rest
197 .clone()
198 .unwrap_or_else(|| urls::rest_url(self.environment).to_string())
199 }
200
201 #[must_use]
203 pub fn ws_url(&self) -> String {
204 self.base_url_ws
205 .clone()
206 .unwrap_or_else(|| urls::ws_user_url(self.environment).to_string())
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use rstest::rstest;
213
214 use super::*;
215
216 #[rstest]
217 fn test_data_config_defaults() {
218 let config = CoinbaseDataClientConfig::default();
219 assert_eq!(config.environment, CoinbaseEnvironment::Live);
220 assert_eq!(config.http_timeout_secs, 10);
221 assert_eq!(config.ws_timeout_secs, 30);
222 assert_eq!(config.update_instruments_interval_mins, 60);
223 assert!(!config.has_credentials());
224 }
225
226 #[rstest]
227 fn test_data_config_has_credentials() {
228 let config = CoinbaseDataClientConfig {
229 api_key: Some("key".to_string()),
230 api_secret: Some("secret".to_string()),
231 ..CoinbaseDataClientConfig::default()
232 };
233 assert!(config.has_credentials());
234 }
235
236 #[rstest]
237 fn test_data_config_empty_credentials() {
238 let config = CoinbaseDataClientConfig {
239 api_key: Some(" ".to_string()),
240 api_secret: Some("secret".to_string()),
241 ..CoinbaseDataClientConfig::default()
242 };
243 assert!(!config.has_credentials());
244 }
245
246 #[rstest]
247 fn test_data_config_urls_live() {
248 let config = CoinbaseDataClientConfig::default();
249 assert!(config.rest_url().contains("api.coinbase.com"));
250 assert!(config.ws_url().contains("advanced-trade-ws.coinbase.com"));
251 }
252
253 #[rstest]
254 fn test_data_config_urls_sandbox() {
255 let config = CoinbaseDataClientConfig {
256 environment: CoinbaseEnvironment::Sandbox,
257 ..CoinbaseDataClientConfig::default()
258 };
259 assert!(config.rest_url().contains("sandbox"));
260 assert!(config.ws_url().contains("sandbox"));
261 }
262
263 #[rstest]
264 fn test_exec_config_defaults() {
265 let config = CoinbaseExecClientConfig::default();
266 assert_eq!(config.environment, CoinbaseEnvironment::Live);
267 assert_eq!(config.http_timeout_secs, 10);
268 assert_eq!(config.max_retries, 3);
269 }
270
271 #[rstest]
272 fn test_exec_config_ws_url_uses_user_endpoint() {
273 let config = CoinbaseExecClientConfig::default();
274 assert!(config.ws_url().contains("user"));
275 }
276}