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