Files
rustytorch/demos/quantumport-shared/src/lib.rs
T
2026-03-04 00:08:42 +00:00

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);
}
}