quantwave_core/indicators/incremental/
overlap_ta.rs1use crate::indicators::incremental::rolling::{MAX, MIN};
4use crate::indicators::incremental::talib_ema::TalibEma;
5use crate::indicators::incremental::talib_sma::TalibSma;
6use crate::traits::Next;
7
8#[inline]
9fn trima_periods(period: usize) -> (usize, usize) {
10 if period % 2 == 0 {
11 (period / 2, period / 2 + 1)
12 } else {
13 let n = (period + 1) / 2;
14 (n, n)
15 }
16}
17
18#[derive(Debug, Clone)]
20#[allow(non_camel_case_types)]
21pub struct TRIMA {
22 pub timeperiod: usize,
23 sma1: TalibSma,
24 sma2: TalibSma,
25}
26
27impl TRIMA {
28 pub fn new(timeperiod: usize) -> Self {
29 let (n1, n2) = trima_periods(timeperiod);
30 Self {
31 timeperiod,
32 sma1: TalibSma::new(n1),
33 sma2: TalibSma::new(n2),
34 }
35 }
36}
37
38impl Next<f64> for TRIMA {
39 type Output = f64;
40
41 fn next(&mut self, input: f64) -> Self::Output {
42 let mid = self.sma1.next(input);
43 if mid.is_nan() {
44 f64::NAN
45 } else {
46 self.sma2.next(mid)
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53#[allow(non_camel_case_types)]
54pub struct MIDPOINT {
55 pub timeperiod: usize,
56 max: MAX,
57 min: MIN,
58}
59
60impl MIDPOINT {
61 pub fn new(timeperiod: usize) -> Self {
62 Self {
63 timeperiod,
64 max: MAX::new(timeperiod),
65 min: MIN::new(timeperiod),
66 }
67 }
68}
69
70impl Next<f64> for MIDPOINT {
71 type Output = f64;
72
73 fn next(&mut self, input: f64) -> Self::Output {
74 let hi = self.max.next(input);
75 let lo = self.min.next(input);
76 if hi.is_nan() || lo.is_nan() {
77 f64::NAN
78 } else {
79 (hi + lo) / 2.0
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86#[allow(non_camel_case_types)]
87pub struct MIDPRICE {
88 pub timeperiod: usize,
89 max: MAX,
90 min: MIN,
91}
92
93impl MIDPRICE {
94 pub fn new(timeperiod: usize) -> Self {
95 Self {
96 timeperiod,
97 max: MAX::new(timeperiod),
98 min: MIN::new(timeperiod),
99 }
100 }
101}
102
103impl Next<(f64, f64)> for MIDPRICE {
104 type Output = f64;
105
106 fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
107 let hi = self.max.next(high);
108 let lo = self.min.next(low);
109 if hi.is_nan() || lo.is_nan() {
110 f64::NAN
111 } else {
112 (hi + lo) / 2.0
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
119#[allow(non_camel_case_types)]
120pub struct KAMA {
121 pub timeperiod: usize,
122 fast_sc: f64,
123 slow_sc: f64,
124 history: Vec<f64>,
125 prev_kama: f64,
126}
127
128impl KAMA {
129 pub fn new(timeperiod: usize) -> Self {
130 Self {
131 timeperiod,
132 fast_sc: 2.0 / (2.0 + 1.0),
133 slow_sc: 2.0 / (30.0 + 1.0),
134 history: Vec::new(),
135 prev_kama: 0.0,
136 }
137 }
138}
139
140impl Next<f64> for KAMA {
141 type Output = f64;
142
143 fn next(&mut self, input: f64) -> Self::Output {
144 self.history.push(input);
145 let p = self.timeperiod;
146 let n = self.history.len();
147
148 if n <= p {
149 return f64::NAN;
150 }
151 if n == p + 1 {
152 self.prev_kama = self.history[p - 1];
153 }
154
155 let today = n - 1;
156 let trailing = today - p;
157 let mut sum_roc1 = 0.0;
158 for i in 1..=p {
159 let idx_cur = today - i + 1;
160 let idx_prev = today - i;
161 sum_roc1 += (self.history[idx_cur] - self.history[idx_prev]).abs();
162 }
163 let sum_roc2 = (self.history[today] - self.history[trailing]).abs();
164 let er = if sum_roc1 != 0.0 {
165 sum_roc2 / sum_roc1
166 } else {
167 0.0
168 };
169 let sc = (er * (self.fast_sc - self.slow_sc) + self.slow_sc).powi(2);
170 self.prev_kama += sc * (input - self.prev_kama);
171 self.prev_kama
172 }
173}
174
175#[derive(Debug, Clone)]
177#[allow(non_camel_case_types)]
178pub struct T3 {
179 pub timeperiod: usize,
180 pub v_factor: f64,
181 e1: TalibEma,
182 e2: TalibEma,
183 e3: TalibEma,
184 e4: TalibEma,
185 e5: TalibEma,
186 e6: TalibEma,
187}
188
189impl T3 {
190 pub fn new(timeperiod: usize, v_factor: f64) -> Self {
191 Self {
192 timeperiod,
193 v_factor,
194 e1: TalibEma::new(timeperiod),
195 e2: TalibEma::new(timeperiod),
196 e3: TalibEma::new(timeperiod),
197 e4: TalibEma::new(timeperiod),
198 e5: TalibEma::new(timeperiod),
199 e6: TalibEma::new(timeperiod),
200 }
201 }
202}
203
204impl Next<f64> for T3 {
205 type Output = f64;
206
207 fn next(&mut self, input: f64) -> Self::Output {
208 let v1 = self.e1.next(input);
209 let v2 = self.e2.next(v1);
210 let v3 = self.e3.next(v2);
211 let v4 = self.e4.next(v3);
212 let v5 = self.e5.next(v4);
213 let v6 = self.e6.next(v5);
214 if v6.is_nan() {
215 return f64::NAN;
216 }
217 let v = self.v_factor;
218 let c1 = -v.powi(3);
219 let c2 = 3.0 * v * v + 3.0 * v.powi(3);
220 let c3 = -6.0 * v * v - 3.0 * v - 3.0 * v.powi(3);
221 let c4 = 1.0 + 3.0 * v + v.powi(3) + 3.0 * v * v;
222 c1 * v6 + c2 * v5 + c3 * v4 + c4 * v3
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 use proptest::prelude::*;
231
232 proptest! {
233 #[test]
234 fn test_trima_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
235 let period = 10;
236 let mut trima = TRIMA::new(period);
237 let streaming: Vec<f64> = input.iter().map(|&x| trima.next(x)).collect();
238 let batch = talib_rs::overlap::trima(&input, period)
239 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
240 for (s, b) in streaming.iter().zip(batch.iter()) {
241 if s.is_nan() { assert!(b.is_nan()); }
242 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
243 }
244 }
245
246 #[test]
247 fn test_midpoint_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
248 let period = 10;
249 let mut mid = MIDPOINT::new(period);
250 let streaming: Vec<f64> = input.iter().map(|&x| mid.next(x)).collect();
251 let batch = talib_rs::overlap::midpoint(&input, period)
252 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
253 for (s, b) in streaming.iter().zip(batch.iter()) {
254 if s.is_nan() { assert!(b.is_nan()); }
255 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
256 }
257 }
258
259 #[test]
260 fn test_kama_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
261 let period = 10;
262 let mut kama = KAMA::new(period);
263 let streaming: Vec<f64> = input.iter().map(|&x| kama.next(x)).collect();
264 let batch = talib_rs::overlap::kama(&input, period)
265 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
266 for (s, b) in streaming.iter().zip(batch.iter()) {
267 if s.is_nan() { assert!(b.is_nan()); }
268 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
269 }
270 }
271
272 #[test]
273 fn test_t3_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
274 let period = 10;
275 let vf = 0.7;
276 let mut t3 = T3::new(period, vf);
277 let streaming: Vec<f64> = input.iter().map(|&x| t3.next(x)).collect();
278 let batch = talib_rs::overlap::t3(&input, period, vf)
279 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
280 for (s, b) in streaming.iter().zip(batch.iter()) {
281 if s.is_nan() { assert!(b.is_nan()); }
282 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
283 }
284 }
285 }
286}