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    usage: "Apply to RSI or other oscillators to rescale them to a ±1 range with sharp threshold behaviour. Values near ±1 indicate high-confidence overbought/oversold conditions.",
35    keywords: &["oscillator", "ehlers", "normalization", "momentum"],
36    ehlers_summary: "The Inverse Fisher Transform maps input values to (-1, +1) via a hyperbolic tangent function. Ehlers uses it in Cybernetic Analysis to create oscillators whose output clusters near the extremes, making crossovers of fixed thresholds reliable trading signals.",
37    params: &[],
38    formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/TheInverseFisherTransform.pdf",
39    formula_latex: r#"
40\[
41IFT(x) = \frac{e^{2x} - 1}{e^{2x} + 1} = \tanh(x)
42\]
43"#,
44    gold_standard_file: "inverse_fisher.json",
45    category: "Ehlers DSP",
46};
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::traits::Next;
52    use proptest::prelude::*;
53
54    #[test]
55    fn test_inverse_fisher_basic() {
56        let mut ift = InverseFisherTransform::new();
57        // Values > 2 should be close to 1
58        approx::assert_relative_eq!(ift.next(2.0), 0.96402758, epsilon = 1e-6);
59        approx::assert_relative_eq!(ift.next(5.0), 0.9999092, epsilon = 1e-6);
60        // Values < -2 should be close to -1
61        approx::assert_relative_eq!(ift.next(-2.0), -0.96402758, epsilon = 1e-6);
62        // Value 0 should be 0
63        approx::assert_relative_eq!(ift.next(0.0), 0.0, epsilon = 1e-6);
64    }
65
66    proptest! {
67        #[test]
68        fn test_inverse_fisher_parity(input in prop::collection::vec(-5.0..5.0, 1..100)) {
69            let mut ift = InverseFisherTransform::new();
70            for &val in &input {
71                let s = ift.next(val);
72                let b = val.tanh();
73                approx::assert_relative_eq!(s, b, epsilon = 1e-10);
74            }
75        }
76    }
77}