finlib_ta/indicators/
hull_moving_average.rs

1use core::fmt;
2
3use crate::errors::{Result, TaError};
4use crate::indicators::WeightedMovingAverage;
5use crate::{Close, Next, Period, Reset};
6#[cfg(feature = "std")]
7use core::f64;
8#[cfg(not(feature = "std"))]
9use libm::sqrt;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// Hull Moving Average (HMA).
14///
15/// A moving average that attemps to reduce or remove price lag while maintaining curve smoothness.
16///
17/// # Example
18///
19/// ```
20/// use finlib_ta::indicators::HullMovingAverage;
21/// use finlib_ta::Next;
22///
23/// let mut hma = HullMovingAverage::new(3).unwrap();
24/// assert_eq!(hma.next(10.0), 10.0);
25/// assert_eq!(hma.next(13.0), 14.0);
26/// assert_eq!(hma.next(16.0), 18.0);
27/// assert_eq!(hma.next(14.0), 13.5);
28/// ```
29///
30/// # Links
31///
32/// * [Hull Moving Average, Alan Hull](https://alanhull.com/hull-moving-average)
33///
34
35#[doc(alias = "HMA")]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[derive(Debug, Clone)]
38pub struct HullMovingAverage {
39    period: usize,
40    short_wma: WeightedMovingAverage,
41    regular_wma: WeightedMovingAverage,
42    wrapping_wma: WeightedMovingAverage,
43}
44
45impl HullMovingAverage {
46    pub fn new(period: usize) -> Result<Self> {
47        match period {
48            0 | 1 => Err(TaError::InvalidParameter),
49            _ => {
50                Ok(Self {
51                    period,
52                    short_wma: WeightedMovingAverage::new(period / 2)?,
53                    regular_wma: WeightedMovingAverage::new(period)?,
54                    wrapping_wma: WeightedMovingAverage::new(Self::sqrt(period))?,
55                })
56            }
57        }
58    }
59
60    fn sqrt(period: usize) -> usize {
61        #[cfg(feature = "std")]
62        {
63            return (period as f64).sqrt() as usize;
64        }
65        #[cfg(not(feature = "std"))]
66        {
67            return sqrt(period as f64) as usize;
68        }
69    }
70}
71
72impl Period for HullMovingAverage {
73    fn period(&self) -> usize {
74        self.period
75    }
76}
77
78impl Next<f64> for HullMovingAverage {
79    type Output = f64;
80
81    fn next(&mut self, input: f64) -> Self::Output {
82        // pinescript formula
83        // hma = wma(2*wma(src, length/2)-wma(src, length), round(sqrt(length)))
84        let source = (2.0 * self.short_wma.next(input)) - self.regular_wma.next(input);
85        self.wrapping_wma.next(source)
86    }
87}
88
89impl<T: Close> Next<&T> for HullMovingAverage {
90    type Output = f64;
91
92    fn next(&mut self, input: &T) -> Self::Output {
93        self.next(input.close())
94    }
95}
96
97impl Reset for HullMovingAverage {
98    fn reset(&mut self) {
99        self.short_wma.reset();
100        self.regular_wma.reset();
101        self.wrapping_wma.reset();
102    }
103}
104
105impl Default for HullMovingAverage {
106    fn default() -> Self {
107        Self::new(9).unwrap()
108    }
109}
110
111impl fmt::Display for HullMovingAverage {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        write!(f, "HMA({})", self.period)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::test_helper::*;
121    use alloc::format;
122
123    test_indicator!(HullMovingAverage);
124
125    #[test]
126    fn test_new() {
127        assert!(HullMovingAverage::new(0).is_err());
128        assert!(HullMovingAverage::new(1).is_err());
129        assert!(HullMovingAverage::new(2).is_ok());
130        assert!(HullMovingAverage::new(9).is_ok());
131    }
132
133    #[test]
134    fn test_next() {
135        let mut hma = HullMovingAverage::new(3).unwrap();
136
137        assert_eq!(round(hma.next(12.0)), 12.0);
138        assert_eq!(round(hma.next(9.0)), 8.0);
139        assert_eq!(round(hma.next(7.0)), 5.5);
140        assert_eq!(round(hma.next(13.0)), 15.667);
141
142        let mut hma = HullMovingAverage::new(3).unwrap();
143        let bar1 = Bar::new().close(8);
144        let bar2 = Bar::new().close(5);
145        assert_eq!(hma.next(&bar1), 8.0);
146        assert_eq!(hma.next(&bar2), 4.0);
147    }
148
149    #[test]
150    fn test_reset() {
151        let mut hma = HullMovingAverage::new(5).unwrap();
152
153        assert_eq!(hma.next(4.0), 4.0);
154        hma.next(10.0);
155        hma.next(15.0);
156        hma.next(20.0);
157        assert_ne!(hma.next(4.0), 4.0);
158
159        hma.reset();
160        assert_eq!(hma.next(4.0), 4.0);
161    }
162
163    #[test]
164    fn test_default() {
165        HullMovingAverage::default();
166    }
167
168    #[test]
169    fn test_display() {
170        let hma = HullMovingAverage::new(7).unwrap();
171        assert_eq!(format!("{}", hma), "HMA(7)");
172    }
173}