1use thiserror::Error;
7
8#[derive(Debug, Error)]
13pub enum TradingError {
14 #[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("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("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 #[error("Risk limit exceeded: {message}")]
42 RiskLimit {
43 message: String,
44 violation_type: RiskViolationType,
45 },
46
47 #[error("Configuration error: {message}")]
49 Config {
50 message: String,
51 #[source]
52 source: Option<Box<dyn std::error::Error + Send + Sync>>,
53 },
54
55 #[error("Portfolio error: {message}")]
57 Portfolio { message: String },
58
59 #[error("Network error: {message}")]
61 Network {
62 message: String,
63 #[source]
64 source: Option<Box<dyn std::error::Error + Send + Sync>>,
65 },
66
67 #[error("Validation error: {message}")]
69 Validation { message: String },
70
71 #[error("Not found: {resource_type} '{resource_id}'")]
73 NotFound {
74 resource_type: String,
75 resource_id: String,
76 },
77
78 #[error("Operation timed out after {timeout_ms}ms: {operation}")]
80 Timeout { operation: String, timeout_ms: u64 },
81
82 #[error("Authentication error: {message}")]
84 Auth { message: String },
85
86 #[error("Database error: {message}")]
88 Database {
89 message: String,
90 #[source]
91 source: Option<Box<dyn std::error::Error + Send + Sync>>,
92 },
93
94 #[error("Serialization error: {message}")]
96 Serialization {
97 message: String,
98 #[source]
99 source: Option<Box<dyn std::error::Error + Send + Sync>>,
100 },
101
102 #[error("Internal error: {message}")]
104 Internal { message: String },
105
106 #[error("Invalid state: {message}")]
108 InvalidState { message: String },
109
110 #[error("Not implemented: {feature}")]
112 NotImplemented { feature: String },
113
114 #[error(transparent)]
116 Other(#[from] anyhow::Error),
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum RiskViolationType {
122 PositionSizeExceeded,
124 DailyLossLimitExceeded,
126 MaxDrawdownExceeded,
128 LeverageExceeded,
130 SectorConcentrationExceeded,
132 CorrelationExceeded,
134}
135
136impl TradingError {
137 pub fn market_data(message: impl Into<String>) -> Self {
139 Self::MarketData {
140 message: message.into(),
141 source: None,
142 }
143 }
144
145 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 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 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 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 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 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 pub fn config(message: impl Into<String>) -> Self {
206 Self::Config {
207 message: message.into(),
208 source: None,
209 }
210 }
211
212 pub fn validation(message: impl Into<String>) -> Self {
214 Self::Validation {
215 message: message.into(),
216 }
217 }
218
219 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 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 pub fn internal(message: impl Into<String>) -> Self {
237 Self::Internal {
238 message: message.into(),
239 }
240 }
241
242 pub fn invalid_state(message: impl Into<String>) -> Self {
244 Self::InvalidState {
245 message: message.into(),
246 }
247 }
248
249 pub fn not_implemented(feature: impl Into<String>) -> Self {
251 Self::NotImplemented {
252 feature: feature.into(),
253 }
254 }
255}
256
257pub 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}