quantwave_core/regimes/
analytics.rs1use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use nalgebra::DMatrix;
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 { return vec![]; }
61
62 let mut mat_data = Vec::with_capacity(n * n);
63 for row in transition_matrix {
64 mat_data.extend_from_slice(row);
65 }
66
67 let m = DMatrix::from_row_slice(n, n, &mat_data);
70 let m_n = m.pow(steps as u32);
71
72 let mut initial_dist = vec![0.0; n];
73 if (current_state as usize) < n {
74 initial_dist[current_state as usize] = 1.0;
75 } else {
76 return vec![0.0; n];
77 }
78
79 let v = nalgebra::DVector::from_vec(initial_dist);
80 let result = m_n.transpose() * v;
84 result.as_slice().to_vec()
85 }
86
87 pub fn duration_stats(states: &[u32], num_states: usize) -> Vec<DurationStats> {
89 let mut durations: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
90
91 if states.is_empty() { return vec![]; }
92
93 let mut current_regime = states[0];
94 let mut current_duration = 1;
95
96 for &state in &states[1..] {
97 if state == current_regime {
98 current_duration += 1;
99 } else {
100 durations.entry(current_regime).or_default().push(current_duration);
101 current_regime = state;
102 current_duration = 1;
103 }
104 }
105 durations.entry(current_regime).or_default().push(current_duration);
106
107 let mut results = Vec::new();
108 for i in 0..num_states as u32 {
109 if let Some(d_list) = durations.get(&i) {
110 let total_obs: usize = d_list.iter().sum();
111 let n = d_list.len() as f64;
112 let mean = total_obs as f64 / n;
113
114 let mut sorted = d_list.clone();
115 sorted.sort_unstable();
116 let median = sorted[sorted.len() / 2] as f64;
117 let max_dur = *sorted.last().unwrap_or(&0);
118
119 let variance = d_list.iter()
120 .map(|&d| (d as f64 - mean).powi(2))
121 .sum::<f64>() / n;
122 let std = variance.sqrt();
123
124 results.push(DurationStats {
125 regime_id: i,
126 mean_duration: mean,
127 median_duration: median,
128 std_duration: std,
129 max_duration: max_dur,
130 total_observations: total_obs,
131 });
132 }
133 }
134 results
135 }
136
137 pub fn stability_score(states: &[u32]) -> f64 {
141 if states.len() < 2 { return 1.0; }
142
143 let mut switches = 0;
144 for pair in states.windows(2) {
145 if pair[0] != pair[1] {
146 switches += 1;
147 }
148 }
149
150 1.0 - (switches as f64 / (states.len() - 1) as f64)
151 }
152}