yata/indicators/
ease_of_movement.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use super::HLC;
5use crate::core::{Error, Method, MovingAverageConstructor, PeriodType, Window, OHLCV};
6use crate::core::{IndicatorConfig, IndicatorInstance, IndicatorResult};
7use crate::helpers::MA;
8use crate::methods::Cross;
9
10/// Ease Of Movement
11///
12/// ## Links
13///
14/// * <https://en.wikipedia.org/wiki/Ease_of_movement>
15/// * <https://www.investopedia.com/terms/e/easeofmovement.asp>
16///
17/// # 1 value
18///
19/// * Main value
20///
21/// Range in \(`-inf`; `+inf`\)
22///
23/// # 1 signal
24///
25/// * Signal 1 appears when `main value` crosses zero line.
26/// When `main value` crosses zero line upwards, returns full buy signal.
27/// When `main value` crosses zero line downwards, returns full sell signal.
28#[derive(Debug, Clone, Copy)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct EaseOfMovement<M: MovingAverageConstructor = MA> {
31	/// Main moving average type.
32	///
33	/// Default is [`SMA(13)`](crate::methods::SMA)
34	///
35	/// Period range in \[`2`; [`PeriodType::MAX`](crate::core::PeriodType)\).
36	pub ma: M,
37	/// Differencial period size. Default is `1`.
38	///
39	/// Range in \[`1`; [`PeriodType::MAX`](crate::core::PeriodType)\].
40	pub period2: PeriodType,
41}
42
43impl<M: MovingAverageConstructor> IndicatorConfig for EaseOfMovement<M> {
44	type Instance = EaseOfMovementInstance<M>;
45
46	const NAME: &'static str = "EaseOfMovement";
47
48	fn init<T: OHLCV>(self, candle: &T) -> Result<Self::Instance, Error> {
49		if !self.validate() {
50			return Err(Error::WrongConfig);
51		}
52
53		let cfg = self;
54		Ok(Self::Instance {
55			m1: cfg.ma.init(0.)?, //method(cfg.method, cfg.period1, 0.)?,
56			w: Window::new(cfg.period2, HLC::from(candle)),
57			cross: Cross::new((), &(0.0, 0.0))?,
58
59			cfg,
60		})
61	}
62
63	fn validate(&self) -> bool {
64		self.ma.ma_period() > 1 && self.ma.ma_period() < PeriodType::MAX && self.period2 >= 1
65	}
66
67	fn set(&mut self, name: &str, value: String) -> Result<(), Error> {
68		match name {
69			"ma" => match value.parse() {
70				Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
71				Ok(value) => self.ma = value,
72			},
73			"period2" => match value.parse() {
74				Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
75				Ok(value) => self.period2 = value,
76			},
77
78			_ => {
79				return Err(Error::ParameterParse(name.to_string(), value));
80			}
81		};
82
83		Ok(())
84	}
85
86	fn size(&self) -> (u8, u8) {
87		(1, 1)
88	}
89}
90
91impl Default for EaseOfMovement<MA> {
92	fn default() -> Self {
93		Self {
94			ma: MA::SMA(13),
95			period2: 1,
96		}
97	}
98}
99
100#[derive(Debug, Clone)]
101#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
102pub struct EaseOfMovementInstance<M: MovingAverageConstructor = MA> {
103	cfg: EaseOfMovement<M>,
104
105	m1: M::Instance,
106	w: Window<HLC>,
107	cross: Cross,
108}
109
110impl<M: MovingAverageConstructor> IndicatorInstance for EaseOfMovementInstance<M> {
111	type Config = EaseOfMovement<M>;
112
113	fn config(&self) -> &Self::Config {
114		&self.cfg
115	}
116
117	fn next<T: OHLCV>(&mut self, candle: &T) -> IndicatorResult {
118		let prev_candle = self.w.push(HLC::from(candle));
119
120		let d_high = candle.high() - prev_candle.high();
121		let d_low = candle.low() - prev_candle.low();
122
123		let d = (d_high + d_low) * 0.5;
124
125		let v = if candle.volume() == 0.0 {
126			0.0
127		} else {
128			d * (candle.high() - candle.low()) / candle.volume()
129		};
130
131		debug_assert!(v.is_finite() && !v.is_nan());
132
133		let value = self.m1.next(&v);
134
135		// let signal = if value > 0. {
136		// 	1
137		// } else if value < 0. {
138		// 	-1
139		// } else {
140		// 	0
141		// };
142		let signal = self.cross.next(&(value, 0.0));
143
144		IndicatorResult::new(&[value], &[signal])
145	}
146}