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> {
116 let mut out = vec![0.0; inputs.len() * 4];
117 self.batch_bands_into(inputs, &mut out);
118 out
119 }
120
121 pub fn batch_bands_into(&mut self, inputs: &[f64], out: &mut [f64]) {
137 assert_eq!(
138 out.len(),
139 inputs.len() * 4,
140 "batch_bands output must hold four values per input"
141 );
142 if self.count != 0 || !inputs.iter().all(|x| x.is_finite()) {
145 for (row, &x) in out.chunks_exact_mut(4).zip(inputs) {
146 match self.update(x) {
147 Some(o) => row.copy_from_slice(&[o.upper, o.middle, o.lower, o.stddev]),
148 None => row.fill(f64::NAN),
149 }
150 }
151 return;
152 }
153
154 let p = self.period;
155 let mult = self.multiplier;
156 let mut moments = self.moments.clone();
157 let (mut head, mut count) = (self.head, self.count);
158 let buf = &mut self.buf[..];
159 for (row, &x) in out.chunks_exact_mut(4).zip(inputs) {
160 if count == p {
161 moments.evict(buf[head]);
162 buf[head] = x;
163 moments.push(x);
164 } else {
165 buf[head] = x;
166 moments.push(x);
167 count += 1;
168 }
169 head += 1;
170 if head == p {
171 head = 0;
172 }
173 if moments.needs_reseed(p) {
176 debug_assert_eq!(count, p);
177 let (newer, older) = buf.split_at(head);
178 moments.reseed(older.iter().chain(newer).copied());
179 }
180 if count == p {
181 let mean = moments.mean(p);
182 let stddev = moments.std_dev(p);
183 let band = mult * stddev;
184 row.copy_from_slice(&[mean + band, mean, mean - band, stddev]);
185 } else {
186 row.fill(f64::NAN);
187 }
188 }
189 self.moments = moments;
190 self.head = head;
191 self.count = count;
192 }
193
194 pub fn batch_bands_fast_into(&mut self, inputs: &[f64], out: &mut [f64]) {
209 assert_eq!(
210 out.len(),
211 inputs.len() * 4,
212 "batch_bands output must hold four values per input"
213 );
214 let p = self.period;
215 if self.count != 0 || inputs.len() < p || !crate::fast::in_range(inputs) {
216 self.batch_bands_into(inputs, out);
217 return;
218 }
219 crate::fast::with_scratch(crate::fast::power_scratch_len(2, p), |scratch| {
220 wickra_simd::dispatch(crate::fast::BollingerFast {
221 x: inputs,
222 period: p,
223 multiplier: self.multiplier,
224 scratch,
225 out,
226 _borrow: std::marker::PhantomData,
227 });
228 });
229 crate::fast::replay_tail(self, &inputs[inputs.len() - p..]);
230 }
231
232 pub fn batch_bands_fast(&mut self, inputs: &[f64]) -> Vec<f64> {
235 let mut out = vec![0.0; inputs.len() * 4];
236 self.batch_bands_fast_into(inputs, &mut out);
237 out
238 }
239
240 fn current(&self) -> Option<BollingerOutput> {
241 if self.count != self.period {
242 return None;
243 }
244 let mean = self.moments.mean(self.period);
245 let stddev = self.moments.std_dev(self.period);
246 Some(BollingerOutput {
247 upper: mean + self.multiplier * stddev,
248 middle: mean,
249 lower: mean - self.multiplier * stddev,
250 stddev,
251 })
252 }
253}
254
255impl Indicator for BollingerBands {
256 type Input = f64;
257 type Output = BollingerOutput;
258
259 #[inline]
260 fn update(&mut self, input: f64) -> Option<BollingerOutput> {
261 if !input.is_finite() {
262 return None;
263 }
264 if self.count == self.period {
265 self.moments.evict(self.buf[self.head]);
266 self.buf[self.head] = input;
267 self.moments.push(input);
268 } else {
269 self.buf[self.head] = input;
270 self.moments.push(input);
271 self.count += 1;
272 }
273 self.head += 1;
274 if self.head == self.period {
275 self.head = 0;
276 }
277 if self.moments.needs_reseed(self.period) {
278 debug_assert_eq!(self.count, self.period);
284 let (newer, older) = self.buf.split_at(self.head);
285 self.moments.reseed(older.iter().chain(newer).copied());
286 }
287 self.current()
288 }
289
290 fn reset(&mut self) {
291 self.head = 0;
292 self.count = 0;
293 self.moments.reset();
294 }
295
296 #[inline]
297 fn warmup_period(&self) -> usize {
298 self.period
299 }
300
301 #[inline]
302 fn is_ready(&self) -> bool {
303 self.count == self.period
304 }
305
306 #[inline]
307 fn name(&self) -> &'static str {
308 "BollingerBands"
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::traits::BatchExt;
316 use approx::assert_relative_eq;
317 use std::collections::VecDeque;
318
319 fn naive(prices: &[f64], period: usize, mult: f64) -> BollingerOutput {
320 assert!(
321 prices.len() >= period,
322 "naive requires at least `period` prices"
323 );
324 let w = &prices[prices.len() - period..];
325 let mean = w.iter().sum::<f64>() / period as f64;
326 let var = w.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
327 let s = var.sqrt();
328 BollingerOutput {
329 upper: mean + mult * s,
330 middle: mean,
331 lower: mean - mult * s,
332 stddev: s,
333 }
334 }
335
336 #[test]
337 fn rejects_zero_period() {
338 assert!(matches!(
339 BollingerBands::new(0, 2.0),
340 Err(Error::PeriodZero)
341 ));
342 }
343
344 #[test]
345 fn rejects_non_positive_multiplier() {
346 assert!(matches!(
347 BollingerBands::new(20, 0.0),
348 Err(Error::NonPositiveMultiplier)
349 ));
350 assert!(matches!(
351 BollingerBands::new(20, -1.0),
352 Err(Error::NonPositiveMultiplier)
353 ));
354 assert!(matches!(
355 BollingerBands::new(20, f64::NAN),
356 Err(Error::NonPositiveMultiplier)
357 ));
358 }
359
360 #[test]
366 fn classic_and_accessors_and_metadata() {
367 let bb = BollingerBands::classic();
368 assert_eq!(bb.period(), 20);
369 assert_relative_eq!(bb.multiplier(), 2.0, epsilon = 1e-12);
370 assert_eq!(bb.warmup_period(), 20);
371 assert_eq!(bb.name(), "BollingerBands");
372 }
373
374 #[test]
375 fn warmup_returns_none() {
376 let mut bb = BollingerBands::new(5, 2.0).unwrap();
377 for v in [1.0, 2.0, 3.0, 4.0] {
378 assert!(bb.update(v).is_none());
379 }
380 assert!(bb.update(5.0).is_some());
381 }
382
383 #[test]
388 fn bands_stay_accurate_when_the_level_dwarfs_the_spread() {
389 for level in [1.0e2_f64, 1.0e5, 1.0e8] {
390 let prices: Vec<f64> = (0..60)
391 .map(|i| level + (f64::from(i) * 0.7).sin())
392 .collect();
393 let mut bb = BollingerBands::new(20, 2.0).unwrap();
394 let mut got = 0.0;
395 for price in &prices {
396 if let Some(o) = bb.update(*price) {
397 got = o.stddev;
398 }
399 }
400 let window = &prices[40..];
401 let n = window.len() as f64;
402 let mean = window.iter().sum::<f64>() / n;
403 let want = (window.iter().map(|x| (x - mean) * (x - mean)).sum::<f64>() / n).sqrt();
404 assert_relative_eq!(got, want, max_relative = 1e-9);
405 }
406 }
407
408 #[test]
409 fn constant_series_yields_zero_stddev() {
410 let mut bb = BollingerBands::new(10, 2.0).unwrap();
411 let out = bb.batch(&[5.0_f64; 30]);
412 let last = out.iter().rev().flatten().next().unwrap();
413 assert_relative_eq!(last.middle, 5.0, epsilon = 1e-12);
414 assert_relative_eq!(last.stddev, 0.0, epsilon = 1e-12);
415 assert_relative_eq!(last.upper, 5.0, epsilon = 1e-12);
416 assert_relative_eq!(last.lower, 5.0, epsilon = 1e-12);
417 }
418
419 #[test]
420 fn matches_naive_definition() {
421 let prices: Vec<f64> = (1..=60)
422 .map(|i| (f64::from(i) * 0.3).sin() * 10.0 + 50.0)
423 .collect();
424 let mut bb = BollingerBands::new(20, 2.0).unwrap();
425 let out = bb.batch(&prices);
426 for i in 19..prices.len() {
427 let got = out[i].unwrap();
428 let want = naive(&prices[..=i], 20, 2.0);
429 assert_relative_eq!(got.middle, want.middle, epsilon = 1e-9);
430 assert_relative_eq!(got.stddev, want.stddev, epsilon = 1e-9);
431 assert_relative_eq!(got.upper, want.upper, epsilon = 1e-9);
432 assert_relative_eq!(got.lower, want.lower, epsilon = 1e-9);
433 }
434 }
435
436 #[test]
437 fn upper_above_middle_above_lower() {
438 let prices: Vec<f64> = (1..=100).map(f64::from).collect();
439 let mut bb = BollingerBands::new(20, 2.0).unwrap();
440 for o in bb.batch(&prices).into_iter().flatten() {
441 assert!(o.upper >= o.middle);
442 assert!(o.middle >= o.lower);
443 }
444 }
445
446 #[test]
447 fn batch_equals_streaming() {
448 let prices: Vec<f64> = (1..=50).map(|i| f64::from(i) * 0.7).collect();
449 let mut a = BollingerBands::new(10, 2.0).unwrap();
450 let mut b = BollingerBands::new(10, 2.0).unwrap();
451 assert_eq!(
452 a.batch(&prices),
453 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
454 );
455 }
456
457 #[test]
458 fn reset_clears_state() {
459 let mut bb = BollingerBands::new(5, 2.0).unwrap();
460 bb.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
461 assert!(bb.is_ready());
462 bb.reset();
463 assert!(!bb.is_ready());
464 }
465
466 #[test]
472 fn long_stream_drift_stays_bounded() {
473 let period = 20;
474 let mult = 2.0;
475 let mut bb = BollingerBands::new(period, mult).unwrap();
476 let mut window: VecDeque<f64> = VecDeque::with_capacity(period);
477 let n_updates = 16 * period * 5;
479 let mut last = None;
480 for i in 0..n_updates {
481 let v = if i % 2 == 0 { 1e6 } else { 1.0 };
482 last = bb.update(v);
483 if window.len() == period {
484 window.pop_front();
485 }
486 window.push_back(v);
487 }
488 let scratch = naive(&window.iter().copied().collect::<Vec<_>>(), period, mult);
489 let got = last.expect("warmed up");
490 assert!(
491 (got.middle - scratch.middle).abs() < 1e-3,
492 "middle drift: got={}, scratch={}",
493 got.middle,
494 scratch.middle,
495 );
496 assert!(
497 (got.stddev - scratch.stddev).abs() < 1e-3,
498 "stddev drift: got={}, scratch={}",
499 got.stddev,
500 scratch.stddev,
501 );
502 }
503
504 fn bits_eq(a: &[f64], b: &[f64]) -> bool {
505 a.len() == b.len()
506 && a.iter()
507 .zip(b)
508 .all(|(x, y)| x == y || (x.is_nan() && y.is_nan()))
509 }
510
511 fn bb_replay(period: usize, mult: f64, series: &[f64]) -> Vec<f64> {
513 let mut bb = BollingerBands::new(period, mult).unwrap();
514 let mut out = Vec::with_capacity(series.len() * 4);
515 for &x in series {
516 match bb.update(x) {
517 Some(o) => out.extend_from_slice(&[o.upper, o.middle, o.lower, o.stddev]),
518 None => out.extend_from_slice(&[f64::NAN; 4]),
519 }
520 }
521 out
522 }
523
524 #[test]
527 fn batch_bands_into_overwrites_a_dirty_buffer() {
528 let series: Vec<f64> = (0..400)
529 .map(|i| (f64::from(i) * 0.17).sin() * 6.0 + f64::from(i % 5) + 90.0)
530 .collect();
531 let mut out = vec![5.5; series.len() * 4];
532 BollingerBands::new(20, 2.0)
533 .unwrap()
534 .batch_bands_into(&series, &mut out);
535 let want = bb_replay(20, 2.0, &series);
536 let bits = |v: &[f64]| v.iter().map(|x| x.to_bits()).collect::<Vec<_>>();
537 assert_eq!(bits(&out), bits(&want));
538 }
539
540 #[test]
541 #[should_panic(expected = "batch_bands output must hold four values per input")]
542 fn batch_bands_into_rejects_a_short_buffer() {
543 let mut out = vec![0.0; 7];
544 BollingerBands::new(3, 2.0)
545 .unwrap()
546 .batch_bands_into(&[1.0, 2.0], &mut out);
547 }
548
549 #[test]
550 fn batch_bands_fast_path_is_bit_identical_with_reseed() {
551 let series: Vec<f64> = (0..500)
553 .map(|i| (f64::from(i) * 0.2).sin() * 10.0 + 50.0)
554 .collect();
555 let mut bb = BollingerBands::new(20, 2.0).unwrap();
556 let got = bb.batch_bands(&series);
557 assert!(bits_eq(&got, &bb_replay(20, 2.0, &series)));
558 let mut ref_bb = BollingerBands::new(20, 2.0).unwrap();
560 for &x in &series {
561 ref_bb.update(x);
562 }
563 assert_eq!(bb.update(55.0), ref_bb.update(55.0));
564 }
565
566 #[test]
567 fn batch_bands_falls_back_on_non_finite() {
568 let series = [1.0, 2.0, 3.0, f64::NAN, 5.0, 6.0, 7.0];
569 let mut bb = BollingerBands::new(3, 2.0).unwrap();
570 assert!(bits_eq(
571 &bb.batch_bands(&series),
572 &bb_replay(3, 2.0, &series)
573 ));
574 }
575
576 #[test]
577 fn batch_bands_falls_back_when_not_fresh() {
578 let mut bb = BollingerBands::new(3, 2.0).unwrap();
579 bb.update(99.0);
580 let series = [1.0, 2.0, 3.0, 4.0];
581 let mut ref_bb = BollingerBands::new(3, 2.0).unwrap();
582 ref_bb.update(99.0);
583 let mut want = Vec::new();
584 for &x in &series {
585 match ref_bb.update(x) {
586 Some(o) => want.extend_from_slice(&[o.upper, o.middle, o.lower, o.stddev]),
587 None => want.extend_from_slice(&[f64::NAN; 4]),
588 }
589 }
590 assert!(bits_eq(&bb.batch_bands(&series), &want));
591 }
592
593 #[test]
594 fn batch_bands_sub_period_slice_is_all_nan() {
595 let series = [1.0, 2.0, 3.0];
596 let mut bb = BollingerBands::new(10, 2.0).unwrap();
597 let got = bb.batch_bands(&series);
598 assert!(bits_eq(&got, &bb_replay(10, 2.0, &series)));
599 assert!(got.iter().all(|x| x.is_nan()) && got.len() == 12);
600 }
601
602 #[test]
603 fn ignores_non_finite_input() {
604 let mut bb = BollingerBands::new(5, 2.0).unwrap();
605 bb.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]);
606 assert_eq!(bb.update(f64::NAN), None);
608 assert_eq!(bb.update(f64::INFINITY), None);
609 let after = bb.update(6.0).unwrap();
611 assert_relative_eq!(
612 after.middle,
613 (2.0 + 3.0 + 4.0 + 5.0 + 6.0) / 5.0,
614 epsilon = 1e-12
615 );
616 }
617
618 #[test]
619 fn rejects_period_above_maximum() {
620 assert!(matches!(
621 BollingerBands::new(crate::error::MAX_PERIOD + 1, 2.0),
622 Err(Error::InvalidPeriod { .. })
623 ));
624 }
625}