quantwave_core/regimes/
analytics.rs1use nalgebra::DMatrix;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct DurationStats {
12 pub regime_id: u32,
13 pub mean_duration: f64,
14 pub median_duration: f64,
15 pub std_duration: f64,
16 pub max_duration: usize,
17 pub total_observations: usize,
18}
19
20pub struct RegimeAnalytics;
22
23impl RegimeAnalytics {
24 pub fn transition_matrix(states: &[u32], num_states: usize) -> Vec<Vec<f64>> {
28 let mut transitions = vec![vec![0usize; num_states]; num_states];
29 let mut row_totals = vec![0usize; num_states];
30
31 for pair in states.windows(2) {
32 let from = pair[0] as usize;
33 let to = pair[1] as usize;
34 if from < num_states && to < num_states {
35 transitions[from][to] += 1;
36 row_totals[from] += 1;
37 }
38 }
39
40 let mut matrix = vec![vec![0.0; num_states]; num_states];
41 for i in 0..num_states {
42 if row_totals[i] > 0 {
43 for j in 0..num_states {
44 matrix[i][j] = transitions[i][j] as f64 / row_totals[i] as f64;
45 }
46 }
47 }
48 matrix
49 }
50
51 pub fn forecast_state(
55 transition_matrix: &[Vec<f64>],
56 current_state: u32,
57 steps: usize,
58 ) -> Vec<f64> {
59 let n = transition_matrix.len();
60 if n == 0 {
61 return vec![];
62 }
63
64 let mut mat_data = Vec::with_capacity(n * n);
65 for row in transition_matrix {
66 mat_data.extend_from_slice(row);
67 }
68
69 let m = DMatrix::from_row_slice(n, n, &mat_data);
72 let m_n = m.pow(steps as u32);
73
74 let mut initial_dist = vec![0.0; n];
75 if (current_state as usize) < n {
76 initial_dist[current_state as usize] = 1.0;
77 } else {
78 return vec![0.0; n];
79 }
80
81 let v = nalgebra::DVector::from_vec(initial_dist);
82 let result = m_n.transpose() * v;
86 result.as_slice().to_vec()
87 }
88
89 pub fn duration_stats(states: &[u32], num_states: usize) -> Vec<DurationStats> {
91 let mut durations: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
92
93 if states.is_empty() {
94 return vec![];
95 }
96
97 let mut current_regime = states[0];
98 let mut current_duration = 1;
99
100 for &state in &states[1..] {
101 if state == current_regime {
102 current_duration += 1;
103 } else {
104 durations
105 .entry(current_regime)
106 .or_default()
107 .push(current_duration);
108 current_regime = state;
109 current_duration = 1;
110 }
111 }
112 durations
113 .entry(current_regime)
114 .or_default()
115 .push(current_duration);
116
117 let mut results = Vec::new();
118 for i in 0..num_states as u32 {
119 if let Some(d_list) = durations.get(&i) {
120 let total_obs: usize = d_list.iter().sum();
121 let n = d_list.len() as f64;
122 let mean = total_obs as f64 / n;
123
124 let mut sorted = d_list.clone();
125 sorted.sort_unstable();
126 let median = sorted[sorted.len() / 2] as f64;
127 let max_dur = *sorted.last().unwrap_or(&0);
128
129 let variance = d_list
130 .iter()
131 .map(|&d| (d as f64 - mean).powi(2))
132 .sum::<f64>()
133 / n;
134 let std = variance.sqrt();
135
136 results.push(DurationStats {
137 regime_id: i,
138 mean_duration: mean,
139 median_duration: median,
140 std_duration: std,
141 max_duration: max_dur,
142 total_observations: total_obs,
143 });
144 }
145 }
146 results
147 }
148
149 pub fn stability_score(states: &[u32]) -> f64 {
153 if states.len() < 2 {
154 return 1.0;
155 }
156
157 let mut switches = 0;
158 for pair in states.windows(2) {
159 if pair[0] != pair[1] {
160 switches += 1;
161 }
162 }
163
164 1.0 - (switches as f64 / (states.len() - 1) as f64)
165 }
166}