e67b0f8376
New Rust portfolio_optimization module with PyO3 bindings: - CARA/CRRA utility maximization via projected gradient descent - General-purpose convex objective solver on simplex (ProjectedGradientSolver) - Mean-variance optimization (max Sharpe, target return, min variance) - Equal Risk Contribution (ERC) portfolio allocation - Python bindings: cara_optimal_weights, mean_variance_optimal_weights, min_variance_weights, erc_weights - 6/6 unit tests passing Convergence fix: removed gradient-norm criterion on simplex boundary (projected gradient never vanishes at constrained optimum). Default learning rate increased from 0.005 to 0.1.
76 lines
2.1 KiB
Rust
76 lines
2.1 KiB
Rust
//! Core traits for portfolio utility and convex optimisation.
|
||
|
||
use crate::core::OptimizrError;
|
||
|
||
/// A utility function U(x) mapping wealth → utility.
|
||
pub trait UtilityFunction: Send + Sync {
|
||
/// U(x) — the utility of wealth level x.
|
||
fn utility(&self, x: f64) -> f64;
|
||
|
||
/// U'(x) — first derivative (marginal utility).
|
||
fn marginal_utility(&self, x: f64) -> f64;
|
||
|
||
/// (U')^{-1}(y) — inverse marginal utility (used in duality).
|
||
fn inverse_marginal(&self, y: f64) -> f64;
|
||
|
||
/// Risk-aversion coefficient A(x) = -U''(x) / U'(x).
|
||
fn risk_aversion(&self, x: f64) -> f64;
|
||
|
||
/// Name identifier for logging / serialisation.
|
||
fn name(&self) -> &str;
|
||
}
|
||
|
||
/// Convex objective f(w) over portfolio weights w ∈ ℝ^n.
|
||
///
|
||
/// Used by convex solvers (projected gradient, ADMM, etc.).
|
||
pub trait ConvexObjective: Send + Sync {
|
||
/// f(w) — objective value.
|
||
fn value(&self, w: &[f64]) -> f64;
|
||
|
||
/// ∇f(w) — gradient vector.
|
||
fn gradient(&self, w: &[f64]) -> Vec<f64>;
|
||
|
||
/// Dimension of the weight vector.
|
||
fn dim(&self) -> usize;
|
||
}
|
||
|
||
/// Convex constraint g(w) ≤ 0.
|
||
pub trait ConvexConstraint: Send + Sync {
|
||
/// g(w) — constraint value (feasible when ≤ 0).
|
||
fn value(&self, w: &[f64]) -> f64;
|
||
|
||
/// ∇g(w) — gradient of constraint function.
|
||
fn gradient(&self, w: &[f64]) -> Vec<f64>;
|
||
}
|
||
|
||
/// Result of a portfolio optimisation.
|
||
#[derive(Debug, Clone)]
|
||
pub struct PortfolioResult {
|
||
pub weights: Vec<f64>,
|
||
pub utility: f64,
|
||
pub expected_return: f64,
|
||
pub portfolio_variance: f64,
|
||
pub iterations: usize,
|
||
pub converged: bool,
|
||
}
|
||
|
||
impl PortfolioResult {
|
||
pub fn sharpe_ratio(&self, risk_free: f64) -> f64 {
|
||
let vol = self.portfolio_variance.sqrt();
|
||
if vol < 1e-15 {
|
||
return 0.0;
|
||
}
|
||
(self.expected_return - risk_free) / vol
|
||
}
|
||
}
|
||
|
||
/// Generic portfolio optimiser trait.
|
||
pub trait PortfolioOptimizer: Send + Sync {
|
||
fn optimize(
|
||
&self,
|
||
mu: &[f64],
|
||
cov: &[Vec<f64>],
|
||
max_weight: f64,
|
||
) -> Result<PortfolioResult, OptimizrError>;
|
||
}
|