yata/indicators/
aroon.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::core::{Error, Method, PeriodType, ValueType, OHLCV};
5use crate::core::{IndicatorConfig, IndicatorInstance, IndicatorResult};
6use crate::methods::{Cross, HighestIndex, LowestIndex};
7
8// https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/aroon-indicator
9// Aroon-Up = [(Period Specified – Periods Since the Highest High within Period Specified) / Period Specified]
10// Aroon-Down = [(Period Specified – Periods Since the Lowest Low for Period Specified) / Period Specified]
11// If the Aroon-Up crosses above the Aroon-Down, then a new uptrend may start soon. Conversely, if Aroon-Down
12// crosses above the Aroon-Up, then a new downtrend may start soon.
13// When Aroon-Up reaches `1.0`, a new uptrend may have begun. If it remains persistently between `0.7` and `1.0`,
14// and the Aroon-Down remains between 0 and 0.3, then a new uptrend is underway.
15/// Aroon indicator
16///
17/// ## Links
18///
19/// * <https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/aroon-indicator>
20///
21/// # 2 values
22///
23/// * `AroonUp`
24///
25/// Range in \[`0.0`; `1.0`\]
26///
27/// * `AroonDown`
28///
29/// Range in \[`0.0`; `1.0`\]
30///
31/// # 3 signals
32///
33/// * When `AroonUp` crosses `AroonDown` upwards, gives full positive #0 signal.
34///   When `AroonDown` crosses `AroonUp` upwards, gives full negative #0 signal.
35///   Otherwise gives no #0 signal.
36/// * When `AroonUp` rises up to 1.0, gives full positive #1 signal. When `AroonDown` rises up to 1.0, gives full negative #1 signal.
37/// * Gives positive #2 signal when `AroonUp` stays above `(1.0-signal_zone)` and `AroonDown` stays under `signal_zone`.
38///   Gives negative #2 signal when `AroonDown` stays above `(1.0-signal_zone)` and `AroonUp` stays under `signal_zone`.
39#[derive(Debug, Clone, Copy)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub struct Aroon {
42	/// main period length. Default is `14`.
43	///
44	/// Range in *\[`2`; [`PeriodType::MAX`](crate::core::PeriodType)\)*
45	pub period: PeriodType,
46	/// zone value determines when signal #2 appears. Default is `0.3`.
47	///
48	/// Range in *\[`0.0`; `1.0`\]*
49	pub signal_zone: ValueType,
50	/// period until signal #2 appears in full strength. Default is `7`.
51	///
52	/// Range in *\[`1`; [`PeriodType::MAX`](crate::core::PeriodType)\)*
53	pub over_zone_period: PeriodType,
54}
55
56impl IndicatorConfig for Aroon {
57	type Instance = AroonInstance;
58
59	const NAME: &'static str = "Aroon";
60
61	fn init<T: OHLCV>(self, candle: &T) -> Result<Self::Instance, Error> {
62		if !self.validate() {
63			return Err(Error::WrongConfig);
64		}
65
66		let cfg = self;
67
68		Ok(Self::Instance {
69			lowest_index: LowestIndex::new(cfg.period, &candle.low())?,
70			highest_index: HighestIndex::new(cfg.period, &candle.high())?,
71			cross: Cross::default(),
72			uptrend: 0,
73			downtrend: 0,
74			cfg,
75		})
76	}
77
78	fn validate(&self) -> bool {
79		self.signal_zone >= 0.0
80			&& self.signal_zone <= 1.0
81			&& self.period > 1
82			&& self.period < PeriodType::MAX
83			&& self.over_zone_period > 0
84			&& self.over_zone_period < PeriodType::MAX
85	}
86
87	fn set(&mut self, name: &str, value: String) -> Result<(), Error> {
88		match name {
89			"signal_zone" => match value.parse() {
90				Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
91				Ok(value) => self.signal_zone = value,
92			},
93			"over_zone_period" => match value.parse() {
94				Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
95				Ok(value) => self.over_zone_period = value,
96			},
97			"period" => match value.parse() {
98				Err(_) => return Err(Error::ParameterParse(name.to_string(), value.to_string())),
99				Ok(value) => self.period = value,
100			},
101
102			_ => {
103				return Err(Error::ParameterParse(name.to_string(), value));
104			}
105		};
106
107		Ok(())
108	}
109
110	fn size(&self) -> (u8, u8) {
111		(2, 3)
112	}
113}
114
115impl Default for Aroon {
116	fn default() -> Self {
117		Self {
118			signal_zone: 0.3,
119			period: 14,
120			over_zone_period: 7,
121		}
122	}
123}
124
125/// Aroon state structure
126#[derive(Debug, Clone)]
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
128pub struct AroonInstance {
129	cfg: Aroon,
130	lowest_index: LowestIndex,
131	highest_index: HighestIndex,
132	cross: Cross,
133	uptrend: isize,
134	downtrend: isize,
135}
136
137impl IndicatorInstance for AroonInstance {
138	type Config = Aroon;
139
140	// type Input = dyn OHLCV;
141
142	fn config(&self) -> &Self::Config {
143		&self.cfg
144	}
145
146	fn next<T: OHLCV>(&mut self, candle: &T) -> IndicatorResult {
147		let highest_index = self.highest_index.next(&candle.high());
148		let lowest_index = self.lowest_index.next(&candle.low());
149
150		let aroon_up =
151			(self.cfg.period - highest_index) as ValueType / self.cfg.period as ValueType;
152
153		let aroon_down =
154			(self.cfg.period - lowest_index) as ValueType / self.cfg.period as ValueType;
155
156		let trend_signal = self.cross.next(&(aroon_up, aroon_down));
157		let edge_signal = (highest_index == 0) as i8 - (lowest_index == 0) as i8;
158
159		let is_up_over = (aroon_up >= (1.0 - self.cfg.signal_zone)) as isize;
160		let is_up_under = (aroon_up <= self.cfg.signal_zone) as isize;
161		let is_down_over = (aroon_down >= (1.0 - self.cfg.signal_zone)) as isize;
162		let is_down_under = (aroon_down <= self.cfg.signal_zone) as isize;
163
164		self.uptrend = (self.uptrend + 1) * is_up_over * is_down_under;
165		self.downtrend = (self.downtrend + 1) * is_down_over * is_up_under;
166
167		let trend_value =
168			(self.uptrend - self.downtrend) as ValueType / self.cfg.over_zone_period as ValueType;
169
170		IndicatorResult::new(
171			&[aroon_up, aroon_down],
172			&[trend_signal, edge_signal.into(), trend_value.into()],
173		)
174	}
175}