Skip to main content

quantwave_core/indicators/
inverse_fisher.rs

1use crate::indicators::metadata::IndicatorMetadata;
2use crate::traits::Next;
3
4/// Inverse Fisher Transform (IFT)
5///
6/// Based on John Ehlers' "The Inverse Fisher Transform".
7/// The Inverse Fisher Transform is a compressive transform that alters the
8/// Probability Distribution Function (PDF) of an oscillator to produce clear
9/// black-or-white signals.
10///
11/// It is mathematically equivalent to the Hyperbolic Tangent (tanh) function.
12#[derive(Debug, Clone, Default)]
13pub struct InverseFisherTransform;
14
15impl InverseFisherTransform {
16    pub fn new() -> Self {
17        Self
18    }
19}
20
21impl Next<f64> for InverseFisherTransform {
22    type Output = f64;
23
24    fn next(&mut self, input: f64) -> Self::Output {
25        // IFT(x) = (exp(2*x) - 1) / (exp(2*x) + 1)
26        // This is exactly tanh(x)
27        input.tanh()
28    }
29}
30
31pub const INVERSE_FISHER_METADATA: IndicatorMetadata = IndicatorMetadata {
32    name: "Inverse Fisher Transform",
33    description: "A compressive transform that forces oscillator values towards +1 or -1, creating clear buy/sell signals.",
34    params: &[],
35    formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/TheInverseFisherTransform.pdf",
36    formula_latex: r#"
37\[
38IFT(x) = \frac{e^{2x} - 1}{e^{2x} + 1} = \tanh(x)
39\]
40"#,
41    gold_standard_file: "inverse_fisher.json",
42    category: "Ehlers DSP",
43};
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::traits::Next;
49    use proptest::prelude::*;
50
51    #[test]
52    fn test_inverse_fisher_basic() {
53        let mut ift = InverseFisherTransform::new();
54        // Values > 2 should be close to 1
55        approx::assert_relative_eq!(ift.next(2.0), 0.96402758, epsilon = 1e-6);
56        approx::assert_relative_eq!(ift.next(5.0), 0.9999092, epsilon = 1e-6);
57        // Values < -2 should be close to -1
58        approx::assert_relative_eq!(ift.next(-2.0), -0.96402758, epsilon = 1e-6);
59        // Value 0 should be 0
60        approx::assert_relative_eq!(ift.next(0.0), 0.0, epsilon = 1e-6);
61    }
62
63    proptest! {
64        #[test]
65        fn test_inverse_fisher_parity(input in prop::collection::vec(-5.0..5.0, 1..100)) {
66            let mut ift = InverseFisherTransform::new();
67            for &val in &input {
68                let s = ift.next(val);
69                let b = val.tanh();
70                approx::assert_relative_eq!(s, b, epsilon = 1e-10);
71            }
72        }
73    }
74}