// Self-contained Bollinger Bands indicator // Includes math, metadata, signal calculation, and base class // Signal constants (defined in each indicator file) const SIGNAL_TYPES = { BUY: 'buy', SELL: 'sell', HOLD: 'hold' }; const SIGNAL_COLORS = { buy: '#26a69a', hold: '#787b86', sell: '#ef5350' }; // Base class (inline replacement for BaseIndicator) class BaseIndicator { constructor(config) { this.id = config.id; this.type = config.type; this.name = config.name; this.params = config.params || {}; this.timeframe = config.timeframe || '1m'; this.series = []; this.visible = config.visible !== false; this.cachedResults = null; this.cachedMeta = null; this.lastSignalTimestamp = null; this.lastSignalType = null; } } // Signal calculation for Bollinger Bands function calculateBollingerBandsSignal(indicator, lastCandle, prevCandle, values, prevValues) { const close = lastCandle.close; const prevClose = prevCandle?.close; const upper = values?.upper; const lower = values?.lower; const prevUpper = prevValues?.upper; const prevLower = prevValues?.lower; if (!upper || !lower || prevUpper === undefined || prevLower === undefined || prevClose === undefined) { return null; } // BUY: Price crosses DOWN through lower band (reversal/bounce play) if (prevClose > prevLower && close <= lower) { return { type: SIGNAL_TYPES.BUY, strength: 70, value: close, reasoning: `Price crossed DOWN through lower Bollinger Band` }; } // SELL: Price crosses UP through upper band (overextended play) else if (prevClose < prevUpper && close >= upper) { return { type: SIGNAL_TYPES.SELL, strength: 70, value: close, reasoning: `Price crossed UP through upper Bollinger Band` }; } return null; } // Bollinger Bands Indicator class export class BollingerBandsIndicator extends BaseIndicator { constructor(config) { super(config); this.lastSignalTimestamp = null; this.lastSignalType = null; } calculate(candles) { const period = this.params.period || 20; const stdDevMult = this.params.stdDev || 2; const results = new Array(candles.length).fill(null); for (let i = period - 1; i < candles.length; i++) { let sum = 0; for (let j = 0; j < period; j++) sum += candles[i-j].close; const sma = sum / period; let diffSum = 0; for (let j = 0; j < period; j++) diffSum += Math.pow(candles[i-j].close - sma, 2); const stdDev = Math.sqrt(diffSum / period); results[i] = { middle: sma, upper: sma + (stdDevMult * stdDev), lower: sma - (stdDevMult * stdDev) }; } return results; } getMetadata() { return { name: 'Bollinger Bands', description: 'Volatility bands around a moving average', inputs: [ { name: 'period', label: 'Period', type: 'number', default: 20, min: 1, max: 100 }, { name: 'stdDev', label: 'Std Dev', type: 'number', default: 2, min: 0.5, max: 5, step: 0.5 } ], plots: [ { id: 'upper', color: '#4caf50', title: 'Upper' }, { id: 'middle', color: '#4caf50', title: 'Middle', lineStyle: 2 }, { id: 'lower', color: '#4caf50', title: 'Lower' } ], displayMode: 'overlay' }; } } export { calculateBollingerBandsSignal };