deribit_fix/config/
base.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6
7use crate::config::utils::{get_env_optional, get_env_or_default};
8use crate::constants::{
9    DEFAULT_CONNECTION_TIMEOUT_SECS, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_LOG_LEVEL,
10    DEFAULT_PROD_HOST, DEFAULT_PROD_PORT, DEFAULT_RECONNECT_ATTEMPTS, DEFAULT_RECONNECT_DELAY_SECS,
11    DEFAULT_SENDER_COMP_ID, DEFAULT_SSL_PORT, DEFAULT_TARGET_COMP_ID, DEFAULT_TEST_HOST,
12    DEFAULT_TEST_PORT,
13};
14use crate::error::{DeribitFixError, Result};
15use dotenv::dotenv;
16use serde::{Deserialize, Serialize};
17use std::{fmt::Debug, time::Duration};
18use tracing::debug;
19
20/// Configuration for the Deribit FIX client
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct DeribitFixConfig {
23    /// Deribit username
24    pub username: String,
25    /// Deribit password
26    pub password: String,
27    /// FIX server host (default: www.deribit.com for production, test.deribit.com for test)
28    pub host: String,
29    /// FIX server port (default: 9881 for test, 9880 for production)
30    pub port: u16,
31    /// Whether to use SSL connection (default: false for raw TCP)
32    pub use_ssl: bool,
33    /// Whether to use test environment
34    pub test_mode: bool,
35    /// Heartbeat interval in seconds (default: 30)
36    pub heartbeat_interval: u32,
37    /// Connection timeout in seconds (default: 10)
38    pub connection_timeout: Duration,
39    /// Reconnection attempts (default: 3)
40    pub reconnect_attempts: u32,
41    /// Reconnection delay in seconds (default: 5)
42    pub reconnect_delay: Duration,
43    /// Enable logging (default: true)
44    pub enable_logging: bool,
45    /// Log level filter
46    pub log_level: String,
47    /// Sender company ID for FIX messages
48    pub sender_comp_id: String,
49    /// Target company ID for FIX messages (DERIBITSERVER)
50    pub target_comp_id: String,
51    /// Cancel orders on disconnect (default: false)
52    pub cancel_on_disconnect: bool,
53    /// Application ID for registered applications
54    pub app_id: Option<String>,
55    /// Application secret for registered applications
56    pub app_secret: Option<String>,
57    /// Use word-safe tags (custom tags start at 5000 instead of 100000)
58    pub use_wordsafe_tags: Option<bool>,
59    /// Enable sequential FIX messaging (single queue for all messages)
60    pub deribit_sequential: Option<bool>,
61    /// Unsubscribe from notificational Execution Reports
62    pub unsubscribe_execution_reports: Option<bool>,
63    /// Receive Execution Reports only for orders created in this connection
64    pub connection_only_execution_reports: Option<bool>,
65    /// Report fills as Execution Reports with ExecType = F(TRADE)
66    pub report_fills_as_exec_reports: Option<bool>,
67    /// Include price increment steps in symbol entries
68    pub display_increment_steps: Option<bool>,
69}
70
71impl DeribitFixConfig {
72    /// Create a new configuration from environment variables with optional username and password
73    pub fn new() -> Self {
74        // Load .env file if available
75        match dotenv() {
76            Ok(_) => debug!("Successfully loaded .env file"),
77            Err(e) => debug!("Failed to load .env file: {}", e),
78        }
79
80        let test_mode = get_env_or_default("DERIBIT_TEST_MODE", true);
81        let use_ssl = get_env_or_default("DERIBIT_USE_SSL", false);
82
83        let (default_host, default_port) = if test_mode {
84            if use_ssl {
85                (DEFAULT_TEST_HOST, DEFAULT_SSL_PORT)
86            } else {
87                (DEFAULT_TEST_HOST, DEFAULT_TEST_PORT)
88            }
89        } else if use_ssl {
90            (DEFAULT_PROD_HOST, DEFAULT_SSL_PORT)
91        } else {
92            (DEFAULT_PROD_HOST, DEFAULT_PROD_PORT)
93        };
94
95        Self {
96            username: get_env_or_default("DERIBIT_USERNAME", String::new()),
97            password: get_env_or_default("DERIBIT_PASSWORD", String::new()),
98            host: get_env_or_default("DERIBIT_HOST", default_host.to_string()),
99            port: get_env_or_default("DERIBIT_PORT", default_port),
100            use_ssl,
101            test_mode,
102            heartbeat_interval: get_env_or_default(
103                "DERIBIT_HEARTBEAT_INTERVAL",
104                DEFAULT_HEARTBEAT_INTERVAL,
105            ),
106            connection_timeout: Duration::from_secs(get_env_or_default(
107                "DERIBIT_CONNECTION_TIMEOUT",
108                DEFAULT_CONNECTION_TIMEOUT_SECS,
109            )),
110            reconnect_attempts: get_env_or_default(
111                "DERIBIT_RECONNECT_ATTEMPTS",
112                DEFAULT_RECONNECT_ATTEMPTS,
113            ),
114            reconnect_delay: Duration::from_secs(get_env_or_default(
115                "DERIBIT_RECONNECT_DELAY",
116                DEFAULT_RECONNECT_DELAY_SECS,
117            )),
118            enable_logging: get_env_or_default("DERIBIT_ENABLE_LOGGING", true),
119            log_level: get_env_or_default("DERIBIT_LOG_LEVEL", DEFAULT_LOG_LEVEL.to_string()),
120            sender_comp_id: get_env_or_default(
121                "DERIBIT_SENDER_COMP_ID",
122                DEFAULT_SENDER_COMP_ID.to_string(),
123            ),
124            target_comp_id: get_env_or_default(
125                "DERIBIT_TARGET_COMP_ID",
126                DEFAULT_TARGET_COMP_ID.to_string(),
127            ),
128            cancel_on_disconnect: get_env_or_default("DERIBIT_CANCEL_ON_DISCONNECT", false),
129            app_id: get_env_optional("DERIBIT_APP_ID"),
130            app_secret: get_env_optional("DERIBIT_APP_SECRET"),
131            use_wordsafe_tags: get_env_optional::<String>("DERIBIT_USE_WORDSAFE_TAGS")
132                .map(|v| v == "Y" || v == "true"),
133            deribit_sequential: get_env_optional::<String>("DERIBIT_SEQUENTIAL")
134                .map(|v| v == "Y" || v == "true"),
135            unsubscribe_execution_reports: get_env_optional::<String>(
136                "DERIBIT_UNSUBSCRIBE_EXECUTION_REPORTS",
137            )
138            .map(|v| v == "Y" || v == "true"),
139            connection_only_execution_reports: get_env_optional::<String>(
140                "DERIBIT_CONNECTION_ONLY_EXECUTION_REPORTS",
141            )
142            .map(|v| v == "Y" || v == "true"),
143            report_fills_as_exec_reports: get_env_optional::<String>(
144                "DERIBIT_REPORT_FILLS_AS_EXEC_REPORTS",
145            )
146            .map(|v| v == "Y" || v == "true"),
147            display_increment_steps: get_env_optional::<String>("DERIBIT_DISPLAY_INCREMENT_STEPS")
148                .map(|v| v == "Y" || v == "true"),
149        }
150    }
151
152    /// Set credentials
153    pub fn with_credentials(mut self, username: String, password: String) -> Self {
154        self.username = username;
155        self.password = password;
156        self
157    }
158
159    /// Create configuration for production environment
160    pub fn production() -> Self {
161        let mut config = Self::new();
162        config.test_mode = false;
163        config.host = get_env_or_default("DERIBIT_HOST", DEFAULT_PROD_HOST.to_string());
164        config.port = if config.use_ssl {
165            get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT)
166        } else {
167            get_env_or_default("DERIBIT_PORT", DEFAULT_PROD_PORT)
168        };
169        config
170    }
171
172    /// Create configuration for production environment with credentials
173    pub fn production_with_credentials(username: String, password: String) -> Self {
174        let mut config = Self::production();
175        config.username = username;
176        config.password = password;
177        config
178    }
179
180    /// Create configuration for production environment with SSL
181    pub fn production_ssl() -> Self {
182        let mut config = Self::production();
183        config.use_ssl = true;
184        config.port = get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT);
185        config
186    }
187
188    /// Create configuration for test environment with SSL
189    pub fn test_ssl() -> Self {
190        let mut config = Self::new();
191        config.use_ssl = true;
192        config.port = get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT);
193        config
194    }
195
196    /// Set custom host and port
197    pub fn with_endpoint(mut self, host: String, port: u16) -> Self {
198        self.host = host;
199        self.port = port;
200        self
201    }
202
203    /// Enable or disable SSL
204    pub fn with_ssl(mut self, use_ssl: bool) -> Self {
205        self.use_ssl = use_ssl;
206        self
207    }
208
209    /// Set heartbeat interval
210    pub fn with_heartbeat_interval(mut self, interval: u32) -> Self {
211        self.heartbeat_interval = interval;
212        self
213    }
214
215    /// Set connection timeout
216    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
217        self.connection_timeout = timeout;
218        self
219    }
220
221    /// Set reconnection parameters
222    pub fn with_reconnection(mut self, attempts: u32, delay: Duration) -> Self {
223        self.reconnect_attempts = attempts;
224        self.reconnect_delay = delay;
225        self
226    }
227
228    /// Set logging configuration
229    pub fn with_logging(mut self, enabled: bool, level: String) -> Self {
230        self.enable_logging = enabled;
231        self.log_level = level;
232        self
233    }
234
235    /// Set FIX session identifiers
236    pub fn with_session_ids(mut self, sender_comp_id: String, target_comp_id: String) -> Self {
237        self.sender_comp_id = sender_comp_id;
238        self.target_comp_id = target_comp_id;
239        self
240    }
241
242    /// Set cancel on disconnect behavior
243    pub fn with_cancel_on_disconnect(mut self, cancel_on_disconnect: bool) -> Self {
244        self.cancel_on_disconnect = cancel_on_disconnect;
245        self
246    }
247
248    /// Set application credentials for registered applications
249    pub fn with_app_credentials(mut self, app_id: String, app_secret: String) -> Self {
250        self.app_id = Some(app_id);
251        self.app_secret = Some(app_secret);
252        self
253    }
254
255    /// Set whether to use word-safe tags (custom tags start at 5000 instead of 100000)
256    pub fn with_use_wordsafe_tags(mut self, use_wordsafe_tags: bool) -> Self {
257        self.use_wordsafe_tags = Some(use_wordsafe_tags);
258        self
259    }
260
261    /// Set whether to enable sequential FIX messaging (single queue for all messages)
262    pub fn with_deribit_sequential(mut self, deribit_sequential: bool) -> Self {
263        self.deribit_sequential = Some(deribit_sequential);
264        self
265    }
266
267    /// Set whether to unsubscribe from notificational Execution Reports
268    pub fn with_unsubscribe_execution_reports(mut self, unsubscribe: bool) -> Self {
269        self.unsubscribe_execution_reports = Some(unsubscribe);
270        self
271    }
272
273    /// Set whether to receive Execution Reports only for orders created in this connection
274    pub fn with_connection_only_execution_reports(mut self, connection_only: bool) -> Self {
275        self.connection_only_execution_reports = Some(connection_only);
276        self
277    }
278
279    /// Set whether to report fills as Execution Reports with ExecType = F(TRADE)
280    pub fn with_report_fills_as_exec_reports(mut self, report_fills: bool) -> Self {
281        self.report_fills_as_exec_reports = Some(report_fills);
282        self
283    }
284
285    /// Set whether to include price increment steps in symbol entries
286    pub fn with_display_increment_steps(mut self, display_steps: bool) -> Self {
287        self.display_increment_steps = Some(display_steps);
288        self
289    }
290
291    /// Get the connection URL
292    pub fn connection_url(&self) -> String {
293        format!("{}:{}", self.host, self.port)
294    }
295
296    /// Validate the configuration
297    pub fn validate(&self) -> Result<()> {
298        if self.username.is_empty() {
299            return Err(DeribitFixError::Config(
300                "Username cannot be empty".to_string(),
301            ));
302        }
303
304        if self.password.is_empty() {
305            return Err(DeribitFixError::Config(
306                "Password cannot be empty".to_string(),
307            ));
308        }
309
310        if self.host.is_empty() {
311            return Err(DeribitFixError::Config("Host cannot be empty".to_string()));
312        }
313
314        if self.port == 0 {
315            return Err(DeribitFixError::Config(
316                "Port must be greater than 0".to_string(),
317            ));
318        }
319
320        if self.heartbeat_interval == 0 {
321            return Err(DeribitFixError::Config(
322                "Heartbeat interval must be greater than 0".to_string(),
323            ));
324        }
325
326        if self.sender_comp_id.is_empty() {
327            return Err(DeribitFixError::Config(
328                "Sender company ID cannot be empty".to_string(),
329            ));
330        }
331
332        if self.target_comp_id.is_empty() {
333            return Err(DeribitFixError::Config(
334                "Target company ID cannot be empty".to_string(),
335            ));
336        }
337
338        // Validate app credentials if provided
339        if self.app_id.is_some() && self.app_secret.is_none() {
340            return Err(DeribitFixError::Config(
341                "Application secret is required when app ID is provided".to_string(),
342            ));
343        }
344
345        if self.app_secret.is_some() && self.app_id.is_none() {
346            return Err(DeribitFixError::Config(
347                "Application ID is required when app secret is provided".to_string(),
348            ));
349        }
350
351        Ok(())
352    }
353}
354
355impl Default for DeribitFixConfig {
356    fn default() -> Self {
357        Self::new()
358    }
359}