Skip to main content

finance_query/indicators/
aroon.rs

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