Skip to main content

nautilus_deribit/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration structures for the Deribit adapter.
17
18#[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/// Configuration for the Deribit data client.
35#[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    /// Optional API key for authenticated endpoints.
47    pub api_key: Option<SecretString>,
48    /// Optional API secret for authenticated endpoints.
49    pub api_secret: Option<SecretString>,
50    /// Product types to load (e.g., Future, Option, Spot).
51    #[builder(default = vec![DeribitProductType::Future])]
52    pub product_types: Vec<DeribitProductType>,
53    /// The Deribit environment (mainnet or testnet).
54    #[builder(default)]
55    pub environment: DeribitEnvironment,
56    /// Optional override for the HTTP base URL.
57    pub base_url_http: Option<String>,
58    /// Optional override for the WebSocket URL.
59    pub base_url_ws: Option<String>,
60    /// Optional proxy URL for HTTP and WebSocket transports.
61    pub proxy_url: Option<SecretString>,
62    /// HTTP timeout in seconds.
63    #[builder(default = 60)]
64    pub http_timeout_secs: u64,
65    /// Maximum retry attempts for requests.
66    #[builder(default = 3)]
67    pub max_retries: u32,
68    /// Initial retry delay in milliseconds.
69    #[builder(default = 1_000)]
70    pub retry_delay_initial_ms: u64,
71    /// Maximum retry delay in milliseconds.
72    #[builder(default = 10_000)]
73    pub retry_delay_max_ms: u64,
74    /// Heartbeat interval in seconds for WebSocket connection.
75    #[builder(default = 30)]
76    pub heartbeat_interval_secs: u64,
77    /// Optional WebSocket authentication timeout (seconds), defaulting to
78    /// `AUTHENTICATION_TIMEOUT_SECS` when unset.
79    pub auth_timeout_secs: Option<u64>,
80    /// Interval for refreshing instruments (in minutes).
81    #[builder(default = 60)]
82    pub update_instruments_interval_mins: u64,
83    /// If `true`, subscribes for uncached instruments lazy-load via HTTP; otherwise fail fast.
84    #[builder(default = false)]
85    pub auto_load_missing_instruments: bool,
86    /// WebSocket transport backend (defaults to `Tungstenite`).
87    #[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    /// Creates a new configuration with default settings.
116    #[must_use]
117    pub fn new() -> Self {
118        Self::default()
119    }
120
121    /// Returns `true` when API credentials are available (in config or env vars).
122    #[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    /// Returns the HTTP base URL, falling back to the default when unset.
131    #[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    /// Returns the WebSocket URL, respecting the environment and overrides.
139    #[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/// Configuration for the Deribit execution client.
148#[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    /// The account ID for this client.
160    #[builder(default = AccountId::from("DERIBIT-001"))]
161    pub account_id: AccountId,
162    /// Optional API key for authenticated endpoints.
163    pub api_key: Option<SecretString>,
164    /// Optional API secret for authenticated endpoints.
165    pub api_secret: Option<SecretString>,
166    /// Product types to load (e.g., Future, Option, Spot).
167    #[builder(default = vec![DeribitProductType::Future])]
168    pub product_types: Vec<DeribitProductType>,
169    /// The Deribit environment (mainnet or testnet).
170    #[builder(default)]
171    pub environment: DeribitEnvironment,
172    /// Optional override for the HTTP base URL.
173    pub base_url_http: Option<String>,
174    /// Optional override for the WebSocket URL.
175    pub base_url_ws: Option<String>,
176    /// Optional proxy URL for HTTP and WebSocket transports.
177    pub proxy_url: Option<SecretString>,
178    /// HTTP timeout in seconds.
179    #[builder(default = 60)]
180    pub http_timeout_secs: u64,
181    /// Maximum retry attempts for requests.
182    #[builder(default = 3)]
183    pub max_retries: u32,
184    /// Initial retry delay in milliseconds.
185    #[builder(default = 1_000)]
186    pub retry_delay_initial_ms: u64,
187    /// Maximum retry delay in milliseconds.
188    #[builder(default = 10_000)]
189    pub retry_delay_max_ms: u64,
190    /// Optional WebSocket authentication timeout (seconds), defaulting to
191    /// `AUTHENTICATION_TIMEOUT_SECS` when unset.
192    pub auth_timeout_secs: Option<u64>,
193    /// WebSocket transport backend (defaults to `Tungstenite`).
194    #[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    /// Returns `true` when API credentials are available (in config or env vars).
221    #[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    /// Returns the HTTP base URL, falling back to the default when unset.
230    #[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    /// Returns the WebSocket URL, respecting the environment and overrides.
238    #[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}