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 && let Some(oldest) = self.window.pop_front() {
85 self.sum -= oldest;
86 self.sum_sq -= oldest * oldest;
87 }
88
89 let n = self.window.len() as f64;
90 let mean = self.sum / n;
91 let variance = (self.sum_sq / n) - (mean * mean);
92
93 variance.max(0.0).sqrt()
95 }
96}
97
98#[derive(Debug, Clone)]
101pub struct LinearRegression {
102 period: usize,
103 window: VecDeque<f64>,
104 sum_x: f64,
106 sum_x2: f64,
107}
108
109impl LinearRegression {
110 pub fn new(period: usize) -> Self {
111 let _n = period as f64;
112 let mut sum_x = 0.0;
113 let mut sum_x2 = 0.0;
114 for i in 0..period {
115 let x = i as f64;
116 sum_x += x;
117 sum_x2 += x * x;
118 }
119
120 Self {
121 period,
122 window: VecDeque::with_capacity(period),
123 sum_x,
124 sum_x2,
125 }
126 }
127}
128
129impl From<usize> for LinearRegression {
130 fn from(period: usize) -> Self {
131 Self::new(period)
132 }
133}
134
135impl Next<f64> for LinearRegression {
136 type Output = f64;
137
138 fn next(&mut self, input: f64) -> Self::Output {
139 self.window.push_back(input);
140 if self.window.len() > self.period {
141 self.window.pop_front();
142 }
143
144 if self.window.len() < self.period {
145 let n = self.window.len() as f64;
149 let mut sum_x = 0.0;
150 let mut sum_x2 = 0.0;
151 let mut sum_y = 0.0;
152 let mut sum_xy = 0.0;
153 for (i, &y) in self.window.iter().enumerate() {
154 let x = i as f64;
155 sum_x += x;
156 sum_x2 += x * x;
157 sum_y += y;
158 sum_xy += x * y;
159 }
160
161 let denominator = n * sum_x2 - sum_x * sum_x;
162 if denominator == 0.0 {
163 return input;
164 }
165
166 let b = (n * sum_xy - sum_x * sum_y) / denominator;
167 let a = (sum_y - b * sum_x) / n;
168 return a + b * (n - 1.0);
169 }
170
171 let n = self.period as f64;
172 let mut sum_y = 0.0;
173 let mut sum_xy = 0.0;
174 for (i, &y) in self.window.iter().enumerate() {
175 let x = i as f64;
176 sum_y += y;
177 sum_xy += x * y;
178 }
179
180 let denominator = n * self.sum_x2 - self.sum_x * self.sum_x;
181 if denominator == 0.0 {
182 return input;
183 }
184
185 let b = (n * sum_xy - self.sum_x * sum_y) / denominator;
186 let a = (sum_y - b * self.sum_x) / n;
187
188 a + b * (n - 1.0)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_stdev_basic() {
198 let mut sd = StandardDeviation::new(3);
199 assert_eq!(sd.next(10.0), 0.0);
201 assert_eq!(sd.next(20.0), 5.0);
203 approx::assert_relative_eq!(sd.next(30.0), 8.1649658092, epsilon = 1e-6);
205 }
206
207 #[test]
208 fn test_linreg_basic() {
209 let mut lr = LinearRegression::new(3);
210 lr.next(1.0);
212 lr.next(2.0);
213 let res = lr.next(3.0);
214 approx::assert_relative_eq!(res, 3.0);
215
216 let mut lr2 = LinearRegression::new(3);
218 lr2.next(5.0);
219 lr2.next(7.0);
220 let res2 = lr2.next(9.0);
221 approx::assert_relative_eq!(res2, 9.0);
222 }
223
224 use proptest::prelude::*;
225 proptest! {
226 #[test]
227 fn test_ta_stddev_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
228 let period = 10;
229 let nbdev = 1.0;
230 let mut ta_stddev = TaSTDDEV::new(period, nbdev);
231 let streaming_results: Vec<f64> = input.iter().map(|&x| ta_stddev.next(x)).collect();
232 let batch_results = talib_rs::statistic::stddev(&input, period, nbdev).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
233
234 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
235 if s.is_nan() {
236 assert!(b.is_nan());
237 } else {
238 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
239 }
240 }
241 }
242
243 #[test]
244 fn test_ta_linearreg_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
245 let period = 10;
246 let mut ta_lr = TaLINEARREG::new(period);
247 let streaming_results: Vec<f64> = input.iter().map(|&x| ta_lr.next(x)).collect();
248 let batch_results = talib_rs::statistic::linearreg(&input, period).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
249
250 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
251 if s.is_nan() {
252 assert!(b.is_nan());
253 } else {
254 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
255 }
256 }
257 }
258 }
259}
260
261pub const STDDEV_METADATA: IndicatorMetadata = IndicatorMetadata {
262 name: "Standard Deviation",
263 description: "Standard Deviation is a statistical measure of market volatility.",
264 usage: "Use for statistical analysis of price series: linear regression, standard deviation, correlation coefficients, and other descriptive statistics used as indicator inputs.",
265 keywords: &["statistics", "classic", "volatility", "trend"],
266 ehlers_summary: "Standard statistical measures provide the mathematical foundation for many technical indicators. Linear regression finds the best-fit line through price, standard deviation quantifies dispersion, and correlation coefficients measure how closely two series move together — all are essential for quantitative strategy construction.",
267 params: &[ParamDef {
268 name: "period",
269 default: "14",
270 description: "Period",
271 }],
272 formula_source: "https://www.investopedia.com/terms/s/standarddeviation.asp",
273 formula_latex: r#"
274\[
275\sigma = \sqrt{ \frac{\sum (x_i - \mu)^2}{N} }
276\]
277"#,
278 gold_standard_file: "stddev.json",
279 category: "Classic",
280};
281
282pub const LINREG_METADATA: IndicatorMetadata = IndicatorMetadata {
283 name: "Linear Regression",
284 description: "Linear Regression plots a straight line that best fits the data prices.",
285 usage: "Use for statistical analysis of price series: linear regression, standard deviation, correlation coefficients, and other descriptive statistics used as indicator inputs.",
286 keywords: &["statistics", "classic", "volatility", "trend"],
287 ehlers_summary: "Standard statistical measures provide the mathematical foundation for many technical indicators. Linear regression finds the best-fit line through price, standard deviation quantifies dispersion, and correlation coefficients measure how closely two series move together — all are essential for quantitative strategy construction.",
288 params: &[ParamDef {
289 name: "period",
290 default: "14",
291 description: "Period",
292 }],
293 formula_source: "https://www.investopedia.com/terms/l/linearregression.asp",
294 formula_latex: r#"
295\[
296y = a + bx
297\]
298"#,
299 gold_standard_file: "linreg.json",
300 category: "Classic",
301};
302
303pub const CORREL_METADATA: IndicatorMetadata = IndicatorMetadata {
304 name: "Correlation Coefficient (CORREL)",
305 description: "A statistical measure that determines the degree to which two securities move in relation to each other.",
306 usage: "Use to measure the strength and direction of the linear relationship between two assets. Values range from -1.0 (inverse correlation) to +1.0 (perfect correlation).",
307 keywords: &["statistics", "correlation", "classic"],
308 ehlers_summary: "The Pearson Correlation Coefficient measures the strength and direction of a linear relationship between two price series. It is a fundamental tool for pair trading and portfolio diversification, allowing traders to quantify how much of a security's movement is explained by another. — StockCharts ChartSchool",
309 params: &[ParamDef { name: "timeperiod", default: "30", description: "Lookback period" }],
310 formula_source: "https://www.investopedia.com/terms/c/correlationcoefficient.asp",
311 formula_latex: r#"
312\[
313\rho_{X,Y} = \frac{\text{cov}(X,Y)}{\sigma_X \sigma_Y}
314\]
315"#,
316 gold_standard_file: "correl.json",
317 category: "Classic",
318};
319
320pub const BETA_METADATA: IndicatorMetadata = IndicatorMetadata {
321 name: "Beta (BETA)",
322 description: "A measure of a security's volatility in relation to the overall market.",
323 usage: "Use to understand the systematic risk of an asset. A beta of 1.0 indicates the asset moves with the market; >1.0 means it is more volatile, and <1.0 means it is less volatile.",
324 keywords: &["statistics", "risk", "classic", "volatility"],
325 ehlers_summary: "Beta is a measure of the volatility—or systematic risk—of a security or portfolio compared to the market as a whole. It is used in the Capital Asset Pricing Model (CAPM) to calculate the expected return of an asset based on its beta and expected market returns. — Investopedia",
326 params: &[ParamDef { name: "timeperiod", default: "30", description: "Lookback period" }],
327 formula_source: "https://www.investopedia.com/terms/b/beta.asp",
328 formula_latex: r#"
329\[
330\beta = \frac{\text{Cov}(R_i, R_m)}{\text{Var}(R_m)}
331\]
332"#,
333 gold_standard_file: "beta.json",
334 category: "Classic",
335};