finance_query/risk/
beta.rs1pub fn beta(asset_returns: &[f64], benchmark_returns: &[f64]) -> Option<f64> {
10 let n = asset_returns.len();
11 if n < 2 || n != benchmark_returns.len() {
12 return None;
13 }
14
15 let asset_mean = asset_returns.iter().sum::<f64>() / n as f64;
16 let bench_mean = benchmark_returns.iter().sum::<f64>() / n as f64;
17
18 let covariance: f64 = asset_returns
19 .iter()
20 .zip(benchmark_returns.iter())
21 .map(|(a, b)| (a - asset_mean) * (b - bench_mean))
22 .sum::<f64>()
23 / (n - 1) as f64;
24
25 let bench_variance: f64 = benchmark_returns
26 .iter()
27 .map(|b| (b - bench_mean).powi(2))
28 .sum::<f64>()
29 / (n - 1) as f64;
30
31 if bench_variance == 0.0 {
32 return None;
33 }
34
35 Some(covariance / bench_variance)
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn test_beta_identical() {
44 let r = vec![0.01, -0.02, 0.03, -0.01, 0.02];
46 let b = beta(&r, &r).unwrap();
47 assert!((b - 1.0).abs() < 1e-9, "expected 1.0, got {b}");
48 }
49
50 #[test]
51 fn test_beta_zero_benchmark_variance() {
52 let asset = vec![0.01, 0.02, 0.03];
53 let bench = vec![0.01, 0.01, 0.01]; assert!(beta(&asset, &bench).is_none());
55 }
56
57 #[test]
58 fn test_beta_length_mismatch() {
59 assert!(beta(&[0.01], &[0.01, 0.02]).is_none());
60 }
61
62 #[test]
63 fn test_beta_inverse() {
64 let bench = vec![0.01, -0.02, 0.03, -0.01];
66 let asset: Vec<f64> = bench.iter().map(|x| -x).collect();
67 let b = beta(&asset, &bench).unwrap();
68 assert!((b + 1.0).abs() < 1e-9, "expected -1.0, got {b}");
69 }
70}