ta_lib_in_rust/indicators/volatility/
trange.rs

1use polars::prelude::*;
2
3/// Calculates True Range (TRANGE)
4/// True Range is the greatest of the following:
5/// 1. Current High - Current Low
6/// 2. |Current High - Previous Close|
7/// 3. |Current Low - Previous Close|
8///
9/// # Arguments
10///
11/// * `df` - DataFrame containing the price data
12///
13/// # Returns
14///
15/// Returns a PolarsResult containing the True Range Series
16pub fn calculate_trange(df: &DataFrame) -> PolarsResult<Series> {
17    // Check required columns
18    if !df.schema().contains("high")
19        || !df.schema().contains("low")
20        || !df.schema().contains("close")
21    {
22        return Err(PolarsError::ShapeMismatch(
23            "DataFrame must contain 'high', 'low', and 'close' columns for TRANGE calculation"
24                .into(),
25        ));
26    }
27
28    let high = df.column("high")?.f64()?;
29    let low = df.column("low")?.f64()?;
30    let close = df.column("close")?.f64()?;
31    let prev_close = close.shift(1);
32
33    let mut tr_values = Vec::with_capacity(df.height());
34
35    // For the first value, True Range is simply High - Low
36    // (since we don't have a previous close)
37    let first_tr = {
38        let h = high.get(0).unwrap_or(f64::NAN);
39        let l = low.get(0).unwrap_or(f64::NAN);
40
41        if h.is_nan() || l.is_nan() {
42            f64::NAN
43        } else {
44            h - l
45        }
46    };
47    tr_values.push(first_tr);
48
49    // Calculate True Range for the rest of the data points
50    for i in 1..df.height() {
51        let h = high.get(i).unwrap_or(f64::NAN);
52        let l = low.get(i).unwrap_or(f64::NAN);
53        let pc = prev_close.get(i).unwrap_or(f64::NAN);
54
55        if h.is_nan() || l.is_nan() || pc.is_nan() {
56            tr_values.push(f64::NAN);
57        } else {
58            let range1 = h - l;
59            let range2 = (h - pc).abs();
60            let range3 = (l - pc).abs();
61
62            let tr = range1.max(range2).max(range3);
63            tr_values.push(tr);
64        }
65    }
66
67    Ok(Series::new("trange".into(), tr_values))
68}