골든크로스는 단기 이동평균선이 장기 이동평균선을 넘을 때 매수하는 익숙한 규칙입니다. 차트에서는 교차가 명확해 보이지만, 신호가 나온 날 이미 추세가 상당히 진행됐을 수 있고 횡보장에서는 교차가 반복됩니다.
이번 글에서는 SMA 20·60 교차를 KODEX 200, 삼성전자, NAVER에 같은 체결·비용 조건으로 적용했습니다. 질문은 하나입니다. 교차 신호가 단순보유보다 더 많은 수익을 주는가, 아니면 낙폭을 줄이는 대가로 상승 구간을 놓치는가입니다.
이 글의 핵심 결론
- 세 대상 모두에서 SMA 교차의 누적수익률은 단순보유보다 낮았습니다.
- KODEX 200과 삼성전자에서는 최대낙폭이 줄었지만, NAVER에서는 오히려 더 커졌습니다.
- 2018년 삼성전자 하락에서는 손실을 줄였지만 2019년 회복에서는 크게 뒤처졌습니다. 같은 규칙이라도 시장 국면에 따라 역할이 달라집니다.
SMA 계산법: 20일선은 정말 주가를 지지해줄까?에서 다룬 것처럼 이동평균선은 최근 가격을 평균한 후행 지표입니다. 이 글에서는 그 교차를 실제 매매 규칙으로 옮겼을 때 어떤 성적표가 나오는지 확인합니다.
검증 규칙을 먼저 고정합니다
신호와 체결 시점을 섞으면 결과가 과장될 수 있습니다. 종가로 계산한 이동평균선은 장 마감 뒤에야 확정되므로, 신호가 발생한 당일 종가 체결로 계산하지 않았습니다.
| 항목 | 고정한 조건 |
|---|---|
| 대상 | KODEX 200(069500), 삼성전자(005930), NAVER(035420) |
| 가격 | yfinance의 auto_adjust=True 일봉 |
| 요청 기간 | 2015-01-01~2026-08-30 |
| 실제 측정 구간 | SMA 60일 계산 뒤 2015-03-31~2026-08-28 |
| 매수 조건 | 당일 종가 기준 SMA 20이 SMA 60보다 높음 |
| 매도 조건 | 당일 종가 기준 SMA 20이 SMA 60보다 낮음 |
| 체결 | 신호 다음 거래일 시가 |
| 비용 | 포지션 변화마다 편도 0.10% |
| 비교 기준 | 같은 가격의 단순보유, 첫 매수 때만 편도 0.10% 차감 |
전략은 현금 또는 해당 자산을 100% 보유하는 방식입니다. 공매도, 분할매수, 이자수익, 호가 잔량, 거래정지·상하한가 체결 가능성은 반영하지 않았습니다. 마지막 보유분은 강제 청산하지 않고 시가평가했습니다.
수익률은 낮았지만 낙폭도 항상 줄지는 않았습니다
| 대상 | SMA 교차 누적수익률 | 단순보유 누적수익률 | SMA 교차 CAGR | SMA 교차 MDD | 단순보유 MDD | 샤프 비율 | 진입 횟수 | 시장 노출 |
|---|---|---|---|---|---|---|---|---|
| KODEX 200 | +299.0% | +420.1% | +12.9% | -32.2% | -39.9% | 0.77 | 23회 | 60.8% |
| 삼성전자 | +727.1% | +1,080.1% | +20.3% | -40.2% | -43.0% | 0.81 | 25회 | 59.7% |
| NAVER | -33.2% | +65.5% | -3.5% | -77.8% | -66.2% | 0.00 | 28회 | 50.6% |
KODEX 200에서는 SMA 교차가 단순보유보다 121.1%포인트 낮은 누적수익률을 기록했습니다. 대신 최대낙폭은 -39.9%에서 -32.2%로 작았습니다. 전체 기간의 약 61%만 시장에 노출됐기 때문에 하락 일부를 피했지만 상승 일부도 현금 상태로 지나간 결과입니다.
삼성전자도 비슷한 방향이었습니다. 누적수익률은 단순보유보다 낮았고 최대낙폭은 조금 작았습니다. 반면 NAVER는 누적수익률과 최대낙폭 모두 단순보유보다 나빴습니다. 교차 전략이 낙폭을 줄인다는 설명도 모든 자산에 그대로 적용되지는 않았습니다.

