quantwave_core/regimes/
multi_asset.rs1use crate::regimes::MarketRegime;
7use crate::regimes::volatility_clustering::VolatilityClusterer;
8use crate::traits::Next;
9use crate::utils::RingBuffer as VecDeque;
10use serde::{Deserialize, Serialize};
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 {
54 1.0
55 } else {
56 total_corr / pairs as f64
57 }
58 }
59
60 fn correlation(&self, i: usize, j: usize) -> f64 {
61 let x = &self.history[i];
62 let y = &self.history[j];
63 let n = x.len() as f64;
64
65 let mean_x = x.iter().sum::<f64>() / n;
66 let mean_y = y.iter().sum::<f64>() / n;
67
68 let mut cov = 0.0;
69 let mut var_x = 0.0;
70 let mut var_y = 0.0;
71
72 for k in 0..x.len() {
73 let dx = x[k] - mean_x;
74 let dy = y[k] - mean_y;
75 cov += dx * dy;
76 var_x += dx * dx;
77 var_y += dy * dy;
78 }
79
80 let den = (var_x * var_y).sqrt();
81 if den == 0.0 { 1.0 } else { cov / den }
82 }
83}
84
85impl Next<&[f64]> for MultiAssetClusterer {
86 type Output = MarketRegime;
87
88 fn next(&mut self, returns: &[f64]) -> Self::Output {
89 if returns.len() != self.n_assets {
90 return MarketRegime::Steady;
91 }
92
93 for (i, &r) in returns.iter().enumerate() {
95 self.history[i].push_back(r);
96 if self.history[i].len() > self.window_size {
97 self.history[i].pop_front();
98 }
99 }
100
101 let mean_abs_ret = returns.iter().map(|r| r.abs()).sum::<f64>() / self.n_assets as f64;
104
105 let mean_ret = returns.iter().sum::<f64>() / self.n_assets as f64;
107 let dispersion =
108 returns.iter().map(|r| (r - mean_ret).powi(2)).sum::<f64>() / self.n_assets as f64;
109
110 let avg_corr = self.calculate_average_correlation();
112
113 self.inner.next((
117 mean_abs_ret,
118 mean_abs_ret * (1.0 - dispersion.sqrt()),
119 avg_corr,
120 ))
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_multi_asset_clusterer_basic() {
130 let mut clusterer = MultiAssetClusterer::new(2, 5, 2);
131
132 for _ in 0..10 {
134 clusterer.next(&[0.01, 0.01]);
135 }
136 let r1 = clusterer.next(&[0.01, 0.01]);
137
138 for _ in 0..10 {
140 clusterer.next(&[0.05, 0.05]);
141 }
142 let r2 = clusterer.next(&[0.05, 0.05]);
143
144 assert!(matches!(
147 r1,
148 MarketRegime::Steady | MarketRegime::Cluster(_)
149 ));
150 assert!(matches!(
151 r2,
152 MarketRegime::Steady | MarketRegime::Cluster(_)
153 ));
154 }
155}