Skip to content

Commit

Permalink
Added DI-/DI+, DM-/DM+, DX and ADX. Needs verification & tests and so…
Browse files Browse the repository at this point in the history
…me docs.
  • Loading branch information
virtualritz committed Jul 20, 2022
1 parent 47c0213 commit 55ea749
Show file tree
Hide file tree
Showing 8 changed files with 960 additions and 8 deletions.
140 changes: 140 additions & 0 deletions src/indicators/average_directional_index.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
Ok(Self {
previous: 0.0,
dx: DirectionalMovementIndex::new(period)?,
})
}
}

impl Period for AverageDirectionalIndex {
fn period(&self) -> usize {
self.dx.period()
}
}

impl Next<f64> 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<T: High> 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)");
}
}
*/
233 changes: 233 additions & 0 deletions src/indicators/directional_indicator.rs
Original file line number Diff line number Diff line change
@@ -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-<sub>t</sub> / ATR(period)<sub>t</sub>
///
/// Where:
///
/// * _SDM-(period)<sub>t</sub>_ – [Smoothed negative directional
/// movement](crate::indicators::SmoothedNegativeDirectionalMovement) over
/// _period_ at time _t_.
/// * _ATR(period)<sub>t</sub>_ – [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<Self> {
Ok(Self {
sndm: SmoothedNegativeDirectionalMovement::new(period)?,
atr: AverageTrueRange::new(period)?,
})
}
}

impl Period for NegativeDirectionalIndicator {
fn period(&self) -> usize {
self.sndm.period()
}
}

impl Next<f64> for NegativeDirectionalIndicator {
type Output = f64;

fn next(&mut self, input: f64) -> Self::Output {
100.0 * (self.sndm.next(input) / self.atr.next(input))
}
}

impl<T: High> 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)<sub>t</sub> = SDM+(period)<sub>t</sub> / ATR(period)<sub>t</sub>
///
/// Where:
///
/// * _SDM+(period)<sub>t</sub>_ – [Smoothed positive directional
/// movement](crate::indicators::SmoothedPositiveDirectionalMovement) over
/// _period_ at time _t_.
/// * _ATR(period)<sub>t</sub>_ – [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<Self> {
Ok(Self {
spdm: SmoothedPositiveDirectionalMovement::new(period)?,
atr: AverageTrueRange::new(period)?,
})
}
}

impl Period for PositiveDirectionalIndicator {
fn period(&self) -> usize {
self.spdm.period()
}
}

impl Next<f64> for PositiveDirectionalIndicator {
type Output = f64;

fn next(&mut self, input: f64) -> Self::Output {
100.0 * (self.spdm.next(input) / self.atr.next(input))
}
}

impl<T: High> 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)");
}
}*/
Loading

0 comments on commit 55ea749

Please # to comment.