use crate::error::common::OperationErrorKind;
use crate::error::{GreeksError, OptionsError, PositionError, SimulationError, TradeError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StrategyError {
#[error("Price error: {0}")]
PriceError(PriceErrorKind),
#[error("Break-even error: {0}")]
BreakEvenError(BreakEvenErrorKind),
#[error("Profit/loss error: {0}")]
ProfitLossError(ProfitLossErrorKind),
#[error("Operation error: {0}")]
OperationError(OperationErrorKind),
#[error("Standard error: {reason}")]
StdError {
reason: String,
},
#[error("Not implemented")]
NotImplemented,
#[error(transparent)]
GreeksError(#[from] GreeksError),
#[error(transparent)]
PositiveError(#[from] positive::PositiveError),
}
#[derive(Error, Debug)]
pub enum PriceErrorKind {
#[error("Invalid underlying price: {reason}")]
InvalidUnderlyingPrice {
reason: String,
},
#[error("Invalid price range ({start} to {end}): {reason}")]
InvalidPriceRange {
start: f64,
end: f64,
reason: String,
},
}
#[derive(Error, Debug)]
pub enum BreakEvenErrorKind {
#[error("Break-even calculation error: {reason}")]
CalculationError {
reason: String,
},
#[error("No break-even points exist for this strategy")]
NoBreakEvenPoints,
}
#[derive(Error, Debug)]
pub enum ProfitLossErrorKind {
#[error("Maximum profit calculation error: {reason}")]
MaxProfitError {
reason: String,
},
#[error("Maximum loss calculation error: {reason}")]
MaxLossError {
reason: String,
},
#[error("Profit range calculation error: {reason}")]
ProfitRangeError {
reason: String,
},
}
pub type StrategyResult<T> = Result<T, StrategyError>;
impl StrategyError {
pub fn operation_not_supported(operation: &str, strategy_type: &str) -> Self {
StrategyError::OperationError(OperationErrorKind::NotSupported {
operation: operation.to_string(),
reason: strategy_type.to_string(),
})
}
pub fn invalid_parameters(operation: &str, reason: &str) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: operation.to_string(),
reason: reason.to_string(),
})
}
}
impl From<PositionError> for StrategyError {
fn from(err: PositionError) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: "Position".to_string(),
reason: err.to_string(),
})
}
}
impl From<OptionsError> for StrategyError {
fn from(err: OptionsError) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: "Options".to_string(),
reason: err.to_string(),
})
}
}
impl From<Box<dyn std::error::Error>> for StrategyError {
fn from(err: Box<dyn std::error::Error>) -> Self {
StrategyError::StdError {
reason: err.to_string(),
}
}
}
impl From<crate::error::PricingError> for StrategyError {
fn from(err: crate::error::PricingError) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: "Pricing".to_string(),
reason: err.to_string(),
})
}
}
impl From<SimulationError> for StrategyError {
fn from(value: SimulationError) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: "Simulation".to_string(),
reason: value.to_string(),
})
}
}
impl From<TradeError> for StrategyError {
fn from(value: TradeError) -> Self {
StrategyError::OperationError(OperationErrorKind::InvalidParameters {
operation: "Trade".to_string(),
reason: value.to_string(),
})
}
}
#[cfg(test)]
mod tests_from_str {
use super::*;
use crate::error::ProbabilityError;
#[test]
fn test_strategy_to_probability_error_conversion() {
let strategy_error = StrategyError::operation_not_supported("max_profit", "TestStrategy");
let probability_error = ProbabilityError::from(strategy_error);
assert!(probability_error.to_string().contains("max_profit"));
assert!(probability_error.to_string().contains("TestStrategy"));
}
#[test]
fn test_profit_loss_error_conversion() {
let strategy_error = StrategyError::ProfitLossError(ProfitLossErrorKind::MaxProfitError {
reason: "Test error".to_string(),
});
let probability_error = ProbabilityError::from(strategy_error);
assert!(probability_error.to_string().contains("Test error"));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strategy_error_creation() {
let error = StrategyError::operation_not_supported("max_profit", "TestStrategy");
assert!(matches!(
error,
StrategyError::OperationError(OperationErrorKind::NotSupported { .. })
));
}
#[test]
fn test_error_messages() {
let error = StrategyError::operation_not_supported("max_profit", "TestStrategy");
let error_string = error.to_string();
assert!(error_string.contains("max_profit"));
assert!(error_string.contains("TestStrategy"));
}
}
#[cfg(test)]
mod tests_display {
use super::*;
#[test]
fn test_price_error_display() {
let error = StrategyError::PriceError(PriceErrorKind::InvalidUnderlyingPrice {
reason: "Price cannot be negative".to_string(),
});
assert!(error.to_string().contains("Price cannot be negative"));
}
#[test]
fn test_break_even_error_display() {
let error = StrategyError::BreakEvenError(BreakEvenErrorKind::CalculationError {
reason: "Invalid input parameters".to_string(),
});
assert!(error.to_string().contains("Invalid input parameters"));
}
#[test]
fn test_profit_loss_error_display() {
let error = StrategyError::ProfitLossError(ProfitLossErrorKind::MaxProfitError {
reason: "Cannot calculate maximum profit".to_string(),
});
assert!(
error
.to_string()
.contains("Cannot calculate maximum profit")
);
}
#[test]
fn test_operation_error_display() {
let error = StrategyError::operation_not_supported("max_profit", "TestStrategy");
assert!(error.to_string().contains("max_profit"));
assert!(error.to_string().contains("TestStrategy"));
}
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_strategy_error_std_error() {
let error = StrategyError::StdError {
reason: "General failure".to_string(),
};
assert_eq!(format!("{error}"), "Standard error: General failure");
}
#[test]
fn test_price_error_invalid_price_range() {
let error = PriceErrorKind::InvalidPriceRange {
start: 10.0,
end: 50.0,
reason: "Start price must be less than end price".to_string(),
};
assert_eq!(
format!("{error}"),
"Invalid price range (10 to 50): Start price must be less than end price"
);
}
#[test]
fn test_break_even_error_no_points() {
let error = BreakEvenErrorKind::NoBreakEvenPoints;
assert_eq!(
format!("{error}"),
"No break-even points exist for this strategy"
);
}
#[test]
fn test_profit_loss_error_max_loss_error() {
let error = ProfitLossErrorKind::MaxLossError {
reason: "Loss exceeds margin requirements".to_string(),
};
assert_eq!(
format!("{error}"),
"Maximum loss calculation error: Loss exceeds margin requirements"
);
}
#[test]
fn test_profit_loss_error_profit_range_error() {
let error = ProfitLossErrorKind::ProfitRangeError {
reason: "Profit calculation failed".to_string(),
};
assert_eq!(
format!("{error}"),
"Profit range calculation error: Profit calculation failed"
);
}
#[test]
fn test_strategy_error_invalid_parameters_constructor() {
let error = StrategyError::invalid_parameters("Open position", "Margin insufficient");
assert_eq!(
format!("{error}"),
"Operation error: Invalid parameters for operation 'Open position': Margin insufficient"
);
}
#[test]
fn test_strategy_error_from_boxed_error() {
let boxed_error: Box<dyn std::error::Error> =
Box::new(std::io::Error::other("Underlying failure"));
let error: StrategyError = boxed_error.into();
assert_eq!(format!("{error}"), "Standard error: Underlying failure");
}
#[test]
fn test_strategy_error_from_trade_error_invalid_trade() {
let trade_error = TradeError::invalid_trade("Trade failure");
let error: StrategyError = trade_error.into();
assert_eq!(
format!("{error}"),
"Operation error: Invalid parameters for operation 'Trade': Invalid trade: Trade failure"
);
}
#[test]
fn test_strategy_error_from_trade_error_invalid_trade_status() {
let trade_error = TradeError::invalid_trade_status("Trade status failure");
let error: StrategyError = trade_error.into();
assert_eq!(
format!("{error}"),
"Operation error: Invalid parameters for operation 'Trade': Invalid trade status: Trade status failure"
);
}
}