quant-indicators 0.7.0

Pure indicator math library for trading — MA, RSI, Bollinger, MACD, ATR, HRP
Documentation
//! Bollinger Bands indicator.

use quant_primitives::Candle;
use rust_decimal::Decimal;

use crate::error::IndicatorError;
use crate::indicator::Indicator;
use crate::series::Series;
use crate::sma::Sma;
use crate::stddev::StdDev;

/// Bollinger Bands result containing all three bands.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BollingerResult {
    /// Upper band (middle + multiplier * stddev)
    pub upper: Series,
    /// Middle band (SMA)
    pub middle: Series,
    /// Lower band (middle - multiplier * stddev)
    pub lower: Series,
}

impl BollingerResult {
    /// Get the number of values in the bands.
    #[must_use]
    pub fn len(&self) -> usize {
        self.middle.len()
    }

    /// Check if the result is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.middle.is_empty()
    }
}

/// Bollinger Bands indicator.
///
/// Bollinger Bands consist of:
/// - Middle band: Simple Moving Average
/// - Upper band: Middle + (multiplier * standard deviation)
/// - Lower band: Middle - (multiplier * standard deviation)
///
/// # Standard Parameters
///
/// - Period: 20
/// - Multiplier: 2.0
///
/// # Example
///
/// ```
/// use quant_indicators::BollingerBands;
/// use quant_primitives::Candle;
/// use chrono::Utc;
/// use rust_decimal_macros::dec;
///
/// let ts = Utc::now();
/// let candles: Vec<Candle> = (0..20).map(|i| {
///     Candle::new(dec!(100), dec!(110), dec!(90), dec!(100) + rust_decimal::Decimal::from(i), dec!(1000), ts).unwrap()
/// }).collect();
/// let bb = BollingerBands::new(20, dec!(2)).unwrap();
/// let result = bb.compute(&candles).unwrap();
/// // result.upper, result.middle, result.lower
/// ```
#[derive(Debug, Clone)]
pub struct BollingerBands {
    period: usize,
    multiplier: Decimal,
    name: String,
}

impl BollingerBands {
    /// Create a new Bollinger Bands indicator.
    ///
    /// # Arguments
    ///
    /// * `period` - Period for SMA and StdDev (typically 20)
    /// * `multiplier` - StdDev multiplier for bands (typically 2)
    ///
    /// # Errors
    ///
    /// Returns `InvalidParameter` if period is 0 or multiplier is negative.
    pub fn new(period: usize, multiplier: Decimal) -> Result<Self, IndicatorError> {
        if period == 0 {
            return Err(IndicatorError::InvalidParameter {
                message: "BollingerBands period must be > 0".to_string(),
            });
        }
        if multiplier.is_sign_negative() {
            return Err(IndicatorError::InvalidParameter {
                message: "BollingerBands multiplier must be >= 0".to_string(),
            });
        }
        Ok(Self {
            period,
            multiplier,
            name: format!("BB({},{})", period, multiplier),
        })
    }

    /// Create Bollinger Bands with standard parameters (20, 2).
    pub fn standard() -> Result<Self, IndicatorError> {
        Self::new(20, Decimal::TWO)
    }

    /// Get the indicator name.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Minimum number of candles required.
    pub fn warmup_period(&self) -> usize {
        self.period
    }

    /// Compute Bollinger Bands from candle data.
    ///
    /// Returns a `BollingerResult` with upper, middle, and lower bands.
    pub fn compute(&self, candles: &[Candle]) -> Result<BollingerResult, IndicatorError> {
        if candles.len() < self.period {
            return Err(IndicatorError::InsufficientData {
                required: self.period,
                actual: candles.len(),
            });
        }

        let sma = Sma::new(self.period)?;
        let stddev = StdDev::new(self.period)?;

        let middle_series = sma.compute(candles)?;
        let stddev_series = stddev.compute(candles)?;

        let middle_values = middle_series.values();
        let stddev_values = stddev_series.values();

        let mut upper_values = Vec::with_capacity(middle_values.len());
        let mut lower_values = Vec::with_capacity(middle_values.len());

        for (i, (ts, middle)) in middle_values.iter().enumerate() {
            let std = stddev_values[i].1;
            let band_width = self.multiplier * std;
            upper_values.push((*ts, *middle + band_width));
            lower_values.push((*ts, *middle - band_width));
        }

        Ok(BollingerResult {
            upper: Series::new(upper_values),
            middle: middle_series,
            lower: Series::new(lower_values),
        })
    }
}

#[cfg(test)]
#[path = "bollinger_tests.rs"]
mod tests;