/** * 보조지표 계산 Web Worker * - 메인 스레드를 차단하지 않고 지표 계산 * - 복잡한 계산(MA, RSI, MACD 등)을 백그라운드에서 처리 */ self.addEventListener('message', (event) => { const { type, data } = event.data; try { switch (type) { case 'CALCULATE_MA': const maResult = calculateMA(data.candles, data.periods); self.postMessage({ type: 'MA_RESULT', data: maResult }); break; case 'CALCULATE_RSI': const rsiResult = calculateRSI(data.candles, data.period); self.postMessage({ type: 'RSI_RESULT', data: rsiResult }); break; case 'CALCULATE_MACD': const macdResult = calculateMACD(data.candles, data.fastPeriod, data.slowPeriod, data.signalPeriod); self.postMessage({ type: 'MACD_RESULT', data: macdResult }); break; case 'CALCULATE_STOCHASTIC': const stochResult = calculateStochastic(data.candles, data.kPeriod, data.dPeriod); self.postMessage({ type: 'STOCHASTIC_RESULT', data: stochResult }); break; case 'CALCULATE_ALL': const allResults = calculateAllIndicators(data.candles, data.config); self.postMessage({ type: 'ALL_RESULT', data: allResults }); break; default: self.postMessage({ type: 'ERROR', error: 'Unknown command' }); } } catch (error) { self.postMessage({ type: 'ERROR', error: error.message }); } }); /** * 이동평균선 계산 */ function calculateMA(candles, periods) { const results = {}; periods.forEach(period => { const ma = []; for (let i = 0; i < candles.length; i++) { if (i < period - 1) { ma.push(null); } else { let sum = 0; for (let j = 0; j < period; j++) { sum += candles[i - j].close; } ma.push(sum / period); } } results[`ma${period}`] = ma; }); return results; } /** * RSI 계산 */ function calculateRSI(candles, period = 14) { const rsi = []; const gains = []; const losses = []; for (let i = 0; i < candles.length; i++) { if (i === 0) { rsi.push(null); continue; } const change = candles[i].close - candles[i - 1].close; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? Math.abs(change) : 0); if (i < period) { rsi.push(null); continue; } const avgGain = gains.slice(-period).reduce((a, b) => a + b, 0) / period; const avgLoss = losses.slice(-period).reduce((a, b) => a + b, 0) / period; if (avgLoss === 0) { rsi.push(100); } else { const rs = avgGain / avgLoss; rsi.push(100 - (100 / (1 + rs))); } } return rsi; } /** * MACD 계산 */ function calculateMACD(candles, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { // EMA 계산 헬퍼 const calculateEMA = (values, period) => { const ema = []; const multiplier = 2 / (period + 1); for (let i = 0; i < values.length; i++) { if (i < period - 1) { ema.push(null); } else if (i === period - 1) { const sum = values.slice(0, period).reduce((a, b) => a + b, 0); ema.push(sum / period); } else { const value = (values[i] - ema[i - 1]) * multiplier + ema[i - 1]; ema.push(value); } } return ema; }; const closes = candles.map(c => c.close); const fastEMA = calculateEMA(closes, fastPeriod); const slowEMA = calculateEMA(closes, slowPeriod); const macdLine = []; for (let i = 0; i < candles.length; i++) { if (fastEMA[i] === null || slowEMA[i] === null) { macdLine.push(null); } else { macdLine.push(fastEMA[i] - slowEMA[i]); } } const signalLine = calculateEMA( macdLine.map(v => v === null ? 0 : v), signalPeriod ); const histogram = []; for (let i = 0; i < candles.length; i++) { if (macdLine[i] === null || signalLine[i] === null) { histogram.push(null); } else { histogram.push(macdLine[i] - signalLine[i]); } } return { macdLine, signalLine, histogram }; } /** * Stochastic 계산 */ function calculateStochastic(candles, kPeriod = 14, dPeriod = 3) { const stochK = []; for (let i = 0; i < candles.length; i++) { if (i < kPeriod - 1) { stochK.push(null); continue; } const slice = candles.slice(i - kPeriod + 1, i + 1); const highest = Math.max(...slice.map(c => c.high)); const lowest = Math.min(...slice.map(c => c.low)); const current = candles[i].close; if (highest === lowest) { stochK.push(50); } else { stochK.push(((current - lowest) / (highest - lowest)) * 100); } } // %D 계산 (K의 이동평균) const stochD = []; for (let i = 0; i < stochK.length; i++) { if (i < dPeriod - 1 || stochK[i] === null) { stochD.push(null); } else { const sum = stochK.slice(i - dPeriod + 1, i + 1).reduce((a, b) => a + (b || 0), 0); stochD.push(sum / dPeriod); } } return { stochK, stochD }; } /** * 모든 지표 한 번에 계산 */ function calculateAllIndicators(candles, config) { const results = {}; // MA if (config.maPeriods && config.maPeriods.length > 0) { Object.assign(results, calculateMA(candles, config.maPeriods)); } // RSI if (config.rsi) { results.rsi = calculateRSI(candles, config.rsiPeriod || 14); } // MACD if (config.macd) { const macd = calculateMACD( candles, config.macdFastPeriod || 12, config.macdSlowPeriod || 26, config.macdSignalPeriod || 9 ); results.macdLine = macd.macdLine; results.macdSignal = macd.signalLine; results.macdHistogram = macd.histogram; } // Stochastic if (config.stochastic) { const stoch = calculateStochastic( candles, config.stochasticKPeriod || 14, config.stochasticDPeriod || 3 ); results.stochasticK = stoch.stochK; results.stochasticD = stoch.stochD; } return results; } console.log('📊 Indicator Worker initialized');