// Self-contained Moving Average indicator with SMA/EMA/RMA/WMA/VWMA support // 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; } } // Moving Average math (SMA/EMA/RMA/WMA/VWMA) function calculateSMA(candles, period, source = 'close') { const results = new Array(candles.length).fill(null); let sum = 0; for (let i = 0; i < candles.length; i++) { sum += candles[i][source]; if (i >= period) sum -= candles[i - period][source]; if (i >= period - 1) results[i] = sum / period; } return results; } function calculateEMA(candles, period, source = 'close') { const multiplier = 2 / (period + 1); const results = new Array(candles.length).fill(null); let ema = 0; let sum = 0; for (let i = 0; i < candles.length; i++) { if (i < period) { sum += candles[i][source]; if (i === period - 1) { ema = sum / period; results[i] = ema; } } else { ema = (candles[i][source] - ema) * multiplier + ema; results[i] = ema; } } return results; } function calculateRMA(candles, period, source = 'close') { const multiplier = 1 / period; const results = new Array(candles.length).fill(null); let rma = 0; let sum = 0; for (let i = 0; i < candles.length; i++) { if (i < period) { sum += candles[i][source]; if (i === period - 1) { rma = sum / period; results[i] = rma; } } else { rma = (candles[i][source] - rma) * multiplier + rma; results[i] = rma; } } return results; } function calculateWMA(candles, period, source = 'close') { const results = new Array(candles.length).fill(null); const weightSum = (period * (period + 1)) / 2; for (let i = period - 1; i < candles.length; i++) { let sum = 0; for (let j = 0; j < period; j++) { sum += candles[i - j][source] * (period - j); } results[i] = sum / weightSum; } return results; } function calculateVWMA(candles, period, source = 'close') { const results = new Array(candles.length).fill(null); for (let i = period - 1; i < candles.length; i++) { let sumPV = 0; let sumV = 0; for (let j = 0; j < period; j++) { sumPV += candles[i - j][source] * candles[i - j].volume; sumV += candles[i - j].volume; } results[i] = sumV !== 0 ? sumPV / sumV : null; } return results; } // Signal calculation for Moving Average function calculateMASignal(indicator, lastCandle, prevCandle, values) { const close = lastCandle.close; const ma = values?.ma; if (!ma && ma !== 0) { return null; } if (close > ma) { return { type: SIGNAL_TYPES.BUY, strength: Math.min(60 + ((close - ma) / ma) * 500, 100), value: close, reasoning: `Price (${close.toFixed(2)}) is above MA (${ma.toFixed(2)})` }; } else if (close < ma) { return { type: SIGNAL_TYPES.SELL, strength: Math.min(60 + ((ma - close) / ma) * 500, 100), value: close, reasoning: `Price (${close.toFixed(2)}) is below MA (${ma.toFixed(2)})` }; } else { return null; } } // MA Indicator class export class MAIndicator extends BaseIndicator { constructor(config) { super(config); this.lastSignalTimestamp = null; this.lastSignalType = null; } calculate(candles) { const maType = (this.params.maType || 'SMA').toLowerCase(); const period = this.params.period || 44; let maValues; switch (maType) { case 'sma': maValues = calculateSMA(candles, period, this.params.source || 'close'); break; case 'ema': maValues = calculateEMA(candles, period, this.params.source || 'close'); break; case 'rma': maValues = calculateRMA(candles, period, this.params.source || 'close'); break; case 'wma': maValues = calculateWMA(candles, period, this.params.source || 'close'); break; case 'vwma': maValues = calculateVWMA(candles, period, this.params.source || 'close'); break; default: maValues = calculateSMA(candles, period, this.params.source || 'close'); } return maValues.map(ma => ({ ma })); } getMetadata() { return { name: 'MA', description: 'Moving Average (SMA/EMA/RMA/WMA/VWMA)', inputs: [ { name: 'period', label: 'Period', type: 'number', default: 44, min: 1, max: 500 }, { name: 'maType', label: 'MA Type', type: 'select', options: ['SMA', 'EMA', 'RMA', 'WMA', 'VWMA'], default: 'SMA' } ], plots: [ { id: 'value', color: '#2962ff', title: 'MA', style: 'solid', width: 1 } ], displayMode: 'overlay' }; } } // Export signal function for external use export { calculateMASignal };