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
18use nautilus_model::identifiers::{AccountId, TraderId};
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/// Configuration for the Deribit data client.
32#[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.core.nautilus_pyo3.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    /// Optional API key for authenticated endpoints.
44    pub api_key: Option<String>,
45    /// Optional API secret for authenticated endpoints.
46    pub api_secret: Option<String>,
47    /// Product types to load (e.g., Future, Option, Spot).
48    #[builder(default = vec![DeribitProductType::Future])]
49    pub product_types: Vec<DeribitProductType>,
50    /// Optional override for the HTTP base URL.
51    pub base_url_http: Option<String>,
52    /// Optional override for the WebSocket URL.
53    pub base_url_ws: Option<String>,
54    /// Optional proxy URL for HTTP and WebSocket transports.
55    pub proxy_url: Option<String>,
56    /// The Deribit environment (mainnet or testnet).
57    #[builder(default)]
58    pub environment: DeribitEnvironment,
59    /// HTTP timeout in seconds.
60    #[builder(default = 60)]
61    pub http_timeout_secs: u64,
62    /// Maximum retry attempts for requests.
63    #[builder(default = 3)]
64    pub max_retries: u32,
65    /// Initial retry delay in milliseconds.
66    #[builder(default = 1_000)]
67    pub retry_delay_initial_ms: u64,
68    /// Maximum retry delay in milliseconds.
69    #[builder(default = 10_000)]
70    pub retry_delay_max_ms: u64,
71    /// Heartbeat interval in seconds for WebSocket connection.
72    #[builder(default = 30)]
73    pub heartbeat_interval_secs: u64,
74    /// Interval for refreshing instruments (in minutes).
75    #[builder(default = 60)]
76    pub update_instruments_interval_mins: u64,
77    /// If `true`, subscribes for uncached instruments lazy-load via HTTP; otherwise fail fast.
78    #[builder(default = false)]
79    pub auto_load_missing_instruments: bool,
80    /// WebSocket transport backend (defaults to `Tungstenite`).
81    #[builder(default)]
82    pub transport_backend: TransportBackend,
83}
84
85impl Default for DeribitDataClientConfig {
86    fn default() -> Self {
87        Self::builder().build()
88    }
89}
90
91impl DeribitDataClientConfig {
92    /// Creates a new configuration with default settings.
93    #[must_use]
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Returns `true` when API credentials are available (in config or env vars).
99    #[must_use]
100    pub fn has_api_credentials(&self) -> bool {
101        let (key_env, secret_env) = credential_env_vars(self.environment);
102        let has_key = self.api_key.is_some() || std::env::var(key_env).is_ok();
103        let has_secret = self.api_secret.is_some() || std::env::var(secret_env).is_ok();
104        has_key && has_secret
105    }
106
107    /// Returns the HTTP base URL, falling back to the default when unset.
108    #[must_use]
109    pub fn http_base_url(&self) -> String {
110        self.base_url_http
111            .clone()
112            .unwrap_or_else(|| get_http_base_url(self.environment).to_string())
113    }
114
115    /// Returns the WebSocket URL, respecting the environment and overrides.
116    #[must_use]
117    pub fn ws_url(&self) -> String {
118        self.base_url_ws
119            .clone()
120            .unwrap_or_else(|| get_ws_url(self.environment).to_string())
121    }
122}
123
124/// Configuration for the Deribit execution client.
125#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
126#[serde(default, deny_unknown_fields)]
127#[cfg_attr(
128    feature = "python",
129    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.deribit", from_py_object)
130)]
131#[cfg_attr(
132    feature = "python",
133    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.deribit")
134)]
135pub struct DeribitExecClientConfig {
136    /// The trader ID for this client.
137    #[builder(default)]
138    pub trader_id: TraderId,
139    /// The account ID for this client.
140    #[builder(default = AccountId::from("DERIBIT-001"))]
141    pub account_id: AccountId,
142    /// Optional API key for authenticated endpoints.
143    pub api_key: Option<String>,
144    /// Optional API secret for authenticated endpoints.
145    pub api_secret: Option<String>,
146    /// Product types to load (e.g., Future, Option, Spot).
147    #[builder(default = vec![DeribitProductType::Future])]
148    pub product_types: Vec<DeribitProductType>,
149    /// Optional override for the HTTP base URL.
150    pub base_url_http: Option<String>,
151    /// Optional override for the WebSocket URL.
152    pub base_url_ws: Option<String>,
153    /// Optional proxy URL for HTTP and WebSocket transports.
154    pub proxy_url: Option<String>,
155    /// The Deribit environment (mainnet or testnet).
156    #[builder(default)]
157    pub environment: DeribitEnvironment,
158    /// HTTP timeout in seconds.
159    #[builder(default = 60)]
160    pub http_timeout_secs: u64,
161    /// Maximum retry attempts for requests.
162    #[builder(default = 3)]
163    pub max_retries: u32,
164    /// Initial retry delay in milliseconds.
165    #[builder(default = 1_000)]
166    pub retry_delay_initial_ms: u64,
167    /// Maximum retry delay in milliseconds.
168    #[builder(default = 10_000)]
169    pub retry_delay_max_ms: u64,
170    /// WebSocket transport backend (defaults to `Tungstenite`).
171    #[builder(default)]
172    pub transport_backend: TransportBackend,
173}
174
175impl Default for DeribitExecClientConfig {
176    fn default() -> Self {
177        Self::builder().build()
178    }
179}
180
181impl DeribitExecClientConfig {
182    /// Returns `true` when API credentials are available (in config or env vars).
183    #[must_use]
184    pub fn has_api_credentials(&self) -> bool {
185        let (key_env, secret_env) = credential_env_vars(self.environment);
186        let has_key = self.api_key.is_some() || std::env::var(key_env).is_ok();
187        let has_secret = self.api_secret.is_some() || std::env::var(secret_env).is_ok();
188        has_key && has_secret
189    }
190
191    /// Returns the HTTP base URL, falling back to the default when unset.
192    #[must_use]
193    pub fn http_base_url(&self) -> String {
194        self.base_url_http
195            .clone()
196            .unwrap_or_else(|| get_http_base_url(self.environment).to_string())
197    }
198
199    /// Returns the WebSocket URL, respecting the environment and overrides.
200    #[must_use]
201    pub fn ws_url(&self) -> String {
202        self.base_url_ws
203            .clone()
204            .unwrap_or_else(|| get_ws_url(self.environment).to_string())
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use rstest::rstest;
211
212    use super::*;
213
214    #[rstest]
215    fn test_default_config() {
216        let config = DeribitDataClientConfig::default();
217        assert_eq!(config.environment, DeribitEnvironment::Mainnet);
218        assert_eq!(config.product_types.len(), 1);
219        assert_eq!(config.http_timeout_secs, 60);
220    }
221
222    #[rstest]
223    fn test_http_base_url_default() {
224        let config = DeribitDataClientConfig::default();
225        assert_eq!(config.http_base_url(), "https://www.deribit.com");
226    }
227
228    #[rstest]
229    fn test_http_base_url_testnet() {
230        let config = DeribitDataClientConfig {
231            environment: DeribitEnvironment::Testnet,
232            ..Default::default()
233        };
234        assert_eq!(config.http_base_url(), "https://test.deribit.com");
235    }
236
237    #[rstest]
238    fn test_ws_url_default() {
239        let config = DeribitDataClientConfig::default();
240        assert_eq!(config.ws_url(), "wss://www.deribit.com/ws/api/v2");
241    }
242
243    #[rstest]
244    fn test_ws_url_testnet() {
245        let config = DeribitDataClientConfig {
246            environment: DeribitEnvironment::Testnet,
247            ..Default::default()
248        };
249        assert_eq!(config.ws_url(), "wss://test.deribit.com/ws/api/v2");
250    }
251
252    #[rstest]
253    fn test_has_api_credentials_in_config() {
254        let config = DeribitDataClientConfig {
255            api_key: Some("test_key".to_string()),
256            api_secret: Some("test_secret".to_string()),
257            ..Default::default()
258        };
259        assert!(config.has_api_credentials());
260    }
261
262    #[rstest]
263    fn test_data_config_toml_minimal() {
264        let config: DeribitDataClientConfig = toml::from_str(
265            r#"
266environment = "testnet"
267product_types = ["future", "option"]
268heartbeat_interval_secs = 15
269auto_load_missing_instruments = true
270"#,
271        )
272        .unwrap();
273
274        assert_eq!(config.environment, DeribitEnvironment::Testnet);
275        assert_eq!(
276            config.product_types,
277            vec![DeribitProductType::Future, DeribitProductType::Option]
278        );
279        assert_eq!(config.heartbeat_interval_secs, 15);
280        assert!(config.auto_load_missing_instruments);
281    }
282
283    #[rstest]
284    fn test_exec_config_toml_empty_uses_defaults() {
285        let config: DeribitExecClientConfig = toml::from_str("").unwrap();
286        let expected = DeribitExecClientConfig::default();
287
288        assert_eq!(config.trader_id, expected.trader_id);
289        assert_eq!(config.account_id, expected.account_id);
290        assert_eq!(config.environment, expected.environment);
291        assert_eq!(config.product_types, expected.product_types);
292        assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
293        assert_eq!(config.max_retries, expected.max_retries);
294        assert_eq!(config.transport_backend, expected.transport_backend);
295    }
296}