Skip to main content

finance_query/indicators/
true_range.rs

1//! True Range indicator.
2
3use super::{IndicatorError, Result};
4
5/// Calculate True Range.
6///
7/// TR = max(high - low, |high - prev_close|, |low - prev_close|)
8///
9/// # Arguments
10///
11/// * `highs` - High prices
12/// * `lows` - Low prices
13/// * `closes` - Close prices
14///
15/// # Example
16///
17/// ```
18/// use finance_query::indicators::true_range;
19///
20/// let highs = vec![10.0, 11.0, 12.0];
21/// let lows = vec![8.0, 9.0, 10.0];
22/// let closes = vec![9.0, 10.0, 11.0];
23/// let result = true_range(&highs, &lows, &closes).unwrap();
24/// ```
25pub fn true_range(highs: &[f64], lows: &[f64], closes: &[f64]) -> Result<Vec<Option<f64>>> {
26    let len = highs.len();
27    if lows.len() != len || closes.len() != len {
28        return Err(IndicatorError::InvalidPeriod(
29            "Data lengths must match".to_string(),
30        ));
31    }
32    if len < 2 {
33        return Err(IndicatorError::InsufficientData { need: 2, got: len });
34    }
35
36    let mut result = vec![None; len];
37
38    result[0] = Some(highs[0] - lows[0]);
39
40    for i in 1..len {
41        let high_low = highs[i] - lows[i];
42        let high_close = (highs[i] - closes[i - 1]).abs();
43        let low_close = (lows[i] - closes[i - 1]).abs();
44        let tr = high_low.max(high_close).max(low_close);
45        result[i] = Some(tr);
46    }
47
48    Ok(result)
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_true_range() {
57        let highs = vec![10.0, 11.0, 12.0];
58        let lows = vec![8.0, 9.0, 10.0];
59        let closes = vec![9.0, 10.0, 11.0];
60        let result = true_range(&highs, &lows, &closes).unwrap();
61
62        assert_eq!(result.len(), 3);
63        assert!(result[0].is_some());
64        assert!(result[1].is_some());
65        assert!(result[2].is_some());
66    }
67}