wickra_core/indicators/
autocorrelation.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::traits::Indicator;
7
8#[derive(Debug, Clone)]
42pub struct Autocorrelation {
43 period: usize,
44 lag: usize,
45 window: VecDeque<f64>,
46}
47
48impl Autocorrelation {
49 pub fn new(period: usize, lag: usize) -> Result<Self> {
54 if lag == 0 {
55 return Err(Error::InvalidPeriod {
56 message: "autocorrelation lag must be >= 1",
57 });
58 }
59 if period <= lag {
60 return Err(Error::InvalidPeriod {
61 message: "autocorrelation needs period > lag",
62 });
63 }
64 Ok(Self {
65 period,
66 lag,
67 window: VecDeque::with_capacity(period),
68 })
69 }
70
71 pub const fn period(&self) -> usize {
73 self.period
74 }
75
76 pub const fn lag(&self) -> usize {
78 self.lag
79 }
80}
81
82impl Indicator for Autocorrelation {
83 type Input = f64;
84 type Output = f64;
85
86 fn update(&mut self, value: f64) -> Option<f64> {
87 if self.window.len() == self.period {
88 self.window.pop_front();
89 }
90 self.window.push_back(value);
91 if self.window.len() < self.period {
92 return None;
93 }
94 let n = self.period as f64;
99 let mean = self.window.iter().sum::<f64>() / n;
100 let mut denom = 0.0;
101 let mut numer = 0.0;
102 let (front, back) = self.window.as_slices();
104 let get = |i: usize| -> f64 {
105 if i < front.len() {
106 front[i]
107 } else {
108 back[i - front.len()]
109 }
110 };
111 for i in 0..self.period {
112 let d = get(i) - mean;
113 denom += d * d;
114 }
115 let lag = self.lag;
116 for i in 0..(self.period - lag) {
117 numer += (get(i) - mean) * (get(i + lag) - mean);
118 }
119 if denom == 0.0 {
120 return Some(0.0);
121 }
122 Some(numer / denom)
123 }
124
125 fn reset(&mut self) {
126 self.window.clear();
127 }
128
129 fn warmup_period(&self) -> usize {
130 self.period
131 }
132
133 fn is_ready(&self) -> bool {
134 self.window.len() == self.period
135 }
136
137 fn name(&self) -> &'static str {
138 "Autocorrelation"
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::traits::BatchExt;
146 use approx::assert_relative_eq;
147
148 #[test]
149 fn rejects_zero_lag() {
150 assert!(Autocorrelation::new(10, 0).is_err());
151 }
152
153 #[test]
154 fn rejects_lag_geq_period() {
155 assert!(Autocorrelation::new(5, 5).is_err());
156 assert!(Autocorrelation::new(5, 10).is_err());
157 }
158
159 #[test]
160 fn accessors_and_metadata() {
161 let a = Autocorrelation::new(14, 2).unwrap();
162 assert_eq!(a.period(), 14);
163 assert_eq!(a.lag(), 2);
164 assert_eq!(a.warmup_period(), 14);
165 assert_eq!(a.name(), "Autocorrelation");
166 }
167
168 #[test]
169 fn constant_series_yields_zero() {
170 let mut a = Autocorrelation::new(10, 1).unwrap();
171 for v in a.batch(&[42.0; 30]).into_iter().flatten() {
172 assert_relative_eq!(v, 0.0, epsilon = 1e-12);
173 }
174 }
175
176 #[test]
177 fn alternating_series_lag_one_is_strongly_negative() {
178 let prices: Vec<f64> = (0..20)
180 .map(|i| if i % 2 == 0 { -1.0 } else { 1.0 })
181 .collect();
182 let mut a = Autocorrelation::new(10, 1).unwrap();
183 let last = a.batch(&prices).into_iter().flatten().last().unwrap();
184 assert!(
185 last < -0.5,
186 "alternating series should be strongly negative, got {last}"
187 );
188 }
189
190 #[test]
191 fn repeating_series_is_strongly_positive_at_period() {
192 let pattern = [1.0, 2.0, 3.0, 4.0];
194 let prices: Vec<f64> = (0..32).map(|i| pattern[i % 4]).collect();
195 let mut a = Autocorrelation::new(16, 4).unwrap();
196 let last = a.batch(&prices).into_iter().flatten().last().unwrap();
197 assert!(
198 last > 0.5,
199 "period-4 repeat should ACF(4) > 0.5, got {last}"
200 );
201 }
202
203 #[test]
204 fn reset_clears_state() {
205 let mut a = Autocorrelation::new(5, 1).unwrap();
206 a.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
207 assert!(a.is_ready());
208 a.reset();
209 assert!(!a.is_ready());
210 assert_eq!(a.update(1.0), None);
211 }
212
213 #[test]
214 fn batch_equals_streaming() {
215 let prices: Vec<f64> = (0..60).map(|i| (f64::from(i) * 0.3).sin()).collect();
216 let batch = Autocorrelation::new(14, 2).unwrap().batch(&prices);
217 let mut b = Autocorrelation::new(14, 2).unwrap();
218 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
219 assert_eq!(batch, streamed);
220 }
221}