use chrono::{DateTime, FixedOffset};
use thiserror::Error;
pub type Result<T> = std::result::Result<T, HyperliquidBacktestError>;
#[derive(Debug, Error)]
pub enum HyperliquidBacktestError {
#[error("Hyperliquid API error: {0}")]
HyperliquidApi(String),
#[error("Data conversion error: {0}")]
DataConversion(String),
#[error("Invalid time range: start {start} >= end {end}")]
InvalidTimeRange { start: u64, end: u64 },
#[error("Unsupported interval: {0}")]
UnsupportedInterval(String),
#[error("Missing funding data for timestamp: {0}")]
MissingFundingData(DateTime<FixedOffset>),
#[error("Backtesting error: {0}")]
Backtesting(String),
#[error("Network error: {0}")]
Network(String),
#[error("JSON parsing error: {0}")]
JsonParsing(#[from] serde_json::Error),
#[error("CSV error: {0}")]
Csv(#[from] csv::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP client error: {0}")]
Http(String),
#[error("DateTime parsing error: {0}")]
DateTimeParsing(#[from] chrono::ParseError),
#[error("Number parsing error: {0}")]
NumberParsing(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Rate limit exceeded: {0}")]
RateLimit(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Data integrity error: {0}")]
DataIntegrity(String),
}
impl From<std::num::ParseFloatError> for HyperliquidBacktestError {
fn from(err: std::num::ParseFloatError) -> Self {
HyperliquidBacktestError::NumberParsing(err.to_string())
}
}
impl From<std::num::ParseIntError> for HyperliquidBacktestError {
fn from(err: std::num::ParseIntError) -> Self {
HyperliquidBacktestError::NumberParsing(err.to_string())
}
}
impl HyperliquidBacktestError {
pub fn api_error(message: impl Into<String>) -> Self {
Self::HyperliquidApi(message.into())
}
pub fn conversion_error(message: impl Into<String>) -> Self {
Self::DataConversion(message.into())
}
pub fn validation_error(message: impl Into<String>) -> Self {
Self::Validation(message.into())
}
pub fn config_error(message: impl Into<String>) -> Self {
Self::Configuration(message.into())
}
pub fn user_message(&self) -> String {
match self {
Self::HyperliquidApi(msg) => {
format!(
"Hyperliquid API Error: {}\n\n\
💡 Suggestions:\n\
• Check your internet connection\n\
• Verify the trading pair symbol (e.g., 'BTC', 'ETH')\n\
• Ensure the time range is valid and not too large\n\
• Try again in a few moments if rate limited",
msg
)
},
Self::UnsupportedInterval(interval) => {
format!(
"Unsupported time interval: '{}'\n\n\
💡 Supported intervals:\n\
• '1m' - 1 minute\n\
• '5m' - 5 minutes\n\
• '15m' - 15 minutes\n\
• '1h' - 1 hour\n\
• '4h' - 4 hours\n\
• '1d' - 1 day\n\n\
Example: HyperliquidData::fetch(\"BTC\", \"1h\", start, end)",
interval
)
},
Self::InvalidTimeRange { start, end } => {
format!(
"Invalid time range: start time ({}) must be before end time ({})\n\n\
💡 Suggestions:\n\
• Ensure start_time < end_time\n\
• Use Unix timestamps in seconds\n\
• Example: let start = Utc::now().timestamp() - 86400; // 24 hours ago",
start, end
)
},
Self::MissingFundingData(timestamp) => {
format!(
"Missing funding data for timestamp: {}\n\n\
💡 This usually means:\n\
• The timestamp is outside the funding data range\n\
• Funding data is not available for this time period\n\
• Consider using a different time range or disabling funding calculations",
timestamp.format("%Y-%m-%d %H:%M:%S UTC")
)
},
Self::DataConversion(msg) => {
format!(
"Data conversion error: {}\n\n\
💡 This usually indicates:\n\
• Invalid data format from the API\n\
• Corrupted or incomplete data\n\
• Try fetching data for a different time period",
msg
)
},
Self::Network(msg) => {
format!(
"Network error: {}\n\n\
💡 Suggestions:\n\
• Check your internet connection\n\
• Verify firewall settings\n\
• Try again in a few moments\n\
• Consider using a VPN if in a restricted region",
msg
)
},
Self::RateLimit(msg) => {
format!(
"Rate limit exceeded: {}\n\n\
💡 Suggestions:\n\
• Wait a few minutes before making more requests\n\
• Reduce the frequency of API calls\n\
• Consider caching data locally\n\
• Use larger time intervals to fetch less data",
msg
)
},
Self::Validation(msg) => {
format!(
"Validation error: {}\n\n\
💡 Please check:\n\
• Input parameters are within valid ranges\n\
• Required fields are not empty\n\
• Data types match expected formats\n\
Details: {}",
msg, msg
)
},
Self::Configuration(msg) => {
format!(
"Configuration error: {}\n\n\
💡 Please verify:\n\
• All required configuration values are set\n\
• Configuration file format is correct\n\
• Environment variables are properly set\n\
Details: {}",
msg, msg
)
},
_ => self.to_string(),
}
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::Network(_) | Self::RateLimit(_) | Self::HyperliquidApi(_)
)
}
pub fn is_user_error(&self) -> bool {
matches!(
self,
Self::UnsupportedInterval(_) |
Self::InvalidTimeRange { .. } |
Self::Validation(_) |
Self::Configuration(_)
)
}
pub fn category(&self) -> &'static str {
match self {
Self::HyperliquidApi(_) => "api",
Self::DataConversion(_) => "data",
Self::InvalidTimeRange { .. } => "validation",
Self::UnsupportedInterval(_) => "validation",
Self::MissingFundingData(_) => "data",
Self::Backtesting(_) => "computation",
Self::Network(_) => "network",
Self::JsonParsing(_) => "parsing",
Self::Csv(_) => "csv",
Self::Io(_) => "io",
Self::Http(_) => "network",
Self::DateTimeParsing(_) => "parsing",
Self::NumberParsing(_) => "parsing",
Self::Configuration(_) => "config",
Self::Validation(_) => "validation",
Self::RateLimit(_) => "rate_limit",
Self::Authentication(_) => "auth",
Self::DataIntegrity(_) => "data",
}
}
pub fn hyperliquid_api<S: Into<String>>(msg: S) -> Self {
HyperliquidBacktestError::HyperliquidApi(msg.into())
}
pub fn data_conversion<S: Into<String>>(msg: S) -> Self {
HyperliquidBacktestError::DataConversion(msg.into())
}
pub fn invalid_time_range(start: u64, end: u64) -> Self {
HyperliquidBacktestError::InvalidTimeRange { start, end }
}
pub fn unsupported_interval<S: Into<String>>(interval: S) -> Self {
HyperliquidBacktestError::UnsupportedInterval(interval.into())
}
pub fn missing_funding_data(timestamp: DateTime<FixedOffset>) -> Self {
HyperliquidBacktestError::MissingFundingData(timestamp)
}
pub fn validation<S: Into<String>>(msg: S) -> Self {
HyperliquidBacktestError::Validation(msg.into())
}
pub fn network<S: Into<String>>(msg: S) -> Self {
HyperliquidBacktestError::Network(msg.into())
}
pub fn rate_limit<S: Into<String>>(msg: S) -> Self {
HyperliquidBacktestError::RateLimit(msg.into())
}
}
impl From<tokio::task::JoinError> for HyperliquidBacktestError {
fn from(err: tokio::task::JoinError) -> Self {
HyperliquidBacktestError::Backtesting(format!("Task join error: {}", err))
}
}
impl From<hyperliquid_rust_sdk::Error> for HyperliquidBacktestError {
fn from(err: hyperliquid_rust_sdk::Error) -> Self {
match err {
hyperliquid_rust_sdk::Error::ClientRequest { status_code, error_code, error_message, error_data } => {
HyperliquidBacktestError::HyperliquidApi(format!(
"Client error: status {}, code {:?}, message: {}, data: {:?}",
status_code, error_code, error_message, error_data
))
},
hyperliquid_rust_sdk::Error::ServerRequest { status_code, error_message } => {
HyperliquidBacktestError::HyperliquidApi(format!(
"Server error: status {}, message: {}",
status_code, error_message
))
},
hyperliquid_rust_sdk::Error::GenericRequest(msg) => {
HyperliquidBacktestError::Network(msg)
},
hyperliquid_rust_sdk::Error::JsonParse(_msg) => {
HyperliquidBacktestError::JsonParsing(serde_json::from_str::<serde_json::Value>("").unwrap_err())
},
hyperliquid_rust_sdk::Error::Websocket(msg) => {
HyperliquidBacktestError::Network(format!("WebSocket error: {}", msg))
},
_ => {
HyperliquidBacktestError::HyperliquidApi(format!("Hyperliquid SDK error: {:?}", err))
}
}
}
}
impl From<String> for HyperliquidBacktestError {
fn from(msg: String) -> Self {
HyperliquidBacktestError::Validation(msg)
}
}