quantwave_core/indicators/
voss_predictor.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use crate::indicators::bandpass::BandPass;
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone)]
12pub struct VossPredictor {
13 bandpass: BandPass,
14 order: usize,
15 voss_history: VecDeque<f64>,
16}
17
18impl VossPredictor {
19 pub fn new(period: usize, predict: usize) -> Self {
20 let order = 3 * predict;
21 Self {
22 bandpass: BandPass::new(period, 0.25), order,
24 voss_history: VecDeque::with_capacity(order + 1),
25 }
26 }
27}
28
29impl Default for VossPredictor {
30 fn default() -> Self {
31 Self::new(20, 3)
32 }
33}
34
35impl Next<f64> for VossPredictor {
36 type Output = (f64, f64); fn next(&mut self, input: f64) -> Self::Output {
39 let filt = self.bandpass.next(input);
40
41 let mut sum_c = 0.0;
42 if self.order > 0 {
43 for count in 0..self.order {
44 let idx = self.order - count;
45 let val = if idx <= self.voss_history.len() {
48 self.voss_history[idx - 1]
49 } else {
50 0.0
51 };
52 sum_c += ((count + 1) as f64 / self.order as f64) * val;
53 }
54 }
55
56 let voss = ((3.0 + self.order as f64) / 2.0) * filt - sum_c;
57
58 self.voss_history.push_front(voss);
59 if self.voss_history.len() > self.order {
60 self.voss_history.pop_back();
61 }
62
63 (filt, voss)
64 }
65}
66
67pub const VOSS_PREDICTOR_METADATA: IndicatorMetadata = IndicatorMetadata {
68 name: "VossPredictor",
69 description: "A predictive filter with negative group delay for band-limited signals.",
70 params: &[
71 ParamDef { name: "period", default: "20", description: "Center period of the BandPass filter" },
72 ParamDef { name: "predict", default: "3", description: "Number of bars of prediction" },
73 ],
74 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/A%20PEEK%20INTO%20THE%20FUTURE.pdf",
75 formula_latex: r#"
76\[
77Filt = \text{BandPass}(Price, Period, 0.25)
78\]
79\[
80Order = 3 \cdot Predict
81\]
82\[
83SumC = \sum_{n=0}^{Order-1} \frac{n+1}{Order} Voss_{t-(Order-n)}
84\]
85\[
86Voss = \frac{3 + Order}{2} Filt - SumC
87\]
88"#,
89 gold_standard_file: "voss_predictor.json",
90 category: "Ehlers DSP",
91};
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::traits::Next;
97 use crate::test_utils::{load_gold_standard_tuple, assert_indicator_parity_tuple};
98 use proptest::prelude::*;
99
100 #[test]
101 fn test_voss_gold_standard() {
102 let case = load_gold_standard_tuple("voss_predictor");
103 let vp = VossPredictor::new(20, 3);
104 assert_indicator_parity_tuple(vp, &case.input, &case.expected);
105 }
106
107 #[test]
108 fn test_voss_basic() {
109 let mut vp = VossPredictor::default();
110 let inputs = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0];
111 for input in inputs {
112 let (filt, voss) = vp.next(input);
113 assert!(!filt.is_nan());
114 assert!(!voss.is_nan());
115 }
116 }
117
118 proptest! {
119 #[test]
120 fn test_voss_parity(
121 inputs in prop::collection::vec(1.0..100.0, 60..120),
122 ) {
123 let period = 20;
124 let predict = 3;
125 let mut vp = VossPredictor::new(period, predict);
126 let streaming_results: Vec<(f64, f64)> = inputs.iter().map(|&x| vp.next(x)).collect();
127
128 let mut batch_results = Vec::with_capacity(inputs.len());
130 let mut bp = BandPass::new(period, 0.25);
131 let order = 3 * predict;
132 let mut v_hist = VecDeque::new();
133
134 for &input in &inputs {
135 let filt = bp.next(input);
136 let mut sum_c = 0.0;
137 for count in 0..order {
138 let idx = order - count;
139 let val = if idx <= v_hist.len() {
140 v_hist[idx - 1]
141 } else {
142 0.0
143 };
144 sum_c += ((count + 1) as f64 / order as f64) * val;
145 }
146
147 let voss = ((3.0 + order as f64) / 2.0) * filt - sum_c;
148 v_hist.push_front(voss);
149 if v_hist.len() > order {
150 v_hist.pop_back();
151 }
152 batch_results.push((filt, voss));
153 }
154
155 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
156 approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-10);
157 approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-10);
158 }
159 }
160 }
161}