use rust_decimal::Decimal;
#[derive(Debug, thiserror::Error)]
pub enum FinError {
#[error("Symbol '{0}' is invalid (empty or contains whitespace)")]
InvalidSymbol(String),
#[error("Price must be positive, got {0}")]
InvalidPrice(Decimal),
#[error("Quantity must be non-negative, got {0}")]
InvalidQuantity(Decimal),
#[error("Order book sequence mismatch: expected {expected}, got {got}")]
SequenceMismatch {
expected: u64,
got: u64,
},
#[error("No liquidity available for requested quantity {0}")]
InsufficientLiquidity(Decimal),
#[error("OHLCV bar invariant violated: {0}")]
BarInvariant(String),
#[error("Signal '{name}' not ready (requires {required} periods, have {have})")]
SignalNotReady {
name: String,
required: usize,
have: usize,
},
#[error("Position not found for symbol '{0}'")]
PositionNotFound(String),
#[error("Insufficient funds: need {need}, have {have}")]
InsufficientFunds {
need: Decimal,
have: Decimal,
},
#[error("Timeframe duration must be positive")]
InvalidTimeframe,
#[error("Arithmetic overflow in financial calculation")]
ArithmeticOverflow,
#[error("Inverted spread: best_bid {best_bid} >= best_ask {best_ask}")]
InvertedSpread {
best_bid: Decimal,
best_ask: Decimal,
},
#[error("Period must be at least 1, got {0}")]
InvalidPeriod(usize),
#[error("Invalid input: {0}")]
InvalidInput(String),
}
impl FinError {
pub fn is_period_error(&self) -> bool {
matches!(self, FinError::InvalidPeriod(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_period_error_true_for_invalid_period() {
let e = FinError::InvalidPeriod(0);
assert!(e.is_period_error());
}
#[test]
fn test_is_period_error_false_for_other_errors() {
let e = FinError::InvalidSymbol("".to_owned());
assert!(!e.is_period_error());
}
#[test]
fn test_invalid_input_error_message() {
let e = FinError::InvalidInput("bad value".to_owned());
assert!(e.to_string().contains("bad value"));
}
}