use crate::error::{GreeksError, OptionsError};
use positive::Positive;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum VolatilityError {
#[error("Invalid price {price}: {reason}")]
InvalidPrice {
price: Positive,
reason: String,
},
#[error("Invalid time {time}: {reason}")]
InvalidTime {
time: Positive,
reason: String,
},
#[error("Vega is zero, cannot calculate implied volatility")]
ZeroVega,
#[error(transparent)]
Greeks(#[from] GreeksError),
#[error(transparent)]
Options(#[from] OptionsError),
#[error("Error calculating vega: {reason}")]
VegaError {
reason: String,
},
#[error("Option error: {reason}")]
OptionError {
reason: String,
},
#[error("No convergence after {iterations} iterations. Last volatility: {last_volatility}")]
NoConvergence {
iterations: u32,
last_volatility: Positive,
},
#[error("implied volatility not found within search grid")]
IvNotFound,
#[error("no valid volatility sample produced a finite price")]
NoValidVolatility,
#[error("volatility simulation numerical failure: {reason}")]
NumericalFailure {
reason: String,
},
#[error("ATM implied volatility is not available: {source}")]
AtmIvUnavailable {
#[source]
source: Box<VolatilityError>,
},
#[error(transparent)]
Chain(Box<crate::error::ChainError>),
#[error(transparent)]
PositiveError(#[from] positive::PositiveError),
#[error(transparent)]
DecimalError(#[from] crate::error::DecimalError),
#[error("volatility non-finite {context}: {value}")]
NonFinite {
context: &'static str,
value: f64,
},
}
impl VolatilityError {
#[cold]
#[inline(never)]
#[must_use]
pub fn non_finite(context: &'static str, value: f64) -> Self {
VolatilityError::NonFinite { context, value }
}
}
impl From<crate::error::ChainError> for VolatilityError {
fn from(error: crate::error::ChainError) -> Self {
Self::Chain(Box::new(error))
}
}
#[cfg(test)]
mod tests_volatility_errors {
use super::*;
use crate::error::greeks::InputErrorKind;
use crate::error::{GreeksError, OptionsError};
use positive::pos_or_panic;
#[test]
fn test_invalid_price_error() {
let error = VolatilityError::InvalidPrice {
price: Positive::ZERO,
reason: "Price cannot be zero".to_string(),
};
assert_eq!(error.to_string(), "Invalid price 0: Price cannot be zero");
}
#[test]
fn test_invalid_time_error() {
let error = VolatilityError::InvalidTime {
time: Positive::ZERO,
reason: "Time cannot be zero".to_string(),
};
assert_eq!(error.to_string(), "Invalid time 0: Time cannot be zero");
}
#[test]
fn test_zero_vega_error() {
let error = VolatilityError::ZeroVega;
assert_eq!(
error.to_string(),
"Vega is zero, cannot calculate implied volatility"
);
}
#[test]
fn test_vega_error() {
let error = VolatilityError::VegaError {
reason: "Failed to calculate vega".to_string(),
};
assert_eq!(
error.to_string(),
"Error calculating vega: Failed to calculate vega"
);
}
#[test]
fn test_option_error() {
let error = VolatilityError::OptionError {
reason: "Invalid option parameters".to_string(),
};
assert_eq!(error.to_string(), "Option error: Invalid option parameters");
}
#[test]
fn test_no_convergence_error() {
let error = VolatilityError::NoConvergence {
iterations: 100,
last_volatility: pos_or_panic!(0.5),
};
assert_eq!(
error.to_string(),
"No convergence after 100 iterations. Last volatility: 0.5"
);
}
#[test]
fn test_from_greeks_error() {
let greeks_error = GreeksError::InputError(InputErrorKind::InvalidVolatility {
value: 0.0,
reason: "Volatility cannot be zero".to_string(),
});
let implied_vol_error: VolatilityError = greeks_error.into();
match implied_vol_error {
VolatilityError::Greeks(_) => {
}
_ => panic!("Wrong error variant"),
}
}
#[test]
fn test_from_options_error() {
let options_error = OptionsError::validation_error("strike", "Invalid option parameters");
let implied_vol_error: VolatilityError = options_error.into();
match implied_vol_error {
VolatilityError::Options(_) => {
}
_ => panic!("Wrong error variant"),
}
}
#[test]
fn test_iv_not_found() {
let error = VolatilityError::IvNotFound;
assert_eq!(
error.to_string(),
"implied volatility not found within search grid"
);
}
#[test]
fn test_no_valid_volatility() {
let error = VolatilityError::NoValidVolatility;
assert_eq!(
error.to_string(),
"no valid volatility sample produced a finite price"
);
}
#[test]
fn test_atm_iv_unavailable() {
let error = VolatilityError::AtmIvUnavailable {
source: Box::new(VolatilityError::IvNotFound),
};
assert!(
error
.to_string()
.contains("ATM implied volatility is not available")
);
}
#[test]
fn test_error_is_send() {
fn assert_send<T: Send>() {}
assert_send::<VolatilityError>();
}
#[test]
fn test_error_is_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<VolatilityError>();
}
}