Skip to main content

finance_query/indicators/
roc.rs

1//! Rate of Change (ROC) indicator.
2
3use super::{IndicatorError, Result};
4
5/// Calculate Rate of Change (ROC).
6///
7/// Percentage change over period.
8/// ROC = ((Price - Price[n periods ago]) / Price[n periods ago]) * 100
9///
10/// # Arguments
11///
12/// * `data` - Price data (typically close prices)
13/// * `period` - Number of periods
14///
15/// # Example
16///
17/// ```
18/// use finance_query::indicators::roc;
19///
20/// let prices = vec![10.0, 11.0, 12.0, 13.0];
21/// let result = roc(&prices, 2).unwrap();
22/// ```
23pub fn roc(data: &[f64], period: usize) -> Result<Vec<Option<f64>>> {
24    if period == 0 {
25        return Err(IndicatorError::InvalidPeriod(
26            "Period must be greater than 0".to_string(),
27        ));
28    }
29    if data.len() <= period {
30        return Err(IndicatorError::InsufficientData {
31            need: period + 1,
32            got: data.len(),
33        });
34    }
35
36    let mut result = vec![None; data.len()];
37
38    for i in period..data.len() {
39        let current = data[i];
40        let past = data[i - period];
41
42        if past != 0.0 {
43            let roc_val = ((current - past) / past) * 100.0;
44            result[i] = Some(roc_val);
45        } else {
46            result[i] = None;
47        }
48    }
49
50    Ok(result)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_roc() {
59        let prices = vec![10.0, 11.0, 12.0, 13.0];
60        let result = roc(&prices, 2).unwrap();
61
62        assert_eq!(result.len(), 4);
63        assert!(result[0].is_none());
64        assert!(result[1].is_none());
65
66        // i=2: (12-10)/10 * 100 = 20
67        assert_eq!(result[2], Some(20.0));
68        // i=3: (13-11)/11 * 100 = 18.18...
69        assert!(result[3].is_some());
70    }
71}