624 lines
19 KiB
Rust
624 lines
19 KiB
Rust
//! Shared IPC types for QuantumPort higher-order portfolio optimization demo.
|
|
//!
|
|
//! This crate provides data structures for communication between
|
|
//! the Tauri frontend and Rust backend for portfolio optimization.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// ============================================================================
|
|
// Asset Types
|
|
// ============================================================================
|
|
|
|
/// Individual asset in the portfolio universe.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Asset {
|
|
/// Asset ticker symbol
|
|
pub symbol: String,
|
|
/// Asset name
|
|
pub name: String,
|
|
/// Asset class
|
|
pub asset_class: AssetClass,
|
|
/// Sector (for equities)
|
|
pub sector: Option<String>,
|
|
/// Currency
|
|
pub currency: String,
|
|
}
|
|
|
|
/// Asset class.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AssetClass {
|
|
/// Equity / Stock
|
|
Equity,
|
|
/// Fixed income / Bond
|
|
FixedIncome,
|
|
/// Commodity
|
|
Commodity,
|
|
/// Real estate
|
|
RealEstate,
|
|
/// Cryptocurrency
|
|
Crypto,
|
|
/// Cash equivalent
|
|
Cash,
|
|
/// Alternative investment
|
|
Alternative,
|
|
}
|
|
|
|
/// Historical returns data for an asset.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AssetReturns {
|
|
/// Asset symbol
|
|
pub symbol: String,
|
|
/// Daily returns (chronological order)
|
|
pub returns: Vec<f64>,
|
|
/// Start date (ISO 8601)
|
|
pub start_date: String,
|
|
/// End date (ISO 8601)
|
|
pub end_date: String,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Statistical Moments
|
|
// ============================================================================
|
|
|
|
/// First four statistical moments for portfolio analysis.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MomentStatistics {
|
|
/// Expected return (1st moment)
|
|
pub mean: f64,
|
|
/// Variance (2nd moment)
|
|
pub variance: f64,
|
|
/// Skewness (3rd moment) - asymmetry of distribution
|
|
pub skewness: f64,
|
|
/// Kurtosis (4th moment) - tail heaviness
|
|
pub kurtosis: f64,
|
|
}
|
|
|
|
/// Covariance matrix for portfolio assets.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CovarianceMatrix {
|
|
/// Asset symbols in order
|
|
pub symbols: Vec<String>,
|
|
/// Flattened covariance matrix (row-major)
|
|
pub data: Vec<f64>,
|
|
/// Matrix dimension (n x n)
|
|
pub dimension: usize,
|
|
}
|
|
|
|
impl CovarianceMatrix {
|
|
/// Get covariance between two assets by index.
|
|
pub fn get(&self, i: usize, j: usize) -> Option<f64> {
|
|
if i < self.dimension && j < self.dimension {
|
|
Some(self.data[i * self.dimension + j])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Co-skewness tensor (3rd order tensor).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CoskewnessTensor {
|
|
/// Asset symbols
|
|
pub symbols: Vec<String>,
|
|
/// Flattened tensor data
|
|
pub data: Vec<f64>,
|
|
/// Tensor dimension (n x n x n)
|
|
pub dimension: usize,
|
|
}
|
|
|
|
/// Co-kurtosis tensor (4th order tensor).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CokurtosisTensor {
|
|
/// Asset symbols
|
|
pub symbols: Vec<String>,
|
|
/// Flattened tensor data
|
|
pub data: Vec<f64>,
|
|
/// Tensor dimension (n x n x n x n)
|
|
pub dimension: usize,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Optimization Types
|
|
// ============================================================================
|
|
|
|
/// Portfolio optimization request.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OptimizationRequest {
|
|
/// Available assets
|
|
pub assets: Vec<Asset>,
|
|
/// Historical returns for each asset
|
|
pub returns: Vec<AssetReturns>,
|
|
/// Optimization objectives
|
|
pub objectives: OptimizationObjectives,
|
|
/// Portfolio constraints
|
|
pub constraints: PortfolioConstraints,
|
|
/// Optimization method
|
|
pub method: OptimizationMethod,
|
|
}
|
|
|
|
/// Optimization objectives (what to optimize for).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OptimizationObjectives {
|
|
/// Target return (optional)
|
|
pub target_return: Option<f64>,
|
|
/// Minimize variance
|
|
pub minimize_variance: bool,
|
|
/// Maximize skewness (prefer positive skew)
|
|
pub maximize_skewness: bool,
|
|
/// Minimize kurtosis (avoid fat tails)
|
|
pub minimize_kurtosis: bool,
|
|
/// Risk aversion coefficient (higher = more risk averse)
|
|
pub risk_aversion: f64,
|
|
/// Skewness preference coefficient
|
|
pub skewness_preference: f64,
|
|
/// Kurtosis aversion coefficient
|
|
pub kurtosis_aversion: f64,
|
|
}
|
|
|
|
impl Default for OptimizationObjectives {
|
|
fn default() -> Self {
|
|
Self {
|
|
target_return: None,
|
|
minimize_variance: true,
|
|
maximize_skewness: true,
|
|
minimize_kurtosis: true,
|
|
risk_aversion: 1.0,
|
|
skewness_preference: 0.5,
|
|
kurtosis_aversion: 0.5,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Portfolio constraints.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortfolioConstraints {
|
|
/// Budget constraint (weights sum to this, typically 1.0)
|
|
pub budget: f64,
|
|
/// Allow short selling
|
|
pub allow_short: bool,
|
|
/// Maximum weight per asset
|
|
pub max_weight: f64,
|
|
/// Minimum weight per asset (0 for long-only)
|
|
pub min_weight: f64,
|
|
/// Maximum number of assets to hold (cardinality)
|
|
pub max_assets: Option<usize>,
|
|
/// Sector constraints (max weight per sector)
|
|
pub sector_constraints: Vec<SectorConstraint>,
|
|
/// Turnover constraint (max change from current portfolio)
|
|
pub max_turnover: Option<f64>,
|
|
}
|
|
|
|
impl Default for PortfolioConstraints {
|
|
fn default() -> Self {
|
|
Self {
|
|
budget: 1.0,
|
|
allow_short: false,
|
|
max_weight: 1.0,
|
|
min_weight: 0.0,
|
|
max_assets: None,
|
|
sector_constraints: vec![],
|
|
max_turnover: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Sector weight constraint.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SectorConstraint {
|
|
/// Sector name
|
|
pub sector: String,
|
|
/// Maximum weight in sector
|
|
pub max_weight: f64,
|
|
/// Minimum weight in sector (optional)
|
|
pub min_weight: Option<f64>,
|
|
}
|
|
|
|
/// Optimization method.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum OptimizationMethod {
|
|
/// Classical mean-variance (Markowitz)
|
|
MeanVariance,
|
|
/// Mean-variance with skewness
|
|
MeanVarianceSkewness,
|
|
/// Full higher-order optimization (mean, variance, skewness, kurtosis)
|
|
HigherOrder,
|
|
/// QAOA-inspired classical optimization
|
|
QAOAInspired,
|
|
/// Minimum variance portfolio
|
|
MinimumVariance,
|
|
/// Maximum Sharpe ratio
|
|
MaximumSharpe,
|
|
/// Risk parity
|
|
RiskParity,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Portfolio Results
|
|
// ============================================================================
|
|
|
|
/// Optimized portfolio result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OptimizedPortfolio {
|
|
/// Asset weights
|
|
pub weights: Vec<PortfolioWeight>,
|
|
/// Portfolio statistics
|
|
pub statistics: PortfolioStatistics,
|
|
/// Risk metrics
|
|
pub risk_metrics: RiskMetrics,
|
|
/// Optimization metadata
|
|
pub metadata: OptimizationMetadata,
|
|
}
|
|
|
|
/// Individual asset weight in portfolio.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortfolioWeight {
|
|
/// Asset symbol
|
|
pub symbol: String,
|
|
/// Weight (fraction of portfolio)
|
|
pub weight: f64,
|
|
/// Value (if portfolio value provided)
|
|
pub value: Option<f64>,
|
|
}
|
|
|
|
/// Portfolio-level statistics.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortfolioStatistics {
|
|
/// Expected annual return
|
|
pub expected_return: f64,
|
|
/// Annual volatility (std dev)
|
|
pub volatility: f64,
|
|
/// Skewness of portfolio returns
|
|
pub skewness: f64,
|
|
/// Excess kurtosis of portfolio returns
|
|
pub kurtosis: f64,
|
|
/// Sharpe ratio
|
|
pub sharpe_ratio: f64,
|
|
/// Sortino ratio
|
|
pub sortino_ratio: f64,
|
|
/// Maximum drawdown
|
|
pub max_drawdown: f64,
|
|
}
|
|
|
|
/// Portfolio risk metrics.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct RiskMetrics {
|
|
/// Value at Risk (95%)
|
|
pub var_95: f64,
|
|
/// Value at Risk (99%)
|
|
pub var_99: f64,
|
|
/// Conditional VaR / Expected Shortfall (95%)
|
|
pub cvar_95: f64,
|
|
/// Conditional VaR / Expected Shortfall (99%)
|
|
pub cvar_99: f64,
|
|
/// Beta to market
|
|
pub beta: f64,
|
|
/// Tracking error (if benchmark provided)
|
|
pub tracking_error: Option<f64>,
|
|
/// Information ratio (if benchmark provided)
|
|
pub information_ratio: Option<f64>,
|
|
}
|
|
|
|
/// Optimization metadata.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OptimizationMetadata {
|
|
/// Method used
|
|
pub method: OptimizationMethod,
|
|
/// Number of iterations
|
|
pub iterations: usize,
|
|
/// Convergence achieved
|
|
pub converged: bool,
|
|
/// Final objective value
|
|
pub objective_value: f64,
|
|
/// Optimization time (seconds)
|
|
pub time_seconds: f64,
|
|
/// Constraints satisfied
|
|
pub constraints_satisfied: bool,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Efficient Frontier
|
|
// ============================================================================
|
|
|
|
/// Efficient frontier response.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EfficientFrontier {
|
|
/// Points on the frontier
|
|
pub points: Vec<FrontierPoint>,
|
|
/// Minimum variance portfolio
|
|
pub min_variance_portfolio: OptimizedPortfolio,
|
|
/// Maximum Sharpe portfolio (tangency)
|
|
pub max_sharpe_portfolio: OptimizedPortfolio,
|
|
/// Frontier type
|
|
pub frontier_type: FrontierType,
|
|
}
|
|
|
|
/// Single point on efficient frontier.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FrontierPoint {
|
|
/// Expected return at this point
|
|
pub expected_return: f64,
|
|
/// Volatility at this point
|
|
pub volatility: f64,
|
|
/// Skewness at this point (for 3D frontier)
|
|
pub skewness: Option<f64>,
|
|
/// Kurtosis at this point
|
|
pub kurtosis: Option<f64>,
|
|
/// Sharpe ratio at this point
|
|
pub sharpe_ratio: f64,
|
|
/// Portfolio weights at this point
|
|
pub weights: Vec<PortfolioWeight>,
|
|
}
|
|
|
|
/// Type of efficient frontier.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum FrontierType {
|
|
/// Classical mean-variance frontier
|
|
MeanVariance,
|
|
/// Mean-variance-skewness frontier (3D)
|
|
MeanVarianceSkewness,
|
|
/// Higher-order frontier
|
|
HigherOrder,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Comparison Types
|
|
// ============================================================================
|
|
|
|
/// Portfolio comparison result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortfolioComparison {
|
|
/// Mean-variance optimized portfolio
|
|
pub mean_variance: OptimizedPortfolio,
|
|
/// Higher-order optimized portfolio
|
|
pub higher_order: OptimizedPortfolio,
|
|
/// Comparison metrics
|
|
pub comparison: ComparisonMetrics,
|
|
}
|
|
|
|
/// Metrics comparing two portfolio approaches.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ComparisonMetrics {
|
|
/// Return difference (higher-order - mean-variance)
|
|
pub return_difference: f64,
|
|
/// Volatility difference
|
|
pub volatility_difference: f64,
|
|
/// Skewness improvement
|
|
pub skewness_improvement: f64,
|
|
/// Kurtosis reduction
|
|
pub kurtosis_reduction: f64,
|
|
/// Sharpe ratio difference
|
|
pub sharpe_difference: f64,
|
|
/// CVaR improvement (reduction in tail risk)
|
|
pub cvar_improvement: f64,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Sample Data
|
|
// ============================================================================
|
|
|
|
/// Get sample optimization request for demo.
|
|
pub fn get_sample_request() -> OptimizationRequest {
|
|
OptimizationRequest {
|
|
assets: get_sample_assets(),
|
|
returns: get_sample_returns(),
|
|
objectives: OptimizationObjectives::default(),
|
|
constraints: PortfolioConstraints::default(),
|
|
method: OptimizationMethod::HigherOrder,
|
|
}
|
|
}
|
|
|
|
/// Get sample assets.
|
|
pub fn get_sample_assets() -> Vec<Asset> {
|
|
vec![
|
|
Asset {
|
|
symbol: "SPY".to_string(),
|
|
name: "S&P 500 ETF".to_string(),
|
|
asset_class: AssetClass::Equity,
|
|
sector: Some("Broad Market".to_string()),
|
|
currency: "USD".to_string(),
|
|
},
|
|
Asset {
|
|
symbol: "QQQ".to_string(),
|
|
name: "NASDAQ 100 ETF".to_string(),
|
|
asset_class: AssetClass::Equity,
|
|
sector: Some("Technology".to_string()),
|
|
currency: "USD".to_string(),
|
|
},
|
|
Asset {
|
|
symbol: "IWM".to_string(),
|
|
name: "Russell 2000 ETF".to_string(),
|
|
asset_class: AssetClass::Equity,
|
|
sector: Some("Small Cap".to_string()),
|
|
currency: "USD".to_string(),
|
|
},
|
|
Asset {
|
|
symbol: "TLT".to_string(),
|
|
name: "20+ Year Treasury Bond ETF".to_string(),
|
|
asset_class: AssetClass::FixedIncome,
|
|
sector: None,
|
|
currency: "USD".to_string(),
|
|
},
|
|
Asset {
|
|
symbol: "GLD".to_string(),
|
|
name: "Gold ETF".to_string(),
|
|
asset_class: AssetClass::Commodity,
|
|
sector: None,
|
|
currency: "USD".to_string(),
|
|
},
|
|
Asset {
|
|
symbol: "VNQ".to_string(),
|
|
name: "Real Estate ETF".to_string(),
|
|
asset_class: AssetClass::RealEstate,
|
|
sector: None,
|
|
currency: "USD".to_string(),
|
|
},
|
|
]
|
|
}
|
|
|
|
/// Get sample returns data (simulated).
|
|
pub fn get_sample_returns() -> Vec<AssetReturns> {
|
|
// Simulate 252 trading days of returns
|
|
vec![
|
|
AssetReturns {
|
|
symbol: "SPY".to_string(),
|
|
returns: simulate_returns(252, 0.0004, 0.012, -0.1, 3.5),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
AssetReturns {
|
|
symbol: "QQQ".to_string(),
|
|
returns: simulate_returns(252, 0.0005, 0.015, -0.2, 4.0),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
AssetReturns {
|
|
symbol: "IWM".to_string(),
|
|
returns: simulate_returns(252, 0.0003, 0.016, -0.3, 4.2),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
AssetReturns {
|
|
symbol: "TLT".to_string(),
|
|
returns: simulate_returns(252, 0.0001, 0.010, 0.1, 3.0),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
AssetReturns {
|
|
symbol: "GLD".to_string(),
|
|
returns: simulate_returns(252, 0.0002, 0.011, 0.3, 3.8),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
AssetReturns {
|
|
symbol: "VNQ".to_string(),
|
|
returns: simulate_returns(252, 0.0003, 0.014, -0.4, 4.5),
|
|
start_date: "2023-01-01".to_string(),
|
|
end_date: "2023-12-31".to_string(),
|
|
},
|
|
]
|
|
}
|
|
|
|
/// Simulate returns with given characteristics.
|
|
fn simulate_returns(n: usize, mean: f64, std: f64, _skew: f64, _kurt: f64) -> Vec<f64> {
|
|
// Simple pseudo-random simulation (deterministic for demo)
|
|
let mut returns = Vec::with_capacity(n);
|
|
let mut seed = 12345u64;
|
|
|
|
for _ in 0..n {
|
|
// LCG pseudo-random
|
|
seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
|
|
let u1 = (seed >> 16) as f64 / 32768.0;
|
|
seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
|
|
let u2 = (seed >> 16) as f64 / 32768.0;
|
|
|
|
// Box-Muller transform
|
|
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
|
|
let ret = mean + std * z;
|
|
returns.push(ret);
|
|
}
|
|
|
|
returns
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_asset_class() {
|
|
let asset = Asset {
|
|
symbol: "AAPL".to_string(),
|
|
name: "Apple Inc.".to_string(),
|
|
asset_class: AssetClass::Equity,
|
|
sector: Some("Technology".to_string()),
|
|
currency: "USD".to_string(),
|
|
};
|
|
assert_eq!(asset.asset_class, AssetClass::Equity);
|
|
}
|
|
|
|
#[test]
|
|
fn test_optimization_objectives_default() {
|
|
let obj = OptimizationObjectives::default();
|
|
assert!(obj.minimize_variance);
|
|
assert!(obj.maximize_skewness);
|
|
assert!(obj.minimize_kurtosis);
|
|
}
|
|
|
|
#[test]
|
|
fn test_portfolio_constraints_default() {
|
|
let constraints = PortfolioConstraints::default();
|
|
assert!((constraints.budget - 1.0).abs() < 1e-10);
|
|
assert!(!constraints.allow_short);
|
|
}
|
|
|
|
#[test]
|
|
fn test_covariance_matrix_get() {
|
|
let cov = CovarianceMatrix {
|
|
symbols: vec!["A".to_string(), "B".to_string()],
|
|
data: vec![0.04, 0.02, 0.02, 0.09],
|
|
dimension: 2,
|
|
};
|
|
assert!((cov.get(0, 0).unwrap() - 0.04).abs() < 1e-10);
|
|
assert!((cov.get(0, 1).unwrap() - 0.02).abs() < 1e-10);
|
|
assert!((cov.get(1, 1).unwrap() - 0.09).abs() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sample_assets() {
|
|
let assets = get_sample_assets();
|
|
assert_eq!(assets.len(), 6);
|
|
assert_eq!(assets[0].symbol, "SPY");
|
|
}
|
|
|
|
#[test]
|
|
fn test_sample_returns() {
|
|
let returns = get_sample_returns();
|
|
assert_eq!(returns.len(), 6);
|
|
assert_eq!(returns[0].returns.len(), 252);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sample_request() {
|
|
let request = get_sample_request();
|
|
assert!(!request.assets.is_empty());
|
|
assert!(!request.returns.is_empty());
|
|
assert_eq!(request.method, OptimizationMethod::HigherOrder);
|
|
}
|
|
|
|
#[test]
|
|
fn test_serialization() {
|
|
let request = get_sample_request();
|
|
let json = serde_json::to_string(&request).unwrap();
|
|
assert!(json.contains("SPY"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_portfolio_weight() {
|
|
let weight = PortfolioWeight {
|
|
symbol: "SPY".to_string(),
|
|
weight: 0.25,
|
|
value: Some(25000.0),
|
|
};
|
|
assert!((weight.weight - 0.25).abs() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_moment_statistics() {
|
|
let stats = MomentStatistics {
|
|
mean: 0.10,
|
|
variance: 0.04,
|
|
skewness: -0.2,
|
|
kurtosis: 3.5,
|
|
};
|
|
assert!((stats.mean - 0.10).abs() < 1e-10);
|
|
assert!((stats.skewness - (-0.2)).abs() < 1e-10);
|
|
}
|
|
}
|