use crate::error::strategies::{BreakEvenErrorKind, ProfitLossErrorKind};
use crate::error::{GreeksError, OperationErrorKind, StrategyError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProbabilityError {
#[error("Probability calculation error: {0}")]
CalculationError(ProbabilityCalculationErrorKind),
#[error("Profit/loss range error: {0}")]
RangeError(ProfitLossRangeErrorKind),
#[error("Expiration error: {0}")]
ExpirationError(ExpirationErrorKind),
#[error("Price error: {0}")]
PriceError(PriceErrorKind),
#[error("Standard error: {0}")]
StdError(String),
#[error("No positions available: {0}")]
NoPositions(String),
#[error(transparent)]
PositiveError(#[from] positive::PositiveError),
}
#[derive(Error, Debug)]
pub enum ProbabilityCalculationErrorKind {
#[error("Invalid probability value {value}: {reason}")]
InvalidProbability {
value: f64,
reason: String,
},
#[error("Expected value calculation error: {reason}")]
ExpectedValueError {
reason: String,
},
#[error("Volatility adjustment error: {reason}")]
VolatilityAdjustmentError {
reason: String,
},
#[error("Price trend error: {reason}")]
TrendError {
reason: String,
},
}
#[derive(Error, Debug)]
pub enum ProfitLossRangeErrorKind {
#[error("Invalid profit range '{range}': {reason}")]
InvalidProfitRange {
range: String,
reason: String,
},
#[error("Invalid loss range '{range}': {reason}")]
InvalidLossRange {
range: String,
reason: String,
},
#[error("Invalid break-even points: {reason}")]
InvalidBreakEvenPoints {
reason: String,
},
}
#[derive(Error, Debug)]
pub enum ExpirationErrorKind {
#[error("Invalid expiration: {reason}")]
InvalidExpiration {
reason: String,
},
#[error("Invalid risk-free rate {rate:?}: {reason}")]
InvalidRiskFreeRate {
rate: Option<f64>,
reason: String,
},
}
#[derive(Error, Debug)]
pub enum PriceErrorKind {
#[error("Invalid underlying price {price}: {reason}")]
InvalidUnderlyingPrice {
price: f64,
reason: String,
},
#[error("Invalid price range '{range}': {reason}")]
InvalidPriceRange {
range: String,
reason: String,
},
}
impl From<Box<dyn std::error::Error>> for ProbabilityError {
fn from(error: Box<dyn std::error::Error>) -> Self {
ProbabilityError::StdError(error.to_string())
}
}
impl From<GreeksError> for ProbabilityError {
fn from(error: GreeksError) -> Self {
ProbabilityError::StdError(error.to_string())
}
}
impl From<crate::error::PricingError> for ProbabilityError {
fn from(error: crate::error::PricingError) -> Self {
ProbabilityError::StdError(error.to_string())
}
}
impl From<crate::error::DecimalError> for ProbabilityError {
fn from(error: crate::error::DecimalError) -> Self {
ProbabilityError::StdError(error.to_string())
}
}
pub type ProbabilityResult<T> = Result<T, ProbabilityError>;
impl From<String> for ProbabilityError {
fn from(msg: String) -> Self {
ProbabilityError::CalculationError(ProbabilityCalculationErrorKind::ExpectedValueError {
reason: msg,
})
}
}
impl From<&str> for ProbabilityError {
fn from(msg: &str) -> Self {
ProbabilityError::CalculationError(ProbabilityCalculationErrorKind::ExpectedValueError {
reason: msg.to_string(),
})
}
}
impl From<StrategyError> for ProbabilityError {
fn from(error: StrategyError) -> Self {
match error {
StrategyError::ProfitLossError(kind) => match kind {
ProfitLossErrorKind::MaxProfitError { reason }
| ProfitLossErrorKind::MaxLossError { reason }
| ProfitLossErrorKind::ProfitRangeError { reason } => {
ProbabilityError::from(reason)
}
},
StrategyError::PriceError(kind) => match kind {
crate::error::strategies::PriceErrorKind::InvalidUnderlyingPrice { reason }
| crate::error::strategies::PriceErrorKind::InvalidPriceRange {
start: _,
end: _,
reason,
} => ProbabilityError::from(reason),
},
StrategyError::BreakEvenError(kind) => match kind {
BreakEvenErrorKind::CalculationError { reason } => ProbabilityError::from(reason),
BreakEvenErrorKind::NoBreakEvenPoints => {
ProbabilityError::from("No break-even points found".to_string())
}
},
StrategyError::OperationError(kind) => match kind {
OperationErrorKind::NotSupported {
operation,
reason: strategy_type,
} => ProbabilityError::from(format!(
"Operation '{operation}' not supported for strategy '{strategy_type}'"
)),
OperationErrorKind::InvalidParameters { operation, reason } => {
ProbabilityError::from(format!(
"Invalid parameters for operation '{operation}': {reason}"
))
}
},
StrategyError::StdError { reason: msg } => ProbabilityError::StdError(msg),
StrategyError::NotImplemented => {
ProbabilityError::StdError("Strategy not implemented".to_string())
}
StrategyError::GreeksError(err) => ProbabilityError::StdError(err.to_string()),
StrategyError::PositiveError(err) => ProbabilityError::StdError(err.to_string()),
}
}
}
impl From<expiration_date::error::ExpirationDateError> for ProbabilityError {
fn from(err: expiration_date::error::ExpirationDateError) -> Self {
ProbabilityError::StdError(err.to_string())
}
}
impl From<OperationErrorKind> for ProbabilityError {
fn from(error: OperationErrorKind) -> Self {
match error {
OperationErrorKind::InvalidParameters { operation, reason } => {
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError {
reason: format!("Invalid parameters for operation '{operation}': {reason}"),
},
)
}
OperationErrorKind::NotSupported { operation, reason } => {
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError {
reason: format!("Operation '{operation}' not supported: {reason}"),
},
)
}
}
}
}
impl ProbabilityError {
pub fn invalid_probability(value: f64, reason: &str) -> Self {
ProbabilityError::CalculationError(ProbabilityCalculationErrorKind::InvalidProbability {
value,
reason: reason.to_string(),
})
}
pub fn invalid_profit_range(range: &str, reason: &str) -> Self {
ProbabilityError::RangeError(ProfitLossRangeErrorKind::InvalidProfitRange {
range: range.to_string(),
reason: reason.to_string(),
})
}
pub fn invalid_expiration(reason: &str) -> Self {
ProbabilityError::ExpirationError(ExpirationErrorKind::InvalidExpiration {
reason: reason.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_probability_error() {
let error = ProbabilityError::invalid_probability(1.2, "Probability cannot exceed 1.0");
assert!(matches!(
error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::InvalidProbability { .. }
)
));
}
#[test]
fn test_string_conversion() {
let error = ProbabilityError::from("Test error message".to_string());
assert!(matches!(
error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
#[test]
fn test_error_formatting() {
let error = ProbabilityError::invalid_probability(1.2, "Probability cannot exceed 1.0");
let error_string = error.to_string();
assert!(error_string.contains("Invalid probability"));
assert!(error_string.contains("1.2"));
assert!(error_string.contains("Probability cannot exceed 1.0"));
}
#[test]
fn test_expiration_error_display() {
let error = ProbabilityError::ExpirationError(ExpirationErrorKind::InvalidExpiration {
reason: "Cannot be in the past".to_string(),
});
assert!(error.to_string().contains("Cannot be in the past"));
}
#[test]
fn test_price_error_display() {
let error = ProbabilityError::PriceError(PriceErrorKind::InvalidUnderlyingPrice {
price: -10.0,
reason: "Price cannot be negative".to_string(),
});
assert!(error.to_string().contains("Price cannot be negative"));
assert!(error.to_string().contains("-10"));
}
#[test]
fn test_str_conversion() {
let error = ProbabilityError::from("Test error message");
assert!(matches!(
error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
}
#[cfg(test)]
mod tests_extended {
use super::*;
use crate::error::strategies;
#[test]
fn test_invalid_probability_error() {
let error = ProbabilityError::invalid_probability(1.2, "Probability cannot exceed 1.0");
assert!(matches!(
error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::InvalidProbability { .. }
)
));
}
#[test]
fn test_string_conversion() {
let error = ProbabilityError::from("Test error message".to_string());
assert!(matches!(
error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
#[test]
fn test_error_formatting() {
let error = ProbabilityError::invalid_probability(1.2, "Probability cannot exceed 1.0");
let error_string = error.to_string();
assert!(error_string.contains("Invalid probability"));
assert!(error_string.contains("1.2"));
assert!(error_string.contains("Probability cannot exceed 1.0"));
}
#[test]
fn test_profit_loss_range_error_display() {
let error = ProbabilityError::RangeError(ProfitLossRangeErrorKind::InvalidProfitRange {
range: "100-200".to_string(),
reason: "Invalid range".to_string(),
});
assert!(error.to_string().contains("100-200"));
assert!(error.to_string().contains("Invalid range"));
let error =
ProbabilityError::RangeError(ProfitLossRangeErrorKind::InvalidBreakEvenPoints {
reason: "No break-even points found".to_string(),
});
assert!(error.to_string().contains("No break-even points found"));
}
#[test]
fn test_calculation_error_display() {
let error = ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::VolatilityAdjustmentError {
reason: "Invalid volatility adjustment".to_string(),
},
);
assert!(error.to_string().contains("Invalid volatility adjustment"));
let error =
ProbabilityError::CalculationError(ProbabilityCalculationErrorKind::TrendError {
reason: "Invalid trend".to_string(),
});
assert!(error.to_string().contains("Invalid trend"));
}
#[test]
fn test_expiration_error() {
let error = ProbabilityError::ExpirationError(ExpirationErrorKind::InvalidRiskFreeRate {
rate: Some(0.05),
reason: "Rate out of bounds".to_string(),
});
assert!(error.to_string().contains("0.05"));
assert!(error.to_string().contains("Rate out of bounds"));
}
#[test]
fn test_strategy_error_conversion() {
let strategy_error = StrategyError::ProfitLossError(ProfitLossErrorKind::MaxProfitError {
reason: "Invalid max profit".to_string(),
});
let prob_error: ProbabilityError = strategy_error.into();
assert!(matches!(
prob_error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
#[test]
fn test_strategy_break_even_error_conversion() {
let strategy_error = StrategyError::BreakEvenError(BreakEvenErrorKind::NoBreakEvenPoints);
let prob_error: ProbabilityError = strategy_error.into();
assert!(matches!(
prob_error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
#[test]
fn test_strategy_operation_error_conversion() {
let strategy_error = StrategyError::OperationError(OperationErrorKind::NotSupported {
operation: "test".to_string(),
reason: "TestStrategy".to_string(),
});
let prob_error: ProbabilityError = strategy_error.into();
assert!(matches!(
prob_error,
ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError { .. }
)
));
}
#[test]
fn test_box_dyn_error_conversion() {
let io_error = std::io::Error::other("test error");
let boxed_error: Box<dyn std::error::Error> = Box::new(io_error);
let prob_error = ProbabilityError::from(boxed_error);
assert!(matches!(prob_error, ProbabilityError::StdError(..)));
}
#[test]
fn test_no_positions_error() {
let error = ProbabilityError::NoPositions("No positions found".to_string());
assert!(error.to_string().contains("No positions found"));
}
#[test]
fn test_probability_result_type() {
let success: ProbabilityResult<f64> = Ok(0.5);
let failure: ProbabilityResult<f64> =
Err(ProbabilityError::invalid_probability(1.5, "Value too high"));
assert!(success.is_ok());
assert!(failure.is_err());
}
#[test]
fn test_probability_error_std_error() {
let error = ProbabilityError::StdError("Calculation failed".to_string());
assert_eq!(format!("{error}"), "Standard error: Calculation failed");
}
#[test]
fn test_price_error_invalid_price_range() {
let error = PriceErrorKind::InvalidPriceRange {
range: "0-100".to_string(),
reason: "Negative values are not allowed".to_string(),
};
assert_eq!(
format!("{error}"),
"Invalid price range '0-100': Negative values are not allowed"
);
}
#[test]
fn test_profit_loss_range_error_invalid_loss_range() {
let error = ProfitLossRangeErrorKind::InvalidLossRange {
range: "-50-0".to_string(),
reason: "Range must be positive".to_string(),
};
assert_eq!(
format!("{error}"),
"Invalid loss range '-50-0': Range must be positive"
);
}
#[test]
fn test_profit_loss_error_max_loss_error() {
let error = ProfitLossErrorKind::MaxLossError {
reason: "Maximum loss exceeded".to_string(),
};
assert_eq!(
format!("{error}"),
"Maximum loss calculation error: Maximum loss exceeded"
);
}
#[test]
fn test_strategy_error_price_error_invalid_price_range() {
let error = StrategyError::PriceError(strategies::PriceErrorKind::InvalidPriceRange {
start: 0.0,
end: 100.0,
reason: "Out of bounds".to_string(),
});
assert!(matches!(error, StrategyError::PriceError(_)));
}
#[test]
fn test_break_even_error_calculation_error() {
let error = StrategyError::BreakEvenError(BreakEvenErrorKind::CalculationError {
reason: "Failed to calculate break-even point".to_string(),
});
let converted_error: ProbabilityError = error.into();
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Failed to calculate break-even point"
);
}
#[test]
fn test_operation_error_invalid_parameters() {
let error = OperationErrorKind::InvalidParameters {
operation: "Calculate P/L".to_string(),
reason: "Invalid input values".to_string(),
};
let converted_error: ProbabilityError = error.into();
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Invalid parameters for operation 'Calculate P/L': Invalid input values"
);
}
#[test]
fn test_strategy_error_std_error() {
let error = StrategyError::StdError {
reason: "General strategy failure".to_string(),
};
let converted_error: ProbabilityError = error.into();
assert_eq!(
format!("{converted_error}"),
"Standard error: General strategy failure"
);
}
#[test]
fn test_invalid_profit_range_constructor() {
let error = ProbabilityError::invalid_profit_range("0-100", "Range mismatch");
assert_eq!(
format!("{error}"),
"Profit/loss range error: Invalid profit range '0-100': Range mismatch"
);
}
#[test]
fn test_invalid_expiration_constructor() {
let error = ProbabilityError::invalid_expiration("Expiration date invalid");
assert_eq!(
format!("{error}"),
"Expiration error: Invalid expiration: Expiration date invalid"
);
}
#[test]
fn test_profit_loss_error_max_loss_error_bis() {
let error = ProfitLossErrorKind::MaxLossError {
reason: "Exceeded allowed loss".to_string(),
};
assert_eq!(
format!("{error}"),
"Maximum loss calculation error: Exceeded allowed loss"
);
}
#[test]
fn test_profit_loss_error_profit_range_error() {
let error = ProfitLossErrorKind::ProfitRangeError {
reason: "Profit range mismatch".to_string(),
};
assert_eq!(
format!("{error}"),
"Profit range calculation error: Profit range mismatch"
);
}
#[test]
fn test_strategy_error_price_error_invalid_underlying_price() {
let error = StrategyError::PriceError(
crate::error::strategies::PriceErrorKind::InvalidUnderlyingPrice {
reason: "Underlying price is negative".to_string(),
},
);
let converted_error: ProbabilityError = ProbabilityError::from(error);
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Underlying price is negative"
);
}
#[test]
fn test_strategy_error_price_error_invalid_price_range_bis() {
let error = StrategyError::PriceError(
crate::error::strategies::PriceErrorKind::InvalidPriceRange {
start: 0.0,
end: 50.0,
reason: "Start price is greater than end price".to_string(),
},
);
let converted_error: ProbabilityError = ProbabilityError::from(error);
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Start price is greater than end price"
);
}
#[test]
fn test_operation_error_invalid_parameters_bis() {
let error = OperationErrorKind::InvalidParameters {
operation: "Calculate P/L".to_string(),
reason: "Invalid input values".to_string(),
};
let converted_error: ProbabilityError =
ProbabilityError::from(format!("Invalid parameters for operation {error}"));
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Invalid parameters for operation Invalid parameters for operation 'Calculate P/L': Invalid input values"
);
}
#[test]
fn test_operation_error_not_supported() {
let error = OperationErrorKind::NotSupported {
operation: "Hedging".to_string(),
reason: "Operation not implemented".to_string(),
};
let converted_error = ProbabilityError::CalculationError(
ProbabilityCalculationErrorKind::ExpectedValueError {
reason: format!("Operation {error}"),
},
);
assert_eq!(
format!("{converted_error}"),
"Probability calculation error: Expected value calculation error: Operation Operation 'Hedging' is not supported for strategy 'Operation not implemented'"
);
}
}