quantwave_core/indicators/
noise_elimination.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
11pub struct NoiseElimination {
12 length: usize,
13 window: VecDeque<f64>,
14 denom: f64,
15}
16
17impl NoiseElimination {
18 pub fn new(length: usize) -> Self {
19 let denom = 0.5 * (length as f64) * (length as f64 - 1.0);
20 Self {
21 length,
22 window: VecDeque::with_capacity(length),
23 denom,
24 }
25 }
26}
27
28impl Default for NoiseElimination {
29 fn default() -> Self {
30 Self::new(14)
31 }
32}
33
34impl Next<f64> for NoiseElimination {
35 type Output = f64;
36
37 fn next(&mut self, input: f64) -> Self::Output {
38 self.window.push_front(input);
39 if self.window.len() > self.length {
40 self.window.pop_back();
41 }
42
43 if self.window.len() < self.length {
44 return 0.0;
45 }
46
47 let mut num = 0.0;
48 for i in 1..self.length {
53 for j in 0..i {
54 let diff = self.window[i] - self.window[j];
55 if diff > 0.0 {
56 num -= 1.0;
57 } else if diff < 0.0 {
58 num += 1.0;
59 }
60 }
61 }
62
63 num / self.denom
64 }
65}
66
67pub const NOISE_ELIMINATION_METADATA: IndicatorMetadata = IndicatorMetadata {
68 name: "Noise Elimination Technology",
69 description: "Nonlinear noise removal using Kendall correlation against a straight line.",
70 params: &[ParamDef {
71 name: "length",
72 default: "14",
73 description: "Correlation length",
74 }],
75 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/Noise%20Elimination%20Technology.pdf",
76 formula_latex: r#"
77\[
78Num = \sum_{i=1}^{N-1} \sum_{j=0}^{i-1} -sgn(X_i - X_j)
79\]
80\[
81Denom = \frac{N(N-1)}{2}
82\]
83\[
84NET = \frac{Num}{Denom}
85\]
86"#,
87 gold_standard_file: "noise_elimination.json",
88 category: "Ehlers DSP",
89};
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::traits::Next;
95 use proptest::prelude::*;
96
97 #[test]
98 fn test_noise_elimination_basic() {
99 let mut net = NoiseElimination::new(14);
100 let inputs = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0];
101 let mut last_net = 0.0;
102 for input in inputs {
103 last_net = net.next(input);
104 }
105 assert_eq!(last_net, 1.0);
107 }
108
109 proptest! {
110 #[test]
111 fn test_noise_elimination_parity(
112 inputs in prop::collection::vec(-1.0..1.0, 20..100),
113 ) {
114 let length = 14;
115 let mut net = NoiseElimination::new(length);
116 let streaming_results: Vec<f64> = inputs.iter().map(|&x| net.next(x)).collect();
117
118 let mut batch_results = Vec::with_capacity(inputs.len());
120 let denom = 0.5 * (length as f64) * (length as f64 - 1.0);
121
122 for i in 0..inputs.len() {
123 if i < length - 1 {
124 batch_results.push(0.0);
125 continue;
126 }
127
128 let mut num = 0.0;
129 for ii in 1..length {
130 for jj in 0..ii {
131 let diff = inputs[i - ii] - inputs[i - jj];
132 if diff > 0.0 {
133 num -= 1.0;
134 } else if diff < 0.0 {
135 num += 1.0;
136 }
137 }
138 }
139 batch_results.push(num / denom);
140 }
141
142 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
143 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
144 }
145 }
146 }
147}