use thiserror::Error;
#[derive(Debug, Error)]
pub enum TradingError {
#[error("Market data error: {message}")]
MarketData {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Strategy error in '{strategy_id}': {message}")]
Strategy {
strategy_id: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Execution error: {message}")]
Execution {
message: String,
order_id: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Risk limit exceeded: {message}")]
RiskLimit {
message: String,
violation_type: RiskViolationType,
},
#[error("Configuration error: {message}")]
Config {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Portfolio error: {message}")]
Portfolio { message: String },
#[error("Network error: {message}")]
Network {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Validation error: {message}")]
Validation { message: String },
#[error("Not found: {resource_type} '{resource_id}'")]
NotFound {
resource_type: String,
resource_id: String,
},
#[error("Operation timed out after {timeout_ms}ms: {operation}")]
Timeout { operation: String, timeout_ms: u64 },
#[error("Authentication error: {message}")]
Auth { message: String },
#[error("Database error: {message}")]
Database {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Serialization error: {message}")]
Serialization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Internal error: {message}")]
Internal { message: String },
#[error("Invalid state: {message}")]
InvalidState { message: String },
#[error("Not implemented: {feature}")]
NotImplemented { feature: String },
#[error(transparent)]
Other(#[from] anyhow::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiskViolationType {
PositionSizeExceeded,
DailyLossLimitExceeded,
MaxDrawdownExceeded,
LeverageExceeded,
SectorConcentrationExceeded,
CorrelationExceeded,
}
impl TradingError {
pub fn market_data(message: impl Into<String>) -> Self {
Self::MarketData {
message: message.into(),
source: None,
}
}
pub fn market_data_with_source(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::MarketData {
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn strategy(strategy_id: impl Into<String>, message: impl Into<String>) -> Self {
Self::Strategy {
strategy_id: strategy_id.into(),
message: message.into(),
source: None,
}
}
pub fn strategy_with_source(
strategy_id: impl Into<String>,
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Strategy {
strategy_id: strategy_id.into(),
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn execution(message: impl Into<String>) -> Self {
Self::Execution {
message: message.into(),
order_id: None,
source: None,
}
}
pub fn execution_with_order(message: impl Into<String>, order_id: impl Into<String>) -> Self {
Self::Execution {
message: message.into(),
order_id: Some(order_id.into()),
source: None,
}
}
pub fn risk_limit(message: impl Into<String>, violation_type: RiskViolationType) -> Self {
Self::RiskLimit {
message: message.into(),
violation_type,
}
}
pub fn config(message: impl Into<String>) -> Self {
Self::Config {
message: message.into(),
source: None,
}
}
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation {
message: message.into(),
}
}
pub fn not_found(resource_type: impl Into<String>, resource_id: impl Into<String>) -> Self {
Self::NotFound {
resource_type: resource_type.into(),
resource_id: resource_id.into(),
}
}
pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
Self::Timeout {
operation: operation.into(),
timeout_ms,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
pub fn invalid_state(message: impl Into<String>) -> Self {
Self::InvalidState {
message: message.into(),
}
}
pub fn not_implemented(feature: impl Into<String>) -> Self {
Self::NotImplemented {
feature: feature.into(),
}
}
}
pub type Result<T> = std::result::Result<T, TradingError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = TradingError::market_data("Connection failed");
assert!(matches!(err, TradingError::MarketData { .. }));
assert!(err.to_string().contains("Market data error"));
let err = TradingError::strategy("momentum", "Invalid parameter");
assert!(matches!(err, TradingError::Strategy { .. }));
assert!(err.to_string().contains("momentum"));
let err = TradingError::risk_limit(
"Position too large",
RiskViolationType::PositionSizeExceeded,
);
assert!(matches!(err, TradingError::RiskLimit { .. }));
}
#[test]
fn test_error_with_source() {
use std::error::Error;
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = TradingError::market_data_with_source("Failed to read data", io_error);
assert!(matches!(err, TradingError::MarketData { .. }));
assert!(err.source().is_some());
}
#[test]
fn test_not_found_error() {
let err = TradingError::not_found("order", "12345");
assert!(matches!(err, TradingError::NotFound { .. }));
assert!(err.to_string().contains("order"));
assert!(err.to_string().contains("12345"));
}
#[test]
fn test_timeout_error() {
let err = TradingError::timeout("place_order", 5000);
assert!(matches!(err, TradingError::Timeout { .. }));
assert!(err.to_string().contains("5000ms"));
}
#[test]
fn test_result_type() {
fn returns_result() -> Result<i32> {
Ok(42)
}
fn returns_error() -> Result<i32> {
Err(TradingError::internal("Test error"))
}
assert!(returns_result().is_ok());
assert!(returns_error().is_err());
}
}