nautilus_indicators/momentum/
aroon.rs1use std::{
17 collections::VecDeque,
18 fmt::{Debug, Display},
19};
20
21use nautilus_model::{
22 data::{Bar, QuoteTick, TradeTick},
23 enums::PriceType,
24};
25
26use crate::indicator::Indicator;
27
28#[repr(C)]
31#[derive(Debug)]
32#[cfg_attr(
33 feature = "python",
34 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")
35)]
36pub struct AroonOscillator {
37 pub period: usize,
38 pub high_inputs: VecDeque<f64>,
39 pub low_inputs: VecDeque<f64>,
40 pub aroon_up: f64,
41 pub aroon_down: f64,
42 pub value: f64,
43 pub count: usize,
44 pub initialized: bool,
45 has_inputs: bool,
46}
47
48impl Display for AroonOscillator {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(f, "{}({})", self.name(), self.period)
51 }
52}
53
54impl Indicator for AroonOscillator {
55 fn name(&self) -> String {
56 stringify!(AroonOscillator).to_string()
57 }
58
59 fn has_inputs(&self) -> bool {
60 self.has_inputs
61 }
62
63 fn initialized(&self) -> bool {
64 self.initialized
65 }
66
67 fn handle_quote(&mut self, quote: &QuoteTick) {
68 let price = quote.extract_price(PriceType::Mid).into();
69 self.update_raw(price, price);
70 }
71
72 fn handle_trade(&mut self, trade: &TradeTick) {
73 let price = trade.price.into();
74 self.update_raw(price, price);
75 }
76
77 fn handle_bar(&mut self, bar: &Bar) {
78 self.update_raw((&bar.close).into(), (&bar.close).into());
79 }
80
81 fn reset(&mut self) {
82 self.high_inputs.clear();
83 self.low_inputs.clear();
84 self.aroon_up = 0.0;
85 self.aroon_down = 0.0;
86 self.value = 0.0;
87 self.count = 0;
88 self.has_inputs = false;
89 self.initialized = false;
90 }
91}
92
93impl AroonOscillator {
94 #[must_use]
96 pub fn new(period: usize) -> Self {
97 Self {
98 period,
99 high_inputs: VecDeque::with_capacity(period),
100 low_inputs: VecDeque::with_capacity(period),
101 aroon_up: 0.0,
102 aroon_down: 0.0,
103 value: 0.0,
104 count: 0,
105 has_inputs: false,
106 initialized: false,
107 }
108 }
109
110 pub fn update_raw(&mut self, high: f64, low: f64) {
111 if self.high_inputs.len() == self.period {
112 self.high_inputs.pop_back();
113 }
114 if self.low_inputs.len() == self.period {
115 self.low_inputs.pop_back();
116 }
117
118 self.high_inputs.push_front(high);
119 self.low_inputs.push_front(low);
120
121 self.increment_count();
122 if self.initialized {
123 self.calculate_aroon();
125 }
126 }
127
128 fn calculate_aroon(&mut self) {
129 let periods_since_high = self
130 .high_inputs
131 .iter()
132 .enumerate()
133 .fold((0, f64::MIN), |(max_idx, max_val), (idx, &val)| {
134 if val > max_val {
135 (idx, val)
136 } else {
137 (max_idx, max_val)
138 }
139 })
140 .0;
141
142 let periods_since_low = self
143 .low_inputs
144 .iter()
145 .enumerate()
146 .fold((0, f64::MAX), |(min_idx, min_val), (idx, &val)| {
147 if val < min_val {
148 (idx, val)
149 } else {
150 (min_idx, min_val)
151 }
152 })
153 .0;
154
155 self.aroon_up = 100.0 * ((self.period - periods_since_high) as f64 / self.period as f64);
156 self.aroon_down = 100.0 * ((self.period - periods_since_low) as f64 / self.period as f64);
157 self.value = self.aroon_up - self.aroon_down;
158 }
159
160 const fn increment_count(&mut self) {
161 self.count += 1;
162
163 if !self.initialized {
164 self.has_inputs = true;
165 if self.count >= self.period {
166 self.initialized = true;
167 }
168 }
169 }
170}
171
172#[cfg(test)]
176mod tests {
177 use rstest::rstest;
178
179 use super::*;
180 use crate::indicator::Indicator;
181
182 #[rstest]
183 fn test_name_returns_expected_string() {
184 let aroon = AroonOscillator::new(10);
185 assert_eq!(aroon.name(), "AroonOscillator");
186 }
187
188 #[rstest]
189 fn test_period() {
190 let aroon = AroonOscillator::new(10);
191 assert_eq!(aroon.period, 10);
192 }
193
194 #[rstest]
195 fn test_initialized_without_inputs_returns_false() {
196 let aroon = AroonOscillator::new(10);
197 assert!(!aroon.initialized());
198 }
199
200 #[rstest]
201 fn test_initialized_with_required_inputs_returns_true() {
202 let mut aroon = AroonOscillator::new(10);
203 for _ in 0..20 {
204 aroon.update_raw(110.08, 109.61);
205 }
206 assert!(aroon.initialized());
207 }
208
209 #[rstest]
210 fn test_value_with_one_input() {
211 let mut aroon = AroonOscillator::new(1);
212 aroon.update_raw(110.08, 109.61);
213 assert_eq!(aroon.aroon_up, 100.0);
214 assert_eq!(aroon.aroon_down, 100.0);
215 assert_eq!(aroon.value, 0.0);
216 }
217
218 #[rstest]
219 fn test_value_with_twenty_inputs() {
220 let mut aroon = AroonOscillator::new(20);
221 let inputs = [
222 (110.08, 109.61),
223 (110.15, 109.91),
224 (110.1, 109.73),
225 (110.06, 109.77),
226 (110.29, 109.88),
227 (110.53, 110.29),
228 (110.61, 110.26),
229 (110.28, 110.17),
230 (110.3, 110.0),
231 (110.25, 110.01),
232 (110.25, 109.81),
233 (109.92, 109.71),
234 (110.21, 109.84),
235 (110.08, 109.95),
236 (110.2, 109.96),
237 (110.16, 109.95),
238 (109.99, 109.75),
239 (110.2, 109.73),
240 (110.1, 109.81),
241 (110.04, 109.96),
242 ];
243 for &(high, low) in &inputs {
244 aroon.update_raw(high, low);
245 }
246 assert_eq!(aroon.aroon_up, 35.0);
247 assert_eq!(aroon.aroon_down, 5.0);
248 assert_eq!(aroon.value, 30.0);
249 }
250
251 #[rstest]
252 fn test_reset_successfully_returns_indicator_to_fresh_state() {
253 let mut aroon = AroonOscillator::new(10);
254 for _ in 0..1000 {
255 aroon.update_raw(110.08, 109.61);
256 }
257 aroon.reset();
258 assert!(!aroon.initialized());
259 assert_eq!(aroon.aroon_up, 0.0);
260 assert_eq!(aroon.aroon_down, 0.0);
261 assert_eq!(aroon.value, 0.0);
262 }
263}