From fc47df64cf0da6b4abee7d8135944e287ef2c172 Mon Sep 17 00:00:00 2001 From: Dmitry <71463689+amv-dev@users.noreply.github.com> Date: Sat, 29 Jan 2022 23:43:33 +0500 Subject: [PATCH] [Issue 19] Last value peeking / output values history access (#21) --- src/core/method.rs | 25 +++++++ src/core/window.rs | 28 +++++-- src/helpers/history.rs | 109 ++++++++++++++++++++++++++++ src/helpers/mod.rs | 3 + src/lib.rs | 2 + src/methods/adi.rs | 7 ++ src/methods/cci.rs | 3 +- src/methods/conv.rs | 7 ++ src/methods/ema.rs | 50 +++++++++++-- src/methods/highest_lowest.rs | 19 +++++ src/methods/highest_lowest_index.rs | 13 ++++ src/methods/hma.rs | 11 ++- src/methods/integral.rs | 7 ++ src/methods/lin_reg.rs | 7 ++ src/methods/mean_abs_dev.rs | 10 ++- src/methods/median_abs_dev.rs | 11 ++- src/methods/past.rs | 18 +++++ src/methods/rma.rs | 7 ++ src/methods/sma.rs | 17 +++++ src/methods/smm.rs | 12 ++- src/methods/st_dev.rs | 7 ++ src/methods/swma.rs | 13 +++- src/methods/trima.rs | 13 ++++ src/methods/tsi.rs | 14 +++- src/methods/vidya.rs | 11 ++- src/methods/volatility.rs | 7 ++ src/methods/vwma.rs | 7 ++ src/methods/wma.rs | 7 ++ src/methods/wsma.rs | 7 ++ 29 files changed, 428 insertions(+), 24 deletions(-) create mode 100644 src/helpers/history.rs diff --git a/src/core/method.rs b/src/core/method.rs index eeedf06..a868a1e 100644 --- a/src/core/method.rs +++ b/src/core/method.rs @@ -1,4 +1,5 @@ use super::{Error, Sequence}; +use crate::helpers::{WithHistory, WithLastValue}; use std::fmt; type BoxedFnMethod<'a, M> = Box::Input) -> ::Output>; @@ -77,6 +78,30 @@ pub trait Method: fmt::Debug { /// Generates next output value based on the given input `value` fn next(&mut self, value: &Self::Input) -> Self::Output; + /// Creates an instance of the method with given `parameters` and initial `value`, wrapped by historical data holder + fn with_history( + parameters: Self::Params, + initial_value: &Self::Input, + ) -> Result, Error> + where + Self: Sized, + Self::Output: fmt::Debug + Clone, + { + WithHistory::new(parameters, initial_value) + } + + /// Creates an instance of the method with given `parameters` and initial `value`, wrapped by last produced value holder + fn with_last_value( + parameters: Self::Params, + initial_value: &Self::Input, + ) -> Result, Error> + where + Self: Sized, + Self::Output: fmt::Debug + Clone, + { + WithLastValue::new(parameters, initial_value) + } + /// Returns a name of the method fn name(&self) -> &str { let parts = std::any::type_name::().split("::"); diff --git a/src/core/window.rs b/src/core/window.rs index c707143..1b0ba51 100644 --- a/src/core/window.rs +++ b/src/core/window.rs @@ -268,6 +268,24 @@ impl Window { pub const fn len(&self) -> PeriodType { self.size } + + /// Returns an element at `index` starting from the newest + #[must_use] + #[inline] + pub fn get(&self, index: PeriodType) -> Option<&T> { + let buf_index = self.slice_index(index)?; + self.buf.get(buf_index as usize) + } + + #[must_use] + #[inline] + fn slice_index(&self, index: PeriodType) -> Option { + let index = self.s_1.checked_sub(index)?; + let saturated = self.index.saturating_add(index); + let overflow = (saturated >= self.size) as PeriodType; + let s = self.size - self.index; + Some(overflow * index.saturating_sub(s) + (1 - overflow) * saturated) + } } impl AsRef<[T]> for Window { @@ -286,13 +304,9 @@ impl std::ops::Index for Window { type Output = T; fn index(&self, index: PeriodType) -> &Self::Output { - debug_assert!(index < self.size, "Window index {:} is out of range", index); - - let index = self.s_1 - index; - let saturated = self.index.saturating_add(index); - let overflow = (saturated >= self.size) as PeriodType; - let s = self.size - self.index; - let buf_index = (overflow * index.saturating_sub(s) + (1 - overflow) * saturated) as usize; + let buf_index = + self.slice_index(index) + .unwrap_or_else(|| panic!("Window index {:} is out of range", index)) as usize; if cfg!(feature = "unsafe_performance") { unsafe { self.buf.get_unchecked(buf_index) } diff --git a/src/helpers/history.rs b/src/helpers/history.rs new file mode 100644 index 0000000..64ab21a --- /dev/null +++ b/src/helpers/history.rs @@ -0,0 +1,109 @@ +use crate::prelude::{Error, Method}; + +/// Trait for picking the very last value for methods and indicators +pub trait Peekable { + /// Peeks the very last value, produced by method or indicator + fn peek(&self) -> V; +} + +/// Trait for picking historical values for methods and indicators +pub trait Buffered { + /// Picks value at `index` position, starting from the newest value + fn get(&self, index: usize) -> Option; +} + +/// Wrapper for holding historical data +#[derive(Debug, Clone)] +pub struct WithHistory { + history: Vec, + instance: T, +} + +impl WithHistory { + /// Picks value at `index` position, starting from the newest value + pub fn get(&self, index: usize) -> Option { + Buffered::get(self, index) + } +} + +impl Buffered for WithHistory { + fn get(&self, index: usize) -> Option { + let index = self.history.len().checked_sub(index + 1)?; + self.history.get(index).cloned() + } +} + +impl Method for WithHistory +where + T: Method, + T::Output: std::fmt::Debug + Clone, +{ + type Params = T::Params; + type Input = T::Input; + type Output = T::Output; + + fn new(parameters: Self::Params, initial_value: &Self::Input) -> Result { + Ok(Self { + instance: T::new(parameters, initial_value)?, + history: Vec::new(), + }) + } + + fn next(&mut self, value: &Self::Input) -> Self::Output { + let next_value = self.instance.next(value); + self.history.push(next_value.clone()); + + next_value + } +} + +/// Wrapper for keeping last produced value +#[derive(Debug, Clone)] +pub struct WithLastValue { + last_value: V, + instance: T, +} + +impl Method for WithLastValue +where + T: Method, + T::Output: std::fmt::Debug + Clone, +{ + type Params = T::Params; + type Input = T::Input; + type Output = T::Output; + + fn new(parameters: Self::Params, initial_value: &Self::Input) -> Result { + let mut instance = T::new(parameters, initial_value)?; + let last_value = instance.next(initial_value); + + Ok(Self { + last_value, + instance, + }) + } + + fn next(&mut self, value: &Self::Input) -> Self::Output { + let next_value = self.instance.next(value); + self.last_value = next_value.clone(); + next_value + } +} + +impl Peekable for WithLastValue { + fn peek(&self) -> V { + self.last_value.clone() + } +} + +impl> Peekable for &T { + fn peek(&self) -> V { + (*self).peek() + } +} + +impl> Buffered for &T { + fn get(&self, index: usize) -> Option { + (*self).get(index) + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 43082f5..d1ff12e 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -2,8 +2,11 @@ //! Additional helping primitives //! +mod history; mod methods; + use crate::core::{Candle, ValueType}; +pub use history::{Buffered, Peekable, WithHistory, WithLastValue}; pub use methods::{MAInstance, MA}; /// sign is like [`f64::signum`] diff --git a/src/lib.rs b/src/lib.rs index 7a3d750..0281013 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,8 @@ pub mod prelude { Candle, Error, IndicatorConfig, IndicatorInstance, Method, Sequence, OHLCV, }; + pub use super::helpers::{Buffered, Peekable}; + /// Dynamically dispatchable traits for indicators creation pub mod dd { pub use crate::core::{IndicatorConfigDyn, IndicatorInstanceDyn}; diff --git a/src/methods/adi.rs b/src/methods/adi.rs index 11f2117..e7e3b73 100644 --- a/src/methods/adi.rs +++ b/src/methods/adi.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window, OHLCV}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -113,6 +114,12 @@ impl Method for ADI { self.cmf_sum -= self.window.push(clvv); } + self.peek() + } +} + +impl Peekable<::Output> for ADI { + fn peek(&self) -> ::Output { self.cmf_sum } } diff --git a/src/methods/cci.rs b/src/methods/cci.rs index bc19708..52991d4 100644 --- a/src/methods/cci.rs +++ b/src/methods/cci.rs @@ -1,6 +1,7 @@ #![allow(unused_imports)] use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; use crate::methods::MeanAbsDev; #[cfg(feature = "serde")] @@ -50,7 +51,7 @@ impl Method for CCI { #[inline] fn next(&mut self, value: &Self::Input) -> Self::Output { let mean = self.0.next(value); - let ma = self.0.get_sma().get_last_value(); + let ma = self.0.get_sma().peek(); if mean > 0.0 { (value - ma) / mean diff --git a/src/methods/conv.rs b/src/methods/conv.rs index 3cbcbbb..0374717 100644 --- a/src/methods/conv.rs +++ b/src/methods/conv.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -67,6 +68,12 @@ impl Method for Conv { #[inline] fn next(&mut self, value: &Self::Input) -> Self::Output { self.window.push(*value); + self.peek() + } +} + +impl Peekable<::Output> for Conv { + fn peek(&self) -> ::Output { self.window .iter() .zip(self.weights.iter().rev()) diff --git a/src/methods/ema.rs b/src/methods/ema.rs index 73abcfc..d8d2264 100644 --- a/src/methods/ema.rs +++ b/src/methods/ema.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -77,6 +78,12 @@ impl Method for EMA { impl MovingAverage for EMA {} +impl Peekable<::Output> for EMA { + fn peek(&self) -> ::Output { + self.value + } +} + /// Simple shortcut for [EMA] over [EMA] /// /// # See also @@ -112,6 +119,12 @@ impl Method for DMA { impl MovingAverage for DMA {} +impl Peekable<::Output> for DMA { + fn peek(&self) -> ::Output { + self.dma.value + } +} + /// Simple shortcut for [EMA] over [EMA] over [EMA] (or [EMA] over [DMA], or [DMA] over [EMA]) /// /// # See also @@ -147,6 +160,12 @@ impl Method for TMA { impl MovingAverage for TMA {} +impl Peekable<::Output> for TMA { + fn peek(&self) -> ::Output { + self.tma.value + } +} + /// [Double Exponential Moving Average](https://en.wikipedia.org/wiki/Double_exponential_moving_average) of specified `length` for timeseries of type [`ValueType`] /// /// # Parameters @@ -214,15 +233,24 @@ impl Method for DEMA { #[inline] fn next(&mut self, value: &Self::Input) -> Self::Output { let e_ma = self.ema.next(value); - let d_ma = self.dma.next(&e_ma); + self.dma.next(&e_ma); - // 2. * ema - dma - e_ma.mul_add(2., -d_ma) + self.peek() } } impl MovingAverage for DEMA {} +impl Peekable<::Output> for DEMA { + fn peek(&self) -> ::Output { + let e_ma = self.ema.value; + let d_ma = self.dma.value; + + // 2. * ema - dma + e_ma.mul_add(2., -d_ma) + } +} + /// [Triple Exponential Moving Average](https://en.wikipedia.org/wiki/Triple_exponential_moving_average) of specified `length` for timeseries of type [`ValueType`] /// /// # Parameters @@ -293,15 +321,25 @@ impl Method for TEMA { fn next(&mut self, value: &Self::Input) -> Self::Output { let e_ma = self.ema.next(value); let d_ma = self.dma.next(&e_ma); - let t_ma = self.tma.next(&d_ma); + self.tma.next(&d_ma); - // 3. * (ema - dma) + tma - (e_ma - d_ma).mul_add(3., t_ma) + self.peek() } } impl MovingAverage for TEMA {} +impl Peekable<::Output> for TEMA { + fn peek(&self) -> ::Output { + let e_ma = self.ema.value; + let d_ma = self.dma.value; + let t_ma = self.tma.value; + + // 3. * (ema - dma) + tma + (e_ma - d_ma).mul_add(3., t_ma) + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/highest_lowest.rs b/src/methods/highest_lowest.rs index d2f1250..367bbad 100644 --- a/src/methods/highest_lowest.rs +++ b/src/methods/highest_lowest.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -118,6 +119,12 @@ impl Method for HighestLowestDelta { } } +impl Peekable<::Output> for HighestLowestDelta { + fn peek(&self) -> ::Output { + self.highest - self.lowest + } +} + /// Returns highest value over the last `length` values for timeseries of type [`ValueType`] /// /// # Parameters @@ -211,6 +218,12 @@ impl Method for Highest { } } +impl Peekable<::Output> for Highest { + fn peek(&self) -> ::Output { + self.value + } +} + /// Returns lowest value over the last `length` values for timeseries of type [`ValueType`] /// /// # Parameters @@ -304,6 +317,12 @@ impl Method for Lowest { } } +impl Peekable<::Output> for Lowest { + fn peek(&self) -> ::Output { + self.value + } +} + #[cfg(test)] mod tests { use super::{Highest, HighestLowestDelta, Lowest}; diff --git a/src/methods/highest_lowest_index.rs b/src/methods/highest_lowest_index.rs index 0373c63..a28dfc1 100644 --- a/src/methods/highest_lowest_index.rs +++ b/src/methods/highest_lowest_index.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -112,6 +113,12 @@ impl Method for HighestIndex { } } +impl Peekable<::Output> for HighestIndex { + fn peek(&self) -> ::Output { + self.index + } +} + /// Returns lowest value index over the last `length` values for timeseries of type [`ValueType`] /// /// If period has more than one minimum values, then returns the index of the newest value (e.g. the smallest index) @@ -220,6 +227,12 @@ impl Method for LowestIndex { } } +impl Peekable<::Output> for LowestIndex { + fn peek(&self) -> ::Output { + self.index + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/methods/hma.rs b/src/methods/hma.rs index e96b013..473daa7 100644 --- a/src/methods/hma.rs +++ b/src/methods/hma.rs @@ -1,5 +1,8 @@ use super::WMA; -use crate::core::{Error, Method, MovingAverage, PeriodType, ValueType}; +use crate::{ + core::{Error, Method, MovingAverage, PeriodType, ValueType}, + helpers::Peekable, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -87,6 +90,12 @@ impl Method for HMA { impl MovingAverage for HMA {} +impl Peekable<::Output> for HMA { + fn peek(&self) -> ::Output { + self.wma3.peek() + } +} + #[cfg(test)] mod tests { use super::{HMA as TestingMethod, WMA}; diff --git a/src/methods/integral.rs b/src/methods/integral.rs index 71ec930..ef3de2f 100644 --- a/src/methods/integral.rs +++ b/src/methods/integral.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -118,6 +119,12 @@ impl Default for Integral { } } +impl Peekable<::Output> for Integral { + fn peek(&self) -> ::Output { + self.value + } +} + #[cfg(test)] mod tests { use super::{Integral as TestingMethod, Method}; diff --git a/src/methods/lin_reg.rs b/src/methods/lin_reg.rs index 574b5d0..7ac1d73 100644 --- a/src/methods/lin_reg.rs +++ b/src/methods/lin_reg.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType, Window}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -108,6 +109,12 @@ impl Method for LinReg { impl MovingAverage for LinReg {} +impl Peekable<::Output> for LinReg { + fn peek(&self) -> ::Output { + self.b() + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/mean_abs_dev.rs b/src/methods/mean_abs_dev.rs index f4c61ee..bd09626 100644 --- a/src/methods/mean_abs_dev.rs +++ b/src/methods/mean_abs_dev.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType}; +use crate::helpers::Peekable; use crate::methods::SMA; #[cfg(feature = "serde")] @@ -53,8 +54,15 @@ impl Method for MeanAbsDev { #[inline] fn next(&mut self, value: &Self::Input) -> Self::Output { - let mean = self.0.next(value); + self.0.next(value); + self.peek() + } +} + +impl Peekable<::Output> for MeanAbsDev { + fn peek(&self) -> ::Output { + let mean = self.0.peek(); self.0 .get_window() .as_slice() diff --git a/src/methods/median_abs_dev.rs b/src/methods/median_abs_dev.rs index 83102cf..b3e6b37 100644 --- a/src/methods/median_abs_dev.rs +++ b/src/methods/median_abs_dev.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType}; +use crate::helpers::Peekable; use crate::methods::SMM; #[cfg(feature = "serde")] @@ -59,7 +60,15 @@ impl Method for MedianAbsDev { #[inline] fn next(&mut self, value: &Self::Input) -> Self::Output { - let smm = self.smm.next(value); + self.smm.next(value); + + self.peek() + } +} + +impl Peekable<::Output> for MedianAbsDev { + fn peek(&self) -> ::Output { + let smm = self.smm.peek(); self.smm .get_window() diff --git a/src/methods/past.rs b/src/methods/past.rs index 1a1d9b8..03ceeeb 100644 --- a/src/methods/past.rs +++ b/src/methods/past.rs @@ -1,5 +1,7 @@ use crate::core::Method; use crate::core::{Error, PeriodType, Window}; +use crate::helpers::{Buffered, Peekable}; +use std::convert::TryInto; use std::fmt; #[cfg(feature = "serde")] @@ -80,6 +82,22 @@ where } } +impl Peekable<::Output> for Past +where + T: Clone + fmt::Debug, +{ + fn peek(&self) -> ::Output { + self.0.newest().clone() + } +} + +impl Buffered<::Output> for Past { + fn get(&self, index: usize) -> Option<::Output> { + let index = index.try_into().ok()?; + self.0.get(index).cloned() + } +} + #[cfg(test)] mod tests { use super::{Method, Past as TestingMethod}; diff --git a/src/methods/rma.rs b/src/methods/rma.rs index 3a0645b..41556a8 100644 --- a/src/methods/rma.rs +++ b/src/methods/rma.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -91,6 +92,12 @@ impl Method for RMA { impl MovingAverage for RMA {} +impl Peekable<::Output> for RMA { + fn peek(&self) -> ::Output { + self.prev_value + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/sma.rs b/src/methods/sma.rs index 84d7c1b..4bc5c6c 100644 --- a/src/methods/sma.rs +++ b/src/methods/sma.rs @@ -1,5 +1,8 @@ +use std::convert::TryInto; + use crate::core::{Error, PeriodType, ValueType, Window}; use crate::core::{Method, MovingAverage}; +use crate::helpers::{Buffered, Peekable}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -69,6 +72,7 @@ impl SMA { /// Returns last result value. Useful for implementing in other methods and indicators. #[inline] #[must_use] + #[deprecated(since = "0.5.1", note = "Use `Peekable::peek` instead")] pub const fn get_last_value(&self) -> ValueType { self.value } @@ -101,6 +105,19 @@ impl Method for SMA { impl MovingAverage for SMA {} +impl Buffered for SMA { + fn get(&self, index: usize) -> Option { + let index = index.try_into().ok()?; + self.window.get(index).copied() + } +} + +impl Peekable<::Output> for SMA { + fn peek(&self) -> ::Output { + self.value + } +} + #[cfg(test)] mod tests { use super::{Method, SMA as TestingMethod}; diff --git a/src/methods/smm.rs b/src/methods/smm.rs index eae4dc2..495d47c 100644 --- a/src/methods/smm.rs +++ b/src/methods/smm.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType, Window}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; use std::{cmp::Ordering, slice::SliceIndex}; #[cfg(feature = "serde")] @@ -132,8 +133,9 @@ impl SMM { /// Returns last result value. Useful for implementing in other methods and indicators. #[inline] #[must_use] + #[deprecated(since = "0.5.1", note = "Use `Peekable::peek` instead")] pub fn get_last_value(&self) -> ValueType { - (get(&self.slice, self.half as usize) + get(&self.slice, self.half_m1 as usize)) * 0.5 + self.peek() } } @@ -214,7 +216,7 @@ impl Method for SMM { self.slice[index] = value; } - self.get_last_value() + self.peek() } } @@ -280,6 +282,12 @@ impl<'de> Deserialize<'de> for SMM { impl MovingAverage for SMM {} +impl Peekable<::Output> for SMM { + fn peek(&self) -> ::Output { + (get(&self.slice, self.half as usize) + get(&self.slice, self.half_m1 as usize)) * 0.5 + } +} + #[cfg(test)] mod tests { use super::{Method, SMM as TestingMethod}; diff --git a/src/methods/st_dev.rs b/src/methods/st_dev.rs index 932f420..ddf2cd4 100644 --- a/src/methods/st_dev.rs +++ b/src/methods/st_dev.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -91,6 +92,12 @@ impl Method for StDev { self.val_sum += diff; self.mean += diff * self.divider; + self.peek() + } +} + +impl Peekable<::Output> for StDev { + fn peek(&self) -> ::Output { // self.sq_val_sum - self.val_sum * self.mean; let sum = self.val_sum.mul_add(self.mean, self.sq_val_sum); diff --git a/src/methods/swma.rs b/src/methods/swma.rs index bc59793..9a709ef 100644 --- a/src/methods/swma.rs +++ b/src/methods/swma.rs @@ -1,4 +1,7 @@ -use crate::core::{Error, Method, MovingAverage, PeriodType, ValueType, Window}; +use crate::{ + core::{Error, Method, MovingAverage, PeriodType, ValueType, Window}, + helpers::Peekable, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -102,12 +105,18 @@ impl Method for SWMA { self.numerator += right_value.mul_add(self.left_float_length, self.left_total); self.left_total += left_prev_value - right_value; - self.numerator * self.invert_sum + self.peek() } } impl MovingAverage for SWMA {} +impl Peekable<::Output> for SWMA { + fn peek(&self) -> ::Output { + self.numerator * self.invert_sum + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/trima.rs b/src/methods/trima.rs index 1dec1ad..8136456 100644 --- a/src/methods/trima.rs +++ b/src/methods/trima.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType}; use crate::core::{Method, MovingAverage}; +use crate::helpers::{Buffered, Peekable}; use crate::methods::SMA; #[cfg(feature = "serde")] @@ -71,6 +72,18 @@ impl Method for TRIMA { impl MovingAverage for TRIMA {} +impl Peekable<::Output> for TRIMA { + fn peek(&self) -> ::Output { + self.sma2.peek() + } +} + +impl Buffered<::Output> for TRIMA { + fn get(&self, index: usize) -> Option<::Output> { + self.sma2.get(index) + } +} + #[cfg(test)] mod tests { use super::{Method, TRIMA as TestingMethod}; diff --git a/src/methods/tsi.rs b/src/methods/tsi.rs index f659571..eb08c3b 100644 --- a/src/methods/tsi.rs +++ b/src/methods/tsi.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType}; +use crate::helpers::Peekable; use crate::methods::EMA; #[cfg(feature = "serde")] @@ -96,8 +97,17 @@ impl Method for TSI { let momentum = value - self.last_value; self.last_value = value; - let numerator = self.ema12.next(&self.ema11.next(&momentum)); - let denominator = self.ema22.next(&self.ema21.next(&momentum.abs())); + self.ema12.next(&self.ema11.next(&momentum)); + self.ema22.next(&self.ema21.next(&momentum.abs())); + + self.peek() + } +} + +impl Peekable<::Output> for TSI { + fn peek(&self) -> ::Output { + let numerator = self.ema12.peek(); + let denominator = self.ema22.peek(); if denominator > 0.0 { numerator / denominator diff --git a/src/methods/vidya.rs b/src/methods/vidya.rs index 53f23a6..c80805d 100644 --- a/src/methods/vidya.rs +++ b/src/methods/vidya.rs @@ -1,4 +1,7 @@ -use crate::core::{Error, Method, MovingAverage, PeriodType, ValueType, Window}; +use crate::{ + core::{Error, Method, MovingAverage, PeriodType, ValueType, Window}, + helpers::Peekable, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -111,6 +114,12 @@ impl Method for Vidya { impl MovingAverage for Vidya {} +impl Peekable<::Output> for Vidya { + fn peek(&self) -> ::Output { + self.last_output + } +} + #[cfg(test)] mod tests { use super::Vidya as TestingMethod; diff --git a/src/methods/volatility.rs b/src/methods/volatility.rs index b0d435b..9815057 100644 --- a/src/methods/volatility.rs +++ b/src/methods/volatility.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -82,6 +83,12 @@ impl Method for LinearVolatility { } } +impl Peekable<::Output> for LinearVolatility { + fn peek(&self) -> ::Output { + self.volatility + } +} + #[cfg(test)] mod tests { use super::{LinearVolatility as TestingMethod, Method}; diff --git a/src/methods/vwma.rs b/src/methods/vwma.rs index 7be81a4..f91ca6f 100644 --- a/src/methods/vwma.rs +++ b/src/methods/vwma.rs @@ -1,5 +1,6 @@ use crate::core::Method; use crate::core::{Error, PeriodType, ValueType, Window}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -79,6 +80,12 @@ impl Method for VWMA { } } +impl Peekable<::Output> for VWMA { + fn peek(&self) -> ::Output { + self.sum / self.vol_sum + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/wma.rs b/src/methods/wma.rs index b042fdb..db0243e 100644 --- a/src/methods/wma.rs +++ b/src/methods/wma.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType, Window}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -92,6 +93,12 @@ impl Method for WMA { impl MovingAverage for WMA {} +impl Peekable<::Output> for WMA { + fn peek(&self) -> ::Output { + self.numerator * self.invert_sum + } +} + #[cfg(test)] #[allow(clippy::suboptimal_flops)] mod tests { diff --git a/src/methods/wsma.rs b/src/methods/wsma.rs index ce3162d..a019b47 100644 --- a/src/methods/wsma.rs +++ b/src/methods/wsma.rs @@ -1,5 +1,6 @@ use crate::core::{Error, PeriodType, ValueType}; use crate::core::{Method, MovingAverage}; +use crate::helpers::Peekable; use crate::methods::EMA; #[cfg(feature = "serde")] @@ -71,6 +72,12 @@ impl Method for WSMA { impl MovingAverage for WSMA {} +impl Peekable<::Output> for WSMA { + fn peek(&self) -> ::Output { + self.0.peek() + } +} + #[cfg(test)] mod tests { use crate::core::Method;