이 차트는 KODEX 200 결과입니다. 2026년의 강한 상승 구간에서는 단순보유 자산 곡선이 더 높게 올라갔고, 전략은 그 전에 현금으로 전환한 구간이 있어 격차가 남았습니다. 반대로 하락 구간에서는 보유를 중단하면서 낙폭이 일부 작아졌습니다.
같은 삼성전자에서도 방어와 실패가 함께 나왔습니다
SMA 교차를 하나의 성공 또는 실패로 부르기 어려운 이유는 연도별 결과에서 더 분명합니다. 아래 수익률은 앞의 동일 규칙으로 계산한 해당 연도 구간의 결과입니다.
| 구간 | SMA 교차 | 단순보유 | 읽을 수 있는 점 |
|---|---|---|---|
| 삼성전자 2018년 | -4.7% | -20.9% | 하락 구간에서 포지션 이탈이 손실 폭을 줄였습니다. |
| 삼성전자 2019년 | -5.0% | +45.0% | 회복 구간을 충분히 보유하지 못해 큰 차이가 났습니다. |
| 삼성전자 2022년 | -15.1% | -27.8% | 하락 방어는 있었지만 손실 자체를 없애지는 못했습니다. |
| 삼성전자 2023년 | +7.2% | +43.8% | 추세가 이어진 상승 구간에서 단순보유를 크게 따라가지 못했습니다. |
2018년의 결과만 보면 교차 전략이 유용해 보이고, 2019년의 결과만 보면 전략을 버려야 할 것처럼 보입니다. 두 결론 모두 표본 하나만 보고 내리면 불완전합니다. 추세추종 규칙의 본질은 모든 상승을 잡는 것이 아니라, 추세가 약해졌다고 판단한 구간에서 시장 노출을 줄이는 데 있습니다. 그 판단이 늦거나 회복이 빠르면 기회비용이 생깁니다.
따라서 누적수익률이 낮다는 사실만으로 전략이 무의미하다고 결론 내릴 수는 없습니다. 반대로 MDD가 작다는 이유만으로 채택할 수도 없습니다. 투자자가 감당할 수 있는 낙폭, 현금 대기 기간, 기대하는 수익의 크기를 같이 비교해야 합니다.
파이썬으로 같은 조건을 재현합니다
처음 실행한다면 터미널에서 pip install yfinance pandas numpy를 설치합니다. yfinance의 download 문서처럼 데이터 제공자의 과거 수정이 있으면 같은 코드라도 일부 값이 달라질 수 있으므로, 기간·가격 조정 여부·비용 가정을 함께 기록하는 편이 좋습니다.
import numpy as np
import pandas as pd
import yfinance as yf
TICKERS = {
"KODEX 200": "069500.KS",
"삼성전자": "005930.KS",
"NAVER": "035420.KS",
}
START_DATE = "2015-01-01"
END_DATE = "2026-08-30"
SHORT_WINDOW = 20
LONG_WINDOW = 60
ONE_WAY_COST = 0.001
def run_sma_crossover(price_data, ticker):
price_frame = price_data.xs(ticker, axis=1, level="Ticker")[["Open", "Close"]].copy()
price_frame = price_frame.rename(columns={"Open": "open", "Close": "close"}).dropna()
price_frame["sma_short"] = price_frame["close"].rolling(SHORT_WINDOW).mean()
price_frame["sma_long"] = price_frame["close"].rolling(LONG_WINDOW).mean()
price_frame = price_frame.dropna(subset=["sma_long"]).copy()
price_frame["signal"] = (price_frame["sma_short"] > price_frame["sma_long"]).astype(float)
price_frame["position"] = price_frame["signal"].shift(1).fillna(0.0)
price_frame["open_to_open_return"] = price_frame["open"].shift(-1).div(price_frame["open"]).sub(1.0).fillna(0.0)
price_frame["position_change"] = price_frame["position"].diff().fillna(price_frame["position"])
price_frame["turnover"] = price_frame["position_change"].abs()
price_frame["entry"] = price_frame["position_change"].clip(lower=0.0)
price_frame["strategy_return"] = price_frame["position"] * price_frame["open_to_open_return"] - price_frame["turnover"] * ONE_WAY_COST
price_frame["buy_hold_return"] = price_frame["open_to_open_return"]
price_frame["strategy_equity"] = (1.0 + price_frame["strategy_return"]).cumprod()
price_frame["buy_hold_equity"] = (1.0 - ONE_WAY_COST) * (1.0 + price_frame["buy_hold_return"]).cumprod()
price_frame["strategy_drawdown"] = price_frame["strategy_equity"].div(price_frame["strategy_equity"].cummax()).sub(1.0)
price_frame["buy_hold_drawdown"] = price_frame["buy_hold_equity"].div(price_frame["buy_hold_equity"].cummax()).sub(1.0)
return price_frame
price_data = yf.download(
list(TICKERS.values()),
start=START_DATE,
end=END_DATE,
auto_adjust=True,
progress=False,
threads=False,
)
summary_rows = []
for asset_name, ticker in TICKERS.items():
result = run_sma_crossover(price_data, ticker)
elapsed_years = (result.index[-1] - result.index[0]).days / 365.25
strategy_return = result["strategy_return"]
summary_rows.append(
{
"대상": asset_name,
"SMA 교차 누적수익률(%)": (result["strategy_equity"].iloc[-1] - 1.0) * 100.0,
"단순보유 누적수익률(%)": (result["buy_hold_equity"].iloc[-1] - 1.0) * 100.0,
"SMA 교차 CAGR(%)": (result["strategy_equity"].iloc[-1] ** (1.0 / elapsed_years) - 1.0) * 100.0,
"SMA 교차 MDD(%)": result["strategy_drawdown"].min() * 100.0,
"단순보유 MDD(%)": result["buy_hold_drawdown"].min() * 100.0,
"샤프 비율": strategy_return.mean() / strategy_return.std(ddof=0) * np.sqrt(252),
"진입 횟수": int(result["entry"].sum()),
"시장 노출(%)": result["position"].mean() * 100.0,
}
)
summary = pd.DataFrame(summary_rows)
print(summary.round(2).to_string(index=False))
코드에서 중요한 부분은 shift(1)입니다. 종가로 신호를 확인한 뒤 다음 거래일 시가부터 포지션을 보유하도록 한 줄입니다. 같은 날 종가에 이미 체결한 것으로 처리하면 아직 알 수 없던 가격을 사용하게 됩니다. 백테스트 결과를 믿기 전 확인할 3가지에서 다룬 미래참조 오류가 여기에 해당합니다.
골든크로스를 평가하는 순서
SMA 교차 전략을 보려면 수익률 하나보다 다음 순서가 안전합니다.
- 체결 시점을 확인합니다. 종가 신호와 시가 체결이 분리됐는지 먼저 봅니다.
- 수익률과 MDD를 나란히 봅니다. 수익을 포기한 대가로 낙폭이 실제로 줄었는지 확인합니다.
- 시장 노출과 거래 횟수를 봅니다. 현금 대기가 많아서 방어한 것인지, 반복 교차로 비용이 쌓였는지 구분합니다.
- 상승·하락 구간을 분리합니다. 전체 평균 뒤에 어떤 해의 결과가 숨어 있는지 확인합니다.
- 다른 자산과 기간에서도 반복합니다. 한 종목의 좋은 구간에 맞춘 규칙인지 점검합니다.
누적수익률은 비슷한데 낙폭은 1.6배, 파이썬 백테스트 성과표 보는 법도 같은 이유로 누적수익률 뒤의 낙폭과 거래 구조를 먼저 살펴봅니다. SMA 교차는 추세를 확인하는 간단한 출발점이지만, 그 자체로 수익을 보장하는 매매법은 아닙니다.
이 글은 공개 가격 데이터를 사용한 과거 검증 예시입니다. 특정 종목의 매수·매도 추천이나 미래 수익을 의미하지 않으며, 실제 투자 판단과 책임은 투자자 본인에게 있습니다.