quantwave_core/indicators/
statistics.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5talib_1_in_1_out!(TaSTDDEV, talib_rs::statistic::stddev, timeperiod: usize, nbdev: f64);
6talib_1_in_1_out!(TaVAR, talib_rs::statistic::var, timeperiod: usize, nbdev: f64);
7talib_2_in_1_out!(TaBETA, talib_rs::statistic::beta, timeperiod: usize);
8impl From<usize> for TaBETA {
9 fn from(p: usize) -> Self {
10 Self::new(p)
11 }
12}
13talib_2_in_1_out!(TaCORREL, talib_rs::statistic::correl, timeperiod: usize);
14impl From<usize> for TaCORREL {
15 fn from(p: usize) -> Self {
16 Self::new(p)
17 }
18}
19talib_1_in_1_out!(TaLINEARREG, talib_rs::statistic::linearreg, timeperiod: usize);
20impl From<usize> for TaLINEARREG {
21 fn from(p: usize) -> Self {
22 Self::new(p)
23 }
24}
25talib_1_in_1_out!(TaLINEARREG_SLOPE, talib_rs::statistic::linearreg_slope, timeperiod: usize);
26impl From<usize> for TaLINEARREG_SLOPE {
27 fn from(p: usize) -> Self {
28 Self::new(p)
29 }
30}
31talib_1_in_1_out!(TaLINEARREG_INTERCEPT, talib_rs::statistic::linearreg_intercept, timeperiod: usize);
32impl From<usize> for TaLINEARREG_INTERCEPT {
33 fn from(p: usize) -> Self {
34 Self::new(p)
35 }
36}
37talib_1_in_1_out!(TaLINEARREG_ANGLE, talib_rs::statistic::linearreg_angle, timeperiod: usize);
38impl From<usize> for TaLINEARREG_ANGLE {
39 fn from(p: usize) -> Self {
40 Self::new(p)
41 }
42}
43talib_1_in_1_out!(TaTSF, talib_rs::statistic::tsf, timeperiod: usize);
44impl From<usize> for TaTSF {
45 fn from(p: usize) -> Self {
46 Self::new(p)
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct StandardDeviation {
53 period: usize,
54 window: VecDeque<f64>,
55 sum: f64,
56 sum_sq: f64,
57}
58
59impl StandardDeviation {
60 pub fn new(period: usize) -> Self {
61 Self {
62 period,
63 window: VecDeque::with_capacity(period),
64 sum: 0.0,
65 sum_sq: 0.0,
66 }
67 }
68}
69
70impl From<usize> for StandardDeviation {
71 fn from(period: usize) -> Self {
72 Self::new(period)
73 }
74}
75
76impl Next<f64> for StandardDeviation {
77 type Output = f64;
78
79 fn next(&mut self, input: f64) -> Self::Output {
80 self.window.push_back(input);
81 self.sum += input;
82 self.sum_sq += input * input;
83
84 if self.window.len() > self.period {
85 if let Some(oldest) = self.window.pop_front() {
86 self.sum -= oldest;
87 self.sum_sq -= oldest * oldest;
88 }
89 }
90
91 let n = self.window.len() as f64;
92 let mean = self.sum / n;
93 let variance = (self.sum_sq / n) - (mean * mean);
94
95 variance.max(0.0).sqrt()
97 }
98}
99
100#[derive(Debug, Clone)]
103pub struct LinearRegression {
104 period: usize,
105 window: VecDeque<f64>,
106 sum_x: f64,
108 sum_x2: f64,
109}
110
111impl LinearRegression {
112 pub fn new(period: usize) -> Self {
113 let _n = period as f64;
114 let mut sum_x = 0.0;
115 let mut sum_x2 = 0.0;
116 for i in 0..period {
117 let x = i as f64;
118 sum_x += x;
119 sum_x2 += x * x;
120 }
121
122 Self {
123 period,
124 window: VecDeque::with_capacity(period),
125 sum_x,
126 sum_x2,
127 }
128 }
129}
130
131impl From<usize> for LinearRegression {
132 fn from(period: usize) -> Self {
133 Self::new(period)
134 }
135}
136
137impl Next<f64> for LinearRegression {
138 type Output = f64;
139
140 fn next(&mut self, input: f64) -> Self::Output {
141 self.window.push_back(input);
142 if self.window.len() > self.period {
143 self.window.pop_front();
144 }
145
146 if self.window.len() < self.period {
147 let n = self.window.len() as f64;
151 let mut sum_x = 0.0;
152 let mut sum_x2 = 0.0;
153 let mut sum_y = 0.0;
154 let mut sum_xy = 0.0;
155 for (i, &y) in self.window.iter().enumerate() {
156 let x = i as f64;
157 sum_x += x;
158 sum_x2 += x * x;
159 sum_y += y;
160 sum_xy += x * y;
161 }
162
163 let denominator = n * sum_x2 - sum_x * sum_x;
164 if denominator == 0.0 {
165 return input;
166 }
167
168 let b = (n * sum_xy - sum_x * sum_y) / denominator;
169 let a = (sum_y - b * sum_x) / n;
170 return a + b * (n - 1.0);
171 }
172
173 let n = self.period as f64;
174 let mut sum_y = 0.0;
175 let mut sum_xy = 0.0;
176 for (i, &y) in self.window.iter().enumerate() {
177 let x = i as f64;
178 sum_y += y;
179 sum_xy += x * y;
180 }
181
182 let denominator = n * self.sum_x2 - self.sum_x * self.sum_x;
183 if denominator == 0.0 {
184 return input;
185 }
186
187 let b = (n * sum_xy - self.sum_x * sum_y) / denominator;
188 let a = (sum_y - b * self.sum_x) / n;
189
190 a + b * (n - 1.0)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_stdev_basic() {
200 let mut sd = StandardDeviation::new(3);
201 assert_eq!(sd.next(10.0), 0.0);
203 assert_eq!(sd.next(20.0), 5.0);
205 approx::assert_relative_eq!(sd.next(30.0), 8.1649658092, epsilon = 1e-6);
207 }
208
209 #[test]
210 fn test_linreg_basic() {
211 let mut lr = LinearRegression::new(3);
212 lr.next(1.0);
214 lr.next(2.0);
215 let res = lr.next(3.0);
216 approx::assert_relative_eq!(res, 3.0);
217
218 let mut lr2 = LinearRegression::new(3);
220 lr2.next(5.0);
221 lr2.next(7.0);
222 let res2 = lr2.next(9.0);
223 approx::assert_relative_eq!(res2, 9.0);
224 }
225
226 use proptest::prelude::*;
227 proptest! {
228 #[test]
229 fn test_ta_stddev_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
230 let period = 10;
231 let nbdev = 1.0;
232 let mut ta_stddev = TaSTDDEV::new(period, nbdev);
233 let streaming_results: Vec<f64> = input.iter().map(|&x| ta_stddev.next(x)).collect();
234 let batch_results = talib_rs::statistic::stddev(&input, period, nbdev).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
235
236 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
237 if s.is_nan() {
238 assert!(b.is_nan());
239 } else {
240 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
241 }
242 }
243 }
244
245 #[test]
246 fn test_ta_linearreg_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
247 let period = 10;
248 let mut ta_lr = TaLINEARREG::new(period);
249 let streaming_results: Vec<f64> = input.iter().map(|&x| ta_lr.next(x)).collect();
250 let batch_results = talib_rs::statistic::linearreg(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
251
252 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
253 if s.is_nan() {
254 assert!(b.is_nan());
255 } else {
256 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
257 }
258 }
259 }
260 }
261}
262
263pub const STDDEV_METADATA: IndicatorMetadata = IndicatorMetadata {
264 name: "Standard Deviation",
265 description: "Standard Deviation is a statistical measure of market volatility.",
266 params: &[ParamDef {
267 name: "period",
268 default: "14",
269 description: "Period",
270 }],
271 formula_source: "https://www.investopedia.com/terms/s/standarddeviation.asp",
272 formula_latex: r#"
273\[
274\sigma = \sqrt{ \frac{\sum (x_i - \mu)^2}{N} }
275\]
276"#,
277 gold_standard_file: "stddev.json",
278 category: "Classic",
279};
280
281pub const LINREG_METADATA: IndicatorMetadata = IndicatorMetadata {
282 name: "Linear Regression",
283 description: "Linear Regression plots a straight line that best fits the data prices.",
284 params: &[ParamDef {
285 name: "period",
286 default: "14",
287 description: "Period",
288 }],
289 formula_source: "https://www.investopedia.com/terms/l/linearregression.asp",
290 formula_latex: r#"
291\[
292y = a + bx
293\]
294"#,
295 gold_standard_file: "linreg.json",
296 category: "Classic",
297};