RSI · MA · 볼린저 밴드 · MACD — 라이브러리 없이 수식으로
지표 라이브러리는 어디든 있습니다. 하지만 수식이 무엇을 보고 있는지 이해해야 신호를 해석할 수 있습니다. n8n Code 노드(JavaScript)로 4개 지표를 직접 계산하고, 결과를 Sheets에 append.
시세이력 시트지표이력 시트 — 종목별 일자별 지표값MA(n) = sum(close[t-n+1 ... t]) / n 권장: MA20 (단기), MA60 (중기), MA120 (장기)
변화량 diff[t] = close[t] - close[t-1] gain = max(diff, 0) loss = max(-diff, 0) avg_gain = EMA(gain, 14) avg_loss = EMA(loss, 14) RS = avg_gain / avg_loss RSI = 100 - (100 / (1 + RS)) 해석: RSI > 70 과매수 / RSI < 30 과매도
middle = MA(close, 20) std = stdev(close, 20) upper = middle + 2*std lower = middle - 2*std 해석: close가 upper 돌파 → 단기 과열, lower 돌파 → 단기 과매도
EMA12 = EMA(close, 12) EMA26 = EMA(close, 26) MACD = EMA12 - EMA26 Signal = EMA(MACD, 9) Hist = MACD - Signal 해석: Hist가 0 위로 교차 → 매수 시그널, 0 아래 교차 → 매도 시그널
// 입력: items = [{ticker, date, close}, ...] (티커별·날짜순 정렬)
// 출력: items + RSI, MA20, MA60, BB_upper, BB_lower, MACD, Signal, Hist
const byTicker = {};
for (const it of items) {
const k = it.json.ticker;
if (!byTicker[k]) byTicker[k] = [];
byTicker[k].push(it.json);
}
const result = [];
for (const ticker of Object.keys(byTicker)) {
const rows = byTicker[ticker].sort((a,b)=>a.date.localeCompare(b.date));
const closes = rows.map(r=>+r.close);
// MA helper
const ma = (arr, n, i) => i<n-1 ? null : arr.slice(i-n+1, i+1).reduce((a,b)=>a+b,0)/n;
// EMA helper
const ema = (arr, n) => {
const k = 2/(n+1); const out = []; let prev = arr[0];
for (let i=0; i<arr.length; i++){ prev = i===0 ? arr[0] : arr[i]*k + prev*(1-k); out.push(prev); }
return out;
};
const ema12 = ema(closes, 12);
const ema26 = ema(closes, 26);
const macd = closes.map((_,i)=>ema12[i]-ema26[i]);
const sig = ema(macd, 9);
for (let i=0; i<rows.length; i++){
const win = closes.slice(Math.max(0,i-19), i+1);
const m20 = ma(closes, 20, i);
const std = Math.sqrt(win.reduce((s,v)=>s+(v-m20)**2, 0)/win.length);
rows[i].MA20 = m20;
rows[i].BB_up = m20 ? m20+2*std : null;
rows[i].BB_lo = m20 ? m20-2*std : null;
rows[i].MACD = macd[i];
rows[i].Signal= sig[i];
rows[i].Hist = macd[i]-sig[i];
// RSI는 별도 함수로 (생략 — 매뉴얼에 전체 코드 동봉)
}
result.push(...rows.map(r => ({json: r})));
}
return result;
cross_signal