Skip to main content

finance_query/indicators/
aroon.rs

1//! Aroon indicator.
2
3use std::collections::VecDeque;
4
5use super::{IndicatorError, Result};
6use serde::{Deserialize, Serialize};
7
8/// Result of Aroon calculation
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct AroonResult {
11    /// Aroon Up line
12    pub aroon_up: Vec<Option<f64>>,
13    /// Aroon Down line
14    pub aroon_down: Vec<Option<f64>>,
15}
16
17/// Calculate Aroon Indicator.
18///
19/// Aroon Up = ((period - periods since highest high) / period) * 100
20/// Aroon Down = ((period - periods since lowest low) / period) * 100
21///
22/// # Arguments
23///
24/// * `highs` - High prices
25/// * `lows` - Low prices
26/// * `period` - Number of periods
27///
28/// # Example
29///
30/// ```
31/// use finance_query::indicators::aroon;
32///
33/// let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
34/// let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
35/// let result = aroon(&highs, &lows, 3).unwrap();
36/// ```
37pub fn aroon(highs: &[f64], lows: &[f64], period: usize) -> Result<AroonResult> {
38    if period == 0 {
39        return Err(IndicatorError::InvalidPeriod(
40            "Period must be greater than 0".to_string(),
41        ));
42    }
43    let len = highs.len();
44    if lows.len() != len {
45        return Err(IndicatorError::InvalidPeriod(
46            "Data lengths must match".to_string(),
47        ));
48    }
49    if len < period {
50        return Err(IndicatorError::InsufficientData {
51            need: period,
52            got: len,
53        });
54    }
55
56    let mut aroon_up = vec![None; len];
57    let mut aroon_down = vec![None; len];
58
59    // Monotonic deques for O(N) sliding window argmax/argmin instead of O(N * period)
60    // Pop back on <= (highs) / >= (lows) so newest index wins on ties, matching original.
61    let mut max_deque: VecDeque<usize> = VecDeque::new();
62    let mut min_deque: VecDeque<usize> = VecDeque::new();
63    let period_f = period as f64;
64
65    for i in 0..len {
66        while max_deque.front().is_some_and(|&j| j + period <= i) {
67            max_deque.pop_front();
68        }
69        while min_deque.front().is_some_and(|&j| j + period <= i) {
70            min_deque.pop_front();
71        }
72        while max_deque.back().is_some_and(|&j| highs[j] <= highs[i]) {
73            max_deque.pop_back();
74        }
75        while min_deque.back().is_some_and(|&j| lows[j] >= lows[i]) {
76            min_deque.pop_back();
77        }
78        max_deque.push_back(i);
79        min_deque.push_back(i);
80
81        if i + 1 >= period {
82            let high_idx = *max_deque.front().unwrap();
83            let low_idx = *min_deque.front().unwrap();
84            let periods_since_high = i - high_idx;
85            let periods_since_low = i - low_idx;
86            aroon_up[i] = Some(((period_f - periods_since_high as f64) / period_f) * 100.0);
87            aroon_down[i] = Some(((period_f - periods_since_low as f64) / period_f) * 100.0);
88        }
89    }
90
91    Ok(AroonResult {
92        aroon_up,
93        aroon_down,
94    })
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_aroon() {
103        let highs = vec![10.0, 11.0, 12.0, 11.0, 10.0];
104        let lows = vec![8.0, 9.0, 10.0, 9.0, 8.0];
105        let result = aroon(&highs, &lows, 3).unwrap();
106
107        assert_eq!(result.aroon_up.len(), 5);
108        assert!(result.aroon_up[0].is_none());
109        assert!(result.aroon_up[1].is_none());
110        assert!(result.aroon_up[2].is_some());
111    }
112}