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