Skip to main content

quant_indicators/
bollinger.rs

1//! Bollinger Bands indicator.
2
3use quant_primitives::Candle;
4use rust_decimal::Decimal;
5
6use crate::error::IndicatorError;
7use crate::indicator::Indicator;
8use crate::series::Series;
9use crate::sma::Sma;
10use crate::stddev::StdDev;
11
12/// Bollinger Bands result containing all three bands.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct BollingerResult {
15    /// Upper band (middle + multiplier * stddev)
16    pub upper: Series,
17    /// Middle band (SMA)
18    pub middle: Series,
19    /// Lower band (middle - multiplier * stddev)
20    pub lower: Series,
21}
22
23impl BollingerResult {
24    /// Get the number of values in the bands.
25    #[must_use]
26    pub fn len(&self) -> usize {
27        self.middle.len()
28    }
29
30    /// Check if the result is empty.
31    #[must_use]
32    pub fn is_empty(&self) -> bool {
33        self.middle.is_empty()
34    }
35}
36
37/// Bollinger Bands indicator.
38///
39/// Bollinger Bands consist of:
40/// - Middle band: Simple Moving Average
41/// - Upper band: Middle + (multiplier * standard deviation)
42/// - Lower band: Middle - (multiplier * standard deviation)
43///
44/// # Standard Parameters
45///
46/// - Period: 20
47/// - Multiplier: 2.0
48///
49/// # Example
50///
51/// ```
52/// use quant_indicators::BollingerBands;
53/// use quant_primitives::Candle;
54/// use chrono::Utc;
55/// use rust_decimal_macros::dec;
56///
57/// let ts = Utc::now();
58/// let candles: Vec<Candle> = (0..20).map(|i| {
59///     Candle::new(dec!(100), dec!(110), dec!(90), dec!(100) + rust_decimal::Decimal::from(i), dec!(1000), ts).unwrap()
60/// }).collect();
61/// let bb = BollingerBands::new(20, dec!(2)).unwrap();
62/// let result = bb.compute(&candles).unwrap();
63/// // result.upper, result.middle, result.lower
64/// ```
65#[derive(Debug, Clone)]
66pub struct BollingerBands {
67    period: usize,
68    multiplier: Decimal,
69    name: String,
70}
71
72impl BollingerBands {
73    /// Create a new Bollinger Bands indicator.
74    ///
75    /// # Arguments
76    ///
77    /// * `period` - Period for SMA and StdDev (typically 20)
78    /// * `multiplier` - StdDev multiplier for bands (typically 2)
79    ///
80    /// # Errors
81    ///
82    /// Returns `InvalidParameter` if period is 0 or multiplier is negative.
83    pub fn new(period: usize, multiplier: Decimal) -> Result<Self, IndicatorError> {
84        if period == 0 {
85            return Err(IndicatorError::InvalidParameter {
86                message: "BollingerBands period must be > 0".to_string(),
87            });
88        }
89        if multiplier.is_sign_negative() {
90            return Err(IndicatorError::InvalidParameter {
91                message: "BollingerBands multiplier must be >= 0".to_string(),
92            });
93        }
94        Ok(Self {
95            period,
96            multiplier,
97            name: format!("BB({},{})", period, multiplier),
98        })
99    }
100
101    /// Create Bollinger Bands with standard parameters (20, 2).
102    pub fn standard() -> Result<Self, IndicatorError> {
103        Self::new(20, Decimal::TWO)
104    }
105
106    /// Get the indicator name.
107    pub fn name(&self) -> &str {
108        &self.name
109    }
110
111    /// Minimum number of candles required.
112    pub fn warmup_period(&self) -> usize {
113        self.period
114    }
115
116    /// Compute Bollinger Bands from candle data.
117    ///
118    /// Returns a `BollingerResult` with upper, middle, and lower bands.
119    pub fn compute(&self, candles: &[Candle]) -> Result<BollingerResult, IndicatorError> {
120        if candles.len() < self.period {
121            return Err(IndicatorError::InsufficientData {
122                required: self.period,
123                actual: candles.len(),
124            });
125        }
126
127        let sma = Sma::new(self.period)?;
128        let stddev = StdDev::new(self.period)?;
129
130        let middle_series = sma.compute(candles)?;
131        let stddev_series = stddev.compute(candles)?;
132
133        let middle_values = middle_series.values();
134        let stddev_values = stddev_series.values();
135
136        let mut upper_values = Vec::with_capacity(middle_values.len());
137        let mut lower_values = Vec::with_capacity(middle_values.len());
138
139        for (i, (ts, middle)) in middle_values.iter().enumerate() {
140            let std = stddev_values[i].1;
141            let band_width = self.multiplier * std;
142            upper_values.push((*ts, *middle + band_width));
143            lower_values.push((*ts, *middle - band_width));
144        }
145
146        Ok(BollingerResult {
147            upper: Series::new(upper_values),
148            middle: middle_series,
149            lower: Series::new(lower_values),
150        })
151    }
152}
153
154#[cfg(test)]
155#[path = "bollinger_tests.rs"]
156mod tests;