use crate::error::VolatilityError;
use crate::error::decimal;
use positive::Positive;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GreeksError {
#[error("Mathematical error: {0}")]
MathError(MathErrorKind),
#[error("Input validation error: {0}")]
InputError(InputErrorKind),
#[error("Greek calculation error: {0}")]
CalculationError(CalculationErrorKind),
#[error("Standard error: {0}")]
StdError(String),
#[error(transparent)]
PositiveError(#[from] positive::PositiveError),
}
#[derive(Error, Debug)]
pub enum MathErrorKind {
#[error("Division by zero")]
DivisionByZero,
#[error("Numerical overflow")]
Overflow,
#[error("Invalid domain value {value}: {reason}")]
InvalidDomain {
value: f64,
reason: String,
},
#[error("Failed to converge after {iterations} iterations (tolerance: {tolerance})")]
ConvergenceFailure {
iterations: usize,
tolerance: f64,
},
}
#[derive(Error, Debug)]
pub enum InputErrorKind {
#[error("Invalid volatility {value}: {reason}")]
InvalidVolatility {
value: f64,
reason: String,
},
#[error("Invalid time {value}: {reason}")]
InvalidTime {
value: Positive,
reason: String,
},
#[error("Invalid price {value}: {reason}")]
InvalidPrice {
value: f64,
reason: String,
},
#[error("Invalid rate {value}: {reason}")]
InvalidRate {
value: f64,
reason: String,
},
#[error("Invalid strike {value}: {reason}")]
InvalidStrike {
value: String,
reason: String,
},
}
#[derive(Error, Debug)]
pub enum CalculationErrorKind {
#[error("Delta calculation error: {reason}")]
DeltaError {
reason: String,
},
#[error("Gamma calculation error: {reason}")]
GammaError {
reason: String,
},
#[error("Theta calculation error: {reason}")]
ThetaError {
reason: String,
},
#[error("Vega calculation error: {reason}")]
VegaError {
reason: String,
},
#[error("Rho calculation error: {reason}")]
RhoError {
reason: String,
},
#[error("Vanna calculation error: {reason}")]
VannaError {
reason: String,
},
#[error("Vomma calculation error: {reason}")]
VommaError {
reason: String,
},
#[error("Veta calculation error: {reason}")]
VetaError {
reason: String,
},
#[error("Charm calculation error: {reason}")]
CharmError {
reason: String,
},
#[error("Color calculation error: {reason}")]
ColorError {
reason: String,
},
#[error(transparent)]
DecimalError(#[from] decimal::DecimalError),
}
pub type GreeksResult<T> = Result<T, GreeksError>;
impl GreeksError {
pub fn invalid_volatility(value: f64, reason: &str) -> Self {
GreeksError::InputError(InputErrorKind::InvalidVolatility {
value,
reason: reason.to_string(),
})
}
pub fn invalid_time(value: Positive, reason: &str) -> Self {
GreeksError::InputError(InputErrorKind::InvalidTime {
value,
reason: reason.to_string(),
})
}
pub fn delta_error(reason: &str) -> Self {
GreeksError::CalculationError(CalculationErrorKind::DeltaError {
reason: reason.to_string(),
})
}
}
impl From<decimal::DecimalError> for GreeksError {
fn from(error: decimal::DecimalError) -> Self {
GreeksError::CalculationError(CalculationErrorKind::DecimalError(error))
}
}
impl From<VolatilityError> for GreeksError {
fn from(error: VolatilityError) -> Self {
GreeksError::InputError(InputErrorKind::InvalidVolatility {
value: 0.0,
reason: error.to_string(),
})
}
}
impl From<Box<dyn std::error::Error>> for GreeksError {
fn from(error: Box<dyn std::error::Error>) -> Self {
GreeksError::StdError(error.to_string())
}
}
impl From<String> for GreeksError {
fn from(s: String) -> Self {
GreeksError::StdError(s)
}
}
impl From<&str> for GreeksError {
fn from(s: &str) -> Self {
GreeksError::StdError(s.to_string())
}
}
impl From<expiration_date::error::ExpirationDateError> for GreeksError {
fn from(err: expiration_date::error::ExpirationDateError) -> Self {
GreeksError::StdError(err.to_string())
}
}
#[cfg(test)]
mod tests_error_greeks {
use super::*;
#[test]
fn test_invalid_volatility_error_creation() {
let error = GreeksError::invalid_volatility(-0.5, "Volatility cannot be negative");
match error {
GreeksError::InputError(InputErrorKind::InvalidVolatility { value, reason }) => {
assert_eq!(value, -0.5);
assert_eq!(reason, "Volatility cannot be negative");
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_delta_error_creation() {
let error = GreeksError::delta_error("Failed to calculate delta");
match error {
GreeksError::CalculationError(CalculationErrorKind::DeltaError { reason }) => {
assert_eq!(reason, "Failed to calculate delta");
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_math_error_display() {
let error = GreeksError::MathError(MathErrorKind::DivisionByZero);
assert_eq!(error.to_string(), "Mathematical error: Division by zero");
let error = GreeksError::MathError(MathErrorKind::InvalidDomain {
value: 1.5,
reason: "Value out of range".to_string(),
});
assert_eq!(
error.to_string(),
"Mathematical error: Invalid domain value 1.5: Value out of range"
);
}
#[test]
fn test_input_error_display() {
let error = GreeksError::InputError(InputErrorKind::InvalidPrice {
value: -100.0,
reason: "Price cannot be negative".to_string(),
});
assert_eq!(
error.to_string(),
"Input validation error: Invalid price -100: Price cannot be negative"
);
let error = GreeksError::InputError(InputErrorKind::InvalidRate {
value: 2.5,
reason: "Rate must be between 0 and 1".to_string(),
});
assert_eq!(
error.to_string(),
"Input validation error: Invalid rate 2.5: Rate must be between 0 and 1"
);
}
#[test]
fn test_calculation_error_display() {
let error = GreeksError::CalculationError(CalculationErrorKind::GammaError {
reason: "Invalid input parameters".to_string(),
});
assert_eq!(
error.to_string(),
"Greek calculation error: Gamma calculation error: Invalid input parameters"
);
let error = GreeksError::CalculationError(CalculationErrorKind::VegaError {
reason: "Calculation overflow".to_string(),
});
assert_eq!(
error.to_string(),
"Greek calculation error: Vega calculation error: Calculation overflow"
);
}
#[test]
fn test_convergence_failure_display() {
let error = GreeksError::MathError(MathErrorKind::ConvergenceFailure {
iterations: 1000,
tolerance: 0.0001,
});
assert_eq!(
error.to_string(),
"Mathematical error: Failed to converge after 1000 iterations (tolerance: 0.0001)"
);
}
#[test]
fn test_result_type() {
fn test_function() -> GreeksResult<f64> {
Err(GreeksError::delta_error("Test error"))
}
let result = test_function();
assert!(result.is_err());
match result {
Err(GreeksError::CalculationError(CalculationErrorKind::DeltaError { reason })) => {
assert_eq!(reason, "Test error");
}
_ => panic!("Wrong error type"),
}
}
#[test]
fn test_debug_implementation() {
let error = GreeksError::delta_error("Test error");
let debug_string = format!("{error:?}");
assert!(debug_string.contains("DeltaError"));
assert!(debug_string.contains("Test error"));
}
}
#[cfg(test)]
mod tests_error_greeks_extended {
use super::*;
use crate::error::decimal::DecimalError::InvalidPrecision;
use positive::pos_or_panic;
#[test]
fn test_greeks_error_std_error() {
let error = GreeksError::StdError("An error occurred".to_string());
assert_eq!(format!("{error}"), "Standard error: An error occurred");
}
#[test]
fn test_math_error_overflow() {
let error = MathErrorKind::Overflow;
assert_eq!(format!("{error}"), "Numerical overflow");
}
#[test]
fn test_input_error_invalid_volatility() {
let error = InputErrorKind::InvalidVolatility {
value: 0.5,
reason: "Out of bounds".to_string(),
};
assert_eq!(format!("{error}"), "Invalid volatility 0.5: Out of bounds");
}
#[test]
fn test_calculation_error_delta() {
let error = CalculationErrorKind::DeltaError {
reason: "Unable to compute delta".to_string(),
};
assert_eq!(
format!("{error}"),
"Delta calculation error: Unable to compute delta"
);
}
#[test]
fn test_calculation_error_theta() {
let error = CalculationErrorKind::ThetaError {
reason: "Negative time decay".to_string(),
};
assert_eq!(
format!("{error}"),
"Theta calculation error: Negative time decay"
);
}
#[test]
fn test_calculation_error_rho() {
let error = CalculationErrorKind::RhoError {
reason: "Interest rate too high".to_string(),
};
assert_eq!(
format!("{error}"),
"Rho calculation error: Interest rate too high"
);
}
#[test]
fn test_calculation_error_vanna() {
let error = CalculationErrorKind::VannaError {
reason: "Unable to compute vanna".to_string(),
};
assert_eq!(
format!("{error}"),
"Vanna calculation error: Unable to compute vanna"
);
}
#[test]
fn test_calculation_error_vomma() {
let error = CalculationErrorKind::VommaError {
reason: "Unable to compute vomma".to_string(),
};
assert_eq!(
format!("{error}"),
"Vomma calculation error: Unable to compute vomma"
);
}
#[test]
fn test_calculation_error_veta() {
let error = CalculationErrorKind::VetaError {
reason: "Unable to compute veta".to_string(),
};
assert_eq!(
format!("{error}"),
"Veta calculation error: Unable to compute veta"
);
}
#[test]
fn test_calculation_error_charm() {
let error = CalculationErrorKind::CharmError {
reason: "Unable to compute charm".to_string(),
};
assert_eq!(
format!("{error}"),
"Charm calculation error: Unable to compute charm"
);
}
#[test]
fn test_calculation_error_color() {
let error = CalculationErrorKind::ColorError {
reason: "Unable to compute color".to_string(),
};
assert_eq!(
format!("{error}"),
"Color calculation error: Unable to compute color"
);
}
#[test]
fn test_calculation_error_decimal() {
use crate::error::decimal::DecimalError as DecErr;
let decimal_error = DecErr::InvalidPrecision {
precision: 0,
reason: "Precision error".to_string(),
};
let error =
GreeksError::CalculationError(CalculationErrorKind::DecimalError(decimal_error));
let error_string = format!("{error}");
assert!(error_string.contains("Invalid decimal precision"));
}
#[test]
fn test_invalid_time_constructor() {
let error = GreeksError::invalid_time(pos_or_panic!(5.0), "Time must be positive");
assert_eq!(
format!("{error}"),
"Input validation error: Invalid time 5: Time must be positive"
);
}
#[test]
fn test_decimal_error_conversion() {
let decimal_error = InvalidPrecision {
precision: 0,
reason: "Precision lost".to_string(),
};
let error: GreeksError = decimal_error.into();
match error {
GreeksError::CalculationError(CalculationErrorKind::DecimalError(_)) => {
}
_ => panic!("Wrong error variant"),
}
}
#[test]
fn test_implied_volatility_error_conversion() {
let iv_error = VolatilityError::ZeroVega;
let error: GreeksError = iv_error.into();
assert_eq!(
format!("{error}"),
"Input validation error: Invalid volatility 0: Vega is zero, cannot calculate implied volatility"
);
}
#[test]
fn test_boxed_error_conversion() {
let boxed_error: Box<dyn std::error::Error> =
Box::new(std::io::Error::other("Some IO error"));
let error: GreeksError = boxed_error.into();
assert_eq!(format!("{error}"), "Standard error: Some IO error");
}
}