// Band indicators and volume-weighted averaging.

///|
/// Bollinger Bands: upper / middle / lower arrays of equal length. Produced
/// by `bbands`; fields are readable everywhere but the struct is only
/// constructed inside this package.
pub struct BBands {
  upper : Array[Double]
  middle : Array[Double]
  lower : Array[Double]
}

///|
/// Number of leading NaN values produced by `bbands` for the given period.
pub fn bbands_lookback(period : Int32) -> Int32 {
  period - 1
}

///|
/// Bollinger Bands over `data` with a moving window of `period` values and
/// `ndev` standard deviations (population stddev, TA-Lib convention).
/// A NaN inside a window makes all three outputs NaN at that position.
pub fn bbands(
  data : Array[Double],
  period : Int32,
  ndev~ : Double,
) -> BBands raise @base.TaError {
  @base.validate_series(data, period)
  let s1 = @base.rolling_sum(data, period)
  let sq : Array[Double] = []
  for v in data {
    sq.push(v * v)
  }
  let s2 = @base.rolling_sum(sq, period)
  let pd = period.to_double()
  let n = data.length()
  let upper : Array[Double] = []
  let middle : Array[Double] = []
  let lower : Array[Double] = []
  for i in 0.. Array[Double] raise @base.TaError {
  @base.validate_series(price, period)
  if volume.length() != price.length() {
    raise @base.MismatchedLength("price and volume must have equal lengths")
  }
  let pv : Array[Double] = []
  for i in 0.. Array[Double] raise @base.TaError {
  @base.validate_candles(candles)
  let hh = @base.rolling_max(candles.high, period)
  let ll = @base.rolling_min(candles.low, period)
  let n = candles.close.length()
  let out : Array[Double] = []
  for i in 0..