use crate::error::StrategyError;
use crate::model::types::{OptionStyle, Side};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PositionError {
#[error("Strategy error: {0}")]
StrategyError(StrategyErrorKind),
#[error("Validation error: {0}")]
ValidationError(PositionValidationErrorKind),
#[error("Limit error: {0}")]
LimitError(PositionLimitErrorKind),
#[error("Update error: {0}")]
UpdateError(PositionUpdateErrorKind),
}
#[derive(Error, Debug)]
pub enum StrategyErrorKind {
#[error("Operation '{operation}' not supported by strategy '{strategy_type}'")]
UnsupportedOperation {
strategy_type: String,
operation: String,
},
#[error("Strategy '{strategy_type}' is full (max positions: {max_positions})")]
StrategyFull {
strategy_type: String,
max_positions: usize,
},
#[error("Invalid strategy configuration: {0}")]
InvalidConfiguration(String),
}
#[derive(Error, Debug)]
pub enum PositionValidationErrorKind {
#[error("Invalid position size {size}: {reason}")]
InvalidSize {
size: f64,
reason: String,
},
#[error("Invalid position price {price}: {reason}")]
InvalidPrice {
price: f64,
reason: String,
},
#[error("Incompatible position side {position_side:?}: {reason}")]
IncompatibleSide {
position_side: Side,
reason: String,
},
#[error("Incompatible option style {style:?}: {reason}")]
IncompatibleStyle {
style: OptionStyle,
reason: String,
},
#[error("Invalid position: {reason}")]
InvalidPosition {
reason: String,
},
#[error("Standard error: {reason}")]
StdError {
reason: String,
},
}
#[derive(Error, Debug)]
pub enum PositionLimitErrorKind {
#[error("Maximum positions reached: {current}/{maximum}")]
MaxPositionsReached {
current: usize,
maximum: usize,
},
#[error("Maximum exposure reached: {current_exposure}/{max_exposure}")]
MaxExposureReached {
current_exposure: f64,
max_exposure: f64,
},
}
#[derive(Error, Debug)]
pub enum PositionUpdateErrorKind {
#[error("Position update error for field '{field}': {reason}")]
PositionFieldUpdateFailure {
field: String,
reason: String,
},
}
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(),
})
}
pub fn invalid_position_update(field: String, reason: String) -> Self {
PositionError::UpdateError(PositionUpdateErrorKind::PositionFieldUpdateFailure {
field,
reason,
})
}
}
impl From<Box<dyn std::error::Error>> for PositionError {
fn from(err: Box<dyn std::error::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 std::error::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}"),
"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}"),
"Limit error: Maximum 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 (max positions: 10)"
);
}
#[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}"),
"Incompatible option style OptionStyle::Call: 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}"), "Standard error: Unexpected null value");
}
#[test]
fn test_position_update_error() {
let error = PositionUpdateErrorKind::PositionFieldUpdateFailure {
field: "premium".to_string(),
reason: "Missing call ask price for long call position".to_string(),
};
assert_eq!(
format!("{error}"),
"Position update error for field 'premium': Missing call ask price for long call position"
);
}
#[test]
fn test_position_update_error_helper_method() {
let error = PositionError::invalid_position_update(
"premium".to_string(),
"Missing call ask price for long call position".to_string(),
);
assert_eq!(
format!("{error}"),
"Update error: Position update error for field 'premium': Missing call ask price for long call position"
);
}
}