quantwave_core/indicators/incremental/
trange.rs1use crate::indicators::incremental::ta_atr::TaATR;
4use crate::traits::Next;
5
6#[derive(Debug, Clone, Default)]
8#[allow(non_camel_case_types)]
9pub struct TaTRANGE {
10 prev_close: Option<f64>,
11 bars_seen: usize,
12}
13
14impl TaTRANGE {
15 pub fn new() -> Self {
16 Self::default()
17 }
18}
19
20impl Next<(f64, f64, f64)> for TaTRANGE {
21 type Output = f64;
22
23 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
24 if self.bars_seen == 0 {
25 self.prev_close = Some(close);
26 self.bars_seen = 1;
27 return f64::NAN;
28 }
29 let pc = self.prev_close.unwrap();
30 let hl = high - low;
31 let hc = (high - pc).abs();
32 let lc = (low - pc).abs();
33 self.prev_close = Some(close);
34 self.bars_seen += 1;
35 hl.max(hc).max(lc)
36 }
37}
38
39#[derive(Debug, Clone)]
41#[allow(non_camel_case_types)]
42pub struct TaNATR {
43 pub timeperiod: usize,
44 atr: TaATR,
45}
46
47impl TaNATR {
48 pub fn new(timeperiod: usize) -> Self {
49 Self {
50 timeperiod,
51 atr: TaATR::new(timeperiod),
52 }
53 }
54}
55
56impl Next<(f64, f64, f64)> for TaNATR {
57 type Output = f64;
58
59 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
60 let atr = self.atr.next((high, low, close));
61 if atr.is_nan() {
62 return f64::NAN;
63 }
64 if close == 0.0 {
65 0.0
66 } else {
67 (atr / close) * 100.0
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use proptest::prelude::*;
76
77 proptest! {
78 #[test]
79 fn test_ta_trange_parity(
80 highs in prop::collection::vec(1.0..100.0, 1..100),
81 lows in prop::collection::vec(1.0..100.0, 1..100),
82 closes in prop::collection::vec(1.0..100.0, 1..100)
83 ) {
84 let len = highs.len().min(lows.len()).min(closes.len());
85 if len < 3 { return Ok(()); }
86 let mut high = Vec::with_capacity(len);
87 let mut low = Vec::with_capacity(len);
88 let mut close = Vec::with_capacity(len);
89 for i in 0..len {
90 let hi: f64 = highs[i];
91 let lo: f64 = lows[i];
92 let cl: f64 = closes[i];
93 high.push(hi.max(lo).max(cl));
94 low.push(hi.min(lo).min(cl));
95 close.push(cl);
96 }
97 let mut tr = TaTRANGE::new();
98 let streaming: Vec<f64> = (0..len).map(|i| tr.next((high[i], low[i], close[i]))).collect();
99 let batch = talib_rs::volatility::trange(&high, &low, &close)
100 .unwrap_or_else(|_| vec![f64::NAN; len]);
101 for (s, b) in streaming.iter().zip(batch.iter()) {
102 if s.is_nan() { assert!(b.is_nan()); }
103 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
104 }
105 }
106
107 #[test]
108 fn test_ta_natr_parity(
109 highs in prop::collection::vec(1.0..100.0, 1..100),
110 lows in prop::collection::vec(1.0..100.0, 1..100),
111 closes in prop::collection::vec(1.0..100.0, 1..100)
112 ) {
113 let len = highs.len().min(lows.len()).min(closes.len());
114 if len < 20 { return Ok(()); }
115 let mut high = Vec::with_capacity(len);
116 let mut low = Vec::with_capacity(len);
117 let mut close = Vec::with_capacity(len);
118 for i in 0..len {
119 let hi: f64 = highs[i];
120 let lo: f64 = lows[i];
121 let cl: f64 = closes[i];
122 high.push(hi.max(lo).max(cl));
123 low.push(hi.min(lo).min(cl));
124 close.push(cl);
125 }
126 let period = 14;
127 let mut natr = TaNATR::new(period);
128 let streaming: Vec<f64> = (0..len).map(|i| natr.next((high[i], low[i], close[i]))).collect();
129 let batch = talib_rs::volatility::natr(&high, &low, &close, period)
130 .unwrap_or_else(|_| vec![f64::NAN; len]);
131 for (s, b) in streaming.iter().zip(batch.iter()) {
132 if s.is_nan() { assert!(b.is_nan()); }
133 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
134 }
135 }
136 }
137}