yata/methods/
heikin_ashi.rs

1use crate::core::{Candle, Error, Method, ValueType, OHLCV};
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// Converts default `OHLCV`s into [Heikin Ashi](https://en.wikipedia.org/wiki/Candlestick_chart#Heikin-Ashi_candlesticks) `OHLCV`s
7#[derive(Debug, Clone, Copy)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub struct HeikinAshi {
10	next_open: ValueType,
11}
12
13impl Method for HeikinAshi {
14	type Params = ();
15	type Input = dyn OHLCV;
16	type Output = Candle;
17
18	fn new((): Self::Params, value: &Self::Input) -> Result<Self, Error> {
19		// It is not so obvious how we should correctly initialize first candle
20		// The reason why `ohlc4` is used is because it's the only way we can achieve stable results
21		// when constant input value is provided, which is one of requiremets for all the methods
22		Ok(Self {
23			next_open: value.ohlc4(),
24		})
25	}
26
27	#[inline]
28	fn next(&mut self, value: &Self::Input) -> Self::Output {
29		let open = self.next_open;
30		let close = value.ohlc4();
31
32		self.next_open = (open + close) * 0.5;
33
34		Candle {
35			open,
36			high: value.high().max(open),
37			low: value.low().min(open),
38			close,
39			volume: value.volume(),
40		}
41	}
42}
43
44#[cfg(test)]
45mod tests {
46	use super::{HeikinAshi, OHLCV};
47	use crate::core::{Candle, Method, ValueType};
48	use crate::helpers::{assert_eq_float, RandomCandles};
49
50	#[test]
51	#[allow(clippy::inspect_for_each)]
52	fn test_heikin_ashi() {
53		let mut candles = RandomCandles::default();
54
55		let first = candles.first();
56		let mut heikin_ashi = HeikinAshi::new((), &first).unwrap();
57
58		let mut prev = Candle {
59			open: first.ohlc4(),
60			high: ValueType::NAN,
61			low: ValueType::NAN,
62			close: first.ohlc4(),
63			volume: ValueType::NAN,
64		};
65
66		candles
67			.take(100)
68			.map(|candle| {
69				let open = (prev.open() + prev.close()) / 2.0;
70				let close = (candle.open() + candle.high() + candle.low() + candle.close()) / 4.0;
71
72				let tested = Candle {
73					open,
74					high: candle.high().max(open).max(close),
75					low: candle.low().min(open).min(close),
76					close,
77					..candle
78				};
79
80				prev = tested;
81
82				(tested, heikin_ashi.next(&candle))
83			})
84			.inspect(|(original, ha)| assert_eq_float(original.close(), ha.close()))
85			.inspect(|(original, ha)| assert_eq_float(original.high(), ha.high()))
86			.inspect(|(original, ha)| assert_eq_float(original.low(), ha.low()))
87			.inspect(|(original, ha)| assert_eq_float(original.close(), ha.close()))
88			.for_each(|(original, ha)| assert_eq_float(original.volume(), ha.volume()));
89	}
90}