quantwave_core/indicators/
noise_elimination.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use crate::utils::RingBuffer as 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 usage: "Use as a pre-filter to remove spike noise from price or intermediate indicator data without introducing lag. Particularly useful when raw tick or 1-minute data is used.",
71 keywords: &["filter", "noise", "ehlers", "dsp", "smoothing"],
72 ehlers_summary: "Ehlers Noise Elimination Technology (NET) is a nonlinear filter that removes isolated noise spikes while leaving genuine price moves intact. It works by comparing each bar to its neighbors and replacing outliers with interpolated values, achieving noise reduction without the lag of conventional smoothers.",
73 params: &[ParamDef {
74 name: "length",
75 default: "14",
76 description: "Correlation length",
77 }],
78 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/Noise%20Elimination%20Technology.pdf",
79 formula_latex: r#"
80\[
81Num = \sum_{i=1}^{N-1} \sum_{j=0}^{i-1} -sgn(X_i - X_j)
82\]
83\[
84Denom = \frac{N(N-1)}{2}
85\]
86\[
87NET = \frac{Num}{Denom}
88\]
89"#,
90 gold_standard_file: "noise_elimination.json",
91 category: "Ehlers DSP",
92};
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::traits::Next;
98 use proptest::prelude::*;
99
100 #[test]
101 fn test_noise_elimination_basic() {
102 let mut net = NoiseElimination::new(14);
103 let inputs = vec![
104 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,
105 ];
106 let mut last_net = 0.0;
107 for input in inputs {
108 last_net = net.next(input);
109 }
110 assert_eq!(last_net, 1.0);
112 }
113
114 proptest! {
115 #[test]
116 fn test_noise_elimination_parity(
117 inputs in prop::collection::vec(-1.0..1.0, 20..100),
118 ) {
119 let length = 14;
120 let mut net = NoiseElimination::new(length);
121 let streaming_results: Vec<f64> = inputs.iter().map(|&x| net.next(x)).collect();
122
123 let mut batch_results = Vec::with_capacity(inputs.len());
125 let denom = 0.5 * (length as f64) * (length as f64 - 1.0);
126
127 for i in 0..inputs.len() {
128 if i < length - 1 {
129 batch_results.push(0.0);
130 continue;
131 }
132
133 let mut num = 0.0;
134 for ii in 1..length {
135 for jj in 0..ii {
136 let diff = inputs[i - ii] - inputs[i - jj];
137 if diff > 0.0 {
138 num -= 1.0;
139 } else if diff < 0.0 {
140 num += 1.0;
141 }
142 }
143 }
144 batch_results.push(num / denom);
145 }
146
147 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
148 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
149 }
150 }
151 }
152}