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

309 lines
8.6 KiB
Rust

//! QuantumPort - Higher-Order Portfolio Optimization Demo.
//!
//! This demo showcases portfolio optimization using higher-order moments
//! (skewness, kurtosis) beyond traditional mean-variance optimization.
pub mod comparison;
pub mod frontier;
pub mod moments;
pub mod optimizer;
pub mod sample_data;
use quantumport_shared::{
EfficientFrontier, OptimizationRequest, OptimizedPortfolio, PortfolioComparison,
PortfolioConstraints,
};
use thiserror::Error;
use comparison::PortfolioComparer;
use frontier::FrontierGenerator;
use optimizer::PortfolioOptimizer;
/// Errors that can occur during portfolio optimization.
#[derive(Debug, Error)]
pub enum QuantumPortError {
/// No assets provided.
#[error("No assets provided for optimization")]
NoAssets,
/// No returns data provided.
#[error("No returns data provided")]
NoReturnsData,
/// Mismatched assets and returns.
#[error("Asset count ({0}) doesn't match returns count ({1})")]
MismatchedData(usize, usize),
/// Optimization failed.
#[error("Optimization failed: {0}")]
OptimizationFailed(String),
/// Invalid constraints.
#[error("Invalid constraints: {0}")]
InvalidConstraints(String),
/// Numerical error.
#[error("Numerical error: {0}")]
NumericalError(String),
}
/// Main QuantumPort system for higher-order portfolio optimization.
#[derive(Debug)]
pub struct QuantumPort {
optimizer: PortfolioOptimizer,
frontier_generator: FrontierGenerator,
comparer: PortfolioComparer,
}
impl Default for QuantumPort {
fn default() -> Self {
Self::new()
}
}
impl QuantumPort {
/// Create a new QuantumPort system.
#[must_use]
pub fn new() -> Self {
Self {
optimizer: PortfolioOptimizer::new(),
frontier_generator: FrontierGenerator::new(),
comparer: PortfolioComparer::new(),
}
}
/// Optimize a portfolio based on the request.
///
/// # Errors
///
/// Returns error if optimization fails or input data is invalid.
pub fn optimize(
&self,
request: &OptimizationRequest,
) -> Result<OptimizedPortfolio, QuantumPortError> {
// Validate input
self.validate_request(request)?;
// Run optimization
self.optimizer.optimize(request)
}
/// Generate efficient frontier.
///
/// # Errors
///
/// Returns error if frontier generation fails.
pub fn generate_frontier(
&self,
request: &OptimizationRequest,
num_points: usize,
) -> Result<EfficientFrontier, QuantumPortError> {
self.validate_request(request)?;
self.frontier_generator.generate(request, num_points)
}
/// Compare mean-variance vs higher-order optimization.
///
/// # Errors
///
/// Returns error if comparison fails.
pub fn compare_methods(
&self,
request: &OptimizationRequest,
) -> Result<PortfolioComparison, QuantumPortError> {
self.validate_request(request)?;
self.comparer.compare(request)
}
/// Validate optimization request.
fn validate_request(&self, request: &OptimizationRequest) -> Result<(), QuantumPortError> {
if request.assets.is_empty() {
return Err(QuantumPortError::NoAssets);
}
if request.returns.is_empty() {
return Err(QuantumPortError::NoReturnsData);
}
if request.assets.len() != request.returns.len() {
return Err(QuantumPortError::MismatchedData(
request.assets.len(),
request.returns.len(),
));
}
self.validate_constraints(&request.constraints)?;
Ok(())
}
/// Validate portfolio constraints.
fn validate_constraints(
&self,
constraints: &PortfolioConstraints,
) -> Result<(), QuantumPortError> {
if constraints.budget <= 0.0 {
return Err(QuantumPortError::InvalidConstraints(
"Budget must be positive".to_string(),
));
}
if constraints.max_weight < constraints.min_weight {
return Err(QuantumPortError::InvalidConstraints(
"max_weight must be >= min_weight".to_string(),
));
}
if !constraints.allow_short && constraints.min_weight < 0.0 {
return Err(QuantumPortError::InvalidConstraints(
"min_weight must be >= 0 when short selling is not allowed".to_string(),
));
}
Ok(())
}
}
/// Run a complete demo with sample data.
///
/// # Errors
///
/// Returns error if demo fails.
pub fn run_demo() -> Result<PortfolioComparison, QuantumPortError> {
let request = quantumport_shared::get_sample_request();
let qp = QuantumPort::new();
qp.compare_methods(&request)
}
#[cfg(test)]
mod tests {
use super::*;
use quantumport_shared::{
Asset, AssetClass, AssetReturns, OptimizationMethod, OptimizationObjectives,
};
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],
start_date: "2024-01-01".to_string(),
end_date: "2024-01-05".to_string(),
},
AssetReturns {
symbol: "B".to_string(),
returns: vec![0.002, 0.001, 0.003, -0.001, 0.002],
start_date: "2024-01-01".to_string(),
end_date: "2024-01-05".to_string(),
},
],
objectives: OptimizationObjectives::default(),
constraints: PortfolioConstraints::default(),
method: OptimizationMethod::HigherOrder,
}
}
#[test]
fn test_quantumport_creation() {
let qp = QuantumPort::new();
assert!(std::mem::size_of_val(&qp) > 0);
}
#[test]
fn test_optimize() {
let request = create_test_request();
let qp = QuantumPort::new();
let result = qp.optimize(&request);
assert!(result.is_ok());
let portfolio = result.unwrap();
assert!(!portfolio.weights.is_empty());
}
#[test]
fn test_no_assets_error() {
let mut request = create_test_request();
request.assets.clear();
request.returns.clear();
let qp = QuantumPort::new();
let result = qp.optimize(&request);
assert!(matches!(result.unwrap_err(), QuantumPortError::NoAssets));
}
#[test]
fn test_mismatched_data_error() {
let mut request = create_test_request();
request.returns.pop();
let qp = QuantumPort::new();
let result = qp.optimize(&request);
assert!(matches!(
result.unwrap_err(),
QuantumPortError::MismatchedData(_, _)
));
}
#[test]
fn test_generate_frontier() {
let request = create_test_request();
let qp = QuantumPort::new();
let result = qp.generate_frontier(&request, 10);
assert!(result.is_ok());
let frontier = result.unwrap();
assert!(!frontier.points.is_empty());
}
#[test]
fn test_compare_methods() {
let request = create_test_request();
let qp = QuantumPort::new();
let result = qp.compare_methods(&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_run_demo() {
let result = run_demo();
assert!(result.is_ok());
}
#[test]
fn test_invalid_constraints() {
let mut request = create_test_request();
request.constraints.max_weight = -1.0;
let qp = QuantumPort::new();
let result = qp.optimize(&request);
assert!(matches!(
result.unwrap_err(),
QuantumPortError::InvalidConstraints(_)
));
}
}