// 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..