// Namespace setup window.App = window.App || {}; window.App.Indicators = (function() { function computeEMA(arr, period) { const k = 2 / (period + 1); const out = []; let emaPrev = null; for (let i = 0; i < arr.length; i++) { const price = arr[i]; if (price == null || !isFinite(price)) { out.push(null); continue; } if (emaPrev == null) { const start = Math.max(0, i - period + 1); const windowArr = arr.slice(start, i + 1).filter(v => v != null && isFinite(v)); const sma = windowArr.length ? windowArr.reduce((a,b)=>a+b,0)/windowArr.length : price; emaPrev = sma; } const ema = price * k + emaPrev * (1 - k); out.push(ema); emaPrev = ema; } return out; } function calculateMA(data, type, length, source) { if (!data || data.length < length) return []; const sourceData = data.map(candle => { switch(source) { case 'open': return candle.open; case 'high': return candle.high; case 'low': return candle.low; case 'close': return candle.close; case 'hl2': return (candle.high + candle.low) / 2; case 'hlc3': return (candle.high + candle.low + candle.close) / 3; case 'ohlc4': return (candle.open + candle.high + candle.low + candle.close) / 4; default: return candle.close; } }); const result = []; for (let i = length - 1; i < sourceData.length; i++) { let value; switch(type) { case 'SMA': value = sourceData.slice(i - length + 1, i + 1).reduce((sum, v) => sum + v, 0) / length; break; case 'EMA': const multiplier = 2 / (length + 1); if (result.length === 0) { value = sourceData.slice(i - length + 1, i + 1).reduce((sum, v) => sum + v, 0) / length; } else { value = sourceData[i] * multiplier + result[result.length - 1].value * (1 - multiplier); } break; case 'WMA': let weightSum = 0; let valueSum = 0; for (let j = 0; j < length; j++) { const weight = j + 1; weightSum += weight; valueSum += sourceData[i - length + 1 + j] * weight; } value = valueSum / weightSum; break; default: value = sourceData[i]; } if (value == null || !isFinite(value) || data[i].time == null || !isFinite(Number(data[i].time))) { continue; } result.push({ time: Math.floor(Number(data[i].time)), value: Number(value) }); } return result; } function calculateBB(data, length, upperMultiplier, lowerMultiplier, source) { if (!data || data.length < length) return []; const sourceData = data.map(candle => { switch(source) { case 'open': return candle.open; case 'high': return candle.high; case 'low': return candle.low; case 'close': return candle.close; case 'hl2': return (candle.high + candle.low) / 2; case 'hlc3': return (candle.high + candle.low + candle.close) / 3; case 'ohlc4': return (candle.open + candle.high + candle.low + candle.close) / 4; default: return candle.close; } }); const result = []; for (let i = length - 1; i < sourceData.length; i++) { const start = Math.max(0, i - length + 1); const slice = sourceData.slice(start, i + 1); const avg = slice.reduce((sum, v) => sum + v, 0) / length; const std = Math.sqrt(slice.reduce((sum, v) => sum + Math.pow(v - avg, 2), 0) / length); result.push({ time: data[i].time, upper: avg + upperMultiplier * std, middle: avg, lower: avg - lowerMultiplier * std }); } return result; } return { computeEMA, calculateMA, calculateBB }; })();