Skip to main content

quantwave_core/indicators/
math.rs

1// Math Transform
2talib_1_in_1_out_no_result!(ACOS, talib_rs::math_transform::acos);
3impl Default for ACOS {
4    fn default() -> Self {
5        Self::new()
6    }
7}
8talib_1_in_1_out_no_result!(ASIN, talib_rs::math_transform::asin);
9impl Default for ASIN {
10    fn default() -> Self {
11        Self::new()
12    }
13}
14talib_1_in_1_out_no_result!(ATAN, talib_rs::math_transform::atan);
15impl Default for ATAN {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20talib_1_in_1_out_no_result!(CEIL, talib_rs::math_transform::ceil);
21impl Default for CEIL {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26talib_1_in_1_out_no_result!(COS, talib_rs::math_transform::cos);
27impl Default for COS {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32talib_1_in_1_out_no_result!(COSH, talib_rs::math_transform::cosh);
33impl Default for COSH {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38talib_1_in_1_out_no_result!(EXP, talib_rs::math_transform::exp);
39impl Default for EXP {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44talib_1_in_1_out_no_result!(FLOOR, talib_rs::math_transform::floor);
45impl Default for FLOOR {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50talib_1_in_1_out_no_result!(LN, talib_rs::math_transform::ln);
51impl Default for LN {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56talib_1_in_1_out_no_result!(LOG10, talib_rs::math_transform::log10);
57impl Default for LOG10 {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62talib_1_in_1_out_no_result!(SIN, talib_rs::math_transform::sin);
63impl Default for SIN {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68talib_1_in_1_out_no_result!(SINH, talib_rs::math_transform::sinh);
69impl Default for SINH {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74talib_1_in_1_out_no_result!(SQRT, talib_rs::math_transform::sqrt);
75impl Default for SQRT {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80talib_1_in_1_out_no_result!(TAN, talib_rs::math_transform::tan);
81impl Default for TAN {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86talib_1_in_1_out_no_result!(TANH, talib_rs::math_transform::tanh);
87impl Default for TANH {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93/// Root Mean Square (RMS)
94#[derive(Debug, Clone)]
95pub struct RMS {
96    period: usize,
97    history: std::collections::VecDeque<f64>,
98    sum_sq: f64,
99}
100
101impl RMS {
102    pub fn new(period: usize) -> Self {
103        Self {
104            period,
105            history: std::collections::VecDeque::with_capacity(period),
106            sum_sq: 0.0,
107        }
108    }
109}
110
111impl crate::traits::Next<f64> for RMS {
112    type Output = f64;
113
114    fn next(&mut self, input: f64) -> Self::Output {
115        let input_sq = input * input;
116        self.sum_sq += input_sq;
117        self.history.push_back(input_sq);
118
119        if self.history.len() > self.period && let Some(old) = self.history.pop_front() {
120            self.sum_sq -= old;
121        }
122
123        if self.history.is_empty() {
124            0.0
125        } else {
126            (self.sum_sq / self.history.len() as f64).sqrt()
127        }
128    }
129}
130
131/// Automatic Gain Control (AGC)
132///
133/// Normalizes a signal based on its decaying peak value.
134/// Commonly used in John Ehlers' oscillators to keep the signal within [-1, 1].
135#[derive(Debug, Clone)]
136pub struct AGC {
137    peak: f64,
138    decay: f64,
139}
140
141impl AGC {
142    pub fn new(decay: f64) -> Self {
143        Self {
144            peak: 0.0000001,
145            decay,
146        }
147    }
148}
149
150impl crate::traits::Next<f64> for AGC {
151    type Output = f64;
152
153    fn next(&mut self, input: f64) -> Self::Output {
154        self.peak *= self.decay;
155        let abs_input = input.abs();
156        if abs_input > self.peak {
157            self.peak = abs_input;
158        }
159
160        if self.peak != 0.0 {
161            input / self.peak
162        } else {
163            0.0
164        }
165    }
166}
167
168// Math Operators
169talib_2_in_1_out!(ADD, talib_rs::math_operator::add);
170impl Default for ADD {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175talib_2_in_1_out!(SUB, talib_rs::math_operator::sub);
176impl Default for SUB {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181talib_2_in_1_out!(MULT, talib_rs::math_operator::mult);
182impl Default for MULT {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187talib_2_in_1_out!(DIV, talib_rs::math_operator::div);
188impl Default for DIV {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193talib_1_in_1_out!(MAX, talib_rs::math_operator::max, timeperiod: usize);
194impl From<usize> for MAX {
195    fn from(p: usize) -> Self {
196        Self::new(p)
197    }
198}
199
200talib_1_in_1_out!(MAXINDEX, talib_rs::math_operator::maxindex, timeperiod: usize);
201impl From<usize> for MAXINDEX {
202    fn from(p: usize) -> Self {
203        Self::new(p)
204    }
205}
206
207talib_1_in_1_out!(MIN, talib_rs::math_operator::min, timeperiod: usize);
208impl From<usize> for MIN {
209    fn from(p: usize) -> Self {
210        Self::new(p)
211    }
212}
213
214talib_1_in_1_out!(MININDEX, talib_rs::math_operator::minindex, timeperiod: usize);
215impl From<usize> for MININDEX {
216    fn from(p: usize) -> Self {
217        Self::new(p)
218    }
219}
220
221talib_1_in_1_out!(SUM, talib_rs::math_operator::sum, timeperiod: usize);
222impl From<usize> for SUM {
223    fn from(p: usize) -> Self {
224        Self::new(p)
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use crate::traits::Next;
232    use proptest::prelude::*;
233
234    proptest! {
235        #[test]
236        fn test_sqrt_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
237            let mut sqrt = SQRT::new();
238            let streaming_results: Vec<f64> = input.iter().map(|&x| sqrt.next(x)).collect();
239            let batch_results = talib_rs::math_transform::sqrt(&input);
240
241            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
242                if s.is_nan() {
243                    assert!(b.is_nan());
244                } else {
245                    approx::assert_relative_eq!(s, b, epsilon = 1e-6);
246                }
247            }
248        }
249
250        #[test]
251        fn test_add_parity(
252            in1 in prop::collection::vec(0.1..100.0, 1..100),
253            in2 in prop::collection::vec(0.1..100.0, 1..100)
254        ) {
255            let len = in1.len().min(in2.len());
256            if len == 0 { return Ok(()); }
257
258            let mut add = ADD::new();
259            let streaming_results: Vec<f64> = (0..len).map(|i| add.next((in1[i], in2[i]))).collect();
260            let batch_results = talib_rs::math_operator::add(&in1[..len], &in2[..len]).unwrap_or_else(|_| vec![f64::NAN; len]);
261
262            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
263                if s.is_nan() {
264                    assert!(b.is_nan());
265                } else {
266                    approx::assert_relative_eq!(s, b, epsilon = 1e-6);
267                }
268            }
269        }
270
271        #[test]
272        fn test_rms_parity(input in prop::collection::vec(0.1..100.0, 10..100)) {
273            let period = 10;
274            let mut rms = RMS::new(period);
275            let streaming_results: Vec<f64> = input.iter().map(|&x| rms.next(x)).collect();
276
277            let mut batch_results = Vec::with_capacity(input.len());
278            for i in 0..input.len() {
279                let start = if i + 1 > period { i + 1 - period } else { 0 };
280                let window = &input[start..i+1];
281                let sum_sq: f64 = window.iter().map(|&x| x*x).sum();
282                batch_results.push((sum_sq / window.len() as f64).sqrt());
283            }
284
285            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
286                approx::assert_relative_eq!(s, b, epsilon = 1e-10);
287            }
288        }
289    }
290}