nt_core/
error.rs

1//! Error types for the Neural Trading system
2//!
3//! This module provides a comprehensive error hierarchy using `thiserror` for automatic
4//! trait implementations and context propagation.
5
6use thiserror::Error;
7
8/// Main error type for the Neural Trading system
9///
10/// This error type covers all possible error conditions across the entire system.
11/// Each variant provides specific context about what went wrong.
12#[derive(Debug, Error)]
13pub enum TradingError {
14    /// Error occurred during market data operations
15    #[error("Market data error: {message}")]
16    MarketData {
17        message: String,
18        #[source]
19        source: Option<Box<dyn std::error::Error + Send + Sync>>,
20    },
21
22    /// Error occurred in strategy execution
23    #[error("Strategy error in '{strategy_id}': {message}")]
24    Strategy {
25        strategy_id: String,
26        message: String,
27        #[source]
28        source: Option<Box<dyn std::error::Error + Send + Sync>>,
29    },
30
31    /// Error occurred during order execution
32    #[error("Execution error: {message}")]
33    Execution {
34        message: String,
35        order_id: Option<String>,
36        #[source]
37        source: Option<Box<dyn std::error::Error + Send + Sync>>,
38    },
39
40    /// Risk limit has been exceeded
41    #[error("Risk limit exceeded: {message}")]
42    RiskLimit {
43        message: String,
44        violation_type: RiskViolationType,
45    },
46
47    /// Configuration error
48    #[error("Configuration error: {message}")]
49    Config {
50        message: String,
51        #[source]
52        source: Option<Box<dyn std::error::Error + Send + Sync>>,
53    },
54
55    /// Portfolio management error
56    #[error("Portfolio error: {message}")]
57    Portfolio { message: String },
58
59    /// Network/API error
60    #[error("Network error: {message}")]
61    Network {
62        message: String,
63        #[source]
64        source: Option<Box<dyn std::error::Error + Send + Sync>>,
65    },
66
67    /// Data validation error
68    #[error("Validation error: {message}")]
69    Validation { message: String },
70
71    /// Resource not found
72    #[error("Not found: {resource_type} '{resource_id}'")]
73    NotFound {
74        resource_type: String,
75        resource_id: String,
76    },
77
78    /// Operation timeout
79    #[error("Operation timed out after {timeout_ms}ms: {operation}")]
80    Timeout { operation: String, timeout_ms: u64 },
81
82    /// Authentication/authorization error
83    #[error("Authentication error: {message}")]
84    Auth { message: String },
85
86    /// Database error
87    #[error("Database error: {message}")]
88    Database {
89        message: String,
90        #[source]
91        source: Option<Box<dyn std::error::Error + Send + Sync>>,
92    },
93
94    /// Serialization/deserialization error
95    #[error("Serialization error: {message}")]
96    Serialization {
97        message: String,
98        #[source]
99        source: Option<Box<dyn std::error::Error + Send + Sync>>,
100    },
101
102    /// Internal system error
103    #[error("Internal error: {message}")]
104    Internal { message: String },
105
106    /// Invalid state error
107    #[error("Invalid state: {message}")]
108    InvalidState { message: String },
109
110    /// Not implemented yet
111    #[error("Not implemented: {feature}")]
112    NotImplemented { feature: String },
113
114    /// Generic error from anyhow
115    #[error(transparent)]
116    Other(#[from] anyhow::Error),
117}
118
119/// Type of risk violation
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum RiskViolationType {
122    /// Position size exceeds maximum allowed
123    PositionSizeExceeded,
124    /// Daily loss limit exceeded
125    DailyLossLimitExceeded,
126    /// Maximum drawdown exceeded
127    MaxDrawdownExceeded,
128    /// Leverage limit exceeded
129    LeverageExceeded,
130    /// Sector concentration limit exceeded
131    SectorConcentrationExceeded,
132    /// Correlation limit exceeded
133    CorrelationExceeded,
134}
135
136impl TradingError {
137    /// Create a market data error
138    pub fn market_data(message: impl Into<String>) -> Self {
139        Self::MarketData {
140            message: message.into(),
141            source: None,
142        }
143    }
144
145    /// Create a market data error with source
146    pub fn market_data_with_source(
147        message: impl Into<String>,
148        source: impl std::error::Error + Send + Sync + 'static,
149    ) -> Self {
150        Self::MarketData {
151            message: message.into(),
152            source: Some(Box::new(source)),
153        }
154    }
155
156    /// Create a strategy error
157    pub fn strategy(strategy_id: impl Into<String>, message: impl Into<String>) -> Self {
158        Self::Strategy {
159            strategy_id: strategy_id.into(),
160            message: message.into(),
161            source: None,
162        }
163    }
164
165    /// Create a strategy error with source
166    pub fn strategy_with_source(
167        strategy_id: impl Into<String>,
168        message: impl Into<String>,
169        source: impl std::error::Error + Send + Sync + 'static,
170    ) -> Self {
171        Self::Strategy {
172            strategy_id: strategy_id.into(),
173            message: message.into(),
174            source: Some(Box::new(source)),
175        }
176    }
177
178    /// Create an execution error
179    pub fn execution(message: impl Into<String>) -> Self {
180        Self::Execution {
181            message: message.into(),
182            order_id: None,
183            source: None,
184        }
185    }
186
187    /// Create an execution error with order ID
188    pub fn execution_with_order(message: impl Into<String>, order_id: impl Into<String>) -> Self {
189        Self::Execution {
190            message: message.into(),
191            order_id: Some(order_id.into()),
192            source: None,
193        }
194    }
195
196    /// Create a risk limit error
197    pub fn risk_limit(message: impl Into<String>, violation_type: RiskViolationType) -> Self {
198        Self::RiskLimit {
199            message: message.into(),
200            violation_type,
201        }
202    }
203
204    /// Create a configuration error
205    pub fn config(message: impl Into<String>) -> Self {
206        Self::Config {
207            message: message.into(),
208            source: None,
209        }
210    }
211
212    /// Create a validation error
213    pub fn validation(message: impl Into<String>) -> Self {
214        Self::Validation {
215            message: message.into(),
216        }
217    }
218
219    /// Create a not found error
220    pub fn not_found(resource_type: impl Into<String>, resource_id: impl Into<String>) -> Self {
221        Self::NotFound {
222            resource_type: resource_type.into(),
223            resource_id: resource_id.into(),
224        }
225    }
226
227    /// Create a timeout error
228    pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
229        Self::Timeout {
230            operation: operation.into(),
231            timeout_ms,
232        }
233    }
234
235    /// Create an internal error
236    pub fn internal(message: impl Into<String>) -> Self {
237        Self::Internal {
238            message: message.into(),
239        }
240    }
241
242    /// Create an invalid state error
243    pub fn invalid_state(message: impl Into<String>) -> Self {
244        Self::InvalidState {
245            message: message.into(),
246        }
247    }
248
249    /// Create a not implemented error
250    pub fn not_implemented(feature: impl Into<String>) -> Self {
251        Self::NotImplemented {
252            feature: feature.into(),
253        }
254    }
255}
256
257/// Result type alias for trading operations
258pub type Result<T> = std::result::Result<T, TradingError>;
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_error_creation() {
266        let err = TradingError::market_data("Connection failed");
267        assert!(matches!(err, TradingError::MarketData { .. }));
268        assert!(err.to_string().contains("Market data error"));
269
270        let err = TradingError::strategy("momentum", "Invalid parameter");
271        assert!(matches!(err, TradingError::Strategy { .. }));
272        assert!(err.to_string().contains("momentum"));
273
274        let err = TradingError::risk_limit(
275            "Position too large",
276            RiskViolationType::PositionSizeExceeded,
277        );
278        assert!(matches!(err, TradingError::RiskLimit { .. }));
279    }
280
281    #[test]
282    fn test_error_with_source() {
283        use std::error::Error;
284
285        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
286        let err = TradingError::market_data_with_source("Failed to read data", io_error);
287
288        assert!(matches!(err, TradingError::MarketData { .. }));
289        assert!(err.source().is_some());
290    }
291
292    #[test]
293    fn test_not_found_error() {
294        let err = TradingError::not_found("order", "12345");
295        assert!(matches!(err, TradingError::NotFound { .. }));
296        assert!(err.to_string().contains("order"));
297        assert!(err.to_string().contains("12345"));
298    }
299
300    #[test]
301    fn test_timeout_error() {
302        let err = TradingError::timeout("place_order", 5000);
303        assert!(matches!(err, TradingError::Timeout { .. }));
304        assert!(err.to_string().contains("5000ms"));
305    }
306
307    #[test]
308    fn test_result_type() {
309        fn returns_result() -> Result<i32> {
310            Ok(42)
311        }
312
313        fn returns_error() -> Result<i32> {
314            Err(TradingError::internal("Test error"))
315        }
316
317        assert!(returns_result().is_ok());
318        assert!(returns_error().is_err());
319    }
320}