1use std::{
2 fmt::Display,
3 hash::{Hash, Hasher},
4 num::NonZero,
5};
6
7use crate::{
8 Indicator, IndicatorConfig, IndicatorConfigBuilder, Ohlcv, Price, PriceSource,
9 price_window::PriceWindow,
10};
11
12#[derive(Clone, Copy, Debug)]
22pub struct StdDev(f64);
23
24impl StdDev {
25 #[must_use]
31 pub fn new(value: f64) -> Self {
32 assert!(!value.is_nan(), "std_dev must not be NaN");
33 assert!(value > 0.0, "std_dev must be positive");
34 Self(value)
35 }
36
37 #[must_use]
38 pub fn value(self) -> f64 {
39 self.0
40 }
41}
42
43impl PartialEq for StdDev {
44 fn eq(&self, other: &Self) -> bool {
45 self.0.to_bits() == other.0.to_bits()
46 }
47}
48
49impl Eq for StdDev {}
50
51impl Hash for StdDev {
52 fn hash<H: Hasher>(&self, state: &mut H) {
53 self.0.to_bits().hash(state);
54 }
55}
56
57impl Default for StdDev {
58 fn default() -> Self {
59 Self(2.0)
60 }
61}
62
63#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
84pub struct BbConfig {
85 length: usize,
86 source: PriceSource,
87 std_dev: StdDev,
88}
89
90impl IndicatorConfig for BbConfig {
91 type Builder = BbConfigBuilder;
92
93 #[inline]
94 fn builder() -> Self::Builder {
95 BbConfigBuilder::new()
96 }
97
98 #[inline]
99 fn length(&self) -> usize {
100 self.length
101 }
102
103 #[inline]
104 fn source(&self) -> &PriceSource {
105 &self.source
106 }
107}
108
109impl BbConfig {
110 #[inline]
111 #[must_use]
112 pub fn std_dev(&self) -> StdDev {
113 self.std_dev
114 }
115
116 #[allow(clippy::missing_panics_doc)]
118 #[must_use]
119 pub fn default_20() -> Self {
120 Self::builder().length(NonZero::new(20).unwrap()).build()
121 }
122
123 #[must_use]
125 pub fn close(length: NonZero<usize>) -> Self {
126 Self::builder().length(length).build()
127 }
128}
129
130impl Display for BbConfig {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(
133 f,
134 "BbConfig({}, {}, {})",
135 self.length,
136 self.source,
137 self.std_dev.value()
138 )
139 }
140}
141
142pub struct BbConfigBuilder {
149 length: Option<usize>,
150 source: PriceSource,
151 std_dev: StdDev,
152}
153
154impl BbConfigBuilder {
155 fn new() -> Self {
156 Self {
157 length: None,
158 source: PriceSource::Close,
159 std_dev: StdDev(2.0),
160 }
161 }
162
163 #[inline]
164 #[must_use]
165 pub fn std_dev(mut self, std_dev: StdDev) -> Self {
166 self.std_dev = std_dev;
167 self
168 }
169}
170
171impl IndicatorConfigBuilder<BbConfig> for BbConfigBuilder {
172 #[inline]
173 fn length(mut self, length: NonZero<usize>) -> Self {
174 self.length.replace(length.get());
175 self
176 }
177
178 #[inline]
179 fn source(mut self, source: PriceSource) -> Self {
180 self.source = source;
181 self
182 }
183
184 #[inline]
185 fn build(self) -> BbConfig {
186 BbConfig {
187 length: self.length.expect("length is required"),
188 source: self.source,
189 std_dev: self.std_dev,
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq)]
205pub struct BbValue {
206 upper: Price,
207 middle: Price,
208 lower: Price,
209}
210
211impl BbValue {
212 #[inline]
214 #[must_use]
215 pub fn upper(&self) -> Price {
216 self.upper
217 }
218
219 #[inline]
221 #[must_use]
222 pub fn middle(&self) -> Price {
223 self.middle
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn lower(&self) -> Price {
230 self.lower
231 }
232
233 #[inline]
239 #[must_use]
240 pub fn width(&self) -> f64 {
241 self.upper - self.lower
242 }
243}
244
245impl Display for BbValue {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 write!(
248 f,
249 "BB(u: {}, m: {}, l: {})",
250 self.upper, self.middle, self.lower
251 )
252 }
253}
254
255#[derive(Clone, Debug)]
299pub struct Bb {
300 config: BbConfig,
301 length_reciprocal: f64,
302 std_dev_multiplier: f64,
303 window: PriceWindow,
304 current: Option<BbValue>,
305}
306
307impl Indicator for Bb {
308 type Config = BbConfig;
309 type Output = BbValue;
310
311 fn new(config: Self::Config) -> Self {
312 let window = PriceWindow::new(config.length, config.source);
313
314 Self {
315 config,
316 #[allow(clippy::cast_precision_loss)]
317 length_reciprocal: 1.0 / config.length as f64,
318 std_dev_multiplier: config.std_dev.0,
319 window,
320 current: None,
321 }
322 }
323
324 fn compute(&mut self, ohlcv: &impl Ohlcv) -> Option<Self::Output> {
325 self.window.add(ohlcv);
326
327 self.current = match (self.window.sum(), self.window.sum_of_squares()) {
328 (Some(sum), Some(sum_of_squares)) => {
329 let mean = sum * self.length_reciprocal;
330
331 let variance = sum_of_squares.mul_add(self.length_reciprocal, -(mean * mean));
333 let std_dev = variance.max(0.0).sqrt() * self.std_dev_multiplier;
334
335 Some(Self::Output {
336 upper: mean + std_dev,
337 middle: mean,
338 lower: mean - std_dev,
339 })
340 }
341 _ => None,
342 };
343
344 self.current
345 }
346
347 fn value(&self) -> Option<Self::Output> {
348 self.current
349 }
350}
351
352impl Display for Bb {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 write!(
355 f,
356 "BB({}, {}, {})",
357 self.config.length, self.config.source, self.std_dev_multiplier,
358 )
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::test_util::Bar;
366 use std::num::NonZero;
367
368 fn bb(length: usize) -> Bb {
369 Bb::new(
370 BbConfig::builder()
371 .length(NonZero::new(length).unwrap())
372 .build(),
373 )
374 }
375
376 fn bb_with_std_dev(length: usize, std_dev: f64) -> Bb {
377 Bb::new(
378 BbConfig::builder()
379 .length(NonZero::new(length).unwrap())
380 .std_dev(StdDev::new(std_dev))
381 .build(),
382 )
383 }
384
385 fn bar(close: f64, time: u64) -> Bar {
386 Bar::new(0.0, 0.0, 0.0, close).at(time)
387 }
388
389 fn assert_bb(value: Option<BbValue>, upper: f64, middle: f64, lower: f64) {
390 let v = value.expect("expected Some(BbValue)");
391 assert!(
392 (v.upper() - upper).abs() < 1e-10,
393 "upper: expected {upper}, got {}",
394 v.upper()
395 );
396 assert!(
397 (v.middle() - middle).abs() < 1e-10,
398 "middle: expected {middle}, got {}",
399 v.middle()
400 );
401 assert!(
402 (v.lower() - lower).abs() < 1e-10,
403 "lower: expected {lower}, got {}",
404 v.lower()
405 );
406 }
407
408 mod filling {
409 use super::*;
410
411 #[test]
412 fn none_until_window_full() {
413 let mut bb = bb(3);
414 assert!(bb.compute(&bar(10.0, 1)).is_none());
415 assert!(bb.compute(&bar(20.0, 2)).is_none());
416 }
417
418 #[test]
419 fn returns_value_when_full() {
420 let mut bb = bb(2);
421 bb.compute(&bar(3.0, 1));
422 assert!(bb.compute(&bar(5.0, 2)).is_some());
423 }
424 }
425
426 mod computation {
427 use super::*;
428
429 #[test]
430 fn basic_bands() {
431 let mut bb = bb(2);
435 bb.compute(&bar(3.0, 1));
436 assert_bb(bb.compute(&bar(5.0, 2)), 6.0, 4.0, 2.0);
437 }
438
439 #[test]
440 fn constant_input_zero_width() {
441 let mut bb = bb(3);
443 bb.compute(&bar(10.0, 1));
444 bb.compute(&bar(10.0, 2));
445 assert_bb(bb.compute(&bar(10.0, 3)), 10.0, 10.0, 10.0);
446 }
447
448 #[test]
449 fn bands_are_symmetric() {
450 let mut bb = bb(2);
451 bb.compute(&bar(3.0, 1));
452 let v = bb.compute(&bar(5.0, 2)).unwrap();
453 let upper_dist = v.upper() - v.middle();
454 let lower_dist = v.middle() - v.lower();
455 assert!((upper_dist - lower_dist).abs() < 1e-10);
456 }
457 }
458
459 mod sliding {
460 use super::*;
461
462 #[test]
463 fn updates_on_advance() {
464 let mut bb = bb(2);
468 bb.compute(&bar(3.0, 1));
469 bb.compute(&bar(5.0, 2));
470 assert_bb(bb.compute(&bar(7.0, 3)), 8.0, 6.0, 4.0);
471 }
472 }
473
474 mod repaint {
475 use super::*;
476
477 #[test]
478 fn replaces_current_bar() {
479 let mut bb = bb(2);
483 bb.compute(&bar(3.0, 1));
484 bb.compute(&bar(5.0, 2));
485 assert_bb(bb.compute(&bar(7.0, 2)), 9.0, 5.0, 1.0);
486 }
487
488 #[test]
489 fn repaint_during_filling() {
490 let mut bb = bb(2);
491 bb.compute(&bar(3.0, 1));
492 bb.compute(&bar(4.0, 1)); assert!(bb.compute(&bar(4.0, 1)).is_none()); assert_bb(bb.compute(&bar(6.0, 2)), 7.0, 5.0, 3.0);
497 }
498 }
499
500 mod std_dev_multiplier {
501 use super::*;
502
503 #[test]
504 fn multiplier_of_one() {
505 let mut bb = bb_with_std_dev(2, 1.0);
508 bb.compute(&bar(3.0, 1));
509 assert_bb(bb.compute(&bar(5.0, 2)), 5.0, 4.0, 3.0);
510 }
511
512 #[test]
513 fn fractional_multiplier() {
514 let mut bb = bb_with_std_dev(2, 1.5);
517 bb.compute(&bar(3.0, 1));
518 assert_bb(bb.compute(&bar(5.0, 2)), 5.5, 4.0, 2.5);
519 }
520
521 #[test]
522 fn wider_multiplier_wider_bands() {
523 let mut bb1 = bb_with_std_dev(2, 1.0);
524 let mut bb2 = bb_with_std_dev(2, 3.0);
525
526 bb1.compute(&bar(3.0, 1));
527 bb2.compute(&bar(3.0, 1));
528
529 let v1 = bb1.compute(&bar(5.0, 2)).unwrap();
530 let v2 = bb2.compute(&bar(5.0, 2)).unwrap();
531
532 assert!(v2.width() > v1.width());
533 }
534 }
535
536 mod width {
537 use super::*;
538
539 #[test]
540 fn equals_upper_minus_lower() {
541 let mut bb = bb(2);
542 bb.compute(&bar(3.0, 1));
543 let v = bb.compute(&bar(5.0, 2)).unwrap();
544 assert!((v.width() - (v.upper() - v.lower())).abs() < 1e-10);
545 }
546
547 #[test]
548 fn zero_for_constant_input() {
549 let mut bb = bb(2);
550 bb.compute(&bar(10.0, 1));
551 let v = bb.compute(&bar(10.0, 2)).unwrap();
552 assert!((v.width()).abs() < 1e-10);
553 }
554 }
555
556 mod value {
557 use super::*;
558
559 #[test]
560 fn returns_last_computed() {
561 let mut bb = bb(2);
562 bb.compute(&bar(3.0, 1));
563 bb.compute(&bar(5.0, 2));
564 assert_eq!(bb.value(), bb.compute(&bar(5.0, 2)));
565 }
566
567 #[test]
568 fn none_before_first_value() {
569 let bb = bb(2);
570 assert!(bb.value().is_none());
571 }
572 }
573
574 mod config {
575 use super::*;
576
577 #[test]
578 fn default_std_dev_is_two() {
579 let config = BbConfig::builder()
580 .length(NonZero::new(20).unwrap())
581 .build();
582 assert!((config.std_dev().value() - 2.0).abs() < f64::EPSILON);
583 }
584
585 #[test]
586 fn default_source_is_close() {
587 let config = BbConfig::builder()
588 .length(NonZero::new(20).unwrap())
589 .build();
590 assert_eq!(*config.source(), PriceSource::Close);
591 }
592
593 #[test]
594 #[should_panic(expected = "length is required")]
595 fn panics_without_length() {
596 let _ = BbConfig::builder().build();
597 }
598
599 #[test]
600 #[should_panic(expected = "std_dev must be positive")]
601 fn std_dev_rejects_zero() {
602 let _ = StdDev::new(0.0);
603 }
604
605 #[test]
606 #[should_panic(expected = "std_dev must be positive")]
607 fn std_dev_rejects_negative() {
608 let _ = StdDev::new(-1.0);
609 }
610
611 #[test]
612 #[should_panic(expected = "std_dev must not be NaN")]
613 fn std_dev_rejects_nan() {
614 let _ = StdDev::new(f64::NAN);
615 }
616 }
617
618 mod clone {
619 use super::*;
620
621 #[test]
622 fn produces_independent_state() {
623 let mut bb = bb(3);
624 bb.compute(&bar(10.0, 1));
625 bb.compute(&bar(20.0, 2));
626
627 let mut cloned = bb.clone();
628
629 assert!(bb.compute(&bar(30.0, 3)).is_some());
632
633 assert_eq!(cloned.value(), None);
635
636 assert!(cloned.compute(&bar(90.0, 3)).is_some());
638 assert!(
639 (bb.value().unwrap().middle() - cloned.value().unwrap().middle()).abs() > 1e-10
640 );
641 }
642 }
643
644 mod price_source {
645 use super::*;
646
647 #[test]
648 fn hl2_source() {
649 let mut bb = Bb::new(
650 BbConfig::builder()
651 .length(NonZero::new(2).unwrap())
652 .source(PriceSource::HL2)
653 .build(),
654 );
655 bb.compute(&Bar::new(0.0, 20.0, 10.0, 0.0).at(1)); let v = bb.compute(&Bar::new(0.0, 30.0, 20.0, 0.0).at(2)).unwrap(); assert!((v.middle() - 20.0).abs() < 1e-10);
660 }
661 }
662
663 mod display {
664 use super::*;
665
666 #[test]
667 fn bb_formats_correctly() {
668 let bb = bb(20);
669 assert_eq!(bb.to_string(), "BB(20, Close, 2)");
670 }
671
672 #[test]
673 fn bb_value_formats_correctly() {
674 let v = BbValue {
675 upper: 6.0,
676 middle: 4.0,
677 lower: 2.0,
678 };
679 assert_eq!(v.to_string(), "BB(u: 6, m: 4, l: 2)");
680 }
681
682 #[test]
683 fn config_formats_correctly() {
684 let config = BbConfig::builder()
685 .length(NonZero::new(20).unwrap())
686 .build();
687 assert_eq!(config.to_string(), "BbConfig(20, Close, 2)");
688 }
689 }
690
691 mod eq_and_hash {
692 use super::*;
693 use std::collections::HashSet;
694
695 #[test]
696 fn identical_configs_match() {
697 let a = BbConfig::builder()
698 .length(NonZero::new(20).unwrap())
699 .build();
700 let b = BbConfig::builder()
701 .length(NonZero::new(20).unwrap())
702 .build();
703 let c = BbConfig::builder()
704 .length(NonZero::new(10).unwrap())
705 .build();
706
707 let mut set = HashSet::new();
708 set.insert(a);
709
710 assert!(set.contains(&b));
711 assert!(!set.contains(&c));
712 }
713 }
714}