1use crate::error::{Error, Result};
4use crate::indicators::rolling_moments::ShiftedMoments;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct BollingerOutput {
10 pub upper: f64,
12 pub middle: f64,
14 pub lower: f64,
16 pub stddev: f64,
19}
20
21#[derive(Debug, Clone)]
48pub struct BollingerBands {
49 period: usize,
50 multiplier: f64,
51 buf: Box<[f64]>,
54 head: usize,
56 count: usize,
58 moments: ShiftedMoments,
62}
63
64impl BollingerBands {
65 pub fn new(period: usize, multiplier: f64) -> Result<Self> {
72 if period == 0 {
73 return Err(Error::PeriodZero);
74 }
75 if period > crate::error::MAX_PERIOD {
76 return Err(Error::InvalidPeriod {
77 message: crate::error::PERIOD_ABOVE_MAX,
78 });
79 }
80 if !multiplier.is_finite() || multiplier <= 0.0 {
81 return Err(Error::NonPositiveMultiplier);
82 }
83 Ok(Self {
84 period,
85 multiplier,
86 buf: vec![0.0; period].into_boxed_slice(),
87 head: 0,
88 count: 0,
89 moments: ShiftedMoments::new(),
90 })
91 }
92
93 pub fn classic() -> Self {
95 Self::new(20, 2.0).expect("classic Bollinger parameters are valid")
96 }
97
98 pub const fn period(&self) -> usize {
100 self.period
101 }
102
103 pub const fn multiplier(&self) -> f64 {
105 self.multiplier
106 }
107
108 pub fn batch_bands(&mut self, inputs: &[f64]) -> Vec<f64> {
122 let p = self.period;
123 let n = inputs.len();
124 if self.count != 0 || !inputs.iter().all(|x| x.is_finite()) {
127 let mut out = vec![f64::NAN; n * 4];
129 for (i, &x) in inputs.iter().enumerate() {
130 if let Some(o) = self.update(x) {
131 out[i * 4] = o.upper;
132 out[i * 4 + 1] = o.middle;
133 out[i * 4 + 2] = o.lower;
134 out[i * 4 + 3] = o.stddev;
135 }
136 }
137 return out;
138 }
139
140 let mult = self.multiplier;
141 let mut out = vec![f64::NAN; n * 4];
144 for (i, &x) in inputs.iter().enumerate() {
145 if self.count == p {
146 self.moments.evict(self.buf[self.head]);
147 self.buf[self.head] = x;
148 self.moments.push(x);
149 } else {
150 self.buf[self.head] = x;
151 self.moments.push(x);
152 self.count += 1;
153 }
154 self.head += 1;
155 if self.head == p {
156 self.head = 0;
157 }
158 if self.moments.needs_reseed(p) {
159 let (older, newer) = if self.count == p {
160 (&self.buf[self.head..], &self.buf[..self.head])
161 } else {
162 (&self.buf[..self.count], &self.buf[..0])
163 };
164 self.moments.reseed(older.iter().chain(newer).copied());
165 }
166 if self.count == p {
167 let mean = self.moments.mean(p);
168 let stddev = self.moments.std_dev(p);
169 let band = mult * stddev;
170 out[i * 4] = mean + band;
171 out[i * 4 + 1] = mean;
172 out[i * 4 + 2] = mean - band;
173 out[i * 4 + 3] = stddev;
174 }
175 }
176 out
177 }
178
179 fn current(&self) -> Option<BollingerOutput> {
180 if self.count != self.period {
181 return None;
182 }
183 let mean = self.moments.mean(self.period);
184 let stddev = self.moments.std_dev(self.period);
185 Some(BollingerOutput {
186 upper: mean + self.multiplier * stddev,
187 middle: mean,
188 lower: mean - self.multiplier * stddev,
189 stddev,
190 })
191 }
192}
193
194impl Indicator for BollingerBands {
195 type Input = f64;
196 type Output = BollingerOutput;
197
198 #[inline]
199 fn update(&mut self, input: f64) -> Option<BollingerOutput> {
200 if !input.is_finite() {
201 return None;
202 }
203 if self.count == self.period {
204 self.moments.evict(self.buf[self.head]);
205 self.buf[self.head] = input;
206 self.moments.push(input);
207 } else {
208 self.buf[self.head] = input;
209 self.moments.push(input);
210 self.count += 1;
211 }
212 self.head += 1;
213 if self.head == self.period {
214 self.head = 0;
215 }
216 if self.moments.needs_reseed(self.period) {
217 let (older, newer) = if self.count == self.period {
221 (&self.buf[self.head..], &self.buf[..self.head])
222 } else {
223 (&self.buf[..self.count], &self.buf[..0])
224 };
225 self.moments.reseed(older.iter().chain(newer).copied());
226 }
227 self.current()
228 }
229
230 fn reset(&mut self) {
231 self.head = 0;
232 self.count = 0;
233 self.moments.reset();
234 }
235
236 #[inline]
237 fn warmup_period(&self) -> usize {
238 self.period
239 }
240
241 #[inline]
242 fn is_ready(&self) -> bool {
243 self.count == self.period
244 }
245
246 #[inline]
247 fn name(&self) -> &'static str {
248 "BollingerBands"
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::traits::BatchExt;
256 use approx::assert_relative_eq;
257 use std::collections::VecDeque;
258
259 fn naive(prices: &[f64], period: usize, mult: f64) -> BollingerOutput {
260 assert!(
261 prices.len() >= period,
262 "naive requires at least `period` prices"
263 );
264 let w = &prices[prices.len() - period..];
265 let mean = w.iter().sum::<f64>() / period as f64;
266 let var = w.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
267 let s = var.sqrt();
268 BollingerOutput {
269 upper: mean + mult * s,
270 middle: mean,
271 lower: mean - mult * s,
272 stddev: s,
273 }
274 }
275
276 #[test]
277 fn rejects_zero_period() {
278 assert!(matches!(
279 BollingerBands::new(0, 2.0),
280 Err(Error::PeriodZero)
281 ));
282 }
283
284 #[test]
285 fn rejects_non_positive_multiplier() {
286 assert!(matches!(
287 BollingerBands::new(20, 0.0),
288 Err(Error::NonPositiveMultiplier)
289 ));
290 assert!(matches!(
291 BollingerBands::new(20, -1.0),
292 Err(Error::NonPositiveMultiplier)
293 ));
294 assert!(matches!(
295 BollingerBands::new(20, f64::NAN),
296 Err(Error::NonPositiveMultiplier)
297 ));
298 }
299
300 #[test]
306 fn classic_and_accessors_and_metadata() {
307 let bb = BollingerBands::classic();
308 assert_eq!(bb.period(), 20);
309 assert_relative_eq!(bb.multiplier(), 2.0, epsilon = 1e-12);
310 assert_eq!(bb.warmup_period(), 20);
311 assert_eq!(bb.name(), "BollingerBands");
312 }
313
314 #[test]
315 fn warmup_returns_none() {
316 let mut bb = BollingerBands::new(5, 2.0).unwrap();
317 for v in [1.0, 2.0, 3.0, 4.0] {
318 assert!(bb.update(v).is_none());
319 }
320 assert!(bb.update(5.0).is_some());
321 }
322
323 #[test]
328 fn bands_stay_accurate_when_the_level_dwarfs_the_spread() {
329 for level in [1.0e2_f64, 1.0e5, 1.0e8] {
330 let prices: Vec<f64> = (0..60)
331 .map(|i| level + (f64::from(i) * 0.7).sin())
332 .collect();
333 let mut bb = BollingerBands::new(20, 2.0).unwrap();
334 let mut got = 0.0;
335 for price in &prices {
336 if let Some(o) = bb.update(*price) {
337 got = o.stddev;
338 }
339 }
340 let window = &prices[40..];
341 let n = window.len() as f64;
342 let mean = window.iter().sum::<f64>() / n;
343 let want = (window.iter().map(|x| (x - mean) * (x - mean)).sum::<f64>() / n).sqrt();
344 assert_relative_eq!(got, want, max_relative = 1e-9);
345 }
346 }
347
348 #[test]
349 fn constant_series_yields_zero_stddev() {
350 let mut bb = BollingerBands::new(10, 2.0).unwrap();
351 let out = bb.batch(&[5.0_f64; 30]);
352 let last = out.iter().rev().flatten().next().unwrap();
353 assert_relative_eq!(last.middle, 5.0, epsilon = 1e-12);
354 assert_relative_eq!(last.stddev, 0.0, epsilon = 1e-12);
355 assert_relative_eq!(last.upper, 5.0, epsilon = 1e-12);
356 assert_relative_eq!(last.lower, 5.0, epsilon = 1e-12);
357 }
358
359 #[test]
360 fn matches_naive_definition() {
361 let prices: Vec<f64> = (1..=60)
362 .map(|i| (f64::from(i) * 0.3).sin() * 10.0 + 50.0)
363 .collect();
364 let mut bb = BollingerBands::new(20, 2.0).unwrap();
365 let out = bb.batch(&prices);
366 for i in 19..prices.len() {
367 let got = out[i].unwrap();
368 let want = naive(&prices[..=i], 20, 2.0);
369 assert_relative_eq!(got.middle, want.middle, epsilon = 1e-9);
370 assert_relative_eq!(got.stddev, want.stddev, epsilon = 1e-9);
371 assert_relative_eq!(got.upper, want.upper, epsilon = 1e-9);
372 assert_relative_eq!(got.lower, want.lower, epsilon = 1e-9);
373 }
374 }
375
376 #[test]
377 fn upper_above_middle_above_lower() {
378 let prices: Vec<f64> = (1..=100).map(f64::from).collect();
379 let mut bb = BollingerBands::new(20, 2.0).unwrap();
380 for o in bb.batch(&prices).into_iter().flatten() {
381 assert!(o.upper >= o.middle);
382 assert!(o.middle >= o.lower);
383 }
384 }
385
386 #[test]
387 fn batch_equals_streaming() {
388 let prices: Vec<f64> = (1..=50).map(|i| f64::from(i) * 0.7).collect();
389 let mut a = BollingerBands::new(10, 2.0).unwrap();
390 let mut b = BollingerBands::new(10, 2.0).unwrap();
391 assert_eq!(
392 a.batch(&prices),
393 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
394 );
395 }
396
397 #[test]
398 fn reset_clears_state() {
399 let mut bb = BollingerBands::new(5, 2.0).unwrap();
400 bb.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
401 assert!(bb.is_ready());
402 bb.reset();
403 assert!(!bb.is_ready());
404 }
405
406 #[test]
412 fn long_stream_drift_stays_bounded() {
413 let period = 20;
414 let mult = 2.0;
415 let mut bb = BollingerBands::new(period, mult).unwrap();
416 let mut window: VecDeque<f64> = VecDeque::with_capacity(period);
417 let n_updates = 16 * period * 5;
419 let mut last = None;
420 for i in 0..n_updates {
421 let v = if i % 2 == 0 { 1e6 } else { 1.0 };
422 last = bb.update(v);
423 if window.len() == period {
424 window.pop_front();
425 }
426 window.push_back(v);
427 }
428 let scratch = naive(&window.iter().copied().collect::<Vec<_>>(), period, mult);
429 let got = last.expect("warmed up");
430 assert!(
431 (got.middle - scratch.middle).abs() < 1e-3,
432 "middle drift: got={}, scratch={}",
433 got.middle,
434 scratch.middle,
435 );
436 assert!(
437 (got.stddev - scratch.stddev).abs() < 1e-3,
438 "stddev drift: got={}, scratch={}",
439 got.stddev,
440 scratch.stddev,
441 );
442 }
443
444 fn bits_eq(a: &[f64], b: &[f64]) -> bool {
445 a.len() == b.len()
446 && a.iter()
447 .zip(b)
448 .all(|(x, y)| x == y || (x.is_nan() && y.is_nan()))
449 }
450
451 fn bb_replay(period: usize, mult: f64, series: &[f64]) -> Vec<f64> {
453 let mut bb = BollingerBands::new(period, mult).unwrap();
454 let mut out = Vec::with_capacity(series.len() * 4);
455 for &x in series {
456 match bb.update(x) {
457 Some(o) => out.extend_from_slice(&[o.upper, o.middle, o.lower, o.stddev]),
458 None => out.extend_from_slice(&[f64::NAN; 4]),
459 }
460 }
461 out
462 }
463
464 #[test]
465 fn batch_bands_fast_path_is_bit_identical_with_reseed() {
466 let series: Vec<f64> = (0..500)
468 .map(|i| (f64::from(i) * 0.2).sin() * 10.0 + 50.0)
469 .collect();
470 let mut bb = BollingerBands::new(20, 2.0).unwrap();
471 let got = bb.batch_bands(&series);
472 assert!(bits_eq(&got, &bb_replay(20, 2.0, &series)));
473 let mut ref_bb = BollingerBands::new(20, 2.0).unwrap();
475 for &x in &series {
476 ref_bb.update(x);
477 }
478 assert_eq!(bb.update(55.0), ref_bb.update(55.0));
479 }
480
481 #[test]
482 fn batch_bands_falls_back_on_non_finite() {
483 let series = [1.0, 2.0, 3.0, f64::NAN, 5.0, 6.0, 7.0];
484 let mut bb = BollingerBands::new(3, 2.0).unwrap();
485 assert!(bits_eq(
486 &bb.batch_bands(&series),
487 &bb_replay(3, 2.0, &series)
488 ));
489 }
490
491 #[test]
492 fn batch_bands_falls_back_when_not_fresh() {
493 let mut bb = BollingerBands::new(3, 2.0).unwrap();
494 bb.update(99.0);
495 let series = [1.0, 2.0, 3.0, 4.0];
496 let mut ref_bb = BollingerBands::new(3, 2.0).unwrap();
497 ref_bb.update(99.0);
498 let mut want = Vec::new();
499 for &x in &series {
500 match ref_bb.update(x) {
501 Some(o) => want.extend_from_slice(&[o.upper, o.middle, o.lower, o.stddev]),
502 None => want.extend_from_slice(&[f64::NAN; 4]),
503 }
504 }
505 assert!(bits_eq(&bb.batch_bands(&series), &want));
506 }
507
508 #[test]
509 fn batch_bands_sub_period_slice_is_all_nan() {
510 let series = [1.0, 2.0, 3.0];
511 let mut bb = BollingerBands::new(10, 2.0).unwrap();
512 let got = bb.batch_bands(&series);
513 assert!(bits_eq(&got, &bb_replay(10, 2.0, &series)));
514 assert!(got.iter().all(|x| x.is_nan()) && got.len() == 12);
515 }
516
517 #[test]
518 fn ignores_non_finite_input() {
519 let mut bb = BollingerBands::new(5, 2.0).unwrap();
520 bb.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
521 assert_eq!(bb.update(f64::NAN), None);
523 assert_eq!(bb.update(f64::INFINITY), None);
524 let after = bb.update(6.0).unwrap();
526 assert_relative_eq!(
527 after.middle,
528 (2.0 + 3.0 + 4.0 + 5.0 + 6.0) / 5.0,
529 epsilon = 1e-12
530 );
531 }
532}