hyperliquid_backtest/
errors.rs

1//! Error types for Hyperliquid backtesting operations
2
3use chrono::{DateTime, FixedOffset};
4use thiserror::Error;
5
6/// Result type alias for consistent error handling throughout the crate
7pub type Result<T> = std::result::Result<T, HyperliquidBacktestError>;
8
9/// Main error type for Hyperliquid backtesting operations
10#[derive(Debug, Error)]
11pub enum HyperliquidBacktestError {
12    /// Error from Hyperliquid API operations
13    #[error("Hyperliquid API error: {0}")]
14    HyperliquidApi(String),
15    
16    /// Error during data conversion between formats
17    #[error("Data conversion error: {0}")]
18    DataConversion(String),
19    
20    /// Invalid time range specified
21    #[error("Invalid time range: start {start} >= end {end}")]
22    InvalidTimeRange { start: u64, end: u64 },
23    
24    /// Unsupported time interval
25    #[error("Unsupported interval: {0}")]
26    UnsupportedInterval(String),
27    
28    /// Missing funding data for a specific timestamp
29    #[error("Missing funding data for timestamp: {0}")]
30    MissingFundingData(DateTime<FixedOffset>),
31    
32    /// General backtesting error
33    #[error("Backtesting error: {0}")]
34    Backtesting(String),
35    
36    /// Network or HTTP related errors
37    #[error("Network error: {0}")]
38    Network(String),
39    
40    /// JSON parsing errors
41    #[error("JSON parsing error: {0}")]
42    JsonParsing(#[from] serde_json::Error),
43    
44    /// CSV processing errors
45    #[error("CSV error: {0}")]
46    Csv(#[from] csv::Error),
47    
48    /// IO errors
49    #[error("IO error: {0}")]
50    Io(#[from] std::io::Error),
51    
52    /// HTTP client errors
53    #[error("HTTP client error: {0}")]
54    Http(String),
55    
56    /// Date/time parsing errors
57    #[error("DateTime parsing error: {0}")]
58    DateTimeParsing(#[from] chrono::ParseError),
59    
60    /// Numeric parsing errors
61    #[error("Number parsing error: {0}")]
62    NumberParsing(String),
63    
64    /// Configuration errors
65    #[error("Configuration error: {0}")]
66    Configuration(String),
67    
68    /// Validation errors
69    #[error("Validation error: {0}")]
70    Validation(String),
71    
72    /// Rate limiting errors
73    #[error("Rate limit exceeded: {0}")]
74    RateLimit(String),
75    
76    /// Authentication errors
77    #[error("Authentication error: {0}")]
78    Authentication(String),
79    
80    /// Data integrity errors
81    #[error("Data integrity error: {0}")]
82    DataIntegrity(String),
83}
84
85// Error conversion implementations for external library errors
86
87impl From<std::num::ParseFloatError> for HyperliquidBacktestError {
88    fn from(err: std::num::ParseFloatError) -> Self {
89        HyperliquidBacktestError::NumberParsing(err.to_string())
90    }
91}
92
93impl From<std::num::ParseIntError> for HyperliquidBacktestError {
94    fn from(err: std::num::ParseIntError) -> Self {
95        HyperliquidBacktestError::NumberParsing(err.to_string())
96    }
97}
98
99// Helper methods for error creation and user guidance
100impl HyperliquidBacktestError {
101    /// Create a new API error with context
102    pub fn api_error(message: impl Into<String>) -> Self {
103        Self::HyperliquidApi(message.into())
104    }
105    
106    /// Create a new data conversion error with context
107    pub fn conversion_error(message: impl Into<String>) -> Self {
108        Self::DataConversion(message.into())
109    }
110    
111    /// Create a new validation error with context
112    pub fn validation_error(message: impl Into<String>) -> Self {
113        Self::Validation(message.into())
114    }
115    
116    /// Create a new configuration error with context
117    pub fn config_error(message: impl Into<String>) -> Self {
118        Self::Configuration(message.into())
119    }
120    
121    /// Get user-friendly error message with suggestions for resolution
122    pub fn user_message(&self) -> String {
123        match self {
124            Self::HyperliquidApi(msg) => {
125                format!(
126                    "Hyperliquid API Error: {}\n\n\
127                    💡 Suggestions:\n\
128                    • Check your internet connection\n\
129                    • Verify the trading pair symbol (e.g., 'BTC', 'ETH')\n\
130                    • Ensure the time range is valid and not too large\n\
131                    • Try again in a few moments if rate limited",
132                    msg
133                )
134            },
135            Self::UnsupportedInterval(interval) => {
136                format!(
137                    "Unsupported time interval: '{}'\n\n\
138                    💡 Supported intervals:\n\
139                    • '1m' - 1 minute\n\
140                    • '5m' - 5 minutes\n\
141                    • '15m' - 15 minutes\n\
142                    • '1h' - 1 hour\n\
143                    • '4h' - 4 hours\n\
144                    • '1d' - 1 day\n\n\
145                    Example: HyperliquidData::fetch(\"BTC\", \"1h\", start, end)",
146                    interval
147                )
148            },
149            Self::InvalidTimeRange { start, end } => {
150                format!(
151                    "Invalid time range: start time ({}) must be before end time ({})\n\n\
152                    💡 Suggestions:\n\
153                    • Ensure start_time < end_time\n\
154                    • Use Unix timestamps in seconds\n\
155                    • Example: let start = Utc::now().timestamp() - 86400; // 24 hours ago",
156                    start, end
157                )
158            },
159            Self::MissingFundingData(timestamp) => {
160                format!(
161                    "Missing funding data for timestamp: {}\n\n\
162                    💡 This usually means:\n\
163                    • The timestamp is outside the funding data range\n\
164                    • Funding data is not available for this time period\n\
165                    • Consider using a different time range or disabling funding calculations",
166                    timestamp.format("%Y-%m-%d %H:%M:%S UTC")
167                )
168            },
169            Self::DataConversion(msg) => {
170                format!(
171                    "Data conversion error: {}\n\n\
172                    💡 This usually indicates:\n\
173                    • Invalid data format from the API\n\
174                    • Corrupted or incomplete data\n\
175                    • Try fetching data for a different time period",
176                    msg
177                )
178            },
179            Self::Network(msg) => {
180                format!(
181                    "Network error: {}\n\n\
182                    💡 Suggestions:\n\
183                    • Check your internet connection\n\
184                    • Verify firewall settings\n\
185                    • Try again in a few moments\n\
186                    • Consider using a VPN if in a restricted region",
187                    msg
188                )
189            },
190            Self::RateLimit(msg) => {
191                format!(
192                    "Rate limit exceeded: {}\n\n\
193                    💡 Suggestions:\n\
194                    • Wait a few minutes before making more requests\n\
195                    • Reduce the frequency of API calls\n\
196                    • Consider caching data locally\n\
197                    • Use larger time intervals to fetch less data",
198                    msg
199                )
200            },
201            Self::Validation(msg) => {
202                format!(
203                    "Validation error: {}\n\n\
204                    💡 Please check:\n\
205                    • Input parameters are within valid ranges\n\
206                    • Required fields are not empty\n\
207                    • Data types match expected formats\n\
208                    Details: {}",
209                    msg, msg
210                )
211            },
212            Self::Configuration(msg) => {
213                format!(
214                    "Configuration error: {}\n\n\
215                    💡 Please verify:\n\
216                    • All required configuration values are set\n\
217                    • Configuration file format is correct\n\
218                    • Environment variables are properly set\n\
219                    Details: {}",
220                    msg, msg
221                )
222            },
223            _ => self.to_string(),
224        }
225    }
226    
227    /// Check if this error is recoverable (user can retry)
228    pub fn is_recoverable(&self) -> bool {
229        matches!(
230            self,
231            Self::Network(_) | Self::RateLimit(_) | Self::HyperliquidApi(_)
232        )
233    }
234    
235    /// Check if this error is due to user input
236    pub fn is_user_error(&self) -> bool {
237        matches!(
238            self,
239            Self::UnsupportedInterval(_) | 
240            Self::InvalidTimeRange { .. } | 
241            Self::Validation(_) | 
242            Self::Configuration(_)
243        )
244    }
245    
246    /// Get error category for logging/monitoring
247    pub fn category(&self) -> &'static str {
248        match self {
249            Self::HyperliquidApi(_) => "api",
250            Self::DataConversion(_) => "data",
251            Self::InvalidTimeRange { .. } => "validation",
252            Self::UnsupportedInterval(_) => "validation",
253            Self::MissingFundingData(_) => "data",
254            Self::Backtesting(_) => "computation",
255            Self::Network(_) => "network",
256            Self::JsonParsing(_) => "parsing",
257            Self::Csv(_) => "csv",
258            Self::Io(_) => "io",
259            Self::Http(_) => "network",
260            Self::DateTimeParsing(_) => "parsing",
261            Self::NumberParsing(_) => "parsing",
262            Self::Configuration(_) => "config",
263            Self::Validation(_) => "validation",
264            Self::RateLimit(_) => "rate_limit",
265            Self::Authentication(_) => "auth",
266            Self::DataIntegrity(_) => "data",
267        }
268    }
269    /// Create a new HyperliquidApi error
270    pub fn hyperliquid_api<S: Into<String>>(msg: S) -> Self {
271        HyperliquidBacktestError::HyperliquidApi(msg.into())
272    }
273
274    /// Create a new DataConversion error
275    pub fn data_conversion<S: Into<String>>(msg: S) -> Self {
276        HyperliquidBacktestError::DataConversion(msg.into())
277    }
278
279    /// Create a new InvalidTimeRange error
280    pub fn invalid_time_range(start: u64, end: u64) -> Self {
281        HyperliquidBacktestError::InvalidTimeRange { start, end }
282    }
283
284    /// Create a new UnsupportedInterval error
285    pub fn unsupported_interval<S: Into<String>>(interval: S) -> Self {
286        HyperliquidBacktestError::UnsupportedInterval(interval.into())
287    }
288
289    /// Create a new MissingFundingData error
290    pub fn missing_funding_data(timestamp: DateTime<FixedOffset>) -> Self {
291        HyperliquidBacktestError::MissingFundingData(timestamp)
292    }
293
294    /// Create a new Validation error
295    pub fn validation<S: Into<String>>(msg: S) -> Self {
296        HyperliquidBacktestError::Validation(msg.into())
297    }
298
299    /// Create a new Network error
300    pub fn network<S: Into<String>>(msg: S) -> Self {
301        HyperliquidBacktestError::Network(msg.into())
302    }
303
304    /// Create a new RateLimit error
305    pub fn rate_limit<S: Into<String>>(msg: S) -> Self {
306        HyperliquidBacktestError::RateLimit(msg.into())
307    }
308}
309
310// Conversion for tokio join errors
311impl From<tokio::task::JoinError> for HyperliquidBacktestError {
312    fn from(err: tokio::task::JoinError) -> Self {
313        HyperliquidBacktestError::Backtesting(format!("Task join error: {}", err))
314    }
315}
316
317// Conversion for hyperliquid_rust_sdk errors
318impl From<hyperliquid_rust_sdk::Error> for HyperliquidBacktestError {
319    fn from(err: hyperliquid_rust_sdk::Error) -> Self {
320        match err {
321            hyperliquid_rust_sdk::Error::ClientRequest { status_code, error_code, error_message, error_data } => {
322                HyperliquidBacktestError::HyperliquidApi(format!(
323                    "Client error: status {}, code {:?}, message: {}, data: {:?}",
324                    status_code, error_code, error_message, error_data
325                ))
326            },
327            hyperliquid_rust_sdk::Error::ServerRequest { status_code, error_message } => {
328                HyperliquidBacktestError::HyperliquidApi(format!(
329                    "Server error: status {}, message: {}",
330                    status_code, error_message
331                ))
332            },
333            hyperliquid_rust_sdk::Error::GenericRequest(msg) => {
334                HyperliquidBacktestError::Network(msg)
335            },
336            hyperliquid_rust_sdk::Error::JsonParse(_msg) => {
337                HyperliquidBacktestError::JsonParsing(serde_json::from_str::<serde_json::Value>("").unwrap_err())
338            },
339            hyperliquid_rust_sdk::Error::Websocket(msg) => {
340                HyperliquidBacktestError::Network(format!("WebSocket error: {}", msg))
341            },
342            _ => {
343                HyperliquidBacktestError::HyperliquidApi(format!("Hyperliquid SDK error: {:?}", err))
344            }
345        }
346    }
347}
348
349// Conversion for String errors (for test compatibility)
350impl From<String> for HyperliquidBacktestError {
351    fn from(msg: String) -> Self {
352        HyperliquidBacktestError::Validation(msg)
353    }
354}
355// Tests moved to tests/errors_tests.rs