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#[derive(Debug, Clone, Copy)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub struct Aroon {
42 pub period: PeriodType,
46 pub signal_zone: ValueType,
50 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#[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 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}