1#[cfg(test)]
19use nautilus_core::string::secret::REDACTED;
20use nautilus_core::string::secret::SecretString;
21use nautilus_model::identifiers::AccountId;
22use nautilus_network::websocket::TransportBackend;
23use serde::{Deserialize, Serialize};
24
25use crate::{
26 common::{
27 credential::credential_env_vars,
28 enums::DeribitEnvironment,
29 urls::{get_http_base_url, get_ws_url},
30 },
31 http::models::DeribitProductType,
32};
33
34#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
36#[serde(default, deny_unknown_fields)]
37#[cfg_attr(
38 feature = "python",
39 pyo3::pyclass(module = "nautilus_trader.adapters.deribit", from_py_object)
40)]
41#[cfg_attr(
42 feature = "python",
43 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.deribit")
44)]
45pub struct DeribitDataClientConfig {
46 pub api_key: Option<SecretString>,
48 pub api_secret: Option<SecretString>,
50 #[builder(default = vec![DeribitProductType::Future])]
52 pub product_types: Vec<DeribitProductType>,
53 #[builder(default)]
55 pub environment: DeribitEnvironment,
56 pub base_url_http: Option<String>,
58 pub base_url_ws: Option<String>,
60 pub proxy_url: Option<SecretString>,
62 #[builder(default = 60)]
64 pub http_timeout_secs: u64,
65 #[builder(default = 3)]
67 pub max_retries: u32,
68 #[builder(default = 1_000)]
70 pub retry_delay_initial_ms: u64,
71 #[builder(default = 10_000)]
73 pub retry_delay_max_ms: u64,
74 #[builder(default = 30)]
76 pub heartbeat_interval_secs: u64,
77 pub auth_timeout_secs: Option<u64>,
80 #[builder(default = 60)]
82 pub update_instruments_interval_mins: u64,
83 #[builder(default = false)]
85 pub auto_load_missing_instruments: bool,
86 #[builder(default)]
88 pub transport_backend: TransportBackend,
89}
90
91#[cfg(feature = "python")]
92nautilus_core::impl_pyo3_config_getters!(DeribitDataClientConfig {
93 product_types: Vec<DeribitProductType>,
94 environment: DeribitEnvironment,
95 base_url_http: Option<String>,
96 base_url_ws: Option<String>,
97 http_timeout_secs: u64,
98 max_retries: u32,
99 retry_delay_initial_ms: u64,
100 retry_delay_max_ms: u64,
101 heartbeat_interval_secs: u64,
102 auth_timeout_secs: Option<u64>,
103 update_instruments_interval_mins: u64,
104 auto_load_missing_instruments: bool,
105 transport_backend: TransportBackend,
106});
107
108impl Default for DeribitDataClientConfig {
109 fn default() -> Self {
110 Self::builder().build()
111 }
112}
113
114impl DeribitDataClientConfig {
115 #[must_use]
117 pub fn new() -> Self {
118 Self::default()
119 }
120
121 #[must_use]
123 pub fn has_api_credentials(&self) -> bool {
124 let (key_env, secret_env) = credential_env_vars(self.environment);
125 let has_key = self.api_key.is_some() || std::env::var(key_env).is_ok();
126 let has_secret = self.api_secret.is_some() || std::env::var(secret_env).is_ok();
127 has_key && has_secret
128 }
129
130 #[must_use]
132 pub fn http_base_url(&self) -> String {
133 self.base_url_http
134 .clone()
135 .unwrap_or_else(|| get_http_base_url(self.environment).to_string())
136 }
137
138 #[must_use]
140 pub fn ws_url(&self) -> String {
141 self.base_url_ws
142 .clone()
143 .unwrap_or_else(|| get_ws_url(self.environment).to_string())
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
149#[serde(default, deny_unknown_fields)]
150#[cfg_attr(
151 feature = "python",
152 pyo3::pyclass(module = "nautilus_trader.adapters.deribit", from_py_object)
153)]
154#[cfg_attr(
155 feature = "python",
156 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.deribit")
157)]
158pub struct DeribitExecutionClientConfig {
159 #[builder(default = AccountId::from("DERIBIT-001"))]
161 pub account_id: AccountId,
162 pub api_key: Option<SecretString>,
164 pub api_secret: Option<SecretString>,
166 #[builder(default = vec![DeribitProductType::Future])]
168 pub product_types: Vec<DeribitProductType>,
169 #[builder(default)]
171 pub environment: DeribitEnvironment,
172 pub base_url_http: Option<String>,
174 pub base_url_ws: Option<String>,
176 pub proxy_url: Option<SecretString>,
178 #[builder(default = 60)]
180 pub http_timeout_secs: u64,
181 #[builder(default = 3)]
183 pub max_retries: u32,
184 #[builder(default = 1_000)]
186 pub retry_delay_initial_ms: u64,
187 #[builder(default = 10_000)]
189 pub retry_delay_max_ms: u64,
190 pub auth_timeout_secs: Option<u64>,
193 #[builder(default)]
195 pub transport_backend: TransportBackend,
196}
197
198#[cfg(feature = "python")]
199nautilus_core::impl_pyo3_config_getters!(DeribitExecutionClientConfig {
200 account_id: AccountId,
201 product_types: Vec<DeribitProductType>,
202 environment: DeribitEnvironment,
203 base_url_http: Option<String>,
204 base_url_ws: Option<String>,
205 http_timeout_secs: u64,
206 max_retries: u32,
207 retry_delay_initial_ms: u64,
208 retry_delay_max_ms: u64,
209 auth_timeout_secs: Option<u64>,
210 transport_backend: TransportBackend,
211});
212
213impl Default for DeribitExecutionClientConfig {
214 fn default() -> Self {
215 Self::builder().build()
216 }
217}
218
219impl DeribitExecutionClientConfig {
220 #[must_use]
222 pub fn has_api_credentials(&self) -> bool {
223 let (key_env, secret_env) = credential_env_vars(self.environment);
224 let has_key = self.api_key.is_some() || std::env::var(key_env).is_ok();
225 let has_secret = self.api_secret.is_some() || std::env::var(secret_env).is_ok();
226 has_key && has_secret
227 }
228
229 #[must_use]
231 pub fn http_base_url(&self) -> String {
232 self.base_url_http
233 .clone()
234 .unwrap_or_else(|| get_http_base_url(self.environment).to_string())
235 }
236
237 #[must_use]
239 pub fn ws_url(&self) -> String {
240 self.base_url_ws
241 .clone()
242 .unwrap_or_else(|| get_ws_url(self.environment).to_string())
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use rstest::rstest;
249
250 use super::*;
251
252 #[rstest]
253 fn test_config_debug_redacts_credentials() {
254 let data = DeribitDataClientConfig {
255 api_key: Some("data-api-key".into()),
256 api_secret: Some("data-api-secret".into()),
257 proxy_url: Some("http://user:data-proxy@localhost".into()),
258 ..Default::default()
259 };
260 let execution = DeribitExecutionClientConfig {
261 api_key: Some("exec-api-key".into()),
262 api_secret: Some("exec-api-secret".into()),
263 proxy_url: Some("http://user:exec-proxy@localhost".into()),
264 ..Default::default()
265 };
266
267 let formatted = format!("{data:?} {execution:?}");
268
269 assert_eq!(formatted.matches(REDACTED).count(), 6);
270
271 for secret in [
272 "data-api-key",
273 "data-api-secret",
274 "data-proxy",
275 "exec-api-key",
276 "exec-api-secret",
277 "exec-proxy",
278 ] {
279 assert!(!formatted.contains(secret));
280 }
281 }
282
283 #[rstest]
284 fn test_default_config() {
285 let config = DeribitDataClientConfig::default();
286 assert_eq!(config.environment, DeribitEnvironment::Mainnet);
287 assert_eq!(config.product_types.len(), 1);
288 assert_eq!(config.http_timeout_secs, 60);
289 }
290
291 #[rstest]
292 fn test_http_base_url_default() {
293 let config = DeribitDataClientConfig::default();
294 assert_eq!(config.http_base_url(), "https://www.deribit.com");
295 }
296
297 #[rstest]
298 fn test_http_base_url_testnet() {
299 let config = DeribitDataClientConfig {
300 environment: DeribitEnvironment::Testnet,
301 ..Default::default()
302 };
303 assert_eq!(config.http_base_url(), "https://test.deribit.com");
304 }
305
306 #[rstest]
307 fn test_ws_url_default() {
308 let config = DeribitDataClientConfig::default();
309 assert_eq!(config.ws_url(), "wss://www.deribit.com/ws/api/v2");
310 }
311
312 #[rstest]
313 fn test_ws_url_testnet() {
314 let config = DeribitDataClientConfig {
315 environment: DeribitEnvironment::Testnet,
316 ..Default::default()
317 };
318 assert_eq!(config.ws_url(), "wss://test.deribit.com/ws/api/v2");
319 }
320
321 #[rstest]
322 fn test_has_api_credentials_in_config() {
323 let config = DeribitDataClientConfig {
324 api_key: Some("test_key".into()),
325 api_secret: Some("test_secret".into()),
326 ..Default::default()
327 };
328 assert!(config.has_api_credentials());
329 }
330
331 #[rstest]
332 fn test_data_config_toml_minimal() {
333 let config: DeribitDataClientConfig = toml::from_str(
334 r#"
335environment = "testnet"
336product_types = ["future", "option"]
337heartbeat_interval_secs = 15
338auto_load_missing_instruments = true
339"#,
340 )
341 .unwrap();
342
343 assert_eq!(config.environment, DeribitEnvironment::Testnet);
344 assert_eq!(
345 config.product_types,
346 vec![DeribitProductType::Future, DeribitProductType::Option]
347 );
348 assert_eq!(config.heartbeat_interval_secs, 15);
349 assert!(config.auto_load_missing_instruments);
350 }
351
352 #[rstest]
353 fn test_exec_config_toml_empty_uses_defaults() {
354 let config: DeribitExecutionClientConfig = toml::from_str("").unwrap();
355 let expected = DeribitExecutionClientConfig::default();
356 assert_eq!(config.account_id, expected.account_id);
357 assert_eq!(config.environment, expected.environment);
358 assert_eq!(config.product_types, expected.product_types);
359 assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
360 assert_eq!(config.max_retries, expected.max_retries);
361 assert_eq!(config.transport_backend, expected.transport_backend);
362 }
363}