291 lines
9.6 KiB
Rust
291 lines
9.6 KiB
Rust
//! 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<PortfolioComparison, QuantumPortError> {
|
|
// 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<Vec<(OptimizationMethod, quantumport_shared::OptimizedPortfolio)>, 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());
|
|
}
|
|
}
|