118 lines
3.7 KiB
JavaScript
118 lines
3.7 KiB
JavaScript
// 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 }; |