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}
58
59impl DeribitFixConfig {
60    /// Create a new configuration from environment variables with optional username and password
61    pub fn new() -> Self {
62        // Load .env file if available
63        match dotenv() {
64            Ok(_) => debug!("Successfully loaded .env file"),
65            Err(e) => debug!("Failed to load .env file: {}", e),
66        }
67
68        let test_mode = get_env_or_default("DERIBIT_TEST_MODE", true);
69        let use_ssl = get_env_or_default("DERIBIT_USE_SSL", false);
70
71        let (default_host, default_port) = if test_mode {
72            if use_ssl {
73                (DEFAULT_TEST_HOST, DEFAULT_SSL_PORT)
74            } else {
75                (DEFAULT_TEST_HOST, DEFAULT_TEST_PORT)
76            }
77        } else if use_ssl {
78            (DEFAULT_PROD_HOST, DEFAULT_SSL_PORT)
79        } else {
80            (DEFAULT_PROD_HOST, DEFAULT_PROD_PORT)
81        };
82
83        Self {
84            username: get_env_or_default("DERIBIT_USERNAME", String::new()),
85            password: get_env_or_default("DERIBIT_PASSWORD", String::new()),
86            host: get_env_or_default("DERIBIT_HOST", default_host.to_string()),
87            port: get_env_or_default("DERIBIT_PORT", default_port),
88            use_ssl,
89            test_mode,
90            heartbeat_interval: get_env_or_default(
91                "DERIBIT_HEARTBEAT_INTERVAL",
92                DEFAULT_HEARTBEAT_INTERVAL,
93            ),
94            connection_timeout: Duration::from_secs(get_env_or_default(
95                "DERIBIT_CONNECTION_TIMEOUT",
96                DEFAULT_CONNECTION_TIMEOUT_SECS,
97            )),
98            reconnect_attempts: get_env_or_default(
99                "DERIBIT_RECONNECT_ATTEMPTS",
100                DEFAULT_RECONNECT_ATTEMPTS,
101            ),
102            reconnect_delay: Duration::from_secs(get_env_or_default(
103                "DERIBIT_RECONNECT_DELAY",
104                DEFAULT_RECONNECT_DELAY_SECS,
105            )),
106            enable_logging: get_env_or_default("DERIBIT_ENABLE_LOGGING", true),
107            log_level: get_env_or_default("DERIBIT_LOG_LEVEL", DEFAULT_LOG_LEVEL.to_string()),
108            sender_comp_id: get_env_or_default(
109                "DERIBIT_SENDER_COMP_ID",
110                DEFAULT_SENDER_COMP_ID.to_string(),
111            ),
112            target_comp_id: get_env_or_default(
113                "DERIBIT_TARGET_COMP_ID",
114                DEFAULT_TARGET_COMP_ID.to_string(),
115            ),
116            cancel_on_disconnect: get_env_or_default("DERIBIT_CANCEL_ON_DISCONNECT", false),
117            app_id: get_env_optional("DERIBIT_APP_ID"),
118            app_secret: get_env_optional("DERIBIT_APP_SECRET"),
119        }
120    }
121
122    /// Set credentials
123    pub fn with_credentials(mut self, username: String, password: String) -> Self {
124        self.username = username;
125        self.password = password;
126        self
127    }
128
129    /// Create configuration for production environment
130    pub fn production() -> Self {
131        let mut config = Self::new();
132        config.test_mode = false;
133        config.host = get_env_or_default("DERIBIT_HOST", DEFAULT_PROD_HOST.to_string());
134        config.port = if config.use_ssl {
135            get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT)
136        } else {
137            get_env_or_default("DERIBIT_PORT", DEFAULT_PROD_PORT)
138        };
139        config
140    }
141
142    /// Create configuration for production environment with credentials
143    pub fn production_with_credentials(username: String, password: String) -> Self {
144        let mut config = Self::production();
145        config.username = username;
146        config.password = password;
147        config
148    }
149
150    /// Create configuration for production environment with SSL
151    pub fn production_ssl() -> Self {
152        let mut config = Self::production();
153        config.use_ssl = true;
154        config.port = get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT);
155        config
156    }
157
158    /// Create configuration for test environment with SSL
159    pub fn test_ssl() -> Self {
160        let mut config = Self::new();
161        config.use_ssl = true;
162        config.port = get_env_or_default("DERIBIT_PORT", DEFAULT_SSL_PORT);
163        config
164    }
165
166    /// Set custom host and port
167    pub fn with_endpoint(mut self, host: String, port: u16) -> Self {
168        self.host = host;
169        self.port = port;
170        self
171    }
172
173    /// Enable or disable SSL
174    pub fn with_ssl(mut self, use_ssl: bool) -> Self {
175        self.use_ssl = use_ssl;
176        self
177    }
178
179    /// Set heartbeat interval
180    pub fn with_heartbeat_interval(mut self, interval: u32) -> Self {
181        self.heartbeat_interval = interval;
182        self
183    }
184
185    /// Set connection timeout
186    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
187        self.connection_timeout = timeout;
188        self
189    }
190
191    /// Set reconnection parameters
192    pub fn with_reconnection(mut self, attempts: u32, delay: Duration) -> Self {
193        self.reconnect_attempts = attempts;
194        self.reconnect_delay = delay;
195        self
196    }
197
198    /// Set logging configuration
199    pub fn with_logging(mut self, enabled: bool, level: String) -> Self {
200        self.enable_logging = enabled;
201        self.log_level = level;
202        self
203    }
204
205    /// Set FIX session identifiers
206    pub fn with_session_ids(mut self, sender_comp_id: String, target_comp_id: String) -> Self {
207        self.sender_comp_id = sender_comp_id;
208        self.target_comp_id = target_comp_id;
209        self
210    }
211
212    /// Set cancel on disconnect behavior
213    pub fn with_cancel_on_disconnect(mut self, cancel_on_disconnect: bool) -> Self {
214        self.cancel_on_disconnect = cancel_on_disconnect;
215        self
216    }
217
218    /// Set application credentials for registered applications
219    pub fn with_app_credentials(mut self, app_id: String, app_secret: String) -> Self {
220        self.app_id = Some(app_id);
221        self.app_secret = Some(app_secret);
222        self
223    }
224
225    /// Get the connection URL
226    pub fn connection_url(&self) -> String {
227        format!("{}:{}", self.host, self.port)
228    }
229
230    /// Validate the configuration
231    pub fn validate(&self) -> Result<()> {
232        if self.username.is_empty() {
233            return Err(DeribitFixError::Config(
234                "Username cannot be empty".to_string(),
235            ));
236        }
237
238        if self.password.is_empty() {
239            return Err(DeribitFixError::Config(
240                "Password cannot be empty".to_string(),
241            ));
242        }
243
244        if self.host.is_empty() {
245            return Err(DeribitFixError::Config("Host cannot be empty".to_string()));
246        }
247
248        if self.port == 0 {
249            return Err(DeribitFixError::Config(
250                "Port must be greater than 0".to_string(),
251            ));
252        }
253
254        if self.heartbeat_interval == 0 {
255            return Err(DeribitFixError::Config(
256                "Heartbeat interval must be greater than 0".to_string(),
257            ));
258        }
259
260        if self.sender_comp_id.is_empty() {
261            return Err(DeribitFixError::Config(
262                "Sender company ID cannot be empty".to_string(),
263            ));
264        }
265
266        if self.target_comp_id.is_empty() {
267            return Err(DeribitFixError::Config(
268                "Target company ID cannot be empty".to_string(),
269            ));
270        }
271
272        // Validate app credentials if provided
273        if self.app_id.is_some() && self.app_secret.is_none() {
274            return Err(DeribitFixError::Config(
275                "Application secret is required when app ID is provided".to_string(),
276            ));
277        }
278
279        if self.app_secret.is_some() && self.app_id.is_none() {
280            return Err(DeribitFixError::Config(
281                "Application ID is required when app secret is provided".to_string(),
282            ));
283        }
284
285        Ok(())
286    }
287}
288
289impl Default for DeribitFixConfig {
290    fn default() -> Self {
291        Self::new()
292    }
293}