use crate::error::SviError;
use optionstratlib_core::error::DecimalError;
use optionstratlib_core::model::{Positive, PositiveError};
use rust_decimal::Decimal;
use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum StaticArbitrageError {
#[error("static arbitrage grid is empty, so nothing would be tested")]
EmptyGrid,
#[error("calendar check needs at least {required} slices, got {got}")]
TooFewSlices {
got: usize,
required: usize,
},
#[error("two slices share the expiry {time_to_expiry}, in years")]
DuplicateExpiry {
time_to_expiry: Positive,
},
#[error("invalid slice with expiry {time_to_expiry} and forward {forward}: {reason}")]
InvalidSlice {
time_to_expiry: Positive,
forward: Positive,
reason: &'static str,
},
#[error("no strike for forward {forward} at log moneyness {log_moneyness}")]
StrikeOutOfRange {
forward: Positive,
log_moneyness: Decimal,
},
#[error("slice could not be evaluated at log moneyness {log_moneyness}: {reason}")]
SliceEvaluation {
log_moneyness: Decimal,
reason: String,
},
#[error(transparent)]
Svi(#[from] SviError),
#[error(transparent)]
Decimal(#[from] DecimalError),
#[error(transparent)]
Positive(#[from] PositiveError),
}
#[cfg(test)]
mod tests_static_arbitrage_error {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_static_arbitrage_error_too_few_slices_names_both_counts() {
let message = StaticArbitrageError::TooFewSlices {
got: 1,
required: 2,
}
.to_string();
assert!(message.contains("at least 2"), "{message}");
assert!(message.contains("got 1"), "{message}");
}
#[test]
fn test_static_arbitrage_error_duplicate_expiry_names_the_expiry() {
let message = StaticArbitrageError::DuplicateExpiry {
time_to_expiry: Positive::new_decimal(dec!(0.25)).unwrap(),
}
.to_string();
assert!(message.contains("0.25"), "{message}");
}
#[test]
fn test_static_arbitrage_error_strike_out_of_range_names_both_values() {
let message = StaticArbitrageError::StrikeOutOfRange {
forward: Positive::HUNDRED,
log_moneyness: dec!(1000),
}
.to_string();
assert!(message.contains("100"), "{message}");
assert!(message.contains("1000"), "{message}");
}
#[test]
fn test_static_arbitrage_error_lower_errors_convert_with_from() {
let decimal: StaticArbitrageError = DecimalError::arithmetic_error("arb", "boom").into();
assert!(matches!(decimal, StaticArbitrageError::Decimal(_)));
let svi: StaticArbitrageError = SviError::ZeroForward.into();
assert!(matches!(svi, StaticArbitrageError::Svi(_)));
}
}