quantwave_core/indicators/incremental/
ultosc.rs1use crate::traits::Next;
4
5#[derive(Debug, Clone)]
7#[allow(non_camel_case_types)]
8pub struct ULTOSC {
9 pub timeperiod1: usize,
10 pub timeperiod2: usize,
11 pub timeperiod3: usize,
12 max_period: usize,
13 prev_close: Option<f64>,
14 bp: Vec<f64>,
15 tr: Vec<f64>,
16 sum_bp1: f64,
17 sum_tr1: f64,
18 sum_bp2: f64,
19 sum_tr2: f64,
20 sum_bp3: f64,
21 sum_tr3: f64,
22 initialized: bool,
23}
24
25impl ULTOSC {
26 pub fn new(timeperiod1: usize, timeperiod2: usize, timeperiod3: usize) -> Self {
27 let max_period = timeperiod1.max(timeperiod2).max(timeperiod3);
28 Self {
29 timeperiod1,
30 timeperiod2,
31 timeperiod3,
32 max_period,
33 prev_close: None,
34 bp: vec![0.0],
35 tr: vec![0.0],
36 sum_bp1: 0.0,
37 sum_tr1: 0.0,
38 sum_bp2: 0.0,
39 sum_tr2: 0.0,
40 sum_bp3: 0.0,
41 sum_tr3: 0.0,
42 initialized: false,
43 }
44 }
45
46 #[inline]
47 fn ultosc_value(&self) -> f64 {
48 let avg1 = if self.sum_tr1 > 0.0 {
49 self.sum_bp1 / self.sum_tr1
50 } else {
51 0.0
52 };
53 let avg2 = if self.sum_tr2 > 0.0 {
54 self.sum_bp2 / self.sum_tr2
55 } else {
56 0.0
57 };
58 let avg3 = if self.sum_tr3 > 0.0 {
59 self.sum_bp3 / self.sum_tr3
60 } else {
61 0.0
62 };
63 100.0 * (4.0 * avg1 + 2.0 * avg2 + avg3) / 7.0
64 }
65
66 fn init_sums(&mut self, i: usize) {
67 let p1 = self.timeperiod1;
68 let p2 = self.timeperiod2;
69 let p3 = self.timeperiod3;
70 self.sum_bp1 = self.bp[(i + 1 - p1)..=i].iter().sum();
71 self.sum_tr1 = self.tr[(i + 1 - p1)..=i].iter().sum();
72 self.sum_bp2 = self.bp[(i + 1 - p2)..=i].iter().sum();
73 self.sum_tr2 = self.tr[(i + 1 - p2)..=i].iter().sum();
74 self.sum_bp3 = self.bp[(i + 1 - p3)..=i].iter().sum();
75 self.sum_tr3 = self.tr[(i + 1 - p3)..=i].iter().sum();
76 self.initialized = true;
77 }
78
79 fn slide_sums(&mut self, i: usize) {
80 let p1 = self.timeperiod1;
81 let p2 = self.timeperiod2;
82 let p3 = self.timeperiod3;
83 self.sum_bp1 += self.bp[i] - self.bp[i - p1];
84 self.sum_tr1 += self.tr[i] - self.tr[i - p1];
85 self.sum_bp2 += self.bp[i] - self.bp[i - p2];
86 self.sum_tr2 += self.tr[i] - self.tr[i - p2];
87 self.sum_bp3 += self.bp[i] - self.bp[i - p3];
88 self.sum_tr3 += self.tr[i] - self.tr[i - p3];
89 }
90}
91
92impl Next<(f64, f64, f64)> for ULTOSC {
93 type Output = f64;
94
95 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
96 let max_period = self.max_period;
97
98 let Some(prev_close) = self.prev_close else {
99 self.prev_close = Some(close);
100 return f64::NAN;
101 };
102
103 let true_low = low.min(prev_close);
104 let true_high = high.max(prev_close);
105 let bp_val = close - true_low;
106 let tr_val = true_high - true_low;
107
108 self.bp.push(bp_val);
109 self.tr.push(tr_val);
110 self.prev_close = Some(close);
111
112 let i = self.bp.len() - 1;
113 if i < max_period {
114 return f64::NAN;
115 }
116
117 if !self.initialized {
118 self.init_sums(i);
119 return self.ultosc_value();
120 }
121
122 self.slide_sums(i);
123 self.ultosc_value()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use proptest::prelude::*;
131
132 fn ordered_hlc(
133 h: &[f64],
134 l: &[f64],
135 c: &[f64],
136 ) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
137 let len = h.len().min(l.len()).min(c.len());
138 let mut high = Vec::with_capacity(len);
139 let mut low = Vec::with_capacity(len);
140 let mut close = Vec::with_capacity(len);
141 for i in 0..len {
142 let vh = h[i];
143 let vl = l[i];
144 let vc = c[i];
145 high.push(vh.max(vl).max(vc));
146 low.push(vh.min(vl).min(vc));
147 close.push(vc);
148 }
149 (high, low, close)
150 }
151
152 proptest! {
153 #[test]
154 fn test_ultosc_parity(
155 h in prop::collection::vec(1.0..100.0, 10..100),
156 l in prop::collection::vec(1.0..100.0, 10..100),
157 c in prop::collection::vec(1.0..100.0, 10..100),
158 ) {
159 let (high, low, close) = ordered_hlc(&h, &l, &c);
160 let len = high.len();
161 if len == 0 { return Ok(()); }
162 let p1 = 7;
163 let p2 = 14;
164 let p3 = 28;
165 let mut ult = ULTOSC::new(p1, p2, p3);
166 let streaming: Vec<f64> =
167 (0..len).map(|i| ult.next((high[i], low[i], close[i]))).collect();
168 let batch = talib_rs::momentum::ultosc(&high, &low, &close, p1, p2, p3)
169 .unwrap_or_else(|_| vec![f64::NAN; len]);
170 for (s, b) in streaming.iter().zip(batch.iter()) {
171 if s.is_nan() {
172 assert!(b.is_nan());
173 } else if !b.is_nan() {
174 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
175 }
176 }
177 }
178 }
179}