243 lines
6.0 KiB
JavaScript
243 lines
6.0 KiB
JavaScript
/**
|
|
* 보조지표 계산 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');
|