Files
foxhunt/crates/risk/src/error.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

604 lines
22 KiB
Rust

//! Error types for the risk management system
// #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // COMMENTED: Crate-level allows applied
use thiserror::Error;
use common::error::CommonError;
use common::types::Price;
use crate::risk_types::RiskSeverity;
/// Comprehensive error types for the risk management system
///
/// This enum covers all possible error conditions that can occur during risk
/// management operations, from configuration issues to critical safety violations.
/// Each error variant includes context-specific information to aid in debugging
/// and incident response.
#[derive(Debug, Error)]
#[error(transparent)]
pub enum RiskError {
/// Configuration error occurred during system initialization or validation
#[error("Configuration error: {0}")]
Config(String),
/// Database operation failed (connection, query, transaction, etc.)
#[error("Database error: {0}")]
Database(String),
/// Position limit exceeded for a specific instrument
#[error("Position limit exceeded: {instrument} has {current} but limit is {limit}")]
PositionLimitExceeded {
/// The financial instrument that exceeded its limit
instrument: String,
/// Current position size
current: Price,
/// Maximum allowed position size
limit: Price,
},
/// Value at Risk limit exceeded, indicating portfolio risk is too high
#[error("VaR limit exceeded: {var} exceeds limit of {limit}")]
VarLimitExceeded {
/// Current `VaR` value
var: Price,
/// Maximum allowed `VaR`
limit: Price,
},
/// Drawdown limit exceeded, indicating excessive portfolio losses
#[error("Drawdown limit exceeded: {drawdown}% exceeds limit of {limit}%")]
DrawdownLimitExceeded {
/// Current drawdown percentage
drawdown: Price,
/// Maximum allowed drawdown percentage
limit: Price,
},
/// Daily loss limit exceeded, triggering risk controls
#[error("Daily loss limit exceeded: {loss} exceeds limit of {limit}")]
DailyLossLimitExceeded {
/// Current daily loss amount
loss: Price,
/// Maximum allowed daily loss
limit: Price,
},
/// Circuit breaker is active, preventing trading on an instrument
#[error("Circuit breaker active for {instrument}: {reason}")]
CircuitBreakerActive {
/// The instrument with an active circuit breaker
instrument: String,
/// Reason why the circuit breaker was triggered
reason: String,
},
/// Kill switch is active, immediately halting operations
#[error("Kill switch active for {scope:?}: {message}")]
KillSwitchActive {
/// Scope of the kill switch (global, strategy, symbol)
scope: crate::risk_types::KillSwitchScope,
/// Detailed message about why the kill switch was activated
message: String,
},
/// Market data is unavailable for required calculations
#[error("Market data unavailable for {instrument}")]
MarketDataUnavailable {
/// The instrument for which market data is missing
instrument: String,
},
/// Insufficient historical data for reliable risk calculations
#[error("Insufficient historical data: need {required} but have {available}")]
InsufficientHistoricalData {
/// Number of data points required
required: usize,
/// Number of data points available
available: usize,
},
/// Correlation calculation failed between instruments
#[error("Correlation calculation failed: {reason}")]
CorrelationCalculationFailed {
/// Detailed reason for the correlation calculation failure
reason: String,
},
/// Stress test scenario failed, indicating portfolio vulnerability
#[error("Stress test failed: {scenario}")]
StressTestFailed {
/// The stress test scenario that failed
scenario: String,
},
/// Performance metric violated its threshold
#[error("Performance violation: {metric} = {value}, threshold = {threshold}")]
PerformanceViolation {
/// The performance metric that was violated
metric: String,
/// Current value of the metric
value: Price,
/// Threshold value that was exceeded
threshold: Price,
},
/// Regulatory compliance rule was violated
#[error("Compliance violation: {rule}")]
ComplianceViolation {
/// The compliance rule that was violated
rule: String,
},
/// User authorization failed for the requested operation
#[error("Authorization failed: {reason}")]
AuthorizationFailed {
/// Reason why authorization failed
reason: String,
},
/// Order validation failed
#[error("Invalid order: {reason}")]
InvalidOrder {
/// Reason why the order is invalid
reason: String,
},
/// Required service is unavailable
#[error("Service unavailable: {service}")]
ServiceUnavailable {
/// Name of the unavailable service
service: String,
},
/// Operation timed out after specified duration
#[error("Operation timeout: {timeout_ms}ms")]
Timeout {
/// Timeout duration in milliseconds
timeout_ms: u64,
},
/// Data serialization/deserialization failed
#[error("Serialization error: {0}")]
Serialization(String),
/// Network communication error occurred
#[error("Network error: {0}")]
Network(String),
/// Internal system error - should not occur in normal operation
#[error("Internal error: {0}")]
Internal(String),
/// Data validation failed for a specific field
#[error("Validation error: {field} - {message}")]
Validation {
/// The field that failed validation
field: String,
/// Detailed validation error message
message: String,
},
/// System resource was exhausted (memory, connections, etc.)
#[error("Resource exhausted: {resource}")]
ResourceExhausted {
/// The resource that was exhausted
resource: String,
},
/// Mathematical calculation failed
#[error("Calculation error: {operation} failed - {reason}")]
Calculation {
/// The calculation operation that failed
operation: String,
/// Reason for the calculation failure
reason: String,
},
/// Rate limit exceeded, operation must wait
#[error("Rate limited: {remaining_ms}ms until reset")]
RateLimited {
/// Milliseconds remaining until rate limit resets
remaining_ms: u64,
},
/// Order side (buy/sell) is invalid
#[error("Invalid order side: {side}")]
InvalidOrderSide {
/// The invalid order side value
side: String,
},
/// Order type is invalid or not supported
#[error("Invalid order type: {order_type}")]
InvalidOrderType {
/// The invalid order type value
order_type: String,
},
/// Order quantity is invalid (negative, zero, too large, etc.)
#[error("Invalid quantity: {quantity}")]
InvalidQuantity {
/// The invalid quantity value
quantity: String,
},
/// Order price is invalid (negative, zero, outside valid range, etc.)
#[error("Invalid price: {price}")]
InvalidPrice {
/// The invalid price value
price: String,
},
/// Generic connection error occurred
#[error("Connection error: {message}")]
ConnectionError {
/// Detailed connection error message
message: String,
},
/// Configuration-related error occurred
#[error("Configuration error: {message}")]
Configuration {
/// Detailed configuration error message
message: String,
},
/// Generic validation error occurred
#[error("Validation error: {message}")]
ValidationError {
/// Detailed validation error message
message: String,
},
/// Serialization/deserialization error occurred
#[error("Serialization error: {message}")]
SerializationError {
/// Detailed serialization error message
message: String,
},
// NEW: Enhanced error types for hardened risk management
/// Type conversion failed between incompatible types
#[error("Type conversion error: {from_type} to {to_type} failed - {reason}")]
TypeConversion {
/// Source type being converted from
from_type: String,
/// Target type being converted to
to_type: String,
/// Reason for the conversion failure
reason: String,
},
/// Calculation error with simplified message
#[error("Calculation error: {0}")]
CalculationError(String),
/// Required configuration parameter is missing
#[error("Missing configuration: {config_key}")]
MissingConfiguration {
/// The configuration key that is missing
config_key: String,
},
/// Environment validation failed - missing requirements
#[error("Environment validation failed: {environment} requires {requirement}")]
EnvironmentValidation {
/// The environment being validated
environment: String,
/// The requirement that was not met
requirement: String,
},
/// Data integrity check failed
#[error("Data integrity error: {data_type} validation failed - {details}")]
DataIntegrity {
/// Type of data that failed integrity check
data_type: String,
/// Detailed information about the integrity failure
details: String,
},
/// Resource is temporarily unavailable
#[error("Resource unavailable: {resource} temporarily unavailable - {reason}")]
ResourceUnavailable {
/// The unavailable resource
resource: String,
/// Reason why the resource is unavailable
reason: String,
},
/// Safety limit exceeded - immediate intervention required
#[error("Safety limit exceeded: {limit_type} current={current} max={maximum}")]
SafetyLimitExceeded {
/// Type of safety limit that was exceeded
limit_type: String,
/// Current value that exceeded the limit
current: String,
/// Maximum allowed value
maximum: String,
},
/// Emergency stop triggered - all operations must halt immediately
#[error("Emergency stop triggered: {trigger} - immediate halt required")]
EmergencyStop {
/// The trigger that caused the emergency stop
trigger: String,
},
/// Production safety violation detected
#[error("Production safety violation: {violation} in {environment}")]
ProductionSafety {
/// Description of the safety violation
violation: String,
/// Environment where the violation occurred
environment: String,
},
/// Broker system error occurred
#[error("Broker connection error: {0}")]
BrokerError(String),
/// Broker connection failed
#[error("Broker connection error: {message}")]
BrokerConnection {
/// Detailed broker connection error message
message: String,
},
/// Connection to specific endpoint failed
#[error("Connection failed to {endpoint}: {reason}")]
Connection {
/// The endpoint that failed to connect
endpoint: String,
/// Reason for the connection failure
reason: String,
},
/// Market data system error occurred
#[error("Market data error: {0}")]
MarketDataError(String),
/// Required data is unavailable for calculations
#[error("Data unavailable: {resource} - {reason}")]
DataUnavailable {
/// The data resource that is unavailable
resource: String,
/// Reason why the data is unavailable
reason: String,
},
/// Arithmetic overflow detected during calculation
#[error("Arithmetic overflow: {operation} - {context}")]
ArithmeticOverflow {
/// The operation that overflowed
operation: String,
/// Context about the overflow
context: String,
},
}
/// Result type for risk management operations
pub type RiskResult<T> = Result<T, RiskError>;
/// Safe conversion helpers to eliminate `unwrap()` patterns
use num::ToPrimitive;
use rust_decimal::Decimal;
use std::fmt::Display;
/// Safely convert f64 to Price with context
pub fn f64_to_price_safe(value: f64, context: &str) -> RiskResult<Price> {
Price::from_f64(value).map_err(|_| RiskError::TypeConversion {
from_type: "f64".to_owned(),
to_type: "Price".to_owned(),
reason: format!("{context}: invalid f64 value {value}"),
})
}
/// Safely convert f64 to Decimal with context
pub fn f64_to_decimal_safe(value: f64, context: &str) -> RiskResult<Decimal> {
Decimal::try_from(value).map_err(|_| RiskError::TypeConversion {
from_type: "f64".to_owned(),
to_type: "Decimal".to_owned(),
reason: format!("{context}: invalid f64 value {value}"),
})
}
/// Safely convert Price to Decimal with context
pub fn price_to_decimal_safe(price: Price, context: &str) -> RiskResult<Decimal> {
price.to_decimal().map_err(|_| RiskError::TypeConversion {
from_type: "Price".to_owned(),
to_type: "Decimal".to_owned(),
reason: format!("{context}: price conversion failed"),
})
}
/// Safely convert Decimal to f64 with context
pub fn decimal_to_f64_safe(decimal: Decimal, context: &str) -> RiskResult<f64> {
decimal.to_f64().ok_or_else(|| RiskError::TypeConversion {
from_type: "Decimal".to_owned(),
to_type: "f64".to_owned(),
reason: format!("{context}: decimal too large for f64"),
})
}
/// Safely parse environment variable with context
pub fn parse_env_var<T: std::str::FromStr>(var_name: &str, context: &str) -> RiskResult<T>
where
T::Err: Display,
{
std::env::var(var_name)
.map_err(|_| RiskError::MissingConfiguration {
config_key: var_name.to_owned(),
})?
.parse()
.map_err(|e| RiskError::EnvironmentValidation {
environment: var_name.to_owned(),
requirement: format!("{context}: {e}"),
})
}
/// Safe division with zero check
pub fn safe_divide(numerator: Decimal, denominator: Decimal, context: &str) -> RiskResult<Decimal> {
if denominator.is_zero() {
return Err(RiskError::Calculation {
operation: "division".to_owned(),
reason: format!("{context}: division by zero"),
});
}
Ok(numerator / denominator)
}
// ELIMINATED: Re-exports removed to force explicit imports
// Also support FoxhuntResult<T> for consistency with error-handling framework
// Removed core dependency - use core instead
impl RiskError {
/// Get the severity level of this error
#[must_use]
pub const fn severity(&self) -> RiskSeverity {
match self {
RiskError::KillSwitchActive { .. }
| RiskError::DailyLossLimitExceeded { .. }
| RiskError::DrawdownLimitExceeded { .. }
| RiskError::EmergencyStop { .. }
| RiskError::ProductionSafety { .. } => RiskSeverity::Critical,
RiskError::PositionLimitExceeded { .. }
| RiskError::VarLimitExceeded { .. }
| RiskError::CircuitBreakerActive { .. }
| RiskError::ComplianceViolation { .. } => RiskSeverity::High,
RiskError::PerformanceViolation { .. }
| RiskError::MarketDataUnavailable { .. }
| RiskError::AuthorizationFailed { .. } => RiskSeverity::Medium,
_ => RiskSeverity::Low,
}
}
/// Check if the error should trigger a kill switch
#[must_use]
pub const fn should_trigger_kill_switch(&self) -> bool {
matches!(
self,
RiskError::DailyLossLimitExceeded { .. } | RiskError::DrawdownLimitExceeded { .. }
)
}
/// Check if the error should trigger a circuit breaker
#[must_use]
pub const fn should_trigger_circuit_breaker(&self) -> bool {
matches!(
self,
RiskError::PositionLimitExceeded { .. }
| RiskError::VarLimitExceeded { .. }
| RiskError::PerformanceViolation { .. }
)
}
/// Get error code for logging and monitoring
#[must_use]
pub const fn error_code(&self) -> &'static str {
match self {
RiskError::Config(_) => "CONFIG_ERROR",
RiskError::Database(_) => "DATABASE_ERROR",
RiskError::PositionLimitExceeded { .. } => "POSITION_LIMIT_EXCEEDED",
RiskError::VarLimitExceeded { .. } => "VAR_LIMIT_EXCEEDED",
RiskError::DrawdownLimitExceeded { .. } => "DRAWDOWN_LIMIT_EXCEEDED",
RiskError::DailyLossLimitExceeded { .. } => "DAILY_LOSS_LIMIT_EXCEEDED",
RiskError::CircuitBreakerActive { .. } => "CIRCUIT_BREAKER_ACTIVE",
RiskError::KillSwitchActive { .. } => "KILL_SWITCH_ACTIVE",
RiskError::MarketDataUnavailable { .. } => "MARKET_DATA_UNAVAILABLE",
RiskError::InsufficientHistoricalData { .. } => "INSUFFICIENT_HISTORICAL_DATA",
RiskError::CorrelationCalculationFailed { .. } => "CORRELATION_CALCULATION_FAILED",
RiskError::StressTestFailed { .. } => "STRESS_TEST_FAILED",
RiskError::PerformanceViolation { .. } => "PERFORMANCE_VIOLATION",
RiskError::ComplianceViolation { .. } => "COMPLIANCE_VIOLATION",
RiskError::AuthorizationFailed { .. } => "AUTHORIZATION_FAILED",
RiskError::InvalidOrder { .. } => "INVALID_ORDER",
RiskError::ServiceUnavailable { .. } => "SERVICE_UNAVAILABLE",
RiskError::Timeout { .. } => "TIMEOUT",
RiskError::Serialization(_) => "SERIALIZATION_ERROR",
RiskError::Network(_) => "NETWORK_ERROR",
RiskError::Internal(_) => "INTERNAL_ERROR",
RiskError::Validation { .. } => "VALIDATION_ERROR",
RiskError::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED",
RiskError::Calculation { .. } => "CALCULATION_ERROR",
RiskError::RateLimited { .. } => "RATE_LIMITED",
RiskError::InvalidOrderSide { .. } => "INVALID_ORDER_SIDE",
RiskError::InvalidOrderType { .. } => "INVALID_ORDER_TYPE",
RiskError::InvalidQuantity { .. } => "INVALID_QUANTITY",
RiskError::InvalidPrice { .. } => "INVALID_PRICE",
RiskError::ConnectionError { .. } => "CONNECTION_ERROR",
RiskError::Configuration { .. } => "CONFIGURATION_ERROR",
RiskError::ValidationError { .. } => "VALIDATION_ERROR",
RiskError::SerializationError { .. } => "SERIALIZATION_ERROR",
// NEW: Enhanced error codes
RiskError::TypeConversion { .. } => "TYPE_CONVERSION_ERROR",
RiskError::CalculationError(_) => "CALCULATION_ERROR",
RiskError::MissingConfiguration { .. } => "MISSING_CONFIGURATION",
RiskError::EnvironmentValidation { .. } => "ENVIRONMENT_VALIDATION_ERROR",
RiskError::DataIntegrity { .. } => "DATA_INTEGRITY_ERROR",
RiskError::ResourceUnavailable { .. } => "RESOURCE_UNAVAILABLE",
RiskError::SafetyLimitExceeded { .. } => "SAFETY_LIMIT_EXCEEDED",
RiskError::EmergencyStop { .. } => "EMERGENCY_STOP",
RiskError::ProductionSafety { .. } => "PRODUCTION_SAFETY_VIOLATION",
RiskError::BrokerError(_) => "BROKER_ERROR",
RiskError::BrokerConnection { .. } => "BROKER_CONNECTION_ERROR",
RiskError::Connection { .. } => "CONNECTION_ERROR",
RiskError::MarketDataError(_) => "MARKET_DATA_ERROR",
RiskError::DataUnavailable { .. } => "DATA_UNAVAILABLE",
RiskError::ArithmeticOverflow { .. } => "ARITHMETIC_OVERFLOW",
}
}
}
/// Convert from tokio timeout error
impl From<tokio::time::error::Elapsed> for RiskError {
fn from(_: tokio::time::error::Elapsed) -> Self {
RiskError::Timeout { timeout_ms: 0 }
}
}
/// Convert from `anyhow::Error`
impl From<anyhow::Error> for RiskError {
fn from(err: anyhow::Error) -> Self {
RiskError::Internal(err.to_string())
}
}
/// Convert from `CommonError` to `RiskError`
impl From<CommonError> for RiskError {
fn from(err: CommonError) -> Self {
RiskError::Internal(format!("CommonError: {err}"))
}
}
/// Convert from `CommonTypeError`
impl From<common::types::CommonTypeError> for RiskError {
fn from(err: common::types::CommonTypeError) -> Self {
RiskError::Internal(format!("CommonTypeError: {err}"))
}
}
/// Convert from `serde_json::Error`
impl From<serde_json::Error> for RiskError {
fn from(err: serde_json::Error) -> Self {
RiskError::Serialization(err.to_string())
}
}
/// Convert from `std::num::ParseFloatError`
impl From<std::num::ParseFloatError> for RiskError {
fn from(err: std::num::ParseFloatError) -> Self {
RiskError::TypeConversion {
from_type: "string".to_owned(),
to_type: "f64".to_owned(),
reason: err.to_string(),
}
}
}
/// Convert from `std::num::ParseIntError`
impl From<std::num::ParseIntError> for RiskError {
fn from(err: std::num::ParseIntError) -> Self {
RiskError::TypeConversion {
from_type: "string".to_owned(),
to_type: "integer".to_owned(),
reason: err.to_string(),
}
}
}
/// Convert from `std::env::VarError`
impl From<std::env::VarError> for RiskError {
fn from(err: std::env::VarError) -> Self {
match err {
std::env::VarError::NotPresent => RiskError::MissingConfiguration {
config_key: "unknown".to_owned(),
},
std::env::VarError::NotUnicode(_) => RiskError::EnvironmentValidation {
environment: "unknown".to_owned(),
requirement: "valid unicode".to_owned(),
},
}
}
}
/// Convert from `std::io::Error`
impl From<std::io::Error> for RiskError {
fn from(err: std::io::Error) -> Self {
RiskError::Network(format!("IO error: {err}"))
}
}