From b1e7f53c566adc729e41046c76b0c963a352a0ef Mon Sep 17 00:00:00 2001 From: menkaru Date: Sat, 7 Aug 2021 13:35:24 -0600 Subject: [PATCH 1/4] copied wma as a base and fixed docs/renamed --- src/indicators/hull_moving_average.rs | 141 ++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/indicators/hull_moving_average.rs diff --git a/src/indicators/hull_moving_average.rs b/src/indicators/hull_moving_average.rs new file mode 100644 index 0000000..5136145 --- /dev/null +++ b/src/indicators/hull_moving_average.rs @@ -0,0 +1,141 @@ +use std::fmt; + +use crate::errors::{Result, TaError}; +use crate::{Close, Next, Period, Reset}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Hull Moving Average (HMA). +/// +/// A moving average that attemps to reduce or remove price lag while maintaining curve smoothness. +/// +/// # Example +/// +/// ``` +/// use ta::indicators::HullMovingAverage; +/// use ta::Next; +/// +/// let mut hma = HullMovingAverage::new(3).unwrap(); +/// assert_eq!(hma.next(10.0), 10.0); +/// assert_eq!(hma.next(13.0), 12.0); +/// assert_eq!(hma.next(16.0), 14.0); +/// assert_eq!(hma.next(14.0), 14.5); +/// ``` +/// +/// # Links +/// +/// * [Hull Moving Average, Alan Hull](https://alanhull.com/hull-moving-average) +/// + +#[doc(alias = "WMA")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct HullMovingAverage { + period: usize, +} + +impl HullMovingAverage { + pub fn new(period: usize) -> Result { + match period { + 0 => Err(TaError::InvalidParameter), + _ => Ok(Self { + period, + }), + } + } +} + +impl Period for HullMovingAverage { + fn period(&self) -> usize { + self.period + } +} + +impl Next for HullMovingAverage { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + input + } +} + +impl Next<&T> for HullMovingAverage { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.close()) + } +} + +impl Reset for HullMovingAverage { + fn reset(&mut self) { + + } +} + +impl Default for HullMovingAverage { + fn default() -> Self { + Self::new(9).unwrap() + } +} + +impl fmt::Display for HullMovingAverage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "HMA({})", self.period) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helper::*; + + test_indicator!(HullMovingAverage); + + #[test] + fn test_new() { + assert!(HullMovingAverage::new(0).is_err()); + assert!(HullMovingAverage::new(1).is_ok()); + } + + #[test] + fn test_next() { + let mut hma = HullMovingAverage::new(3).unwrap(); + + assert_eq!(hma.next(12.0), 12.0); + assert_eq!(hma.next(3.0), 6.0); + assert_eq!(hma.next(3.0), 4.5); + assert_eq!(hma.next(5.0), 4.0); + + let mut hma = HullMovingAverage::new(3).unwrap(); + let bar1 = Bar::new().close(2); + let bar2 = Bar::new().close(5); + assert_eq!(hma.next(&bar1), 2.0); + assert_eq!(hma.next(&bar2), 4.0); + } + + #[test] + fn test_reset() { + let mut hma = HullMovingAverage::new(5).unwrap(); + + assert_eq!(hma.next(4.0), 4.0); + hma.next(10.0); + hma.next(15.0); + hma.next(20.0); + assert_ne!(hma.next(4.0), 4.0); + + hma.reset(); + assert_eq!(hma.next(4.0), 4.0); + } + + #[test] + fn test_default() { + HullMovingAverage::default(); + } + + #[test] + fn test_display() { + let hma = HullMovingAverage::new(7).unwrap(); + assert_eq!(format!("{}", hma), "HMA(7)"); + } +} From de33f5d77df098018a8001c254d05d1aa341e977 Mon Sep 17 00:00:00 2001 From: menkaru Date: Sat, 7 Aug 2021 13:47:03 -0600 Subject: [PATCH 2/4] added hma to bench, crate and docs --- benches/indicators.rs | 5 +++-- src/indicators/hull_moving_average.rs | 2 +- src/indicators/mod.rs | 3 +++ src/lib.rs | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/benches/indicators.rs b/benches/indicators.rs index b62b5b1..04228b2 100644 --- a/benches/indicators.rs +++ b/benches/indicators.rs @@ -5,7 +5,7 @@ use ta::indicators::{ ExponentialMovingAverage, FastStochastic, KeltnerChannel, Maximum, MeanAbsoluteDeviation, Minimum, MoneyFlowIndex, MovingAverageConvergenceDivergence, OnBalanceVolume, PercentagePriceOscillator, RateOfChange, RelativeStrengthIndex, SimpleMovingAverage, - SlowStochastic, StandardDeviation, TrueRange, WeightedMovingAverage + SlowStochastic, StandardDeviation, TrueRange, WeightedMovingAverage, HullMovingAverage }; use ta::{DataItem, Next}; @@ -73,5 +73,6 @@ bench_indicators!( SlowStochastic, StandardDeviation, TrueRange, - WeightedMovingAverage + WeightedMovingAverage, + HullMovingAverage ); diff --git a/src/indicators/hull_moving_average.rs b/src/indicators/hull_moving_average.rs index 5136145..135eff4 100644 --- a/src/indicators/hull_moving_average.rs +++ b/src/indicators/hull_moving_average.rs @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize}; /// * [Hull Moving Average, Alan Hull](https://alanhull.com/hull-moving-average) /// -#[doc(alias = "WMA")] +#[doc(alias = "HMA")] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone)] pub struct HullMovingAverage { diff --git a/src/indicators/mod.rs b/src/indicators/mod.rs index 66a08d3..d3d73c9 100644 --- a/src/indicators/mod.rs +++ b/src/indicators/mod.rs @@ -4,6 +4,9 @@ pub use self::exponential_moving_average::ExponentialMovingAverage; mod weighted_moving_average; pub use self::weighted_moving_average::WeightedMovingAverage; +mod hull_moving_average; +pub use self::hull_moving_average::HullMovingAverage; + mod simple_moving_average; pub use self::simple_moving_average::SimpleMovingAverage; diff --git a/src/lib.rs b/src/lib.rs index 17b40db..87d191f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ //! //! * Trend //! * [Exponential Moving Average (EMA)](crate::indicators::ExponentialMovingAverage) +//! * [Hull Moving Average (HMA)](crate::indicators::HullMovingAverage) //! * [Weighted Moving Average] (WMA)(crate::indicators::WeightedMovingAverage) //! * [Simple Moving Average (SMA)](crate::indicators::SimpleMovingAverage) //! * Oscillators From ef6244726d045d40eb7aac9dfc6307426c438248 Mon Sep 17 00:00:00 2001 From: menkaru Date: Sat, 7 Aug 2021 15:23:35 -0600 Subject: [PATCH 3/4] added tests for hma and made them pass --- src/indicators/hull_moving_average.rs | 40 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/indicators/hull_moving_average.rs b/src/indicators/hull_moving_average.rs index 135eff4..97fb590 100644 --- a/src/indicators/hull_moving_average.rs +++ b/src/indicators/hull_moving_average.rs @@ -1,6 +1,7 @@ use std::fmt; use crate::errors::{Result, TaError}; +use crate::indicators::WeightedMovingAverage; use crate::{Close, Next, Period, Reset}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -17,9 +18,9 @@ use serde::{Deserialize, Serialize}; /// /// let mut hma = HullMovingAverage::new(3).unwrap(); /// assert_eq!(hma.next(10.0), 10.0); -/// assert_eq!(hma.next(13.0), 12.0); -/// assert_eq!(hma.next(16.0), 14.0); -/// assert_eq!(hma.next(14.0), 14.5); +/// assert_eq!(hma.next(13.0), 14.0); +/// assert_eq!(hma.next(16.0), 18.0); +/// assert_eq!(hma.next(14.0), 13.5); /// ``` /// /// # Links @@ -32,14 +33,20 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct HullMovingAverage { period: usize, + short_wma: WeightedMovingAverage, + regular_wma: WeightedMovingAverage, + wrapping_wma: WeightedMovingAverage, } impl HullMovingAverage { pub fn new(period: usize) -> Result { match period { - 0 => Err(TaError::InvalidParameter), + 0 | 1 => Err(TaError::InvalidParameter), _ => Ok(Self { period, + short_wma: WeightedMovingAverage::new(period / 2)?, + regular_wma: WeightedMovingAverage::new(period)?, + wrapping_wma: WeightedMovingAverage::new((period as f64).sqrt() as usize)?, }), } } @@ -55,7 +62,10 @@ impl Next for HullMovingAverage { type Output = f64; fn next(&mut self, input: f64) -> Self::Output { - input + // pinescript formula + // hma = wma(2*wma(src, length/2)-wma(src, length), round(sqrt(length))) + let source = (2.0 * self.short_wma.next(input)) - self.regular_wma.next(input); + self.wrapping_wma.next(source) } } @@ -69,7 +79,9 @@ impl Next<&T> for HullMovingAverage { impl Reset for HullMovingAverage { fn reset(&mut self) { - + self.short_wma.reset(); + self.regular_wma.reset(); + self.wrapping_wma.reset(); } } @@ -95,22 +107,24 @@ mod tests { #[test] fn test_new() { assert!(HullMovingAverage::new(0).is_err()); - assert!(HullMovingAverage::new(1).is_ok()); + assert!(HullMovingAverage::new(1).is_err()); + assert!(HullMovingAverage::new(2).is_ok()); + assert!(HullMovingAverage::new(9).is_ok()); } #[test] fn test_next() { let mut hma = HullMovingAverage::new(3).unwrap(); - assert_eq!(hma.next(12.0), 12.0); - assert_eq!(hma.next(3.0), 6.0); - assert_eq!(hma.next(3.0), 4.5); - assert_eq!(hma.next(5.0), 4.0); + assert_eq!(round(hma.next(12.0)), 12.0); + assert_eq!(round(hma.next(9.0)), 8.0); + assert_eq!(round(hma.next(7.0)), 5.5); + assert_eq!(round(hma.next(13.0)), 15.667); let mut hma = HullMovingAverage::new(3).unwrap(); - let bar1 = Bar::new().close(2); + let bar1 = Bar::new().close(8); let bar2 = Bar::new().close(5); - assert_eq!(hma.next(&bar1), 2.0); + assert_eq!(hma.next(&bar1), 8.0); assert_eq!(hma.next(&bar2), 4.0); } From 9617358fdfaeeed2c2a3e88af7701104c0bd562f Mon Sep 17 00:00:00 2001 From: menkaru Date: Tue, 10 Aug 2021 10:17:51 -0600 Subject: [PATCH 4/4] Fix merge issues and add readme entries --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f793f40..00f9481 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ So far there are the following indicators available. * Trend * Exponential Moving Average (EMA) + * Hull Moving Average (HMA) + * Weighted Moving Average (WMA) * Simple Moving Average (SMA) * Oscillators * Relative Strength Index (RSI) @@ -127,3 +129,4 @@ Our NEAR wallet address is `ta-rs.near` - [Devin Gunay](https://github.com/dgunay) - serde support - [Youngchan Lee](https://github.com/edwardycl) - bugfix - [tommady](https://github.com/tommady) - get rid of error-chain dependency +- [menkaru](https://github.com/menkaru) - WMA, HMA