From 55ea74919c5370845f5d524a3066d39ad5059f50 Mon Sep 17 00:00:00 2001 From: Moritz Moeller Date: Wed, 20 Jul 2022 19:29:50 +0200 Subject: [PATCH] Added DI-/DI+, DM-/DM+, DX and ADX. Needs verification & tests and some docs. --- src/indicators/average_directional_index.rs | 140 +++++++++++ src/indicators/directional_indicator.rs | 233 ++++++++++++++++++ src/indicators/directional_movement.rs | 206 ++++++++++++++++ src/indicators/directional_movement_index.rs | 147 +++++++++++ src/indicators/exponential_moving_average.rs | 9 +- src/indicators/mod.rs | 17 ++ .../smoothed_directional_movement.rs | 207 ++++++++++++++++ src/indicators/weighted_moving_average.rs | 9 +- 8 files changed, 960 insertions(+), 8 deletions(-) create mode 100644 src/indicators/average_directional_index.rs create mode 100644 src/indicators/directional_indicator.rs create mode 100644 src/indicators/directional_movement.rs create mode 100644 src/indicators/directional_movement_index.rs create mode 100644 src/indicators/smoothed_directional_movement.rs diff --git a/src/indicators/average_directional_index.rs b/src/indicators/average_directional_index.rs new file mode 100644 index 0000000..56232cb --- /dev/null +++ b/src/indicators/average_directional_index.rs @@ -0,0 +1,140 @@ +use crate::{errors::Result, indicators::DirectionalMovementIndex, High, Next, Period, Reset}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Average Directional Index (ADX) +/// +/// A direction indicator, originally developed by J. Welles Wilder. The +/// average directional movement index is an N-sample smoothed moving average of +/// a combination of positive & negative directional indicator (DI) values. +/// +/// # Parameters +/// +/// * `period` - Smoothing period (samples) of SDM and ATR (nonzero integer) +/// used in the DIs. +/// +/// # Links +/// +/// * [Averager directional movement index, Wikipedia](https://en.wikipedia.org/wiki/Average_directional_movement_index) +#[doc(alias = "ADX")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct AverageDirectionalIndex { + previous: f64, + dx: DirectionalMovementIndex, +} + +impl AverageDirectionalIndex { + pub fn new(period: usize) -> Result { + Ok(Self { + previous: 0.0, + dx: DirectionalMovementIndex::new(period)?, + }) + } +} + +impl Period for AverageDirectionalIndex { + fn period(&self) -> usize { + self.dx.period() + } +} + +impl Next for AverageDirectionalIndex { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + let current = self.dx.next(input); + let adx = (self.previous * (self.dx.period() - 1) as f64 + current) / self.dx.period() as f64; + self.previous = current; + + adx + } +} + +impl Next<&T> for AverageDirectionalIndex { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for AverageDirectionalIndex { + fn reset(&mut self) { + self.previous = 0.0; + self.dx.reset() + } +} + +impl Default for AverageDirectionalIndex { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for AverageDirectionalIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ADX({})", self.period()) + } +} + +// TODO: implement AverageDirectionalIndexDetailed where next() returns a tuple +// of (DI-, ADX, DI+) + +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helper::*; + + test_indicator!(ExponentialMovingAverage); + + #[test] + fn test_new() { + assert!(ExponentialMovingAverage::new(0).is_err()); + assert!(ExponentialMovingAverage::new(1).is_ok()); + } + + #[test] + fn test_next() { + let mut ema = ExponentialMovingAverage::new(3).unwrap(); + + assert_eq!(ema.next(2.0), 2.0); + assert_eq!(ema.next(5.0), 3.5); + assert_eq!(ema.next(1.0), 2.25); + assert_eq!(ema.next(6.25), 4.25); + + let mut ema = ExponentialMovingAverage::new(3).unwrap(); + let bar1 = Bar::new().close(2); + let bar2 = Bar::new().close(5); + assert_eq!(ema.next(&bar1), 2.0); + assert_eq!(ema.next(&bar2), 3.5); + } + + #[test] + fn test_reset() { + let mut ema = ExponentialMovingAverage::new(5).unwrap(); + + assert_eq!(ema.next(4.0), 4.0); + ema.next(10.0); + ema.next(15.0); + ema.next(20.0); + assert_ne!(ema.next(4.0), 4.0); + + ema.reset(); + assert_eq!(ema.next(4.0), 4.0); + } + + #[test] + fn test_default() { + ExponentialMovingAverage::default(); + } + + #[test] + fn test_display() { + let ema = ExponentialMovingAverage::new(7).unwrap(); + assert_eq!(format!("{}", ema), "EMA(7)"); + } +} +*/ diff --git a/src/indicators/directional_indicator.rs b/src/indicators/directional_indicator.rs new file mode 100644 index 0000000..6e29aaa --- /dev/null +++ b/src/indicators/directional_indicator.rs @@ -0,0 +1,233 @@ +use crate::{ + errors::Result, + indicators::{ + AverageTrueRange, SmoothedNegativeDirectionalMovement, SmoothedPositiveDirectionalMovement, + }, + High, Next, Period, Reset, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Negative Directional Indicator (DI-). +/// +/// A downtrend indicator, originally developed by J. Welles Wilder. The +/// negative directional indicator is an N-sample smoothed moving average of the +/// smoothed negative directional movement (SDM-) values normalized by the +/// average true range (ATR). +/// +/// # Formula +/// +/// DI- = SDM-t / ATR(period)t +/// +/// Where: +/// +/// * _SDM-(period)t_ – [Smoothed negative directional +/// movement](crate::indicators::SmoothedNegativeDirectionalMovement) over +/// _period_ at time _t_. +/// * _ATR(period)t_ – [Averag true +/// range](crate::indicators::AverageTrueRange) over _period_ at time _t_. +/// +/// # Parameters +/// +/// * `period` - Smoothing period (number of samples) of SDM- and ATR (positive +/// integer). +/// +/// # Links +/// +/// * [Average directional movement index, Wikipedia](https://en.wikipedia.org/wiki/Average_directional_movement_index) +#[doc(alias = "DI-")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct NegativeDirectionalIndicator { + sndm: SmoothedNegativeDirectionalMovement, + atr: AverageTrueRange, +} + +impl NegativeDirectionalIndicator { + pub fn new(period: usize) -> Result { + Ok(Self { + sndm: SmoothedNegativeDirectionalMovement::new(period)?, + atr: AverageTrueRange::new(period)?, + }) + } +} + +impl Period for NegativeDirectionalIndicator { + fn period(&self) -> usize { + self.sndm.period() + } +} + +impl Next for NegativeDirectionalIndicator { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + 100.0 * (self.sndm.next(input) / self.atr.next(input)) + } +} + +impl Next<&T> for NegativeDirectionalIndicator { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for NegativeDirectionalIndicator { + fn reset(&mut self) { + self.sndm.reset(); + self.atr.reset(); + } +} + +impl Default for NegativeDirectionalIndicator { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for NegativeDirectionalIndicator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DI-({})", self.sndm.period()) + } +} + +/// Positive Directional Indicator (DI+). +/// +/// An uptrend indicator, originally developed by J. Welles +/// Wilder. The positive directional indicator is an N-sample smoothed moving +/// average of the smoothed positive directional movement (SDM+) values +/// normalized by the average true range (ATR). +/// +/// # Formula +/// +/// DI+(period)t = SDM+(period)t / ATR(period)t +/// +/// Where: +/// +/// * _SDM+(period)t_ – [Smoothed positive directional +/// movement](crate::indicators::SmoothedPositiveDirectionalMovement) over +/// _period_ at time _t_. +/// * _ATR(period)t_ – [Averag true +/// range](crate::indicators::AverageTrueRange) over _period_ at time _t_. +/// +/// # Parameters +/// +/// * `period` - Smoothing period (number of samples) of SDM+ and ATR (positive +/// integer). +/// +/// # Links +/// +/// * [Average directional movement index, Wikipedia](https://en.wikipedia.org/wiki/Average_directional_movement_index) +#[doc(alias = "DI+")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct PositiveDirectionalIndicator { + spdm: SmoothedPositiveDirectionalMovement, + atr: AverageTrueRange, +} + +impl PositiveDirectionalIndicator { + pub fn new(period: usize) -> Result { + Ok(Self { + spdm: SmoothedPositiveDirectionalMovement::new(period)?, + atr: AverageTrueRange::new(period)?, + }) + } +} + +impl Period for PositiveDirectionalIndicator { + fn period(&self) -> usize { + self.spdm.period() + } +} + +impl Next for PositiveDirectionalIndicator { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + 100.0 * (self.spdm.next(input) / self.atr.next(input)) + } +} + +impl Next<&T> for PositiveDirectionalIndicator { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for PositiveDirectionalIndicator { + fn reset(&mut self) { + self.spdm.reset(); + self.atr.reset(); + } +} + +impl Default for PositiveDirectionalIndicator { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for PositiveDirectionalIndicator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DI+({})", self.spdm.period()) + } +} + +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helper::*; + + test_indicator!(AverageTrueRange); + + #[test] + fn test_new() { + assert!(AverageTrueRange::new(0).is_err()); + assert!(AverageTrueRange::new(1).is_ok()); + } + #[test] + fn test_next() { + let mut atr = AverageTrueRange::new(3).unwrap(); + + let bar1 = Bar::new().high(10).low(7.5).close(9); + let bar2 = Bar::new().high(11).low(9).close(9.5); + let bar3 = Bar::new().high(9).low(5).close(8); + + assert_eq!(atr.next(&bar1), 2.5); + assert_eq!(atr.next(&bar2), 2.25); + assert_eq!(atr.next(&bar3), 3.375); + } + + #[test] + fn test_reset() { + let mut atr = AverageTrueRange::new(9).unwrap(); + + let bar1 = Bar::new().high(10).low(7.5).close(9); + let bar2 = Bar::new().high(11).low(9).close(9.5); + + atr.next(&bar1); + atr.next(&bar2); + + atr.reset(); + let bar3 = Bar::new().high(60).low(15).close(51); + assert_eq!(atr.next(&bar3), 45.0); + } + + #[test] + fn test_default() { + AverageTrueRange::default(); + } + + #[test] + fn test_display() { + let indicator = AverageTrueRange::new(8).unwrap(); + assert_eq!(format!("{}", indicator), "ATR(8)"); + } +}*/ diff --git a/src/indicators/directional_movement.rs b/src/indicators/directional_movement.rs new file mode 100644 index 0000000..698cc87 --- /dev/null +++ b/src/indicators/directional_movement.rs @@ -0,0 +1,206 @@ +use crate::{errors::Result, High, Low, Next, Reset}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Negative Directional Movement (DM-). +/// +/// A direction indicator that is commonly used as a component of the +/// [(Average)](crate::indicators::AverageDirectionalMovementIndex) +/// [Directional Movement Index](crate::indicators::DirectionalMovementIndex) +/// (ADX/DX). +/// +/// # Formula +/// +/// DM-t = lowt-1 - lowt +/// +/// Where: +/// +/// * _DM-t_ – [Negative Directional +/// Movement](crate::indicators::NegativeDirectionalMovement) at time _t_. +#[doc(alias = "DM-")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct NegativeDirectionalMovement { + current: f64, + is_new: bool, +} + +impl NegativeDirectionalMovement { + pub fn new() -> Result { + Ok(Self { + current: 0.0, + is_new: true, + }) + } +} + +impl Next for NegativeDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + if self.is_new { + self.is_new = false; + self.current = input; + self.current + } else { + let next = self.current - input; + self.current = input; + next + } + } +} + +impl Next<&T> for NegativeDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.low()) + } +} + +impl Reset for NegativeDirectionalMovement { + fn reset(&mut self) { + self.is_new = true; + } +} + +impl Default for NegativeDirectionalMovement { + fn default() -> Self { + Self::new().unwrap() + } +} + +impl fmt::Display for NegativeDirectionalMovement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DM-") + } +} + +/// Positive Directional Movement (DM+). +/// +/// A direction indicator that is commonly used as a component of the +/// [(Average)](crate::indicators::AverageDirectionalMovementIndex) +/// [Directional Movement Index](crate::indicators::DirectionalMovementIndex) +/// (ADX/DX). +/// +/// # Formula +/// +/// DM+t = hight - hight-1 +/// +/// Where: +/// +/// * _DM+t_ – [Positive Directional +/// Movement](crate::indicators::PositiveDirectionalMovement) at time _t_. +#[doc(alias = "+DM")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct PositiveDirectionalMovement { + current: f64, + is_new: bool, +} + +impl PositiveDirectionalMovement { + pub fn new() -> Result { + Ok(Self { + current: 0.0, + is_new: true, + }) + } +} + +impl Next for PositiveDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + if self.is_new { + self.is_new = false; + self.current = input; + self.current + } else { + let next = input - self.current; + self.current = input; + next + } + } +} + +impl Next<&T> for PositiveDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for PositiveDirectionalMovement { + fn reset(&mut self) { + self.is_new = true; + } +} + +impl Default for PositiveDirectionalMovement { + fn default() -> Self { + Self::new().unwrap() + } +} + +impl fmt::Display for PositiveDirectionalMovement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "+DI") + } +} + +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helper::*; + + test_indicator!(AverageTrueRange); + + #[test] + fn test_new() { + assert!(AverageTrueRange::new(0).is_err()); + assert!(AverageTrueRange::new(1).is_ok()); + } + #[test] + fn test_next() { + let mut atr = AverageTrueRange::new(3).unwrap(); + + let bar1 = Bar::new().high(10).low(7.5).close(9); + let bar2 = Bar::new().high(11).low(9).close(9.5); + let bar3 = Bar::new().high(9).low(5).close(8); + + assert_eq!(atr.next(&bar1), 2.5); + assert_eq!(atr.next(&bar2), 2.25); + assert_eq!(atr.next(&bar3), 3.375); + } + + #[test] + fn test_reset() { + let mut atr = AverageTrueRange::new(9).unwrap(); + + let bar1 = Bar::new().high(10).low(7.5).close(9); + let bar2 = Bar::new().high(11).low(9).close(9.5); + + atr.next(&bar1); + atr.next(&bar2); + + atr.reset(); + let bar3 = Bar::new().high(60).low(15).close(51); + assert_eq!(atr.next(&bar3), 45.0); + } + + #[test] + fn test_default() { + AverageTrueRange::default(); + } + + #[test] + fn test_display() { + let indicator = AverageTrueRange::new(8).unwrap(); + assert_eq!(format!("{}", indicator), "ATR(8)"); + } +} +*/ diff --git a/src/indicators/directional_movement_index.rs b/src/indicators/directional_movement_index.rs new file mode 100644 index 0000000..b5c977d --- /dev/null +++ b/src/indicators/directional_movement_index.rs @@ -0,0 +1,147 @@ +use crate::{ + errors::Result, + indicators::{ + AverageTrueRange, SmoothedNegativeDirectionalMovement, SmoothedPositiveDirectionalMovement, + }, + High, Next, Period, Reset, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Directional Movement Index (DX/DMI) +/// +/// A direction indicator, originally developed by J. Welles Wilder. The +/// directional movement index is an N-sample moving average of the +/// a combination of positive & negative directional indicator (DI) values . +/// +/// # Parameters +/// +/// * `period` - Smoothing period (samples) of SDM and ATR (nonzero integer) +/// used in the DIs. +/// +/// # Links +/// +/// * [Averager directional moviement index, Wikipedia](https://en.wikipedia.org/wiki/Average_directional_movement_index) +/// * [Directional movement index, Wikipedia (French)](https://fr.wikipedia.org/wiki/Directional_Movement_Index) +#[doc(alias = "DX")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct DirectionalMovementIndex { + sndm: SmoothedNegativeDirectionalMovement, + spdm: SmoothedPositiveDirectionalMovement, + atr: AverageTrueRange, +} + +impl DirectionalMovementIndex { + pub fn new(period: usize) -> Result { + Ok(Self { + sndm: SmoothedNegativeDirectionalMovement::new(period)?, + spdm: SmoothedPositiveDirectionalMovement::new(period)?, + atr: AverageTrueRange::new(period)?, + }) + } +} + +impl Period for DirectionalMovementIndex { + fn period(&self) -> usize { + self.atr.period() + } +} + +impl Next for DirectionalMovementIndex { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + let atr = self.atr.next(input); + let ndi = self.sndm.next(input) / atr; + let pdi = self.spdm.next(input) / atr; + + 100.0 * ((pdi - ndi).abs() / (pdi + ndi).abs()) + } +} + +impl Next<&T> for DirectionalMovementIndex { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for DirectionalMovementIndex { + fn reset(&mut self) { + self.sndm.reset(); + self.spdm.reset(); + self.atr.reset() + } +} + +impl Default for DirectionalMovementIndex { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for DirectionalMovementIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DX({})", self.period()) + } +} + +/* +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helper::*; + + test_indicator!(ExponentialMovingAverage); + + #[test] + fn test_new() { + assert!(ExponentialMovingAverage::new(0).is_err()); + assert!(ExponentialMovingAverage::new(1).is_ok()); + } + + #[test] + fn test_next() { + let mut ema = ExponentialMovingAverage::new(3).unwrap(); + + assert_eq!(ema.next(2.0), 2.0); + assert_eq!(ema.next(5.0), 3.5); + assert_eq!(ema.next(1.0), 2.25); + assert_eq!(ema.next(6.25), 4.25); + + let mut ema = ExponentialMovingAverage::new(3).unwrap(); + let bar1 = Bar::new().close(2); + let bar2 = Bar::new().close(5); + assert_eq!(ema.next(&bar1), 2.0); + assert_eq!(ema.next(&bar2), 3.5); + } + + #[test] + fn test_reset() { + let mut ema = ExponentialMovingAverage::new(5).unwrap(); + + assert_eq!(ema.next(4.0), 4.0); + ema.next(10.0); + ema.next(15.0); + ema.next(20.0); + assert_ne!(ema.next(4.0), 4.0); + + ema.reset(); + assert_eq!(ema.next(4.0), 4.0); + } + + #[test] + fn test_default() { + ExponentialMovingAverage::default(); + } + + #[test] + fn test_display() { + let ema = ExponentialMovingAverage::new(7).unwrap(); + assert_eq!(format!("{}", ema), "EMA(7)"); + } +} +*/ diff --git a/src/indicators/exponential_moving_average.rs b/src/indicators/exponential_moving_average.rs index e9d3f7c..5c23c7b 100644 --- a/src/indicators/exponential_moving_average.rs +++ b/src/indicators/exponential_moving_average.rs @@ -1,9 +1,10 @@ -use std::fmt; - -use crate::errors::{Result, TaError}; -use crate::{Close, Next, Period, Reset}; +use crate::{ + errors::{Result, TaError}, + Close, Next, Period, Reset, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use std::fmt; /// An exponential moving average (EMA), also known as an exponentially weighted /// moving average (EWMA). diff --git a/src/indicators/mod.rs b/src/indicators/mod.rs index 3e9c6e5..d7f1cf9 100644 --- a/src/indicators/mod.rs +++ b/src/indicators/mod.rs @@ -75,3 +75,20 @@ mod quantitative_qualitative_estimation; pub use self::quantitative_qualitative_estimation::{ QuantitativeQualitativeEstimation, QuantitativeQualitativeEstimationOutput, }; + +mod directional_movement; +pub use self::directional_movement::{NegativeDirectionalMovement, PositiveDirectionalMovement}; + +mod smoothed_directional_movement; +pub use self::smoothed_directional_movement::{ + SmoothedNegativeDirectionalMovement, SmoothedPositiveDirectionalMovement, +}; + +mod directional_indicator; +pub use self::directional_indicator::{NegativeDirectionalIndicator, PositiveDirectionalIndicator}; + +mod directional_movement_index; +pub use self::directional_movement_index::DirectionalMovementIndex; + +mod average_directional_index; +pub use self::average_directional_index::AverageDirectionalIndex; diff --git a/src/indicators/smoothed_directional_movement.rs b/src/indicators/smoothed_directional_movement.rs new file mode 100644 index 0000000..83ad166 --- /dev/null +++ b/src/indicators/smoothed_directional_movement.rs @@ -0,0 +1,207 @@ +use crate::{ + errors::{Result, TaError}, + indicators::{NegativeDirectionalMovement, PositiveDirectionalMovement}, + High, Next, Period, Reset, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use std::{collections::VecDeque, fmt}; + +/// Weighted moving average (WMA). +/// +/// A moving average that assigns weights that decrease in arithmetical +/// progression. In an _n_-day WMA the latest day has weight _n_, the second +/// latest _n−1_, etc., down to one. +/// +/// # Formula +/// +/// ![WMA formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/7780333af18da7e27a1186a3d566e28da21b2840) +/// +/// Where: +/// +/// * _WMAM_ - is the value of the WMA at time _m_ +/// * _n_ - is the period. +/// * _pM_ - is the input value at a time period t. +/// +/// # Example +/// +/// ``` +/// use ta::indicators::WeightedMovingAverage; +/// use ta::Next; +/// +/// let mut wma = WeightedMovingAverage::new(3).unwrap(); +/// assert_eq!(wma.next(10.0), 10.0); +/// assert_eq!(wma.next(13.0), 12.0); +/// assert_eq!(wma.next(16.0), 14.0); +/// assert_eq!(wma.next(14.0), 14.5); +/// ``` +/// +/// # Links +/// +/// * [Weighted moving average, Wikipedia](https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average) + +#[doc(alias = "S+DM")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct SmoothedPositiveDirectionalMovement { + period: usize, + sum: f64, + window: VecDeque, + pdm: PositiveDirectionalMovement, +} + +impl SmoothedPositiveDirectionalMovement { + pub fn new(period: usize) -> Result { + match period { + 0 => Err(TaError::InvalidParameter), + _ => Ok(Self { + period, + sum: 0.0, + window: { + let mut window = VecDeque::with_capacity(period); + window.push_back(0.0); + window + }, + pdm: PositiveDirectionalMovement::new().unwrap(), + }), + } + } +} + +impl Period for SmoothedPositiveDirectionalMovement { + fn period(&self) -> usize { + self.period + } +} + +impl Next for SmoothedPositiveDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + // Remove front of window from sum. + self.sum -= if self.window.len() < self.period { + *self.window.front().unwrap() + } else { + self.window.pop_front().unwrap() + }; + // Calculate current DM. + let dm = self.pdm.next(input); + // Add to window. + self.window.push_back(dm); + // Update sum of values in window + self.sum += dm; + + self.sum - self.sum / self.period as f64 - dm + } +} + +impl Next<&T> for SmoothedPositiveDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for SmoothedPositiveDirectionalMovement { + fn reset(&mut self) { + self.sum = 0.0; + self.window.clear(); + self.window.push_back(0.0); + } +} + +impl Default for SmoothedPositiveDirectionalMovement { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for SmoothedPositiveDirectionalMovement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "S+DM({})", self.period) + } +} + +#[doc(alias = "S+DM")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct SmoothedNegativeDirectionalMovement { + period: usize, + sum: f64, + window: VecDeque, + ndm: NegativeDirectionalMovement, +} + +impl SmoothedNegativeDirectionalMovement { + pub fn new(period: usize) -> Result { + match period { + 0 => Err(TaError::InvalidParameter), + _ => Ok(Self { + period, + sum: 0.0, + window: { + let mut window = VecDeque::with_capacity(period); + window.push_back(0.0); + window + }, + ndm: NegativeDirectionalMovement::new().unwrap(), + }), + } + } +} + +impl Period for SmoothedNegativeDirectionalMovement { + fn period(&self) -> usize { + self.period + } +} + +impl Next for SmoothedNegativeDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: f64) -> Self::Output { + // Remove front of window from sum. + self.sum -= if self.window.len() < self.period { + *self.window.front().unwrap() + } else { + self.window.pop_front().unwrap() + }; + // Calculate current DM. + let dm = self.ndm.next(input); + // Add to window. + self.window.push_back(dm); + // Update sum of values in window + self.sum += dm; + + self.sum - self.sum / self.period as f64 - dm + } +} + +impl Next<&T> for SmoothedNegativeDirectionalMovement { + type Output = f64; + + fn next(&mut self, input: &T) -> Self::Output { + self.next(input.high()) + } +} + +impl Reset for SmoothedNegativeDirectionalMovement { + fn reset(&mut self) { + self.sum = 0.0; + self.window.clear(); + self.window.push_back(0.0); + } +} + +impl Default for SmoothedNegativeDirectionalMovement { + fn default() -> Self { + Self::new(14).unwrap() + } +} + +impl fmt::Display for SmoothedNegativeDirectionalMovement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "S+DM({})", self.period) + } +} diff --git a/src/indicators/weighted_moving_average.rs b/src/indicators/weighted_moving_average.rs index cb18854..a2ec432 100644 --- a/src/indicators/weighted_moving_average.rs +++ b/src/indicators/weighted_moving_average.rs @@ -1,9 +1,10 @@ -use std::fmt; - -use crate::errors::{Result, TaError}; -use crate::{Close, Next, Period, Reset}; +use crate::{ + errors::{Result, TaError}, + Close, Next, Period, Reset, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use std::fmt; /// Weighted moving average (WMA). ///