
Indicator Lab 첫 번째 지표는 가장 기본이 되는 SMA, 단순이동평균입니다.
지표 소개
SMA는 일정 기간 동안의 종가를 단순 평균한 값입니다. 예를 들어 20 SMA라면, 최근 20개 봉의 종가를 더해서 20으로 나눈 것입니다.
SMA = (C₁ + C₂ + ... + Cₙ) / N
- C: 각 봉의 종가 (Close)
- N: 기간 (Period)
가격의 단기적인 노이즈를 제거하고, 전반적인 방향(추세)을 시각적으로 보여주는 역할을 합니다. 기간이 짧으면 가격에 민감하게 반응하고, 길면 느리지만 안정적인 흐름을 보여줍니다.
가설 설정
SMA를 활용한 가장 대표적인 전략은 골든크로스 / 데드크로스입니다.
- 골든크로스 (매수) — 단기 SMA가 장기 SMA를 위로 돌파
- 데드크로스 (매도) — 단기 SMA가 장기 SMA를 아래로 돌파
단기 SMA가 장기 SMA 위에 있다는 건 최근 가격 흐름이 장기 평균보다 강하다는 의미이고, 반대면 약하다는 의미입니다. 이 교차 시점을 진입/청산 신호로 사용합니다.
백테스트 조건
| 항목 | 설정 |
|---|---|
| 종목 | BTC/USDT (Binance Spot) |
| 기간 | 2018.01.01 ~ 2025.12.31 |
| 타임프레임 | 일봉 (1D) |
| 단기 SMA | 20 |
| 장기 SMA | 60 |
| 진입 | 골든크로스 시 매수 |
| 청산 | 데드크로스 시 매도 |
| 초기 자본 | $10,000 |
| 수수료 / 슬리피지 | 미적용 |
심플하게, 골든크로스가 발생하면 전액 매수하고 데드크로스가 발생하면 전액 매도하는 구조입니다.
숏(공매도)은 없고, 롱(매수) 포지션만 진행합니다.
코드 구현
import pandas as pd
import mplfinance as mpf
# ===== 설정 =====
DATA_FILE = "BTCUSDT_1d.csv"
SMA_SHORT = 20
SMA_LONG = 60
INITIAL_CAPITAL = 10000
ZOOM_DAYS = 180 # 확대 차트에 표시할 최근 일수
# ================
# 1. 데이터 로드
df = pd.read_csv(DATA_FILE, index_col="timestamp", parse_dates=True)
# 2. SMA 계산
df["sma_short"] = df["close"].rolling(window=SMA_SHORT).mean()
df["sma_long"] = df["close"].rolling(window=SMA_LONG).mean()
# 3. 신호 생성
# 단기 SMA가 장기 SMA 위에 있으면 1, 아래면 0
df["signal"] = 0
df.loc[df["sma_short"] > df["sma_long"], "signal"] = 1
# 신호가 바뀌는 시점만 추출 (0→1: 매수, 1→0: 매도)
df["position"] = df["signal"].diff()
# 4. 백테스트
capital = INITIAL_CAPITAL
holdings = 0 # 보유 수량
buy_price = 0
trades = [] # 거래 기록
for i in range(len(df)):
if df["position"].iloc[i] == 1: # 골든크로스 → 매수
buy_price = df["close"].iloc[i]
holdings = capital / buy_price
capital = 0
elif df["position"].iloc[i] == -1 and holdings > 0: # 데드크로스 → 매도
sell_price = df["close"].iloc[i]
capital = holdings * sell_price
pnl = (sell_price - buy_price) / buy_price * 100
trades.append({
"buy": buy_price,
"sell": sell_price,
"pnl_pct": round(pnl, 2)
})
holdings = 0
# 마지막에 포지션이 열려 있으면 현재가로 정리
if holdings > 0:
last_price = df["close"].iloc[-1]
capital = holdings * last_price
pnl = (last_price - buy_price) / buy_price * 100
trades.append({
"buy": buy_price,
"sell": last_price,
"pnl_pct": round(pnl, 2)
})
holdings = 0
# 5. 결과 출력
final_capital = capital
total_return = (final_capital - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100
win_trades = [t for t in trades if t["pnl_pct"] > 0]
lose_trades = [t for t in trades if t["pnl_pct"] <= 0]
# 바이앤홀드 비교
first_price = df["close"].dropna().iloc[0]
last_price = df["close"].dropna().iloc[-1]
bnh_return = (last_price - first_price) / first_price * 100
bnh_capital = INITIAL_CAPITAL * (1 + bnh_return / 100)
print("=" * 40)
print(f"SMA {SMA_SHORT}/{SMA_LONG} 백테스트 결과")
print("=" * 40)
print(f"초기 자본: ${INITIAL_CAPITAL:,.0f}")
print(f"최종 자본: ${final_capital:,.0f}")
print(f"총 수익률: {total_return:+.2f}%")
print(f"총 거래 수: {len(trades)}회")
print(f"승리: {len(win_trades)}회")
print(f"패배: {len(lose_trades)}회")
if trades:
print(f"승률: {len(win_trades)/len(trades)*100:.1f}%")
print("-" * 40)
print(f"바이앤홀드: ${bnh_capital:,.0f} ({bnh_return:+.2f}%)")
print("=" * 40)
# 6. 차트 1 — 전체 기간 라인 차트
ap_full = [
mpf.make_addplot(df["sma_short"], color="#FF6F00", width=1.5, label=f"SMA {SMA_SHORT}"),
mpf.make_addplot(df["sma_long"], color="#7B1FA2", width=1.5, label=f"SMA {SMA_LONG}"),
]
mpf.plot(
df,
type="line",
style="charles",
title=f"BTC/USDT SMA {SMA_SHORT}/{SMA_LONG} (Full)",
addplot=ap_full,
volume=False,
figsize=(14, 6),
linecolor="#333333",
)
# 7. 차트 2 — 최근 구간 캔들 차트 (확대)
df_zoom = df.tail(ZOOM_DAYS).copy()
ap_zoom = [
mpf.make_addplot(df_zoom["sma_short"], color="#FF6F00", width=1.5, label=f"SMA {SMA_SHORT}"),
mpf.make_addplot(df_zoom["sma_long"], color="#7B1FA2", width=1.5, label=f"SMA {SMA_LONG}"),
]
mpf.plot(
df_zoom,
type="candle",
style="charles",
title=f"BTC/USDT SMA {SMA_SHORT}/{SMA_LONG} (Last {ZOOM_DAYS}D)",
addplot=ap_zoom,
volume=False,
figsize=(14, 6),
)
사용법
- 이전 글에서 다운로드한 일봉 CSV 파일을 스크립트와 같은 폴더에 둡니다.
DATA_FILE에 파일명이 맞는지 확인합니다.- 스크립트를 실행하면 터미널에 백테스트 결과가 출력되고, 차트 팝업이 2개 뜹니다.
SMA 기간을 바꿔보고 싶으면 상단의 SMA_SHORT와 SMA_LONG 값을 수정하면 됩니다.
결과 분석



+1418%로 바이앤홀드(+555%)를 크게 앞섰습니다.
핵심은 2018년 하락장입니다. BTC는 2018년 초 약 $13,000에서 연말 $3,000대까지 떨어졌습니다. 바이앤홀드는 이 하락을 고스란히 맞지만, SMA 크로스 전략은 데드크로스 시점에 빠져나와서 낙폭 대부분을 피했습니다. 이 차이가 8년간의 누적 수익률에서 큰 격차로 벌어진 겁니다.
승률은 46.4%로 절반에 못 미칩니다. 28번 거래 중 15번이 손실입니다. 하지만 추세추종 전략에서 이건 정상입니다. 횡보 구간에서 골든크로스/데드크로스가 반복 발생하면서 소소하게 손실이 쌓이고, 큰 추세가 올 때 한 번에 만회하는 구조입니다. 승률이 낮아도 이기는 거래의 수익이 크기 때문에 전체적으로 플러스가 됩니다.
정리
SMA는 가장 단순한 이동평균이지만, 그만큼 한계도 명확합니다. 후행성이 강해서 추세 전환을 늦게 잡고, 횡보장에서는 골든크로스/데드크로스가 빈번하게 발생하면서 손실이 쌓일 수 있습니다. 이번 테스트에서는 하락장을 피한 효과가 컸지만, 테스트 기간에 따라 결과는 크게 달라질 수 있습니다.
다만, SMA를 단독 매매 전략이 아니라 추세 필터로 활용하면 이야기가 달라집니다. 예를 들어 다른 매매 전략이 있을 때, "가격이 장기 SMA 위에 있을 때만 매수 신호를 따른다"거나 "SMA의 기울기가 우상향일 때만 진입한다"는 식으로 조건을 걸면, 횡보장이나 하락장에서의 불필요한 진입을 줄일 수 있습니다. 이 부분은 이후 다른 전략을 다룰 때 함께 테스트해볼 예정입니다.
다음 글에서는 SMA의 후행성을 개선하기 위해 최근 가격에 더 높은 가중치를 두는 EMA(지수이동평균)를 다뤄보겠습니다.
본 포스팅은 지표의 원리와 백테스트 과정을 정리한 기술 블로그이며, 특정 자산의 매매를 권유하지 않습니다.
백테스트 결과는 과거 데이터 기반이며 실제 수익을 보장하지 않습니다. 모든 투자의 책임은 본인에게 있습니다.
'Development > Indicator Lab' 카테고리의 다른 글
| HMA (Hull Moving Average, 헐 이동평균) (0) | 2026.03.06 |
|---|---|
| DEMA & TEMA (이중/삼중 지수이동평균) (0) | 2026.03.05 |
| EMA (Exponential Moving Average, 지수이동평균) (0) | 2026.03.04 |
| Binance에서 과거 가격 데이터 받기 (0) | 2026.03.02 |
| Indicator Lab을 시작하며 (0) | 2026.03.01 |