/**
* Bollinger Bands Width Indicator
* Measures the width between Bollinger Bands upper and lower bands
*/
import { calculateSMA } from './moving-averages';
export function calculateBBWidth(closes, period = 20, stdDev = 2) {
// Minimum 5 data points required
if (closes.length < 5) {
return {
width: null,
squeeze: null,
trend: null,
};
}
// Use adaptive period
const effectivePeriod = Math.min(period, closes.length);
// Calculate SMA for middle band
const sma = calculateSMA(closes, effectivePeriod);
let middleBand;
if (sma.length === 0) {
// Fallback: use simple average
middleBand = closes.slice(-effectivePeriod).reduce((a, b) => a + b, 0) / effectivePeriod;
}
else {
middleBand = sma[sma.length - 1];
}
// Calculate standard deviation
const recentData = closes.slice(-effectivePeriod);
const variance = recentData.reduce((sum, value) => {
return sum + Math.pow(value - middleBand, 2);
}, 0) / effectivePeriod;
const standardDeviation = Math.sqrt(variance);
// Calculate bands
const upperBand = middleBand + (standardDeviation * stdDev);
const lowerBand = middleBand - (standardDeviation * stdDev);
// Calculate width
const width = (upperBand - lowerBand) / middleBand;
// Determine squeeze level
let squeeze = null;
if (width < 0.02)
squeeze = 'extreme'; // Very tight bands
else if (width < 0.05)
squeeze = 'tight'; // Tight bands
else if (width > 0.15)
squeeze = 'wide'; // Wide bands
else
squeeze = 'normal';
// Determine trend (simplified - would need historical data for proper trend)
let trend = null;
if (closes.length >= effectivePeriod * 2) {
// Compare with previous period
const prevData = closes.slice(-(effectivePeriod * 2), -effectivePeriod);
const prevSMA = calculateSMA(prevData, effectivePeriod);
const prevMiddle = prevSMA.length > 0 ? prevSMA[prevSMA.length - 1] : prevData.reduce((a, b) => a + b, 0) / prevData.length;
if (prevMiddle) {
const prevVariance = prevData.reduce((sum, value) => {
return sum + Math.pow(value - prevMiddle, 2);
}, 0) / effectivePeriod;
const prevStdDev = Math.sqrt(prevVariance);
const prevUpper = prevMiddle + (prevStdDev * stdDev);
const prevLower = prevMiddle - (prevStdDev * stdDev);
const prevWidth = (prevUpper - prevLower) / prevMiddle;
if (width > prevWidth * 1.1)
trend = 'expanding';
else if (width < prevWidth * 0.9)
trend = 'contracting';
else
trend = 'stable';
}
}
return {
width,
squeeze,
trend,
};
}