yata/methods/
trima.rs

1use crate::core::{Error, PeriodType, ValueType};
2use crate::core::{Method, MovingAverage};
3use crate::helpers::{Buffered, Peekable};
4use crate::methods::SMA;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// Triangular Moving Average of specified `length` for timeseries of type [`ValueType`]
10///
11/// # Parameters
12///
13/// Has a single parameter `length`: [`PeriodType`]
14///
15/// `length` should be > `0`
16///
17/// # Input type
18///
19/// Input type is [`ValueType`]
20///
21/// # Output type
22///
23/// Output type is [`ValueType`]
24///
25/// # Examples
26///
27/// ```
28/// use yata::prelude::*;
29/// use yata::methods::TRIMA;
30///
31/// // TRIMA of length=3
32/// let mut trima = TRIMA::new(4, &1.0).unwrap();
33///
34/// trima.next(&1.0);
35/// trima.next(&2.0);
36///
37/// assert_eq!(trima.next(&3.0), 1.25);
38/// assert_eq!(trima.next(&4.0), 1.625);
39/// ```
40///
41/// # Performance
42///
43/// O(1)
44///
45/// [`ValueType`]: crate::core::ValueType
46/// [`PeriodType`]: crate::core::PeriodType
47
48#[derive(Debug, Clone)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50pub struct TRIMA {
51	sma1: SMA,
52	sma2: SMA,
53}
54
55impl Method for TRIMA {
56	type Params = PeriodType;
57	type Input = ValueType;
58	type Output = Self::Input;
59
60	fn new(length: Self::Params, value: &Self::Input) -> Result<Self, Error> {
61		Ok(Self {
62			sma1: SMA::new(length, value)?,
63			sma2: SMA::new(length, value)?,
64		})
65	}
66
67	#[inline]
68	fn next(&mut self, value: &Self::Input) -> Self::Output {
69		self.sma2.next(&self.sma1.next(value))
70	}
71}
72
73impl MovingAverage for TRIMA {}
74
75impl Peekable<<Self as Method>::Output> for TRIMA {
76	fn peek(&self) -> <Self as Method>::Output {
77		self.sma2.peek()
78	}
79}
80
81impl Buffered<<Self as Method>::Output> for TRIMA {
82	fn get(&self, index: usize) -> Option<<Self as Method>::Output> {
83		self.sma2.get(index)
84	}
85}
86
87#[cfg(test)]
88mod tests {
89	use super::{Method, TRIMA as TestingMethod};
90	use crate::core::ValueType;
91	use crate::helpers::{assert_eq_float, RandomCandles};
92	use crate::methods::tests::test_const;
93
94	#[test]
95	fn test_trima_const() {
96		for i in 1..255 {
97			let input = (i as ValueType + 56.0) / 16.3251;
98			let mut method = TestingMethod::new(i, &input).unwrap();
99
100			let output = method.next(&input);
101			test_const(&mut method, &input, &output);
102		}
103	}
104
105	#[test]
106	fn test_trima1() {
107		let mut candles = RandomCandles::default();
108
109		let mut ma = TestingMethod::new(1, &candles.first().close).unwrap();
110
111		candles.take(100).for_each(|x| {
112			assert_eq_float(x.close, ma.next(&x.close));
113		});
114	}
115
116	#[test]
117	fn test_trima() {
118		let candles = RandomCandles::default();
119
120		let src: Vec<ValueType> = candles.take(300).map(|x| x.close).collect();
121
122		(1..255).for_each(|sma_length| {
123			let mut ma = TestingMethod::new(sma_length, &src[0]).unwrap();
124			let mut level2 = Vec::new();
125
126			src.iter().enumerate().for_each(|(i, x)| {
127				let value = ma.next(x);
128				let slice_from = i.saturating_sub((sma_length - 1) as usize);
129				let slice_to = i;
130				let slice = &src[slice_from..=slice_to];
131
132				let mut sum: ValueType = slice.iter().sum();
133				if slice.len() < sma_length as usize {
134					sum += (sma_length as usize - slice.len()) as ValueType * src.first().unwrap();
135				}
136
137				level2.push(sum / sma_length as ValueType);
138
139				let mut sum: ValueType = level2.iter().rev().take(sma_length as usize).sum();
140				if level2.len() < sma_length as usize {
141					sum += (sma_length as usize - level2.len()) as ValueType * src.first().unwrap();
142				}
143
144				assert_eq_float(sum / sma_length as ValueType, value);
145			});
146		});
147	}
148}