Skip to main content

quantwave_core/indicators/
fisher.rs

1use crate::indicators::metadata::IndicatorMetadata;
2use crate::traits::Next;
3
4/// Fisher Transform
5///
6/// Based on John Ehlers' "Using The Fisher Transform".
7/// The Fisher Transform changes the Probability Density Function (PDF) of any
8/// waveform so that the transformed output has an approximately Gaussian PDF.
9/// This accentuates the largest deviations from the mean, providing sharp
10/// and easy to identify turning points.
11#[derive(Debug, Clone, Default)]
12pub struct FisherTransform;
13
14impl FisherTransform {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Next<f64> for FisherTransform {
21    type Output = f64;
22
23    fn next(&mut self, input: f64) -> Self::Output {
24        // y = 0.5 * ln((1 + x) / (1 - x))
25        // This is exactly atanh(x)
26        // input must be in range (-1, 1)
27        let x = input.clamp(-0.999, 0.999);
28        0.5 * ((1.0 + x) / (1.0 - x)).ln()
29    }
30}
31
32pub const FISHER_METADATA: IndicatorMetadata = IndicatorMetadata {
33    name: "Fisher Transform",
34    description: "Converts inputs to a nearly Gaussian probability distribution, creating sharp peaks at turning points.",
35    params: &[],
36    formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/UsingTheFisherTransform.pdf",
37    formula_latex: r#"
38\[
39Fish(x) = 0.5 \times \ln\left(\frac{1 + x}{1 - x}\right) = \text{atanh}(x)
40\]
41"#,
42    gold_standard_file: "fisher.json",
43    category: "Ehlers DSP",
44};
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::traits::Next;
50    use proptest::prelude::*;
51
52    #[test]
53    fn test_fisher_basic() {
54        let mut fish = FisherTransform::new();
55        // Values close to 1 should be large positive
56        assert!(fish.next(0.9) > 1.0);
57        // Values close to -1 should be large negative
58        assert!(fish.next(-0.9) < -1.0);
59        // Value 0 should be 0
60        approx::assert_relative_eq!(fish.next(0.0), 0.0, epsilon = 1e-6);
61    }
62
63    proptest! {
64        #[test]
65        fn test_fisher_parity(input in prop::collection::vec(-0.99..0.99, 1..100)) {
66            let mut fish = FisherTransform::new();
67            for &val in &input {
68                let s = fish.next(val);
69                let b = 0.5 * ((1.0 + val) / (1.0 - val)).ln();
70                approx::assert_relative_eq!(s, b, epsilon = 1e-10);
71            }
72        }
73    }
74}