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