Skip to main content

quantwave_core/indicators/
cycle.rs

1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2#[allow(unused_imports)]
3use crate::traits::Next;
4
5talib_1_in_1_out!(HT_DCPERIOD, talib_rs::cycle::ht_dcperiod);
6impl Default for HT_DCPERIOD {
7    fn default() -> Self {
8        Self::new()
9    }
10}
11talib_1_in_2_out!(HT_PHASOR, talib_rs::cycle::ht_phasor);
12impl Default for HT_PHASOR {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17talib_1_in_1_out!(HT_DCPHASE, talib_rs::cycle::ht_dcphase);
18impl Default for HT_DCPHASE {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23talib_1_in_2_out!(HT_SINE, talib_rs::cycle::ht_sine);
24impl Default for HT_SINE {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29talib_1_in_1_out_i32!(HT_TRENDMODE, talib_rs::cycle::ht_trendmode);
30impl Default for HT_TRENDMODE {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36pub const HT_DCPERIOD_METADATA: IndicatorMetadata = IndicatorMetadata {
37    name: "Hilbert Transform - Dominant Cycle Period (HT_DCPERIOD)",
38    description: "Identifies the period of the dominant cycle in the price data using the Hilbert Transform.",
39    usage: "Use to dynamically adjust the lookback periods of other indicators (e.g., adaptive moving averages). Knowing the current dominant cycle length allows for more accurate smoothing and trend detection.",
40    keywords: &["cycle", "hilbert", "adaptive", "dsp"],
41    ehlers_summary: "John Ehlers popularized the use of the Hilbert Transform to identify the dominant cycle in financial time series. The DCPERIOD indicator tracks the length of this cycle in bars, providing a crucial parameter for creating market-responsive technical indicators that adapt to changing volatility. — Rocket Science for Traders",
42    params: &[],
43    formula_source: "https://www.tradingview.com/support/solutions/43000502011-hilbert-transform-dominant-cycle-period-ht-dcperiod/",
44    formula_latex: r#"
45\[
46\text{DCPERIOD}_t = \text{Recalculated Dominant Cycle using Hilbert Transform}
47\]
48"#,
49    gold_standard_file: "ht_dcperiod.json",
50    category: "Ehlers DSP",
51};
52
53pub const HT_DCPHASE_METADATA: IndicatorMetadata = IndicatorMetadata {
54    name: "Hilbert Transform - Dominant Cycle Phase (HT_DCPHASE)",
55    description: "Calculates the phase angle (0 to 360 degrees) of the dominant cycle identified by the Hilbert Transform.",
56    usage: "Use to identify the current position within a market cycle. It is the core component for generating the Hilbert Sine Wave indicator, which signals trend vs. cycle regimes.",
57    keywords: &["cycle", "hilbert", "phase", "dsp"],
58    ehlers_summary: "The Dominant Cycle Phase represents the instantaneous position within a detected cycle. By measuring the phase angle, traders can determine if the market is at a peak, trough, or mid-cycle, enabling more precise timing for entry and exit signals. — Rocket Science for Traders",
59    params: &[],
60    formula_source: "https://www.tradingview.com/support/solutions/43000502010-hilbert-transform-dominant-cycle-phase-ht-dcphase/",
61    formula_latex: r#"
62\[
63Phase = \arctan\left(\frac{\text{Quadrature}}{\text{InPhase}}\right)
64\]
65"#,
66    gold_standard_file: "ht_dcphase.json",
67    category: "Ehlers DSP",
68};
69
70pub const HT_PHASOR_METADATA: IndicatorMetadata = IndicatorMetadata {
71    name: "Hilbert Transform - Phasor Components (HT_PHASOR)",
72    description: "Outputs the In-Phase and Quadrature components of the signal, which are used to calculate phase and amplitude.",
73    usage: "Use as building blocks for custom DSP indicators. The In-Phase component is the signal itself, while the Quadrature component is shifted by 90 degrees.",
74    keywords: &["cycle", "hilbert", "phasor", "dsp"],
75    ehlers_summary: "The Phasor components (In-Phase and Quadrature) are the fundamental outputs of the Hilbert Transform. They allow for the decomposition of a signal into its vector representation, which is essential for advanced cycle analysis and the creation of lag-free filters. — Rocket Science for Traders",
76    params: &[],
77    formula_source: "https://www.tradingview.com/support/solutions/43000502012-hilbert-transform-phasor-components-ht-phasor/",
78    formula_latex: r#"
79\[
80\text{Result} = (InPhase, Quadrature)
81\]
82"#,
83    gold_standard_file: "ht_phasor.json",
84    category: "Ehlers DSP",
85};
86
87pub const HT_SINE_METADATA: IndicatorMetadata = IndicatorMetadata {
88    name: "Hilbert Transform - Sine Wave (HT_SINE)",
89    description: "An indicator that plots a sine wave and a lead-sine wave (shifted by 45 degrees) to identify cyclical turns.",
90    usage: "Use to identify cycle turning points and trend regimes. When the two waves are separated and rhythmic, the market is in a cycle; when they are compressed or crossover erratically, the market is in a trend.",
91    keywords: &["cycle", "hilbert", "sine", "dsp"],
92    ehlers_summary: "The Hilbert Sine Wave is one of John Ehlers' most famous contributions. It provides a clear visual indication of market cycles. Crossovers of the Sine and Lead-Sine waves provide high-probability entry points in ranging markets while identifying when a strong trend has taken over. — Rocket Science for Traders",
93    params: &[],
94    formula_source: "https://www.tradingview.com/support/solutions/43000502013-hilbert-transform-sine-wave-ht-sine/",
95    formula_latex: r#"
96\[
97Sine = \sin(Phase) \\ LeadSine = \sin(Phase + 45^\circ)
98\]
99"#,
100    gold_standard_file: "ht_sine.json",
101    category: "Ehlers DSP",
102};
103
104pub const HT_TRENDMODE_METADATA: IndicatorMetadata = IndicatorMetadata {
105    name: "Hilbert Transform - Trend vs. Cycle Mode (HT_TRENDMODE)",
106    description: "A binary indicator that determines if the market is currently in a trending state (1) or a cyclical state (0).",
107    usage: "Use as a master filter for strategy selection. Deploy trend-following tools when TRENDMODE is 1, and mean-reversion tools when TRENDMODE is 0.",
108    keywords: &["cycle", "trend", "hilbert", "regime-detection", "dsp"],
109    ehlers_summary: "Determining the current market regime is the 'holy grail' of technical analysis. The HT_TRENDMODE indicator uses the rate of change of the dominant cycle phase to distinguish between trending and ranging price action, allowing traders to avoid 'whipsaws' in non-conducive environments. — Rocket Science for Traders",
110    params: &[],
111    formula_source: "https://www.tradingview.com/support/solutions/43000502014-hilbert-transform-trend-vs-cycle-mode-ht-trendmode/",
112    formula_latex: r#"
113\[
114\text{TRENDMODE} = \begin{cases} 1 & \text{if trend detected} \\ 0 & \text{if cycle detected} \end{cases}
115\]
116"#,
117    gold_standard_file: "ht_trendmode.json",
118    category: "Ehlers DSP",
119};
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::traits::Next;
125    use proptest::prelude::*;
126
127    proptest! {
128        #[test]
129        fn test_ht_dcperiod_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
130            let mut ht = HT_DCPERIOD::new();
131            let streaming_results: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
132            let batch_results = talib_rs::cycle::ht_dcperiod(&input).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
133
134            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
135                if s.is_nan() {
136                    assert!(b.is_nan());
137                } else {
138                    approx::assert_relative_eq!(s, b, epsilon = 1e-6);
139                }
140            }
141        }
142
143        #[test]
144        fn test_ht_phasor_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
145            let mut ht = HT_PHASOR::new();
146            let streaming_results: Vec<(f64, f64)> = input.iter().map(|&x| ht.next(x)).collect();
147            let (b_inphase, b_quadrature) = talib_rs::cycle::ht_phasor(&input).unwrap_or_else(|_| {
148                (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
149            });
150
151            for (i, (s_in, s_quad)) in streaming_results.into_iter().enumerate() {
152                if s_in.is_nan() {
153                    assert!(b_inphase[i].is_nan());
154                } else {
155                    approx::assert_relative_eq!(s_in, b_inphase[i], epsilon = 1e-6);
156                }
157                if s_quad.is_nan() {
158                    assert!(b_quadrature[i].is_nan());
159                } else {
160                    approx::assert_relative_eq!(s_quad, b_quadrature[i], epsilon = 1e-6);
161                }
162            }
163        }
164
165        #[test]
166        fn test_ht_trendmode_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
167            let mut ht = HT_TRENDMODE::new();
168            let streaming_results: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
169            let batch_results = talib_rs::cycle::ht_trendmode(&input).unwrap_or_else(|_| vec![0; input.len()]);
170
171            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
172                assert_eq!(*s as i32, *b);
173            }
174        }
175    }
176}