Skip to main content

option_chain_orderbook/
error.rs

1//! Error types for the Option-Chain-OrderBook library.
2//!
3//! This module provides a unified error type for all operations in the library,
4//! using `thiserror` for ergonomic error handling.
5
6use rust_decimal::Decimal;
7use thiserror::Error;
8
9/// Result type alias using the library's error type.
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Main error type for the Option-Chain-OrderBook library.
13#[derive(Error, Debug)]
14pub enum Error {
15    /// Error when an option contract is not found.
16    #[error("option contract not found: {symbol}")]
17    ContractNotFound {
18        /// The symbol that was not found.
19        symbol: String,
20    },
21
22    /// Error when an expiration date is not found.
23    #[error("expiration not found: {expiration}")]
24    ExpirationNotFound {
25        /// The expiration date that was not found.
26        expiration: String,
27    },
28
29    /// Error when a strike price is not found.
30    #[error("strike not found: {strike}")]
31    StrikeNotFound {
32        /// The strike price that was not found.
33        strike: u64,
34    },
35
36    /// Error when an underlying is not found.
37    #[error("underlying not found: {underlying}")]
38    UnderlyingNotFound {
39        /// The underlying that was not found.
40        underlying: String,
41    },
42
43    /// Error when no data is available.
44    #[error("no data available: {message}")]
45    NoDataAvailable {
46        /// Description of what data is missing.
47        message: String,
48    },
49
50    /// Error when an order book operation fails.
51    #[error("order book error: {message}")]
52    OrderBookError {
53        /// Description of the order book error.
54        message: String,
55    },
56
57    /// Error when pricing calculation fails.
58    #[error("pricing error: {message}")]
59    PricingError {
60        /// Description of the pricing error.
61        message: String,
62    },
63
64    /// Error when Greeks calculation fails.
65    #[error("greeks calculation error: {message}")]
66    GreeksError {
67        /// Description of the Greeks error.
68        message: String,
69    },
70
71    /// Error when inventory limits are exceeded.
72    #[error(
73        "inventory limit exceeded: {limit_type} limit of {limit} exceeded with value {current}"
74    )]
75    InventoryLimitExceeded {
76        /// Type of limit that was exceeded.
77        limit_type: String,
78        /// The configured limit value.
79        limit: Decimal,
80        /// The current value that exceeded the limit.
81        current: Decimal,
82    },
83
84    /// Error when risk limits are breached.
85    #[error("risk limit breached: {limit_type}")]
86    RiskLimitBreached {
87        /// Type of risk limit that was breached.
88        limit_type: String,
89    },
90
91    /// Error when hedging operation fails.
92    #[error("hedging error: {message}")]
93    HedgingError {
94        /// Description of the hedging error.
95        message: String,
96    },
97
98    /// Error when quote generation fails.
99    #[error("quoting error: {message}")]
100    QuotingError {
101        /// Description of the quoting error.
102        message: String,
103    },
104
105    /// Error when market data is invalid or missing.
106    #[error("market data error: {message}")]
107    MarketDataError {
108        /// Description of the market data error.
109        message: String,
110    },
111
112    /// Error when exchange adapter operation fails.
113    #[error("adapter error for {exchange}: {message}")]
114    AdapterError {
115        /// The exchange where the error occurred.
116        exchange: String,
117        /// Description of the adapter error.
118        message: String,
119    },
120
121    /// Error when configuration is invalid.
122    #[error("configuration error: {message}")]
123    ConfigurationError {
124        /// Description of the configuration error.
125        message: String,
126    },
127
128    /// Error when a validation check fails.
129    #[error("validation error: {message}")]
130    ValidationError {
131        /// Description of the validation error.
132        message: String,
133    },
134
135    /// Error when serialization/deserialization fails.
136    #[error("serialization error: {0}")]
137    SerializationError(#[from] serde_json::Error),
138
139    /// Error when a decimal conversion fails.
140    #[error("decimal conversion error: {message}")]
141    DecimalError {
142        /// Description of the decimal error.
143        message: String,
144    },
145
146    /// Error from optionstratlib decimal operations.
147    #[error("optionstratlib decimal error: {0}")]
148    OptionStratLibDecimal(#[from] optionstratlib::error::decimal::DecimalError),
149}
150
151impl Error {
152    /// Creates a new contract not found error.
153    #[must_use]
154    pub fn contract_not_found(symbol: impl Into<String>) -> Self {
155        Self::ContractNotFound {
156            symbol: symbol.into(),
157        }
158    }
159
160    /// Creates a new expiration not found error.
161    #[must_use]
162    pub fn expiration_not_found(expiration: impl Into<String>) -> Self {
163        Self::ExpirationNotFound {
164            expiration: expiration.into(),
165        }
166    }
167
168    /// Creates a new strike not found error.
169    #[must_use]
170    pub fn strike_not_found(strike: u64) -> Self {
171        Self::StrikeNotFound { strike }
172    }
173
174    /// Creates a new underlying not found error.
175    #[must_use]
176    pub fn underlying_not_found(underlying: impl Into<String>) -> Self {
177        Self::UnderlyingNotFound {
178            underlying: underlying.into(),
179        }
180    }
181
182    /// Creates a new no data available error.
183    #[must_use]
184    pub fn no_data(message: impl Into<String>) -> Self {
185        Self::NoDataAvailable {
186            message: message.into(),
187        }
188    }
189
190    /// Creates a new order book error.
191    #[must_use]
192    pub fn orderbook(message: impl Into<String>) -> Self {
193        Self::OrderBookError {
194            message: message.into(),
195        }
196    }
197
198    /// Creates a new pricing error.
199    #[must_use]
200    pub fn pricing(message: impl Into<String>) -> Self {
201        Self::PricingError {
202            message: message.into(),
203        }
204    }
205
206    /// Creates a new Greeks error.
207    #[must_use]
208    pub fn greeks(message: impl Into<String>) -> Self {
209        Self::GreeksError {
210            message: message.into(),
211        }
212    }
213
214    /// Creates a new inventory limit exceeded error.
215    #[must_use]
216    pub fn inventory_limit_exceeded(
217        limit_type: impl Into<String>,
218        limit: Decimal,
219        current: Decimal,
220    ) -> Self {
221        Self::InventoryLimitExceeded {
222            limit_type: limit_type.into(),
223            limit,
224            current,
225        }
226    }
227
228    /// Creates a new risk limit breached error.
229    #[must_use]
230    pub fn risk_limit_breached(limit_type: impl Into<String>) -> Self {
231        Self::RiskLimitBreached {
232            limit_type: limit_type.into(),
233        }
234    }
235
236    /// Creates a new hedging error.
237    #[must_use]
238    pub fn hedging(message: impl Into<String>) -> Self {
239        Self::HedgingError {
240            message: message.into(),
241        }
242    }
243
244    /// Creates a new quoting error.
245    #[must_use]
246    pub fn quoting(message: impl Into<String>) -> Self {
247        Self::QuotingError {
248            message: message.into(),
249        }
250    }
251
252    /// Creates a new market data error.
253    #[must_use]
254    pub fn market_data(message: impl Into<String>) -> Self {
255        Self::MarketDataError {
256            message: message.into(),
257        }
258    }
259
260    /// Creates a new adapter error.
261    #[must_use]
262    pub fn adapter(exchange: impl Into<String>, message: impl Into<String>) -> Self {
263        Self::AdapterError {
264            exchange: exchange.into(),
265            message: message.into(),
266        }
267    }
268
269    /// Creates a new configuration error.
270    #[must_use]
271    pub fn configuration(message: impl Into<String>) -> Self {
272        Self::ConfigurationError {
273            message: message.into(),
274        }
275    }
276
277    /// Creates a new validation error.
278    #[must_use]
279    pub fn validation(message: impl Into<String>) -> Self {
280        Self::ValidationError {
281            message: message.into(),
282        }
283    }
284
285    /// Creates a new decimal error.
286    #[must_use]
287    pub fn decimal(message: impl Into<String>) -> Self {
288        Self::DecimalError {
289            message: message.into(),
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use rust_decimal_macros::dec;
298
299    #[test]
300    fn test_contract_not_found_error() {
301        let err = Error::contract_not_found("BTC-20240329-50000-C");
302        assert!(err.to_string().contains("BTC-20240329-50000-C"));
303    }
304
305    #[test]
306    fn test_inventory_limit_exceeded_error() {
307        let err = Error::inventory_limit_exceeded("delta", dec!(100000), dec!(150000));
308        let msg = err.to_string();
309        assert!(msg.contains("delta"));
310        assert!(msg.contains("100000"));
311        assert!(msg.contains("150000"));
312    }
313
314    #[test]
315    fn test_adapter_error() {
316        let err = Error::adapter("Deribit", "connection timeout");
317        let msg = err.to_string();
318        assert!(msg.contains("Deribit"));
319        assert!(msg.contains("connection timeout"));
320    }
321
322    #[test]
323    fn test_strike_not_found_error() {
324        let err = Error::strike_not_found(50000);
325        let msg = err.to_string();
326        assert!(msg.contains("50000"));
327    }
328
329    #[test]
330    fn test_underlying_not_found_error() {
331        let err = Error::underlying_not_found("BTC");
332        let msg = err.to_string();
333        assert!(msg.contains("BTC"));
334    }
335
336    #[test]
337    fn test_orderbook_error() {
338        let err = Error::orderbook("order rejected");
339        let msg = err.to_string();
340        assert!(msg.contains("order rejected"));
341    }
342
343    #[test]
344    fn test_pricing_error() {
345        let err = Error::pricing("invalid volatility");
346        let msg = err.to_string();
347        assert!(msg.contains("invalid volatility"));
348    }
349
350    #[test]
351    fn test_greeks_error() {
352        let err = Error::greeks("delta calculation failed");
353        let msg = err.to_string();
354        assert!(msg.contains("delta calculation failed"));
355    }
356
357    #[test]
358    fn test_risk_limit_breached_error() {
359        let err = Error::risk_limit_breached("max_delta");
360        let msg = err.to_string();
361        assert!(msg.contains("max_delta"));
362    }
363
364    #[test]
365    fn test_hedging_error() {
366        let err = Error::hedging("hedge order failed");
367        let msg = err.to_string();
368        assert!(msg.contains("hedge order failed"));
369    }
370
371    #[test]
372    fn test_quoting_error() {
373        let err = Error::quoting("spread too wide");
374        let msg = err.to_string();
375        assert!(msg.contains("spread too wide"));
376    }
377
378    #[test]
379    fn test_market_data_error() {
380        let err = Error::market_data("stale data");
381        let msg = err.to_string();
382        assert!(msg.contains("stale data"));
383    }
384
385    #[test]
386    fn test_configuration_error() {
387        let err = Error::configuration("missing API key");
388        let msg = err.to_string();
389        assert!(msg.contains("missing API key"));
390    }
391
392    #[test]
393    fn test_validation_error() {
394        let err = Error::validation("invalid quantity");
395        let msg = err.to_string();
396        assert!(msg.contains("invalid quantity"));
397    }
398
399    #[test]
400    fn test_decimal_error() {
401        let err = Error::decimal("overflow");
402        let msg = err.to_string();
403        assert!(msg.contains("overflow"));
404    }
405
406    #[test]
407    fn test_no_data_error() {
408        let err = Error::no_data("no strikes available");
409        let msg = err.to_string();
410        assert!(msg.contains("no strikes available"));
411    }
412
413    #[test]
414    fn test_expiration_not_found_error() {
415        let err = Error::expiration_not_found("2024-03-29");
416        let msg = err.to_string();
417        assert!(msg.contains("2024-03-29"));
418    }
419}