Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
+286
View File
@@ -0,0 +1,286 @@
//! Efficient frontier generation.
//!
//! Generates mean-variance and higher-order efficient frontiers.
use crate::QuantumPortError;
use crate::optimizer::PortfolioOptimizer;
use quantumport_shared::{
EfficientFrontier, FrontierPoint, FrontierType, OptimizationMethod, OptimizationObjectives,
OptimizationRequest,
};
/// Efficient frontier generator.
#[derive(Debug)]
pub struct FrontierGenerator {
/// Portfolio optimizer
optimizer: PortfolioOptimizer,
}
impl Default for FrontierGenerator {
fn default() -> Self {
Self::new()
}
}
impl FrontierGenerator {
/// Create a new frontier generator.
#[must_use]
pub fn new() -> Self {
Self {
optimizer: PortfolioOptimizer::new(),
}
}
/// Generate efficient frontier.
pub fn generate(
&self,
request: &OptimizationRequest,
num_points: usize,
) -> Result<EfficientFrontier, QuantumPortError> {
// Determine frontier type
let frontier_type = match request.method {
OptimizationMethod::MeanVariance | OptimizationMethod::MinimumVariance => {
FrontierType::MeanVariance
}
OptimizationMethod::MeanVarianceSkewness => FrontierType::MeanVarianceSkewness,
_ => FrontierType::HigherOrder,
};
// Get minimum variance portfolio
let mut min_var_request = request.clone();
min_var_request.method = OptimizationMethod::MinimumVariance;
let min_variance_portfolio = self.optimizer.optimize(&min_var_request)?;
// Get maximum Sharpe portfolio
let mut max_sharpe_request = request.clone();
max_sharpe_request.method = OptimizationMethod::MaximumSharpe;
let max_sharpe_portfolio = self.optimizer.optimize(&max_sharpe_request)?;
// Generate frontier points
let min_return = min_variance_portfolio.statistics.expected_return;
let max_return = min_return + 0.20; // Extend frontier upward
let points =
self.generate_points(request, num_points, min_return, max_return, frontier_type)?;
Ok(EfficientFrontier {
points,
min_variance_portfolio,
max_sharpe_portfolio,
frontier_type,
})
}
/// Generate frontier points.
fn generate_points(
&self,
request: &OptimizationRequest,
num_points: usize,
min_return: f64,
max_return: f64,
frontier_type: FrontierType,
) -> Result<Vec<FrontierPoint>, QuantumPortError> {
let mut points = Vec::with_capacity(num_points);
for i in 0..num_points {
let target_return =
min_return + (max_return - min_return) * (i as f64 / (num_points - 1) as f64);
// Create request with target return
let mut point_request = request.clone();
point_request.objectives.target_return = Some(target_return);
// Optimize for this target
let portfolio = self.optimizer.optimize(&point_request)?;
let point = FrontierPoint {
expected_return: portfolio.statistics.expected_return,
volatility: portfolio.statistics.volatility,
skewness: if frontier_type != FrontierType::MeanVariance {
Some(portfolio.statistics.skewness)
} else {
None
},
kurtosis: if frontier_type == FrontierType::HigherOrder {
Some(portfolio.statistics.kurtosis)
} else {
None
},
sharpe_ratio: portfolio.statistics.sharpe_ratio,
weights: portfolio.weights,
};
points.push(point);
}
Ok(points)
}
/// Generate 3D frontier (return-volatility-skewness).
pub fn generate_3d_frontier(
&self,
request: &OptimizationRequest,
return_points: usize,
skew_points: usize,
) -> Result<Vec<FrontierPoint>, QuantumPortError> {
let mut points = Vec::new();
// Get range of returns
let mut min_var_request = request.clone();
min_var_request.method = OptimizationMethod::MinimumVariance;
let min_var = self.optimizer.optimize(&min_var_request)?;
let min_return = min_var.statistics.expected_return;
let max_return = min_return + 0.15;
// For each return level, vary skewness preference
for i in 0..return_points {
let target_return =
min_return + (max_return - min_return) * (i as f64 / (return_points - 1) as f64);
for j in 0..skew_points {
let skew_pref = j as f64 / (skew_points - 1) as f64 * 2.0; // 0 to 2
let mut point_request = request.clone();
point_request.objectives = OptimizationObjectives {
target_return: Some(target_return),
minimize_variance: true,
maximize_skewness: true,
minimize_kurtosis: true,
risk_aversion: 1.0,
skewness_preference: skew_pref,
kurtosis_aversion: 0.5,
};
let portfolio = self.optimizer.optimize(&point_request)?;
let point = FrontierPoint {
expected_return: portfolio.statistics.expected_return,
volatility: portfolio.statistics.volatility,
skewness: Some(portfolio.statistics.skewness),
kurtosis: Some(portfolio.statistics.kurtosis),
sharpe_ratio: portfolio.statistics.sharpe_ratio,
weights: portfolio.weights,
};
points.push(point);
}
}
Ok(points)
}
}
#[cfg(test)]
mod tests {
use super::*;
use quantumport_shared::{Asset, AssetClass, AssetReturns, 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_frontier_generator_creation() {
let generator = FrontierGenerator::new();
assert!(std::mem::size_of_val(&generator) > 0);
}
#[test]
fn test_generate_frontier() {
let generator = FrontierGenerator::new();
let request = create_test_request();
let result = generator.generate(&request, 5);
assert!(result.is_ok());
let frontier = result.unwrap();
assert_eq!(frontier.points.len(), 5);
}
#[test]
fn test_frontier_has_min_variance() {
let generator = FrontierGenerator::new();
let request = create_test_request();
let frontier = generator.generate(&request, 5).unwrap();
assert!(!frontier.min_variance_portfolio.weights.is_empty());
}
#[test]
fn test_frontier_has_max_sharpe() {
let generator = FrontierGenerator::new();
let request = create_test_request();
let frontier = generator.generate(&request, 5).unwrap();
assert!(!frontier.max_sharpe_portfolio.weights.is_empty());
}
#[test]
fn test_frontier_points_ordered() {
let generator = FrontierGenerator::new();
let request = create_test_request();
let frontier = generator.generate(&request, 10).unwrap();
// Returns should generally increase along frontier
let first_return = frontier.points[0].expected_return;
let last_return = frontier.points[frontier.points.len() - 1].expected_return;
assert!(last_return >= first_return - 0.1); // Allow some tolerance
}
#[test]
fn test_3d_frontier() {
let generator = FrontierGenerator::new();
let request = create_test_request();
let result = generator.generate_3d_frontier(&request, 3, 3);
assert!(result.is_ok());
let points = result.unwrap();
assert_eq!(points.len(), 9); // 3 x 3
// All points should have skewness
for point in &points {
assert!(point.skewness.is_some());
}
}
}