quantwave_core/regimes/
volatility_clustering.rs1use std::collections::VecDeque;
12use crate::indicators::volatility::ATR;
13use crate::traits::Next;
14use crate::regimes::MarketRegime;
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct VolatilityClusterer {
20 atr: ATR,
21 window: VecDeque<f64>,
22 window_size: usize,
23 k: usize,
24 centroids: Vec<f64>,
25 counts: Vec<usize>,
26 min_observations: usize,
27}
28
29impl VolatilityClusterer {
30 pub fn new(atr_period: usize, window_size: usize, k: usize) -> Self {
37 Self {
38 atr: ATR::new(atr_period),
39 window: VecDeque::with_capacity(window_size),
40 window_size,
41 k,
42 centroids: vec![0.0; k],
43 counts: vec![0; k],
44 min_observations: window_size.max(k * 10), }
46 }
47
48 fn update_clusters(&mut self, val: f64) -> usize {
50 if self.centroids.iter().all(|&c| c == 0.0) {
51 if self.window.len() >= self.k {
53 let mut sorted: Vec<f64> = self.window.iter().copied().collect();
54 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
55 for i in 0..self.k {
56 let idx = (i * (sorted.len() - 1)) / (self.k - 1);
57 self.centroids[i] = sorted[idx];
58 self.counts[i] = 1;
59 }
60 }
61 }
62
63 let mut min_dist = f64::MAX;
65 let mut closest_idx = 0;
66
67 for (i, ¢roid) in self.centroids.iter().enumerate() {
68 let dist = (val - centroid).abs();
69 if dist < min_dist {
70 min_dist = dist;
71 closest_idx = i;
72 }
73 }
74
75 self.counts[closest_idx] += 1;
77 let lr = 1.0 / (self.counts[closest_idx] as f64).sqrt(); self.centroids[closest_idx] += lr * (val - self.centroids[closest_idx]);
79
80 closest_idx
81 }
82}
83
84impl Next<(f64, f64, f64)> for VolatilityClusterer {
85 type Output = MarketRegime;
86
87 fn next(&mut self, input: (f64, f64, f64)) -> Self::Output {
88 let atr_val = self.atr.next(input);
89
90 if self.window.len() >= self.window_size {
91 self.window.pop_front();
92 }
93 self.window.push_back(atr_val);
94
95 let cluster_idx = self.update_clusters(atr_val);
96
97 if self.window.len() < self.min_observations {
98 return MarketRegime::Steady;
99 }
100
101 let mut sorted_indices: Vec<usize> = (0..self.k).collect();
104 sorted_indices.sort_by(|&a, &b| self.centroids[a].partial_cmp(&self.centroids[b]).unwrap());
105
106 let rank = sorted_indices.iter().position(|&i| i == cluster_idx).unwrap_or(0);
107
108 match rank {
109 0 => MarketRegime::Steady, r if r == self.k - 1 => MarketRegime::Crisis, _ => MarketRegime::Cluster(rank as u8),
112 }
113 }
114}