Skip to main content

quantwave_core/indicators/
precision_trend.rs

1use crate::indicators::high_pass::HighPass;
2use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
3use crate::traits::Next;
4
5/// Precision Trend Analysis
6///
7/// Based on John Ehlers' "Precision Trend Analysis" (TASC September 2024).
8/// Uses the difference between two HighPass filters to identify the trend
9/// and its Rate of Change (ROC) to pinpoint reversals.
10/// Returns (Trend, ROC).
11#[derive(Debug, Clone)]
12pub struct PrecisionTrendAnalysis {
13    hp1: HighPass,
14    hp2: HighPass,
15    prev_trend: f64,
16    length2: f64,
17    count: usize,
18}
19
20impl PrecisionTrendAnalysis {
21    pub fn new(length1: usize, length2: usize) -> Self {
22        Self {
23            hp1: HighPass::new(length1),
24            hp2: HighPass::new(length2),
25            prev_trend: 0.0,
26            length2: length2 as f64,
27            count: 0,
28        }
29    }
30}
31
32impl Default for PrecisionTrendAnalysis {
33    fn default() -> Self {
34        Self::new(250, 40)
35    }
36}
37
38impl Next<f64> for PrecisionTrendAnalysis {
39    type Output = (f64, f64);
40
41    fn next(&mut self, input: f64) -> Self::Output {
42        self.count += 1;
43        let v_hp1 = self.hp1.next(input);
44        let v_hp2 = self.hp2.next(input);
45        let trend = v_hp1 - v_hp2;
46        
47        let roc = (self.length2 / 6.28) * (trend - self.prev_trend);
48        
49        self.prev_trend = trend;
50        (trend, roc)
51    }
52}
53
54pub const PRECISION_TREND_ANALYSIS_METADATA: IndicatorMetadata = IndicatorMetadata {
55    name: "Precision Trend Analysis",
56    description: "Trend identification using the difference between two high-pass filters.",
57    params: &[
58        ParamDef {
59            name: "length1",
60            default: "250",
61            description: "First HighPass filter period",
62        },
63        ParamDef {
64            name: "length2",
65            default: "40",
66            description: "Second HighPass filter period",
67        },
68    ],
69    formula_source: "https://github.com/lavs9/quantwave/blob/main/references/traderstipsreference/TRADERS’%20TIPS%20-%20SEPTEMBER%202024.html",
70    formula_latex: r#"
71\[
72HP1 = HighPass(Price, Length1)
73\]
74\[
75HP2 = HighPass(Price, Length2)
76\]
77\[
78Trend = HP1 - HP2
79\]
80\[
81ROC = \frac{Length2}{6.28} \cdot (Trend - Trend_{t-1})
82\]
83"#,
84    gold_standard_file: "precision_trend.json",
85    category: "Ehlers DSP",
86};
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::traits::Next;
92    use proptest::prelude::*;
93
94    #[test]
95    fn test_precision_trend_basic() {
96        let mut pt = PrecisionTrendAnalysis::new(250, 40);
97        let inputs = vec![10.0; 10];
98        for input in inputs {
99            let (trend, roc) = pt.next(input);
100            assert!(!trend.is_nan());
101            assert!(!roc.is_nan());
102        }
103    }
104
105    proptest! {
106        #[test]
107        fn test_precision_trend_parity(
108            inputs in prop::collection::vec(1.0..100.0, 10..100),
109        ) {
110            let l1 = 250;
111            let l2 = 40;
112            let mut pt = PrecisionTrendAnalysis::new(l1, l2);
113            let streaming_results: Vec<(f64, f64)> = inputs.iter().map(|&x| pt.next(x)).collect();
114
115            // Batch implementation
116            let mut hp1 = HighPass::new(l1);
117            let mut hp2 = HighPass::new(l2);
118            let mut prev_trend = 0.0;
119            let mut batch_results = Vec::with_capacity(inputs.len());
120
121            for &input in inputs.iter() {
122                let v_hp1 = hp1.next(input);
123                let v_hp2 = hp2.next(input);
124                let trend = v_hp1 - v_hp2;
125                let roc = (l2 as f64 / 6.28) * (trend - prev_trend);
126                prev_trend = trend;
127                batch_results.push((trend, roc));
128            }
129
130            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
131                approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-10);
132                approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-10);
133            }
134        }
135    }
136}