퀀트 투자 입문 ⑤ — 첫 백테스트: 절대 모멘텀을 코드 30줄로
앞의 네 글에서 준비를 마쳤습니다. 데이터를 받는 법, 수익률을 계산하는 법, 백테스트가 무엇인지, 모멘텀이 무엇인지. 이제 그것들을 하나로 합쳐 실제로 돌아가는 백테스트를 만듭니다. 코스피 지수 하나에 절대 모멘텀 규칙을 적용하고, 그냥 들고 있기(buy & hold)와 비교합니다.
검증할 규칙
가장 단순한 형태로 정합니다.
매월 말, 코스피의 최근 12개월 수익률이 플러스면 다음 달 한 달간 코스피를 보유한다. 마이너스면 현금으로 대피한다(수익률 0).
백테스트 4단계 중 1단계(규칙 정하기)가 이걸로 끝났습니다. 애매한 단어가 하나도 없다는 점을 확인해 보세요. “12개월 수익률 > 0”은 누가 계산해도 같은 답이 나옵니다.
2단계: 데이터 준비
월 단위 규칙이므로 월말 종가만 있으면 됩니다.
import FinanceDataReader as fdr
import pandas as pd
# 코스피 지수 일별 종가 → 월말 종가
kospi = fdr.DataReader('KS11', '2005-01-01')['Close']
monthly = kospi.resample('ME').last()
print(monthly.tail())
print(f"월 데이터 개수: {len(monthly)}")
resample('ME')는 월말(Month End) 기준으로 묶으라는 뜻이고, .last()로 그달의 마지막 값을 취합니다. 2005년부터 잡은 이유는 2008년 금융위기와 2020년 코로나 폭락을 모두 포함시키기 위해서입니다. 앞 글에서 말한 “최소한 큰 하락장을 한 번 이상 포함하라”는 조건입니다.
3단계: 규칙을 기계적으로 적용
여기가 백테스트의 심장입니다. 코드는 다섯 줄이지만 한 줄 한 줄이 중요합니다.
# 월별 수익률
returns = monthly.pct_change()
# 신호: 최근 12개월 수익률이 플러스인가?
momentum = monthly.pct_change(12)
signal = (momentum > 0).astype(int) # 1 = 보유, 0 = 현금
# 핵심: 이번 달 말에 관측한 신호는 '다음 달'에 적용된다
position = signal.shift(1)
# 전략의 월별 수익률
strategy_returns = position * returns
shift(1)이 없으면 백테스트는 무효입니다
position = signal.shift(1) 이 한 줄이 이 코드 전체에서 가장 중요합니다.
signal은 그달 말 종가를 봐야 계산됩니다. 8월 31일 종가를 알아야 8월의 12개월 모멘텀을 알 수 있죠. 그런데 returns의 8월 값은 8월 한 달 동안의 수익률입니다. 이 둘을 그냥 곱하면 어떻게 될까요? “8월이 끝난 뒤에 알게 된 정보로, 이미 지나간 8월에 투자한다”는 시간여행이 됩니다.
shift(1)은 신호를 한 칸 뒤로 미뤄서 “8월 말에 본 신호 → 9월 수익률에 적용”으로 맞춰줍니다. 이것이 앞 글에서 말한 미래 참조 편향(look-ahead bias)을 막는 장치입니다. 이 한 줄을 빼면 백테스트 성과가 마법처럼 좋아지는데, 그건 실력이 아니라 버그입니다.
시험 삼아 shift(1)을 지우고 돌려보세요. 성과가 얼마나 부풀려지는지 직접 보면 이 개념을 평생 잊지 않게 됩니다.
4단계: 성과 평가
이제 buy & hold와 나란히 놓고 비교합니다.
result = pd.DataFrame({
'Buy & Hold': returns,
'Momentum': strategy_returns
}).dropna()
cum = (1 + result).cumprod()
# 연평균 복리 수익률 (CAGR)
n_years = len(result) / 12
cagr = cum.iloc[-1] ** (1 / n_years) - 1
# 최대 낙폭 (MDD): 고점 대비 얼마나 깊이 떨어졌나
def max_drawdown(cum_series):
peak = cum_series.cummax()
return ((cum_series / peak) - 1).min()
mdd = cum.apply(max_drawdown)
print(pd.DataFrame({'CAGR': cagr, 'MDD': mdd}).round(4))
cum.plot(figsize=(12, 5), title='Absolute Momentum vs Buy & Hold (KOSPI)')
여기서 처음 등장한 지표가 MDD(Maximum Drawdown, 최대 낙폭)입니다. 전 고점 대비 가장 깊이 떨어졌던 폭으로, “이 전략을 들고 있었다면 최악의 순간에 계좌가 얼마나 녹아 있었나”를 뜻합니다. 수익률만큼, 어쩌면 수익률보다 중요한 숫자입니다. CAGR이 아무리 좋아도 MDD가 -60%인 전략은 실제로는 대부분의 사람이 중간에 포기하기 때문입니다.
실제로 돌려본 결과
위 코드를 실제 코스피 지수 월말 종가(2005년 1월~2026년 8월, 260개월, FinanceDataReader)로 돌린 결과입니다. 비용은 아직 반영하지 않았습니다.
| Buy & Hold | 절대 모멘텀(12개월) | |
|---|---|---|
| CAGR | 9.66% | 5.91% |
| 연 변동성 | 22.5% | 18.2% |
| MDD | -48.5% | -30.5% |
| 최장 침체 기간 | 71개월 | 115개월 |

