use ndarray::Array2;
use ndarray::ArrayView2;
use crate::factors::shrinkage;
use crate::traits::FloatExt;
pub fn shrinkage_covariance<T: FloatExt>(returns: ArrayView2<T>) -> Array2<T> {
shrinkage::ledoit_wolf_shrinkage(returns).covariance
}
pub fn sample_covariance<T: FloatExt>(returns: ArrayView2<T>) -> Array2<T> {
shrinkage::sample_covariance(returns)
}
pub fn portfolio_variance<T: FloatExt>(weights: &[T], cov: ArrayView2<T>) -> T {
let p = weights.len();
assert_eq!(cov.shape(), &[p, p], "cov shape must match weights length");
let mut acc = T::zero();
for i in 0..p {
for j in 0..p {
acc += weights[i] * weights[j] * cov[[i, j]];
}
}
acc
}
#[cfg(test)]
mod tests {
use ndarray::Array2;
use super::*;
#[test]
fn shrinkage_matches_ledoit_wolf() {
let returns = Array2::<f64>::from_shape_fn((50, 3), |(t, j)| {
((t as f64 * 0.1).sin() + j as f64 * 0.05) * 0.01
});
let cov = shrinkage_covariance(returns.view());
assert_eq!(cov.shape(), &[3, 3]);
}
#[test]
fn portfolio_variance_quadratic_form() {
let cov = Array2::<f64>::from_shape_vec((2, 2), vec![0.04, 0.01, 0.01, 0.09]).unwrap();
let w = [0.5, 0.5];
let v = portfolio_variance(&w, cov.view());
assert!((v - (0.04 * 0.25 + 0.09 * 0.25 + 2.0 * 0.5 * 0.5 * 0.01)).abs() < 1e-12);
}
}