//! Shared IPC types for the Risk Analyzer demo //! //! This crate defines the data structures shared between the Rust backend //! and the TypeScript frontend for the financial risk analysis demo. use serde::{Deserialize, Serialize}; /// VaR calculation method #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum VaRMethod { /// Historical simulation using actual return distribution Historical, /// Parametric (variance-covariance) assuming normal distribution Parametric, /// Monte Carlo simulation with stochastic processes MonteCarlo, } impl VaRMethod { /// Get display name pub fn display_name(&self) -> &'static str { match self { Self::Historical => "Historical Simulation", Self::Parametric => "Parametric (Normal)", Self::MonteCarlo => "Monte Carlo Simulation", } } /// Get description pub fn description(&self) -> &'static str { match self { Self::Historical => "Uses actual historical return distribution", Self::Parametric => "Assumes normal distribution, uses mean and variance", Self::MonteCarlo => "Simulates future paths using stochastic processes", } } } /// Configuration for risk analysis #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskConfig { /// Confidence level (e.g., 0.95 for 95%, 0.99 for 99%) pub confidence_level: f64, /// Time horizon in days (e.g., 1, 10, 252) pub time_horizon_days: u32, /// Number of Monte Carlo simulations (if using MC method) pub num_simulations: u32, /// VaR calculation method pub method: VaRMethod, } impl Default for RiskConfig { fn default() -> Self { Self { confidence_level: 0.95, time_horizon_days: 1, num_simulations: 10_000, method: VaRMethod::Historical, } } } /// Comprehensive risk metrics for a portfolio #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskMetrics { /// Value at Risk (VaR) - maximum expected loss at confidence level pub var: f64, /// Conditional VaR (CVaR/Expected Shortfall) - expected loss beyond VaR pub cvar: f64, /// Annualized volatility (standard deviation) pub volatility: f64, /// Maximum drawdown (largest peak-to-trough decline) pub max_drawdown: f64, /// Sharpe ratio (risk-adjusted return) pub sharpe_ratio: f64, /// Sortino ratio (downside risk-adjusted return) pub sortino_ratio: f64, /// Market beta (if benchmark provided) pub beta: Option, /// Correlation with market (if benchmark provided) pub correlation: Option, /// Average return pub mean_return: f64, /// Skewness of returns pub skewness: f64, /// Kurtosis of returns (excess kurtosis) pub kurtosis: f64, } /// Stress test scenario definition #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StressScenario { /// Scenario name pub name: String, /// Scenario description pub description: String, /// Market shock percentage (e.g., -0.20 for 20% drop) pub market_shock: f64, /// Volatility spike multiplier (e.g., 2.0 for 2x normal vol) pub volatility_spike: f64, /// Correlation change (e.g., 0.2 means correlations increase by 0.2) pub correlation_change: f64, } impl StressScenario { /// Create 2008 Financial Crisis scenario pub fn financial_crisis_2008() -> Self { Self { name: "2008 Financial Crisis".to_string(), description: "Lehman Brothers collapse, credit freeze".to_string(), market_shock: -0.45, volatility_spike: 3.0, correlation_change: 0.3, } } /// Create COVID-19 March 2020 scenario pub fn covid_march_2020() -> Self { Self { name: "COVID-19 March 2020".to_string(), description: "Pandemic lockdowns, market crash".to_string(), market_shock: -0.34, volatility_spike: 2.5, correlation_change: 0.25, } } /// Create Dot-com Crash 2000 scenario pub fn dotcom_crash_2000() -> Self { Self { name: "Dot-com Crash 2000".to_string(), description: "Tech bubble burst".to_string(), market_shock: -0.49, volatility_spike: 2.2, correlation_change: 0.15, } } /// Create Black Monday 1987 scenario pub fn black_monday_1987() -> Self { Self { name: "Black Monday 1987".to_string(), description: "Largest single-day market crash".to_string(), market_shock: -0.23, volatility_spike: 4.0, correlation_change: 0.35, } } /// Get all predefined scenarios pub fn all_predefined() -> Vec { vec![ Self::financial_crisis_2008(), Self::covid_march_2020(), Self::dotcom_crash_2000(), Self::black_monday_1987(), ] } } /// Result of a stress test #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StressTestResult { /// Scenario name pub scenario: String, /// Total portfolio loss under scenario pub portfolio_loss: f64, /// Portfolio loss percentage pub portfolio_loss_pct: f64, /// Whether loss exceeds VaR threshold pub var_breach: bool, /// Worst performing asset ticker pub worst_asset: String, /// Worst asset loss pub worst_asset_loss: f64, /// Best performing asset ticker (least loss or gain) pub best_asset: String, /// Best asset return pub best_asset_return: f64, } /// Monte Carlo simulation result #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MonteCarloResult { /// Simulated portfolio value paths (subset for visualization) pub paths: Vec>, /// Distribution of final values from all simulations pub final_values: Vec, /// Percentiles: (percentile, value) pairs pub percentiles: Vec<(f64, f64)>, /// Mean final value pub mean_final_value: f64, /// Median final value pub median_final_value: f64, /// Standard deviation of final values pub std_final_value: f64, } /// Asset for risk analysis #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskAsset { /// Asset ticker pub ticker: String, /// Asset name pub name: String, /// Portfolio weight (0.0 - 1.0) pub weight: f64, /// Historical returns pub returns: Vec, } /// Portfolio for risk analysis #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskPortfolio { /// Portfolio assets pub assets: Vec, /// Optional benchmark returns for beta calculation pub benchmark_returns: Option>, /// Risk-free rate (annualized) pub risk_free_rate: f64, } /// Risk analysis request #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskAnalysisRequest { /// Portfolio to analyze pub portfolio: RiskPortfolio, /// Risk configuration pub config: RiskConfig, } /// Complete risk analysis result #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RiskAnalysisResult { /// Risk metrics pub metrics: RiskMetrics, /// Monte Carlo result (if MC method used) pub monte_carlo: Option, /// Stress test results (if requested) pub stress_tests: Vec, /// Processing time in milliseconds pub processing_time_ms: f64, /// Method used for VaR calculation pub method_used: VaRMethod, } /// Risk analyzer status #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RiskAnalyzerStatus { /// Whether analyzer is initialized pub initialized: bool, /// Number of analyses performed pub analysis_count: u64, /// Average processing time in ms pub avg_processing_time_ms: f64, } #[cfg(test)] mod tests { use super::*; #[test] fn test_var_method_serialization() { let method = VaRMethod::Historical; let json = serde_json::to_string(&method).unwrap(); let deserialized: VaRMethod = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized, VaRMethod::Historical); } #[test] fn test_var_method_display() { assert_eq!( VaRMethod::Historical.display_name(), "Historical Simulation" ); assert_eq!( VaRMethod::MonteCarlo.display_name(), "Monte Carlo Simulation" ); } #[test] fn test_risk_config_default() { let config = RiskConfig::default(); assert_eq!(config.confidence_level, 0.95); assert_eq!(config.time_horizon_days, 1); assert_eq!(config.num_simulations, 10_000); assert_eq!(config.method, VaRMethod::Historical); } #[test] fn test_risk_config_serialization() { let config = RiskConfig { confidence_level: 0.99, time_horizon_days: 10, num_simulations: 5_000, method: VaRMethod::MonteCarlo, }; let json = serde_json::to_string(&config).unwrap(); let deserialized: RiskConfig = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.confidence_level, 0.99); assert_eq!(deserialized.method, VaRMethod::MonteCarlo); } #[test] fn test_stress_scenario_predefined() { let scenario = StressScenario::financial_crisis_2008(); assert_eq!(scenario.name, "2008 Financial Crisis"); assert_eq!(scenario.market_shock, -0.45); assert_eq!(scenario.volatility_spike, 3.0); } #[test] fn test_all_predefined_scenarios() { let scenarios = StressScenario::all_predefined(); assert_eq!(scenarios.len(), 4); assert!(scenarios.iter().any(|s| s.name.contains("2008"))); assert!(scenarios.iter().any(|s| s.name.contains("COVID"))); } #[test] fn test_risk_asset_serialization() { let asset = RiskAsset { ticker: "AAPL".to_string(), name: "Apple Inc.".to_string(), weight: 0.3, returns: vec![0.01, -0.02, 0.015], }; let json = serde_json::to_string(&asset).unwrap(); let deserialized: RiskAsset = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.ticker, "AAPL"); assert_eq!(deserialized.weight, 0.3); assert_eq!(deserialized.returns.len(), 3); } #[test] fn test_risk_metrics_serialization() { let metrics = RiskMetrics { var: 10000.0, cvar: 12000.0, volatility: 0.15, max_drawdown: 0.25, sharpe_ratio: 1.2, sortino_ratio: 1.5, beta: Some(0.9), correlation: Some(0.85), mean_return: 0.08, skewness: -0.5, kurtosis: 3.0, }; let json = serde_json::to_string(&metrics).unwrap(); let deserialized: RiskMetrics = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.var, 10000.0); assert_eq!(deserialized.beta, Some(0.9)); } #[test] fn test_stress_test_result_serialization() { let result = StressTestResult { scenario: "Test Scenario".to_string(), portfolio_loss: 50000.0, portfolio_loss_pct: 0.20, var_breach: true, worst_asset: "XYZ".to_string(), worst_asset_loss: 0.35, best_asset: "ABC".to_string(), best_asset_return: -0.05, }; let json = serde_json::to_string(&result).unwrap(); let deserialized: StressTestResult = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.scenario, "Test Scenario"); assert!(deserialized.var_breach); } #[test] fn test_monte_carlo_result_serialization() { let result = MonteCarloResult { paths: vec![vec![100.0, 102.0, 105.0], vec![100.0, 98.0, 96.0]], final_values: vec![105.0, 96.0, 110.0], percentiles: vec![(0.05, 90.0), (0.50, 105.0), (0.95, 120.0)], mean_final_value: 105.0, median_final_value: 105.0, std_final_value: 10.0, }; let json = serde_json::to_string(&result).unwrap(); let deserialized: MonteCarloResult = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.paths.len(), 2); assert_eq!(deserialized.percentiles.len(), 3); } #[test] fn test_risk_portfolio_serialization() { let portfolio = RiskPortfolio { assets: vec![RiskAsset { ticker: "SPY".to_string(), name: "S&P 500 ETF".to_string(), weight: 1.0, returns: vec![0.01, -0.005], }], benchmark_returns: Some(vec![0.012, -0.004]), risk_free_rate: 0.02, }; let json = serde_json::to_string(&portfolio).unwrap(); let deserialized: RiskPortfolio = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.assets.len(), 1); assert!(deserialized.benchmark_returns.is_some()); } }