use crate::error::StrategyError;
use crate::model::types::{OptionStyle, Side};
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum PositionError {
StrategyError(StrategyErrorKind),
ValidationError(PositionValidationErrorKind),
LimitError(PositionLimitErrorKind),
}
#[derive(Debug)]
pub enum StrategyErrorKind {
UnsupportedOperation {
strategy_type: String,
operation: String,
},
StrategyFull {
strategy_type: String,
max_positions: usize,
},
InvalidConfiguration(String),
}
#[derive(Debug)]
pub enum PositionValidationErrorKind {
InvalidSize {
size: f64,
reason: String,
},
InvalidPrice {
price: f64,
reason: String,
},
IncompatibleSide {
position_side: Side,
reason: String,
},
IncompatibleStyle {
style: OptionStyle,
reason: String,
},
InvalidPosition {
reason: String,
},
StdError {
reason: String,
},
}
#[derive(Debug)]
pub enum PositionLimitErrorKind {
MaxPositionsReached {
current: usize,
maximum: usize,
},
MaxExposureReached {
current_exposure: f64,
max_exposure: f64,
},
}
impl fmt::Display for PositionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PositionError::StrategyError(kind) => write!(f, "Strategy error: {kind}"),
PositionError::ValidationError(kind) => {
write!(f, "Position validation error: {kind}")
}
PositionError::LimitError(kind) => write!(f, "Position limit error: {kind}"),
}
}
}
impl fmt::Display for StrategyErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StrategyErrorKind::UnsupportedOperation {
strategy_type,
operation,
} => {
write!(
f,
"Operation '{operation}' is not supported for strategy type '{strategy_type}'"
)
}
StrategyErrorKind::StrategyFull {
strategy_type,
max_positions,
} => {
write!(
f,
"Strategy '{strategy_type}' is full (maximum {max_positions} positions)"
)
}
StrategyErrorKind::InvalidConfiguration(msg) => {
write!(f, "Invalid strategy configuration: {msg}")
}
}
}
}
impl fmt::Display for PositionValidationErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PositionValidationErrorKind::InvalidSize { size, reason } => {
write!(f, "Invalid position size {size}: {reason}")
}
PositionValidationErrorKind::InvalidPrice { price, reason } => {
write!(f, "Invalid position price {price}: {reason}")
}
PositionValidationErrorKind::IncompatibleSide {
position_side,
reason: strategy_type,
} => {
write!(
f,
"Position type '{position_side}' is incompatible with strategy '{strategy_type}'"
)
}
PositionValidationErrorKind::InvalidPosition { reason } => {
write!(f, "Invalid position: {reason}")
}
PositionValidationErrorKind::IncompatibleStyle { style, reason } => {
write!(
f,
"Position style '{style}' is incompatible with strategy: {reason}"
)
}
PositionValidationErrorKind::StdError { reason } => {
write!(f, "Error: {reason}")
}
}
}
}
impl fmt::Display for PositionLimitErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PositionLimitErrorKind::MaxPositionsReached { current, maximum } => {
write!(
f,
"Maximum number of positions reached ({current}/{maximum})"
)
}
PositionLimitErrorKind::MaxExposureReached {
current_exposure,
max_exposure,
} => {
write!(
f,
"Maximum exposure reached (current: {current_exposure}, max: {max_exposure})"
)
}
}
}
}
impl Error for PositionError {}
impl PositionError {
pub fn unsupported_operation(strategy_type: &str, operation: &str) -> Self {
PositionError::StrategyError(StrategyErrorKind::UnsupportedOperation {
strategy_type: strategy_type.to_string(),
operation: operation.to_string(),
})
}
pub fn strategy_full(strategy_type: &str, max_positions: usize) -> Self {
PositionError::StrategyError(StrategyErrorKind::StrategyFull {
strategy_type: strategy_type.to_string(),
max_positions,
})
}
pub fn invalid_position_size(size: f64, reason: &str) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::InvalidSize {
size,
reason: reason.to_string(),
})
}
pub fn invalid_position_type(position_side: Side, reason: String) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::IncompatibleSide {
position_side,
reason,
})
}
pub fn invalid_position_style(style: OptionStyle, reason: String) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::IncompatibleStyle {
style,
reason,
})
}
pub fn invalid_position(reason: &str) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::InvalidPosition {
reason: reason.to_string(),
})
}
}
impl From<Box<dyn Error>> for PositionError {
fn from(err: Box<dyn Error>) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::StdError {
reason: err.to_string(),
})
}
}
impl From<&str> for PositionError {
fn from(err: &str) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::StdError {
reason: err.to_string(),
})
}
}
impl From<String> for PositionError {
fn from(err: String) -> Self {
PositionError::ValidationError(PositionValidationErrorKind::StdError {
reason: err.to_string(),
})
}
}
impl From<StrategyError> for PositionError {
fn from(error: StrategyError) -> Self {
PositionError::StrategyError(StrategyErrorKind::UnsupportedOperation {
operation: "".to_string(),
strategy_type: error.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::strategies::base::Positionable;
struct DummyStrategy;
impl Positionable for DummyStrategy {}
#[test]
fn test_unsupported_operation() {
let strategy = DummyStrategy;
let result = strategy.get_positions();
assert!(matches!(
result,
Err(PositionError::StrategyError(
StrategyErrorKind::UnsupportedOperation { .. }
))
));
}
#[test]
fn test_error_messages() {
let error = PositionError::unsupported_operation("TestStrategy", "add_position");
assert!(error.to_string().contains("TestStrategy"));
assert!(error.to_string().contains("add_position"));
}
#[test]
fn test_invalid_position_size() {
let error = PositionError::invalid_position_size(-1.0, "Size cannot be negative");
assert!(matches!(
error,
PositionError::ValidationError(PositionValidationErrorKind::InvalidSize { .. })
));
}
}
#[cfg(test)]
mod tests_extended {
use super::*;
#[test]
fn test_validation_error_display() {
let error = PositionValidationErrorKind::InvalidSize {
size: -1.0,
reason: "Size must be positive".to_string(),
};
assert!(error.to_string().contains("-1"));
assert!(error.to_string().contains("Size must be positive"));
let error = PositionValidationErrorKind::IncompatibleSide {
position_side: Side::Long,
reason: "Strategy requires short positions".to_string(),
};
assert!(error.to_string().contains("Long"));
assert!(error.to_string().contains("Strategy requires short"));
}
#[test]
fn test_limit_error_display() {
let error = PositionLimitErrorKind::MaxPositionsReached {
current: 5,
maximum: 4,
};
assert!(error.to_string().contains("5"));
assert!(error.to_string().contains("4"));
let error = PositionLimitErrorKind::MaxExposureReached {
current_exposure: 1000.0,
max_exposure: 500.0,
};
assert!(error.to_string().contains("1000"));
assert!(error.to_string().contains("500"));
}
#[test]
fn test_error_conversions() {
let str_error: PositionError = "test error".into();
assert!(matches!(
str_error,
PositionError::ValidationError(PositionValidationErrorKind::StdError { .. })
));
let string_error: PositionError = "test error".to_string().into();
assert!(matches!(
string_error,
PositionError::ValidationError(PositionValidationErrorKind::StdError { .. })
));
let std_error: Box<dyn Error> = Box::new(std::io::Error::other("dynamic error"));
let position_error = PositionError::from(std_error);
assert!(matches!(
position_error,
PositionError::ValidationError(PositionValidationErrorKind::StdError { .. })
));
}
#[test]
fn test_position_error_helper_methods() {
let error = PositionError::invalid_position_size(-1.0, "Must be positive");
assert!(matches!(
error,
PositionError::ValidationError(PositionValidationErrorKind::InvalidSize { .. })
));
let error = PositionError::invalid_position_type(
Side::Long,
"Strategy requires short positions".to_string(),
);
assert!(matches!(
error,
PositionError::ValidationError(PositionValidationErrorKind::IncompatibleSide { .. })
));
}
#[test]
fn test_strategy_error_helper_methods() {
let error = PositionError::strategy_full("Iron Condor", 4);
assert!(matches!(
error,
PositionError::StrategyError(StrategyErrorKind::StrategyFull { .. })
));
let error = PositionError::unsupported_operation("Iron Condor", "add_leg");
assert!(matches!(
error,
PositionError::StrategyError(StrategyErrorKind::UnsupportedOperation { .. })
));
}
#[test]
fn test_position_error_validation_error() {
let error = PositionError::ValidationError(PositionValidationErrorKind::InvalidSize {
size: -1.0,
reason: "Size must be positive".to_string(),
});
assert_eq!(
format!("{error}"),
"Position validation error: Invalid position size -1: Size must be positive"
);
}
#[test]
fn test_position_error_limit_error() {
let error = PositionError::LimitError(PositionLimitErrorKind::MaxPositionsReached {
current: 10,
maximum: 5,
});
assert_eq!(
format!("{error}"),
"Position limit error: Maximum number of positions reached (10/5)"
);
}
#[test]
fn test_strategy_error_strategy_full() {
let error = StrategyErrorKind::StrategyFull {
strategy_type: "Iron Condor".to_string(),
max_positions: 10,
};
assert_eq!(
format!("{error}"),
"Strategy 'Iron Condor' is full (maximum 10 positions)"
);
}
#[test]
fn test_strategy_error_invalid_configuration() {
let error = StrategyErrorKind::InvalidConfiguration("Invalid risk parameters".to_string());
assert_eq!(
format!("{error}"),
"Invalid strategy configuration: Invalid risk parameters"
);
}
#[test]
fn test_position_validation_error_invalid_price() {
let error = PositionValidationErrorKind::InvalidPrice {
price: 105.5,
reason: "Outside allowable range".to_string(),
};
assert_eq!(
format!("{error}"),
"Invalid position price 105.5: Outside allowable range"
);
}
#[test]
fn test_position_validation_error_invalid_position() {
let error = PositionValidationErrorKind::InvalidPosition {
reason: "Position size exceeds margin".to_string(),
};
assert_eq!(
format!("{error}"),
"Invalid position: Position size exceeds margin"
);
}
#[test]
fn test_position_validation_error_incompatible_style() {
let error = PositionValidationErrorKind::IncompatibleStyle {
style: OptionStyle::Call,
reason: "Unsupported for Call options".to_string(),
};
assert_eq!(
format!("{error}"),
"Position style 'Call' is incompatible with strategy: Unsupported for Call options"
);
}
#[test]
fn test_position_validation_error_std_error() {
let error = PositionValidationErrorKind::StdError {
reason: "Unexpected null value".to_string(),
};
assert_eq!(format!("{error}"), "Error: Unexpected null value");
}
}