1use chrono::{DateTime, FixedOffset};
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, HyperliquidBacktestError>;
8
9#[derive(Debug, Error)]
11pub enum HyperliquidBacktestError {
12 #[error("Hyperliquid API error: {0}")]
14 HyperliquidApi(String),
15
16 #[error("Data conversion error: {0}")]
18 DataConversion(String),
19
20 #[error("Invalid time range: start {start} >= end {end}")]
22 InvalidTimeRange { start: u64, end: u64 },
23
24 #[error("Unsupported interval: {0}")]
26 UnsupportedInterval(String),
27
28 #[error("Missing funding data for timestamp: {0}")]
30 MissingFundingData(DateTime<FixedOffset>),
31
32 #[error("Backtesting error: {0}")]
34 Backtesting(String),
35
36 #[error("Network error: {0}")]
38 Network(String),
39
40 #[error("JSON parsing error: {0}")]
42 JsonParsing(#[from] serde_json::Error),
43
44 #[error("CSV error: {0}")]
46 Csv(#[from] csv::Error),
47
48 #[error("IO error: {0}")]
50 Io(#[from] std::io::Error),
51
52 #[error("HTTP client error: {0}")]
54 Http(String),
55
56 #[error("DateTime parsing error: {0}")]
58 DateTimeParsing(#[from] chrono::ParseError),
59
60 #[error("Number parsing error: {0}")]
62 NumberParsing(String),
63
64 #[error("Configuration error: {0}")]
66 Configuration(String),
67
68 #[error("Validation error: {0}")]
70 Validation(String),
71
72 #[error("Rate limit exceeded: {0}")]
74 RateLimit(String),
75
76 #[error("Authentication error: {0}")]
78 Authentication(String),
79
80 #[error("Data integrity error: {0}")]
82 DataIntegrity(String),
83}
84
85impl 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
99impl HyperliquidBacktestError {
101 pub fn api_error(message: impl Into<String>) -> Self {
103 Self::HyperliquidApi(message.into())
104 }
105
106 pub fn conversion_error(message: impl Into<String>) -> Self {
108 Self::DataConversion(message.into())
109 }
110
111 pub fn validation_error(message: impl Into<String>) -> Self {
113 Self::Validation(message.into())
114 }
115
116 pub fn config_error(message: impl Into<String>) -> Self {
118 Self::Configuration(message.into())
119 }
120
121 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 pub fn is_recoverable(&self) -> bool {
229 matches!(
230 self,
231 Self::Network(_) | Self::RateLimit(_) | Self::HyperliquidApi(_)
232 )
233 }
234
235 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 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 pub fn hyperliquid_api<S: Into<String>>(msg: S) -> Self {
271 HyperliquidBacktestError::HyperliquidApi(msg.into())
272 }
273
274 pub fn data_conversion<S: Into<String>>(msg: S) -> Self {
276 HyperliquidBacktestError::DataConversion(msg.into())
277 }
278
279 pub fn invalid_time_range(start: u64, end: u64) -> Self {
281 HyperliquidBacktestError::InvalidTimeRange { start, end }
282 }
283
284 pub fn unsupported_interval<S: Into<String>>(interval: S) -> Self {
286 HyperliquidBacktestError::UnsupportedInterval(interval.into())
287 }
288
289 pub fn missing_funding_data(timestamp: DateTime<FixedOffset>) -> Self {
291 HyperliquidBacktestError::MissingFundingData(timestamp)
292 }
293
294 pub fn validation<S: Into<String>>(msg: S) -> Self {
296 HyperliquidBacktestError::Validation(msg.into())
297 }
298
299 pub fn network<S: Into<String>>(msg: S) -> Self {
301 HyperliquidBacktestError::Network(msg.into())
302 }
303
304 pub fn rate_limit<S: Into<String>>(msg: S) -> Self {
306 HyperliquidBacktestError::RateLimit(msg.into())
307 }
308}
309
310impl 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
317impl 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
349impl From<String> for HyperliquidBacktestError {
351 fn from(msg: String) -> Self {
352 HyperliquidBacktestError::Validation(msg)
353 }
354}
355