//! Portfolio comparison between mean-variance and higher-order optimization. use crate::QuantumPortError; use crate::optimizer::PortfolioOptimizer; use quantumport_shared::{ ComparisonMetrics, OptimizationMethod, OptimizationRequest, PortfolioComparison, }; /// Portfolio method comparer. #[derive(Debug)] pub struct PortfolioComparer { /// Optimizer optimizer: PortfolioOptimizer, } impl Default for PortfolioComparer { fn default() -> Self { Self::new() } } impl PortfolioComparer { /// Create a new comparer. #[must_use] pub fn new() -> Self { Self { optimizer: PortfolioOptimizer::new(), } } /// Compare mean-variance and higher-order optimization. pub fn compare( &self, request: &OptimizationRequest, ) -> Result { // Optimize with mean-variance let mut mv_request = request.clone(); mv_request.method = OptimizationMethod::MeanVariance; let mean_variance = self.optimizer.optimize(&mv_request)?; // Optimize with higher-order let mut ho_request = request.clone(); ho_request.method = OptimizationMethod::HigherOrder; let higher_order = self.optimizer.optimize(&ho_request)?; // Calculate comparison metrics let comparison = ComparisonMetrics { return_difference: higher_order.statistics.expected_return - mean_variance.statistics.expected_return, volatility_difference: higher_order.statistics.volatility - mean_variance.statistics.volatility, skewness_improvement: higher_order.statistics.skewness - mean_variance.statistics.skewness, kurtosis_reduction: mean_variance.statistics.kurtosis - higher_order.statistics.kurtosis, sharpe_difference: higher_order.statistics.sharpe_ratio - mean_variance.statistics.sharpe_ratio, cvar_improvement: mean_variance.risk_metrics.cvar_95 - higher_order.risk_metrics.cvar_95, }; Ok(PortfolioComparison { mean_variance, higher_order, comparison, }) } /// Compare all available methods. pub fn compare_all_methods( &self, request: &OptimizationRequest, ) -> Result, QuantumPortError> { let methods = [ OptimizationMethod::MeanVariance, OptimizationMethod::MinimumVariance, OptimizationMethod::HigherOrder, OptimizationMethod::QAOAInspired, OptimizationMethod::RiskParity, ]; let mut results = Vec::new(); for method in methods { let mut method_request = request.clone(); method_request.method = method; let portfolio = self.optimizer.optimize(&method_request)?; results.push((method, portfolio)); } Ok(results) } /// Generate comparison report as text. #[must_use] pub fn generate_report(comparison: &PortfolioComparison) -> String { let mut report = String::new(); report.push_str("PORTFOLIO OPTIMIZATION COMPARISON\n"); report.push_str("=================================\n\n"); report.push_str("MEAN-VARIANCE PORTFOLIO:\n"); report.push_str(&format!( " Expected Return: {:.2}%\n", comparison.mean_variance.statistics.expected_return * 100.0 )); report.push_str(&format!( " Volatility: {:.2}%\n", comparison.mean_variance.statistics.volatility * 100.0 )); report.push_str(&format!( " Sharpe Ratio: {:.3}\n", comparison.mean_variance.statistics.sharpe_ratio )); report.push_str(&format!( " Skewness: {:.3}\n", comparison.mean_variance.statistics.skewness )); report.push_str(&format!( " Kurtosis: {:.3}\n", comparison.mean_variance.statistics.kurtosis )); report.push_str(&format!( " CVaR (95%): {:.2}%\n\n", comparison.mean_variance.risk_metrics.cvar_95 * 100.0 )); report.push_str("HIGHER-ORDER PORTFOLIO:\n"); report.push_str(&format!( " Expected Return: {:.2}%\n", comparison.higher_order.statistics.expected_return * 100.0 )); report.push_str(&format!( " Volatility: {:.2}%\n", comparison.higher_order.statistics.volatility * 100.0 )); report.push_str(&format!( " Sharpe Ratio: {:.3}\n", comparison.higher_order.statistics.sharpe_ratio )); report.push_str(&format!( " Skewness: {:.3}\n", comparison.higher_order.statistics.skewness )); report.push_str(&format!( " Kurtosis: {:.3}\n", comparison.higher_order.statistics.kurtosis )); report.push_str(&format!( " CVaR (95%): {:.2}%\n\n", comparison.higher_order.risk_metrics.cvar_95 * 100.0 )); report.push_str("COMPARISON:\n"); report.push_str(&format!( " Return Diff: {:+.2}%\n", comparison.comparison.return_difference * 100.0 )); report.push_str(&format!( " Volatility Diff: {:+.2}%\n", comparison.comparison.volatility_difference * 100.0 )); report.push_str(&format!( " Skewness Improvement: {:+.3}\n", comparison.comparison.skewness_improvement )); report.push_str(&format!( " Kurtosis Reduction: {:+.3}\n", comparison.comparison.kurtosis_reduction )); report.push_str(&format!( " Sharpe Diff: {:+.3}\n", comparison.comparison.sharpe_difference )); report.push_str(&format!( " CVaR Improvement:{:+.2}%\n", comparison.comparison.cvar_improvement * 100.0 )); report } } #[cfg(test)] mod tests { use super::*; use quantumport_shared::{ Asset, AssetClass, AssetReturns, OptimizationObjectives, PortfolioConstraints, }; fn create_test_request() -> OptimizationRequest { OptimizationRequest { assets: vec![ Asset { symbol: "A".to_string(), name: "Asset A".to_string(), asset_class: AssetClass::Equity, sector: None, currency: "USD".to_string(), }, Asset { symbol: "B".to_string(), name: "Asset B".to_string(), asset_class: AssetClass::FixedIncome, sector: None, currency: "USD".to_string(), }, ], returns: vec![ AssetReturns { symbol: "A".to_string(), returns: vec![ 0.01, -0.02, 0.015, 0.005, -0.01, 0.02, -0.005, 0.01, 0.008, -0.012, ], start_date: "2024-01-01".to_string(), end_date: "2024-01-10".to_string(), }, AssetReturns { symbol: "B".to_string(), returns: vec![ 0.002, 0.001, 0.003, -0.001, 0.002, 0.001, 0.002, -0.001, 0.003, 0.001, ], start_date: "2024-01-01".to_string(), end_date: "2024-01-10".to_string(), }, ], objectives: OptimizationObjectives::default(), constraints: PortfolioConstraints::default(), method: OptimizationMethod::HigherOrder, } } #[test] fn test_comparer_creation() { let comparer = PortfolioComparer::new(); assert!(std::mem::size_of_val(&comparer) > 0); } #[test] fn test_compare() { let comparer = PortfolioComparer::new(); let request = create_test_request(); let result = comparer.compare(&request); assert!(result.is_ok()); let comparison = result.unwrap(); assert!(!comparison.mean_variance.weights.is_empty()); assert!(!comparison.higher_order.weights.is_empty()); } #[test] fn test_compare_all_methods() { let comparer = PortfolioComparer::new(); let request = create_test_request(); let result = comparer.compare_all_methods(&request); assert!(result.is_ok()); let results = result.unwrap(); assert_eq!(results.len(), 5); } #[test] fn test_generate_report() { let comparer = PortfolioComparer::new(); let request = create_test_request(); let comparison = comparer.compare(&request).unwrap(); let report = PortfolioComparer::generate_report(&comparison); assert!(report.contains("MEAN-VARIANCE")); assert!(report.contains("HIGHER-ORDER")); assert!(report.contains("COMPARISON")); } #[test] fn test_comparison_metrics() { let comparer = PortfolioComparer::new(); let request = create_test_request(); let comparison = comparer.compare(&request).unwrap(); // Metrics should be finite numbers assert!(comparison.comparison.return_difference.is_finite()); assert!(comparison.comparison.volatility_difference.is_finite()); assert!(comparison.comparison.skewness_improvement.is_finite()); } }