/** * Indicator Definition Object for Bollinger Bands (BB). * This object is used by the indicator manager to create and control the indicator. */ const BB_INDICATOR = { name: 'BB', label: 'Bollinger Bands (3 Sets)', usesBaseData: true, // This indicator needs the raw 1m data for its own aggregation params: [ { name: 'timeframe', type: 'number', defaultValue: 1440, min: 1, label: 'Timeframe (min)' }, ], // Hardcoded internal parameters, no longer exposed to the user. internalParams: { bb1_len_upper: 20, bb1_std_upper: 1.6, bb1_len_lower: 20, bb1_std_lower: 1.6, bb2_len_upper: 20, bb2_std_upper: 2.4, bb2_len_lower: 20, bb2_std_lower: 2.4, bb3_len_upper: 20, bb3_std_upper: 3.3, bb3_len_lower: 20, bb3_std_lower: 3.3, }, calculateFull: calculateFullBollingerBands, }; /** * Calculates all three sets of Bollinger Bands for the entire dataset. * @param {Array} data An array of candle objects with 'close' and 'time' properties. * @param {Object} params An object containing the indicator's parameters (only timeframe from UI). * @returns {Object} An object containing arrays of data points for each band. */ function calculateFullBollingerBands(data, params) { const { timeframe } = params; const internal = BB_INDICATOR.internalParams; // First, aggregate the base data into the desired timeframe for the BB calculation. const aggregatedData = aggregateCandles(data, timeframe); if (aggregatedData.length === 0) { return { bb1_upper: [], bb1_lower: [], bb2_upper: [], bb2_lower: [], bb3_upper: [], bb3_lower: [] }; } // Calculate each set of bands using the aggregated data const bb1 = calculateBands(aggregatedData, internal.bb1_len_upper, internal.bb1_std_upper, internal.bb1_len_lower, internal.bb1_std_lower); const bb2 = calculateBands(aggregatedData, internal.bb2_len_upper, internal.bb2_std_upper, internal.bb2_len_lower, internal.bb2_std_lower); const bb3 = calculateBands(aggregatedData, internal.bb3_len_upper, internal.bb3_std_upper, internal.bb3_len_lower, internal.bb3_std_lower); // Extend the last point of each band to the latest candle time const lastCandleTime = data[data.length - 1].time; const extendLine = (bandData) => { if (bandData.length > 0) { const lastPoint = bandData[bandData.length - 1]; if (lastPoint.time < lastCandleTime) { bandData.push({ ...lastPoint, time: lastCandleTime }); } } return bandData; }; return { bb1_upper: extendLine(bb1.upper), bb1_lower: extendLine(bb1.lower), bb2_upper: extendLine(bb2.upper), bb2_lower: extendLine(bb2.lower), bb3_upper: extendLine(bb3.upper), bb3_lower: extendLine(bb3.lower), }; } /** * Helper function to calculate a single set of upper and lower Bollinger Bands. * @returns {{upper: Array, lower: Array}} */ function calculateBands(data, upperLength, upperStdDev, lowerLength, lowerStdDev) { const upperBand = []; const lowerBand = []; // Calculate Upper Band for (let i = upperLength - 1; i < data.length; i++) { const slice = data.slice(i - upperLength + 1, i + 1); const sma = slice.reduce((sum, candle) => sum + candle.close, 0) / upperLength; const stdDev = Math.sqrt(slice.reduce((sum, candle) => sum + Math.pow(candle.close - sma, 2), 0) / upperLength); upperBand.push({ time: slice[slice.length - 1].time, value: sma + (stdDev * upperStdDev) }); } // Calculate Lower Band for (let i = lowerLength - 1; i < data.length; i++) { const slice = data.slice(i - lowerLength + 1, i + 1); const sma = slice.reduce((sum, candle) => sum + candle.close, 0) / lowerLength; const stdDev = Math.sqrt(slice.reduce((sum, candle) => sum + Math.pow(candle.close - sma, 2), 0) / lowerLength); lowerBand.push({ time: slice[slice.length - 1].time, value: sma - (stdDev * lowerStdDev) }); } return { upper: upperBand, lower: lowerBand }; }