use crate::error::VolatilityError;
use crate::error::decimal;
use expiration_date::error::ExpirationDateError;
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("Delta neutrality error: {0}")]
DeltaNeutrality(DeltaNeutralityErrorKind),
#[error(transparent)]
ExpirationDate(#[from] ExpirationDateError),
#[error(transparent)]
Pricing(Box<crate::error::PricingError>),
#[error(transparent)]
PositiveError(#[from] positive::PositiveError),
#[error("greeks non-finite {context}: {value}")]
NonFinite {
context: &'static str,
value: f64,
},
}
impl From<crate::error::PricingError> for GreeksError {
#[inline]
fn from(err: crate::error::PricingError) -> Self {
GreeksError::Pricing(Box::new(err))
}
}
#[derive(Error, Debug)]
pub enum DeltaNeutralityErrorKind {
#[error("deltas cannot be zero for delta neutrality")]
ZeroDelta,
#[error("deltas cannot be equal for delta neutrality")]
EqualDeltas,
#[error("deltas must have opposite signs for delta neutrality")]
SameSignDeltas,
#[error("delta neutrality would require negative position sizes")]
NegativePositionSize,
#[error("could not achieve delta neutrality: residual above threshold")]
NotAchievable,
#[error("calculated sizes {calculated} do not match requested total size {expected}")]
SizeMismatch {
calculated: Positive,
expected: Positive,
},
#[error("no options found for delta-neutral calculation")]
EmptyOptions,
#[error("option delta per contract cannot be zero")]
OptionDeltaZero,
#[error("insufficient contracts to perform the requested delta adjustment")]
InsufficientContracts,
}
#[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 {
#[must_use]
#[cold]
#[inline(never)]
pub fn invalid_volatility(value: f64, reason: &str) -> Self {
GreeksError::InputError(InputErrorKind::InvalidVolatility {
value,
reason: reason.to_string(),
})
}
#[must_use]
#[cold]
#[inline(never)]
pub fn invalid_time(value: Positive, reason: &str) -> Self {
GreeksError::InputError(InputErrorKind::InvalidTime {
value,
reason: reason.to_string(),
})
}
#[must_use]
#[cold]
#[inline(never)]
pub fn delta_error(reason: &str) -> Self {
GreeksError::CalculationError(CalculationErrorKind::DeltaError {
reason: reason.to_string(),
})
}
#[cold]
#[inline(never)]
#[must_use]
pub fn non_finite(context: &'static str, value: f64) -> Self {
GreeksError::NonFinite { context, value }
}
}
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<DeltaNeutralityErrorKind> for GreeksError {
#[inline]
fn from(kind: DeltaNeutralityErrorKind) -> Self {
GreeksError::DeltaNeutrality(kind)
}
}
#[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_delta_neutrality() {
let error = GreeksError::DeltaNeutrality(DeltaNeutralityErrorKind::ZeroDelta);
assert_eq!(
format!("{error}"),
"Delta neutrality error: deltas cannot be zero for delta neutrality"
);
}
#[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_delta_neutrality_zero_delta() {
let error = GreeksError::DeltaNeutrality(DeltaNeutralityErrorKind::ZeroDelta);
assert_eq!(
format!("{error}"),
"Delta neutrality error: deltas cannot be zero for delta neutrality"
);
}
}