Skip to content

Commit

Permalink
[Issue 19] Last value peeking / output values history access (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
amv-dev authored Jan 29, 2022
1 parent fa77258 commit fc47df6
Show file tree
Hide file tree
Showing 29 changed files with 428 additions and 24 deletions.
25 changes: 25 additions & 0 deletions src/core/method.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{Error, Sequence};
use crate::helpers::{WithHistory, WithLastValue};
use std::fmt;

type BoxedFnMethod<'a, M> = Box<dyn FnMut(&'a <M as Method>::Input) -> <M as Method>::Output>;
Expand Down Expand Up @@ -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<WithHistory<Self, Self::Output>, 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<WithLastValue<Self, Self::Output>, 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::<Self>().split("::");
Expand Down
28 changes: 21 additions & 7 deletions src/core/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,24 @@ impl<T> Window<T> {
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<PeriodType> {
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<T> AsRef<[T]> for Window<T> {
Expand All @@ -286,13 +304,9 @@ impl<T> std::ops::Index<PeriodType> for Window<T> {
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) }
Expand Down
109 changes: 109 additions & 0 deletions src/helpers/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::prelude::{Error, Method};

/// Trait for picking the very last value for methods and indicators
pub trait Peekable<V> {
/// 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<V> {
/// Picks value at `index` position, starting from the newest value
fn get(&self, index: usize) -> Option<V>;
}

/// Wrapper for holding historical data
#[derive(Debug, Clone)]
pub struct WithHistory<T: ?Sized, V> {
history: Vec<V>,
instance: T,
}

impl<T: ?Sized, V: Clone> WithHistory<T, V> {
/// Picks value at `index` position, starting from the newest value
pub fn get(&self, index: usize) -> Option<V> {
Buffered::get(self, index)
}
}

impl<T: ?Sized, V: Clone> Buffered<V> for WithHistory<T, V> {
fn get(&self, index: usize) -> Option<V> {
let index = self.history.len().checked_sub(index + 1)?;
self.history.get(index).cloned()
}
}

impl<T> Method for WithHistory<T, T::Output>
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<Self, Error> {
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<T: ?Sized, V> {
last_value: V,
instance: T,
}

impl<T> Method for WithLastValue<T, T::Output>
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<Self, Error> {
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<T, V: Clone> Peekable<V> for WithLastValue<T, V> {
fn peek(&self) -> V {
self.last_value.clone()
}
}

impl<V: Clone, T: Peekable<V>> Peekable<V> for &T {
fn peek(&self) -> V {
(*self).peek()
}
}

impl<V: Clone, T: Buffered<V>> Buffered<V> for &T {
fn get(&self, index: usize) -> Option<V> {
(*self).get(index)
}
}
3 changes: 3 additions & 0 deletions src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
7 changes: 7 additions & 0 deletions src/methods/adi.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -113,6 +114,12 @@ impl Method for ADI {
self.cmf_sum -= self.window.push(clvv);
}

self.peek()
}
}

impl Peekable<<Self as Method>::Output> for ADI {
fn peek(&self) -> <Self as Method>::Output {
self.cmf_sum
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/methods/cci.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/methods/conv.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<<Self as Method>::Output> for Conv {
fn peek(&self) -> <Self as Method>::Output {
self.window
.iter()
.zip(self.weights.iter().rev())
Expand Down
50 changes: 44 additions & 6 deletions src/methods/ema.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -77,6 +78,12 @@ impl Method for EMA {

impl MovingAverage for EMA {}

impl Peekable<<Self as Method>::Output> for EMA {
fn peek(&self) -> <Self as Method>::Output {
self.value
}
}

/// Simple shortcut for [EMA] over [EMA]
///
/// # See also
Expand Down Expand Up @@ -112,6 +119,12 @@ impl Method for DMA {

impl MovingAverage for DMA {}

impl Peekable<<Self as Method>::Output> for DMA {
fn peek(&self) -> <Self as Method>::Output {
self.dma.value
}
}

/// Simple shortcut for [EMA] over [EMA] over [EMA] (or [EMA] over [DMA], or [DMA] over [EMA])
///
/// # See also
Expand Down Expand Up @@ -147,6 +160,12 @@ impl Method for TMA {

impl MovingAverage for TMA {}

impl Peekable<<Self as Method>::Output> for TMA {
fn peek(&self) -> <Self as Method>::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
Expand Down Expand Up @@ -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<<Self as Method>::Output> for DEMA {
fn peek(&self) -> <Self as Method>::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
Expand Down Expand Up @@ -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<<Self as Method>::Output> for TEMA {
fn peek(&self) -> <Self as Method>::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 {
Expand Down
Loading

0 comments on commit fc47df6

Please sign in to comment.