Skip to main content

finance_query/risk/
beta.rs

1//! Beta calculation against a benchmark.
2
3/// Compute the beta of an asset relative to a benchmark.
4///
5/// `β = Cov(asset, benchmark) / Var(benchmark)`
6///
7/// Both slices must have the same length and at least 2 observations.
8/// Returns `None` on insufficient data or zero benchmark variance.
9pub 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        // Asset = benchmark → beta = 1.0
45        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]; // constant → variance = 0
54        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        // Asset = -benchmark → beta = -1.0
65        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}