indexes_rs/v1/bollinger/
main.rs

1//! # Bollinger Bands Module
2//!
3//! This module implements Bollinger Bands using a Simple Moving Average (SMA) for the middle band
4//! and the standard deviation of prices for the band width. The upper band is defined as the middle
5//! band plus a specified multiplier times the standard deviation, and the lower band is defined as the
6//! middle band minus that value.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use indexes_rs::v1::bollinger::main::BollingerBands;
12//! use indexes_rs::v1::bollinger::types::BBResult;
13//!
14//! // Create a BollingerBands indicator with a period of 20 and multiplier of 2.0
15//! let mut bb = BollingerBands::new(20, 2.0).unwrap();
16//!
17//! // Feed in prices (e.g., closing prices)
18//! let prices = vec![
19//!     100.0, 101.0, 102.0, 101.5, 100.5, 102.0, 103.0, 102.5, 104.0, 105.0,
20//!     104.5, 105.5, 106.0, 107.0, 106.5, 108.0, 107.5, 108.5, 109.0, 110.0,
21//! ];
22//!
23//! if let Some(result) = prices.into_iter().fold(None, |_, price| bb.calculate(price)) {
24//!     println!("Upper Band: {:.2}", result.upper);
25//!     println!("Middle Band: {:.2}", result.middle);
26//!     println!("Lower Band: {:.2}", result.lower);
27//! }
28//! ```
29
30use super::types::BBResult;
31use crate::v1::sma::main::{SMAError, SimpleMovingAverage};
32use std::collections::VecDeque;
33
34/// Bollinger Bands indicator.
35pub struct BollingerBands {
36    sma: SimpleMovingAverage,
37    period: usize,
38    multiplier: f64,
39    values: VecDeque<f64>,
40}
41
42impl BollingerBands {
43    /// Creates a new BollingerBands indicator.
44    ///
45    /// # Arguments
46    ///
47    /// * `period` - The number of values for the moving average and standard deviation.
48    /// * `multiplier` - The multiplier applied to the standard deviation to determine band width.
49    ///
50    /// # Returns
51    ///
52    /// * `Ok(BollingerBands)` on success, or `Err(SMAError)` if the underlying SMA creation fails.
53    ///
54    /// # Examples
55    ///
56    /// ```rust
57    /// use indexes_rs::v1::bollinger::main::BollingerBands;
58    ///
59    /// let bb = BollingerBands::new(20, 2.0);
60    /// assert!(bb.is_ok());
61    /// ```
62    pub fn new(period: usize, multiplier: f64) -> Result<Self, SMAError> {
63        Ok(BollingerBands {
64            sma: SimpleMovingAverage::new(period)?,
65            period,
66            multiplier,
67            values: VecDeque::with_capacity(period),
68        })
69    }
70
71    /// Calculates the Bollinger Bands for the given price.
72    ///
73    /// The method updates the internal SMA and sliding window of prices.
74    /// It returns a `BBResult` containing the upper, middle, and lower bands when enough data is available.
75    ///
76    /// # Arguments
77    ///
78    /// * `price` - The latest price.
79    ///
80    /// # Returns
81    ///
82    /// * `Some(BBResult)` if enough data is available.
83    /// * `None` if not enough data has been collected.
84    pub fn calculate(&mut self, price: f64) -> Option<BBResult> {
85        self.sma.add_value(price);
86        self.values.push_back(price);
87        if self.values.len() > self.period {
88            self.values.pop_front();
89        }
90        let middle = self.sma.calculate()?;
91        let std_dev = self.calculate_std_dev(middle.value)?;
92        let band_width = std_dev * self.multiplier;
93        Some(BBResult {
94            upper: middle.value + band_width,
95            middle: middle.value,
96            lower: middle.value - band_width,
97        })
98    }
99
100    /// Calculates the standard deviation of the prices in the current window.
101    ///
102    /// # Arguments
103    ///
104    /// * `mean` - The mean (middle band) value.
105    ///
106    /// # Returns
107    ///
108    /// * `Some(f64)` containing the standard deviation if the window is full.
109    /// * `None` if the window does not yet contain enough values.
110    fn calculate_std_dev(&self, mean: f64) -> Option<f64> {
111        if self.values.len() < self.period {
112            return None;
113        }
114        let variance = self
115            .values
116            .iter()
117            .map(|&value| {
118                let diff = mean - value;
119                diff * diff
120            })
121            .sum::<f64>()
122            / self.period as f64;
123        Some(variance.sqrt())
124    }
125}