1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use crate::{Echo, View, EMA};
use std::collections::VecDeque;

#[derive(Clone)]
/// John Ehlers Fisher Transform Indicator
/// from: http://www.mesasoftware.com/papers/UsingTheFisherTransform.pdf
pub struct EhlersFisherTransform {
    view: Box<dyn View>,
    moving_average: Box<dyn View>,
    window_len: usize,
    q_vals: VecDeque<f64>,
    high: f64,
    low: f64,
    q_out: VecDeque<f64>,
}

impl EhlersFisherTransform {
    /// Create a new indicator with a given chained view and a window length
    /// The default EMA is used as in the paper
    pub fn new(view: Box<dyn View>, window_len: usize) -> Box<Self> {
        Self::with_ma(view, EMA::new_final(5), window_len)
    }

    /// Create a new indicator with a window length
    pub fn new_final(window_len: usize) -> Box<Self> {
        Self::new(Echo::new(), window_len)
    }

    /// Create a new indicator with a view, moving average and window length
    pub fn with_ma(view: Box<dyn View>, ma: Box<dyn View>, window_len: usize) -> Box<Self> {
        Box::new(Self {
            view,
            moving_average: ma,
            window_len,
            q_vals: VecDeque::new(),
            high: 0.0,
            low: 0.0,
            q_out: VecDeque::new(),
        })
    }
}

impl View for EhlersFisherTransform {
    fn update(&mut self, val: f64) {
        self.view.update(val);
        let val: f64 = self.view.last();

        if self.q_vals.len() == 0 {
            self.high = val;
            self.low = val;
        }
        if self.q_vals.len() >= self.window_len {
            let old_val = self.q_vals.pop_front().unwrap();
            // update high and low values if needed
            if old_val >= self.high {
                // re-compute high
                self.high = *self
                    .q_vals
                    .iter()
                    .max_by(|x, y| x.partial_cmp(y).unwrap())
                    .unwrap();
            }
            if old_val <= self.low {
                // re-compute low
                self.low = *self
                    .q_vals
                    .iter()
                    .min_by(|x, y| x.partial_cmp(y).unwrap())
                    .unwrap();
            }
        }
        self.q_vals.push_back(val);
        if val > self.high {
            self.high = val;
        } else if val < self.low {
            self.low = val;
        }

        if self.high == self.low {
            self.q_out.push_back(0.0);
            return;
        }
        let val: f64 = 2.0 * ((val - self.low) / (self.high - self.low) - 0.5);
        // smooth with moving average
        self.moving_average.update(val);
        let mut smoothed = self.moving_average.last();
        if smoothed > 0.99 {
            // slight deviation from paper but clipping the value to 0.99 seems to make more sense
            smoothed = 0.99;
        } else if smoothed < -0.99 {
            smoothed = -0.99;
        }
        if self.q_out.len() == 0 {
            // do not insert values when there are not enough values yet
            self.q_out.push_back(0.0);
            return;
        }
        let fish: f64 =
            0.5 * ((1.0 + smoothed) / (1.0 - smoothed)).ln() + 0.5 * self.q_out.back().unwrap();
        self.q_out.push_back(fish);
    }

    fn last(&self) -> f64 {
        *self.q_out.back().unwrap()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::plot::plot_values;
    use crate::test_data::TEST_DATA;

    #[test]
    fn ehlers_fisher_transform_plot() {
        let mut eft = EhlersFisherTransform::new_final(16);
        let mut out: Vec<f64> = vec![];
        for v in &TEST_DATA {
            eft.update(*v);
            out.push(eft.last());
        }
        println!("out: {:?}", out);
        let filename = "img/ehlers_fisher_transform.png";
        plot_values(out, filename).unwrap();
    }
}