(
cov: &DMatrix<f64>,
exp_ret: &[f64],
risk_free: f64,
bounds: &[(f64, f64)],
)
| 320 | } |
| 321 | |
| 322 | fn solve_max_sharpe( |
| 323 | cov: &DMatrix<f64>, |
| 324 | exp_ret: &[f64], |
| 325 | risk_free: f64, |
| 326 | bounds: &[(f64, f64)], |
| 327 | ) -> Result<Vec<f64>, AllocError> { |
| 328 | check_bounds_feasible(bounds)?; |
| 329 | let n = cov.nrows(); |
| 330 | if n == 0 || exp_ret.len() != n { |
| 331 | return Err(AllocError::DimensionMismatch); |
| 332 | } |
| 333 | let excess: Vec<f64> = exp_ret.iter().map(|r| r - risk_free).collect(); |
| 334 | let excess_vec = DVector::from_vec(excess.clone()); |
| 335 | let inv = cov |
| 336 | .clone() |
| 337 | .try_inverse() |
| 338 | .ok_or(AllocError::OptimizationFailed("covariance not invertible"))?; |
| 339 | let mut w: Vec<f64> = (inv.clone() * excess_vec).data.as_vec().clone(); |
| 340 | // normalize to sum 1 |
| 341 | let sum: f64 = w.iter().sum(); |
| 342 | if sum.abs() > 1e-12 { |
| 343 | for wi in w.iter_mut() { |
| 344 | *wi /= sum; |
| 345 | } |
| 346 | } |
| 347 | if w.iter().all(|v| !v.is_finite()) { |
| 348 | return Err(AllocError::NaNResult("weights not finite")); |
| 349 | } |
| 350 | project_to_bounds(&mut w, bounds)?; |
| 351 | Ok(w) |
| 352 | } |
| 353 | |
| 354 | fn efficient_risk_from_inputs( |
| 355 | exp_ret: &[f64], |
no test coverage detected