quantwave_core/regimes/
multi_asset.rs1use crate::traits::Next;
7use crate::regimes::MarketRegime;
8use crate::regimes::volatility_clustering::VolatilityClusterer;
9use serde::{Deserialize, Serialize};
10use std::collections::VecDeque;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MultiAssetClusterer {
15 n_assets: usize,
16 window_size: usize,
17 inner: VolatilityClusterer,
19 history: Vec<VecDeque<f64>>,
20}
21
22impl MultiAssetClusterer {
23 pub fn new(n_assets: usize, window_size: usize, k: usize) -> Self {
24 Self {
30 n_assets,
31 window_size,
32 inner: VolatilityClusterer::new(14, window_size, k),
33 history: vec![VecDeque::with_capacity(window_size); n_assets],
34 }
35 }
36
37 fn calculate_average_correlation(&self) -> f64 {
38 if self.history[0].len() < self.window_size {
39 return 1.0;
40 }
41
42 let mut total_corr = 0.0;
43 let mut pairs = 0;
44
45 for i in 0..self.n_assets {
46 for j in (i + 1)..self.n_assets {
47 let corr = self.correlation(i, j);
48 total_corr += corr;
49 pairs += 1;
50 }
51 }
52
53 if pairs == 0 { 1.0 } else { total_corr / pairs as f64 }
54 }
55
56 fn correlation(&self, i: usize, j: usize) -> f64 {
57 let x = &self.history[i];
58 let y = &self.history[j];
59 let n = x.len() as f64;
60
61 let mean_x = x.iter().sum::<f64>() / n;
62 let mean_y = y.iter().sum::<f64>() / n;
63
64 let mut cov = 0.0;
65 let mut var_x = 0.0;
66 let mut var_y = 0.0;
67
68 for k in 0..x.len() {
69 let dx = x[k] - mean_x;
70 let dy = y[k] - mean_y;
71 cov += dx * dy;
72 var_x += dx * dx;
73 var_y += dy * dy;
74 }
75
76 let den = (var_x * var_y).sqrt();
77 if den == 0.0 { 1.0 } else { cov / den }
78 }
79}
80
81impl Next<&[f64]> for MultiAssetClusterer {
82 type Output = MarketRegime;
83
84 fn next(&mut self, returns: &[f64]) -> Self::Output {
85 if returns.len() != self.n_assets {
86 return MarketRegime::Steady;
87 }
88
89 for (i, &r) in returns.iter().enumerate() {
91 self.history[i].push_back(r);
92 if self.history[i].len() > self.window_size {
93 self.history[i].pop_front();
94 }
95 }
96
97 let mean_abs_ret = returns.iter().map(|r| r.abs()).sum::<f64>() / self.n_assets as f64;
100
101 let mean_ret = returns.iter().sum::<f64>() / self.n_assets as f64;
103 let dispersion = returns.iter().map(|r| (r - mean_ret).powi(2)).sum::<f64>() / self.n_assets as f64;
104
105 let avg_corr = self.calculate_average_correlation();
107
108 self.inner.next((mean_abs_ret, mean_abs_ret * (1.0 - dispersion.sqrt()), avg_corr))
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_multi_asset_clusterer_basic() {
121 let mut clusterer = MultiAssetClusterer::new(2, 5, 2);
122
123 for _ in 0..10 {
125 clusterer.next(&[0.01, 0.01]);
126 }
127 let r1 = clusterer.next(&[0.01, 0.01]);
128
129 for _ in 0..10 {
131 clusterer.next(&[0.05, 0.05]);
132 }
133 let r2 = clusterer.next(&[0.05, 0.05]);
134
135 assert!(matches!(r1, MarketRegime::Steady | MarketRegime::Cluster(_)));
138 assert!(matches!(r2, MarketRegime::Steady | MarketRegime::Cluster(_)));
139 }
140}