트레이딩 봇에 자주 쓰는 기술적 지표 5가지 — 이동평균·RSI·MACD·볼린저·ATR 계산법
봇을 만들 때 지표를 스무 개씩 넣을 필요는 없습니다. 실제로 코드에 남는 건 대개 다섯 개 안쪽입니다. 이 글은 그 다섯 개 — 이동평균, RSI, MACD, 볼린저밴드, ATR — 를 의미 한 줄 + 파이썬 계산 코드로 정리합니다.
⚠️ 이 글은 교육·정보 제공 목적입니다. 특정 지표가 수익을 보장하지 않으며, 투자 손실 책임은 본인에게 있습니다.
아래 코드는 모두 pandas DataFrame df에 high/low/close 컬럼이 있다고 가정합니다.
1. 이동평균(MA) — 추세의 방향
최근 N일 가격의 평균입니다. 잔떨림을 지우고 지금 오르는 흐름인지만 보려는 용도입니다.
df["sma20"] = df["close"].rolling(20).mean() # 단순 이동평균
df["ema20"] = df["close"].ewm(span=20).mean() # 지수 이동평균(최근에 가중)
단순(SMA)은 모든 날을 똑같이, 지수(EMA)는 최근 날에 더 큰 비중을 둡니다. 반응 속도가 필요하면 EMA, 안정성이 필요하면 SMA입니다.
오해 주의: 이동평균은 예측 도구가 아니라 정리 도구입니다. 항상 실제 가격보다 늦게 움직입니다. 늦는 건 결함이 아니라 원리입니다.
2. RSI — 최근 상승·하락의 힘 비율
RSI(상대강도지수)는 0~100 사이 값으로, 최근 오른 폭과 내린 폭의 비율을 나타냅니다. 보통 70 위를 과매수, 30 아래를 과매도라고 부릅니다.
def rsi(close, period=14):
delta = close.diff() # 전일 대비 변화
gain = delta.clip(lower=0) # 오른 날의 상승폭
loss = -delta.clip(upper=0) # 내린 날의 하락폭(양수)
# Wilder 방식 평활 — 표준 RSI 정의
avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
rs = avg_gain / avg_loss.replace(0, 1e-9) # 0 나눗셈 방지
return 100 - (100 / (1 + rs))
df["rsi14"] = rsi(df["close"])
인터넷 예제 중에는 rolling().mean()으로 계산한 것도 많은데, 값이 원조 정의와 미세하게 다릅니다. 차트 프로그램과 숫자를 맞추려면 위처럼 Wilder 평활을 쓰세요.
오해 주의: "RSI 30 아래 = 매수 신호"가 아닙니다. 강한 하락 추세에서는 RSI가 20대에 몇 주씩 머무릅니다. 역추세 진입은 추세 필터와 함께 써야 합니다.
3. MACD — 두 이동평균의 간격
단기 EMA와 장기 EMA의 차이를 그린 것입니다. 간격이 벌어지면 추세가 강해지는 중, 좁아지면 힘이 빠지는 중으로 읽습니다.
def macd(close, fast=12, slow=26, signal=9):
ema_fast = close.ewm(span=fast).mean()
ema_slow = close.ewm(span=slow).mean()
macd_line = ema_fast - ema_slow # MACD 선
signal_line = macd_line.ewm(span=signal).mean()
hist = macd_line - signal_line # 히스토그램
return macd_line, signal_line, hist
df["macd"], df["signal"], df["hist"] = macd(df["close"])
# 골든크로스: 어제는 아래, 오늘은 위
buy = (df["macd"] > df["signal"]) & (df["macd"].shift(1) <= df["signal"].shift(1))
MACD는 이동평균 기반이라 횡보장에서 신호가 폭발합니다. 방향 없이 오르내리는 구간에서 교차가 계속 발생해 수수료만 쌓입니다. 변동성 필터를 같이 쓰는 이유입니다.
4. 볼린저밴드 — 평균에서 얼마나 벗어났나
이동평균 위아래로 표준편차의 2배만큼 선을 그은 것입니다. 가격이 통계적으로 얼마나 튀었는지를 봅니다.
def bollinger(close, period=20, k=2):
mid = close.rolling(period).mean()
std = close.rolling(period).std()
return mid - k * std, mid, mid + k * std
df["bb_low"], df["bb_mid"], df["bb_up"] = bollinger(df["close"])
# 밴드 폭 — 좁으면 변동성 수축(스퀴즈)
df["bb_width"] = (df["bb_up"] - df["bb_low"]) / df["bb_mid"]
실무에서 더 자주 쓰는 건 상·하단 터치보다 밴드 폭입니다. 폭이 극단적으로 좁아진 뒤 큰 움직임이 나오는 경향이 있어, 전략의 진입 타이밍 필터로 씁니다.
5. ATR — 이 종목의 평소 흔들림 크기
가장 저평가된 지표입니다. 방향을 말하지 않고 변동 폭의 크기만 알려줍니다. 손절 폭과 포지션 크기를 정할 때 씁니다.
def atr(df, period=14):
prev_close = df["close"].shift(1)
tr = pd.concat([
df["high"] - df["low"], # 오늘 고저 폭
(df["high"] - prev_close).abs(), # 갭 상승 반영
(df["low"] - prev_close).abs(), # 갭 하락 반영
], axis=1).max(axis=1)
return tr.ewm(alpha=1/period, adjust=False).mean()
df["atr14"] = atr(df)
# 손절선을 ATR 기준으로 (종목마다 자동으로 폭이 달라짐)
entry = df["close"].iloc[-1]
stop = entry - 2 * df["atr14"].iloc[-1]
"손절 3%"처럼 고정 비율을 쓰면, 하루에 5% 움직이는 종목에서는 진입하자마자 털립니다. ATR 기준 손절은 종목의 성격에 맞춰 폭이 자동 조정됩니다.
지표를 쓸 때의 원칙
- 역할이 겹치는 지표를 겹쳐 쓰지 않기 — MACD와 이동평균 교차는 사실상 같은 이야기를 두 번 하는 것입니다.
- 추세용 + 변동성용 조합 — 방향(MA/MACD)과 크기(ATR/밴드폭)를 하나씩 섞는 게 실용적입니다.
- 파라미터를 과하게 맞추지 않기 — 14가 13일 때 수익이 두 배라면, 그건 발견이 아니라 과최적화입니다.
- 신호는 하루 미뤄 적용 — 오늘 종가로 만든 신호를 오늘 종가에 체결하는 백테스트는 현실에 없습니다.
정리
- MA — 방향, 늦는 게 정상
- RSI — 상승/하락 힘의 비율, Wilder 평활로 계산
- MACD — 두 EMA의 간격, 횡보장 취약
- 볼린저 — 이탈보다 밴드 폭이 유용
- ATR — 손절·수량 산정의 기준
지표는 많이 아는 것보다, 왜 그 지표를 넣었는지 설명할 수 있는 것이 중요합니다. 설명이 안 되는 지표는 빼는 편이 대체로 낫습니다.
댓글
댓글 쓰기