quantwave_core/indicators/incremental/
apo.rs1use crate::indicators::incremental::ma_stream::MaStream;
4use crate::indicators::ma_type::MaType;
5use crate::traits::Next;
6
7#[derive(Debug, Clone)]
9#[allow(non_camel_case_types)]
10pub struct APO {
11 pub fastperiod: usize,
12 pub slowperiod: usize,
13 pub matype: MaType,
14 fast_ma: MaStream,
15 slow_ma: MaStream,
16}
17
18impl APO {
19 pub fn new(fastperiod: usize, slowperiod: usize, matype: MaType) -> Self {
20 Self {
21 fastperiod,
22 slowperiod,
23 matype,
24 fast_ma: MaStream::new(fastperiod, matype),
25 slow_ma: MaStream::new(slowperiod, matype),
26 }
27 }
28}
29
30impl Next<f64> for APO {
31 type Output = f64;
32
33 fn next(&mut self, input: f64) -> Self::Output {
34 let fast = self.fast_ma.next(input);
35 let slow = self.slow_ma.next(input);
36 if fast.is_nan() || slow.is_nan() {
37 f64::NAN
38 } else {
39 fast - slow
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
46#[allow(non_camel_case_types)]
47pub struct PPO {
48 pub fastperiod: usize,
49 pub slowperiod: usize,
50 pub matype: MaType,
51 fast_ma: MaStream,
52 slow_ma: MaStream,
53}
54
55impl PPO {
56 pub fn new(fastperiod: usize, slowperiod: usize, matype: MaType) -> Self {
57 Self {
58 fastperiod,
59 slowperiod,
60 matype,
61 fast_ma: MaStream::new(fastperiod, matype),
62 slow_ma: MaStream::new(slowperiod, matype),
63 }
64 }
65}
66
67impl Next<f64> for PPO {
68 type Output = f64;
69
70 fn next(&mut self, input: f64) -> Self::Output {
71 let fast = self.fast_ma.next(input);
72 let slow = self.slow_ma.next(input);
73 if fast.is_nan() || slow.is_nan() || slow == 0.0 {
74 f64::NAN
75 } else {
76 (fast - slow) / slow * 100.0
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use proptest::prelude::*;
85
86 fn assert_parity(streaming: &[f64], batch: &[f64], label: &str) {
87 for (i, (s, b)) in streaming.iter().zip(batch.iter()).enumerate() {
88 if s.is_nan() {
89 assert!(b.is_nan(), "{label} bar {i}: streaming NaN, batch {b}");
90 } else {
91 assert!(!b.is_nan(), "{label} bar {i}: streaming {s}, batch NaN");
92 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
93 }
94 }
95 }
96
97 proptest! {
98 #[test]
99 fn test_apo_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
100 let fast = 12;
101 let slow = 26;
102 let matype = MaType::Ema;
103 let mut apo = APO::new(fast, slow, matype);
104 let streaming: Vec<f64> = input.iter().map(|&x| apo.next(x)).collect();
105 let batch = talib_rs::momentum::apo(&input, fast, slow, matype.into())
106 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
107 assert_parity(&streaming, &batch, "apo/ema");
108 }
109
110 #[test]
111 fn test_ppo_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
112 let fast = 12;
113 let slow = 26;
114 let matype = MaType::Ema;
115 let mut ppo = PPO::new(fast, slow, matype);
116 let streaming: Vec<f64> = input.iter().map(|&x| ppo.next(x)).collect();
117 let batch = talib_rs::momentum::ppo(&input, fast, slow, matype.into())
118 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
119 assert_parity(&streaming, &batch, "ppo/ema");
120 }
121
122 #[test]
125 fn test_apo_ppo_parity_all_matypes(
126 input in prop::collection::vec(0.1..100.0, 120..200),
127 idx in 0usize..9,
128 ) {
129 let matype = MaType::ALL[idx];
130 let (fast, slow) = (12, 26);
131
132 let mut apo = APO::new(fast, slow, matype);
133 let s_apo: Vec<f64> = input.iter().map(|&x| apo.next(x)).collect();
134 let b_apo = talib_rs::momentum::apo(&input, fast, slow, matype.into())
135 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
136 assert_parity(&s_apo, &b_apo, &format!("apo/{matype}"));
137
138 let mut ppo = PPO::new(fast, slow, matype);
139 let s_ppo: Vec<f64> = input.iter().map(|&x| ppo.next(x)).collect();
140 let b_ppo = talib_rs::momentum::ppo(&input, fast, slow, matype.into())
141 .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
142 assert_parity(&s_ppo, &b_ppo, &format!("ppo/{matype}"));
143 }
144 }
145}