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