1use rust_decimal::Decimal;
7
8#[derive(Debug, thiserror::Error)]
10pub enum FinError {
11 #[error("Symbol '{0}' is invalid (empty or contains whitespace)")]
13 InvalidSymbol(String),
14
15 #[error("Price must be positive, got {0}")]
17 InvalidPrice(Decimal),
18
19 #[error("Quantity must be non-negative, got {0}")]
21 InvalidQuantity(Decimal),
22
23 #[error("Order book sequence mismatch: expected {expected}, got {got}")]
25 SequenceMismatch {
26 expected: u64,
28 got: u64,
30 },
31
32 #[error("No liquidity available for requested quantity {0}")]
34 InsufficientLiquidity(Decimal),
35
36 #[error("OHLCV bar invariant violated: {0}")]
38 BarInvariant(String),
39
40 #[error("Signal '{name}' not ready (requires {required} periods, have {have})")]
42 SignalNotReady {
43 name: String,
45 required: usize,
47 have: usize,
49 },
50
51 #[error("Position not found for symbol '{0}'")]
53 PositionNotFound(String),
54
55 #[error("Insufficient funds: need {need}, have {have}")]
57 InsufficientFunds {
58 need: Decimal,
60 have: Decimal,
62 },
63
64 #[error("Timeframe duration must be positive")]
66 InvalidTimeframe,
67
68 #[error("Arithmetic overflow in financial calculation")]
70 ArithmeticOverflow,
71
72 #[error("Inverted spread: best_bid {best_bid} >= best_ask {best_ask}")]
74 InvertedSpread {
75 best_bid: Decimal,
77 best_ask: Decimal,
79 },
80
81 #[error("Period must be at least 1, got {0}")]
83 InvalidPeriod(usize),
84
85 #[error("Invalid input: {0}")]
87 InvalidInput(String),
88}
89
90#[cold]
94pub(crate) fn invariant_broken() -> FinError {
95 FinError::InvalidInput("internal invariant broken (please report this as a fin-primitives bug)".to_owned())
96}
97
98impl FinError {
99 pub fn is_period_error(&self) -> bool {
101 matches!(self, FinError::InvalidPeriod(_))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_is_period_error_true_for_invalid_period() {
111 let e = FinError::InvalidPeriod(0);
112 assert!(e.is_period_error());
113 }
114
115 #[test]
116 fn test_is_period_error_false_for_other_errors() {
117 let e = FinError::InvalidSymbol("".to_owned());
118 assert!(!e.is_period_error());
119 }
120
121 #[test]
122 fn test_invalid_input_error_message() {
123 let e = FinError::InvalidInput("bad value".to_owned());
124 assert!(e.to_string().contains("bad value"));
125 }
126}