quantwave_core/indicators/incremental/
bbands.rs1use crate::indicators::incremental::utils::RingBuffer;
4use crate::traits::Next;
5use talib_rs::MaType;
6
7const NAN_TRIPLE: (f64, f64, f64) = (f64::NAN, f64::NAN, f64::NAN);
8
9#[derive(Debug, Clone)]
11#[allow(non_camel_case_types)]
12pub struct BBANDS {
13 pub timeperiod: usize,
14 pub nbdevup: f64,
15 pub nbdevdn: f64,
16 pub matype: MaType,
17 window: RingBuffer<f64>,
18 sum: f64,
19 sum_sq: f64,
20 history: Vec<f64>,
21}
22
23impl BBANDS {
24 pub fn new(timeperiod: usize, nbdevup: f64, nbdevdn: f64, matype: MaType) -> Self {
25 Self {
26 timeperiod,
27 nbdevup,
28 nbdevdn,
29 matype,
30 window: RingBuffer::with_capacity(timeperiod.max(1)),
31 sum: 0.0,
32 sum_sq: 0.0,
33 history: Vec::new(),
34 }
35 }
36
37 #[inline]
38 fn bands_from_sums(&self) -> (f64, f64, f64) {
39 let n = self.timeperiod as f64;
40 let inv_n = 1.0 / n;
41 let ma_val = self.sum * inv_n;
42 let variance = self.sum_sq * inv_n - ma_val * ma_val;
43 let stddev = variance.max(0.0).sqrt();
44 let upper = ma_val + self.nbdevup * stddev;
45 let lower = ma_val - self.nbdevdn * stddev;
46 (upper, ma_val, lower)
47 }
48
49 fn next_sma(&mut self, input: f64) -> (f64, f64, f64) {
50 let tp = self.timeperiod;
51 if tp == 0 {
52 return NAN_TRIPLE;
53 }
54
55 if self.window.len() == tp {
56 if let Some(old) = self.window.pop_front() {
57 self.sum -= old;
58 self.sum_sq -= old * old;
59 }
60 }
61
62 self.window.push_back(input);
63 self.sum += input;
64 self.sum_sq += input * input;
65
66 if self.window.len() < tp {
67 return NAN_TRIPLE;
68 }
69
70 self.bands_from_sums()
71 }
72
73 fn next_fallback(&mut self, input: f64) -> (f64, f64, f64) {
74 self.history.push(input);
75 let (u, m, l) = talib_rs::overlap::bbands(
76 &self.history,
77 self.timeperiod,
78 self.nbdevup,
79 self.nbdevdn,
80 self.matype,
81 )
82 .unwrap_or_else(|_| {
83 let n = self.history.len();
84 (vec![f64::NAN; n], vec![f64::NAN; n], vec![f64::NAN; n])
85 });
86 (
87 *u.last().unwrap_or(&f64::NAN),
88 *m.last().unwrap_or(&f64::NAN),
89 *l.last().unwrap_or(&f64::NAN),
90 )
91 }
92}
93
94impl Next<f64> for BBANDS {
95 type Output = (f64, f64, f64);
96
97 fn next(&mut self, input: f64) -> Self::Output {
98 if self.matype == MaType::Sma {
99 self.next_sma(input)
100 } else {
101 self.next_fallback(input)
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use proptest::prelude::*;
110
111 proptest! {
112 #[test]
113 fn test_bbands_parity(input in prop::collection::vec(0.1..100.0, 1..100)) {
114 let period = 10;
115 let nbdevup = 2.0;
116 let nbdevdn = 2.0;
117 let matype = MaType::Sma;
118 let mut bbands = BBANDS::new(period, nbdevup, nbdevdn, matype);
119 let streaming_results: Vec<(f64, f64, f64)> =
120 input.iter().map(|&x| bbands.next(x)).collect();
121 let (b_upper, b_middle, b_lower) = talib_rs::overlap::bbands(
122 &input,
123 period,
124 nbdevup,
125 nbdevdn,
126 matype,
127 )
128 .unwrap_or_else(|_| {
129 (
130 vec![f64::NAN; input.len()],
131 vec![f64::NAN; input.len()],
132 vec![f64::NAN; input.len()],
133 )
134 });
135
136 for (i, (s_upper, s_middle, s_lower)) in streaming_results.into_iter().enumerate() {
137 if s_upper.is_nan() {
138 assert!(b_upper[i].is_nan());
139 } else {
140 approx::assert_relative_eq!(s_upper, b_upper[i], epsilon = 1e-6);
141 }
142 if s_middle.is_nan() {
143 assert!(b_middle[i].is_nan());
144 } else {
145 approx::assert_relative_eq!(s_middle, b_middle[i], epsilon = 1e-6);
146 }
147 if s_lower.is_nan() {
148 assert!(b_lower[i].is_nan());
149 } else {
150 approx::assert_relative_eq!(s_lower, b_lower[i], epsilon = 1e-6);
151 }
152 }
153 }
154 }
155}