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 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![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];
104 let mut last_net = 0.0;
105 for input in inputs {
106 last_net = net.next(input);
107 }
108 assert_eq!(last_net, 1.0);
110 }
111
112 proptest! {
113 #[test]
114 fn test_noise_elimination_parity(
115 inputs in prop::collection::vec(-1.0..1.0, 20..100),
116 ) {
117 let length = 14;
118 let mut net = NoiseElimination::new(length);
119 let streaming_results: Vec<f64> = inputs.iter().map(|&x| net.next(x)).collect();
120
121 let mut batch_results = Vec::with_capacity(inputs.len());
123 let denom = 0.5 * (length as f64) * (length as f64 - 1.0);
124
125 for i in 0..inputs.len() {
126 if i < length - 1 {
127 batch_results.push(0.0);
128 continue;
129 }
130
131 let mut num = 0.0;
132 for ii in 1..length {
133 for jj in 0..ii {
134 let diff = inputs[i - ii] - inputs[i - jj];
135 if diff > 0.0 {
136 num -= 1.0;
137 } else if diff < 0.0 {
138 num += 1.0;
139 }
140 }
141 }
142 batch_results.push(num / denom);
143 }
144
145 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
146 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
147 }
148 }
149 }
150}