솔직히 말하면 예상보다 모멘텀 쪽이 훨씬 초라합니다. 연 수익률에서 3.7%p를 내줬고, 21년 반 동안 누적으로는 Buy & Hold가 약 7.4배, 모멘텀은 약 3.5배입니다. 절반도 안 됩니다.
대신 약속한 것은 지켰습니다. 2008년 금융위기에서 Buy & Hold가 -48.5%까지 빠질 때 모멘텀은 -30.5%에서 멈췄습니다. 변동성도 4%p 낮습니다. “큰 하락장을 피한다”는 목적 자체는 달성한 겁니다.
그런데 표에서 가장 놀란 숫자는 최장 침체 기간입니다. 모멘텀이 115개월, 거의 10년입니다. Buy & Hold의 71개월보다 훨씬 깁니다. 낙폭이 얕은 전략이 왜 물속에 더 오래 있었을까요? 아래 낙폭 그래프를 보면 이유가 보입니다.

모멘텀의 낙폭 선에 수평으로 멈춘 구간들이 있습니다. 현금으로 대피해 있는 기간입니다. 그 사이 시장은 반등해서 Buy & Hold의 낙폭은 회복되는데, 모멘텀은 현금에 앉아 있으니 낙폭이 그 자리에 얼어 있습니다. 2011년부터 2017년까지 코스피가 박스권에서 오르내리던 시기에 모멘텀은 샀다 팔았다를 반복하며 계속 물속에 있었습니다. 이것이 앞 글에서 말한 휩쏘의 실제 모습입니다.
그리고 그래프 오른쪽 끝, 2024년 이후의 급등 구간에서 격차가 결정적으로 벌어집니다. 모멘텀도 상승에 올라타긴 했지만(1.3배→3.9배) 한 박자 늦게 진입한 대가가 컸습니다. 모멘텀의 구조적 약점인 “급격한 방향 전환에 늦다”가 그대로 나타났습니다.
이 결과는 코스피라는 하나의 시장, 21년이라는 하나의 표본에서 나온 것입니다. 다른 시장이나 다른 기간이라면 다를 수 있습니다. 다만 “절대 모멘텀은 수익을 올려주는 전략”이 아니라 “수익의 일부를 내주고 최악의 낙폭을 사는 보험”이라는 성격은 분명하게 드러났다고 봅니다. 그 보험료가 연 3.7%p라면 비싼지 싼지는 사람마다 답이 다를 겁니다.
결과를 읽는 법
위 결과를 볼 때 던져야 할 질문을 정리합니다.
- CAGR이 buy & hold보다 높은가? 낮아도 실패가 아닙니다. 다음 질문을 함께 봐야 합니다.
- MDD가 얼마나 줄었는가? 절대 모멘텀의 존재 이유는 수익 극대화가 아니라 큰 하락장 회피입니다. CAGR이 비슷한데 MDD가 절반이면 훌륭한 결과입니다.
- 누적 수익률 곡선의 모양은? 2008년과 2020년 구간에서 두 선이 어떻게 갈라지는지 보세요. 모멘텀 전략이 폭락을 피했다면 그 지점에서 선이 벌어집니다.
- 반대로 어디서 손해를 봤는가? 급반등 구간(2009년, 2020년 4~5월)에서는 현금에 앉아 있느라 상승을 놓쳤을 겁니다. 앞 글에서 말한 모멘텀의 구조적 약점이 실제 데이터에서 어떻게 나타나는지 확인하는 순간입니다.
이 백테스트에 아직 없는 것들
축하하지 말고 의심하라고 했었죠. 지금 이 30줄에는 현실이 여럿 빠져 있습니다.
- 거래 비용 — 매수·매도 수수료와 세금, 슬리피지가 전혀 없습니다. 신호가 자주 바뀌는 구간에서는 이 비용이 성과를 상당히 갉아먹습니다.
- 현금의 수익률 — 대피했을 때 수익률을 0으로 뒀지만, 실제로는 예금이나 단기 채권에 넣어 이자를 받습니다. 이건 오히려 성과를 과소평가한 부분입니다.
- 지수는 살 수 없다 — 코스피 지수 자체는 매매할 수 없고, 실제로는 ETF를 삽니다. ETF는 운용보수가 있고 지수를 완벽히 따라가지도 않습니다.
- 파라미터 12개월은 어디서 왔나 — 제가 그냥 정했습니다. 6개월, 9개월로 바꾸면 결과가 달라지고, “가장 성과 좋은 개월 수”를 찾아 헤매는 순간 과최적화가 시작됩니다.
특히 4번은 다음 단계로 넘어가기 전에 반드시 짚어야 할 주제입니다.
정리
- 절대 모멘텀 백테스트의 뼈대는
pct_change→ 신호 →shift(1)→ 곱하기, 네 단계다. shift(1)은 미래 참조를 막는 장치이고, 이 한 줄이 백테스트의 유효성을 결정한다.- 성과는 CAGR만이 아니라 MDD와 곡선의 모양까지 함께, 반드시 buy & hold와 비교해서 읽는다.
- 첫 백테스트에는 거래 비용, 파라미터 선택 문제 등 현실이 빠져 있다.
다음 글에서는 이 빠진 것들을 하나씩 채워 넣습니다. 거래 비용을 반영하면 성과가 얼마나 달라지는지, 그리고 파라미터를 바꿔가며 최고 성과를 찾는 것이 왜 위험한지를 같은 코드로 직접 확인해 보겠습니다.
이 글은 학습과 정보 공유 목적으로 작성되었으며, 특정 종목이나 상품에 대한 투자 권유가 아닙니다. 투자의 판단과 책임은 투자자 본인에게 있습니다.