퀀트 투자 입문 ② — 수익률 계산, 제대로 알고 시작하기
백테스트의 모든 결과물은 결국 수익률 계산 위에 서 있습니다. 그런데 수익률은 생각보다 헷갈리는 개념입니다. “10% 오르고 10% 떨어지면 본전”이라고 생각했다면 이번 글이 도움이 될 겁니다(정답: 1% 손실입니다). 이번 글에서는 단순 수익률, 로그 수익률, 누적 수익률, 연평균 수익률(CAGR)을 pandas 코드와 함께 정리합니다.
단순 수익률 (Simple Return)
가장 기본이 되는 수익률입니다. 어제 10,000원이던 주식이 오늘 10,500원이 되었다면:
수익률 = (10,500 - 10,000) / 10,000 = 0.05 = 5%
pandas에서는 pct_change() 한 줄입니다.
import FinanceDataReader as fdr
df = fdr.DataReader('005930', '2023-01-01') # 삼성전자
returns = df['Close'].pct_change().dropna()
print(returns.head())
첫 행은 비교할 전일 가격이 없어서 NaN이 되므로 dropna()로 제거합니다.
수익률은 더하면 안 된다
여기서 초보 시절 누구나 한 번은 빠지는 함정이 나옵니다. 단순 수익률은 기간에 걸쳐 더할 수 없습니다.
- 첫날 +10%, 둘째 날 -10%: 합산하면 0%로 보이지만
- 실제로는 1.10 × 0.90 = 0.99, 즉 -1%입니다.
수익률은 더하는 것이 아니라 (1 + 수익률)을 곱하는 것입니다. 이걸 복리(compounding)라고 부릅니다. 하락이 무서운 이유도 여기 있습니다. -50%가 나면 +50%로는 본전이 안 되고 +100%가 필요합니다.
누적 수익률 (Cumulative Return)
그래서 여러 기간의 성과는 곱셈으로 누적합니다.
cum_returns = (1 + returns).cumprod() - 1
print(f"기간 전체 수익률: {cum_returns.iloc[-1]:.2%}")
cum_returns.plot(figsize=(12, 5), title='Cumulative Return')
(1 + returns).cumprod()는 “1원을 투자했으면 지금 얼마가 됐나”를 매일 계산한 것이고, 거기서 1을 빼면 수익률이 됩니다. 백테스트 성과 그래프는 대부분 이 곡선입니다.
로그 수익률 (Log Return)
수익률을 곱셈이 아니라 덧셈으로 다루고 싶을 때 쓰는 것이 로그 수익률입니다.
import numpy as np
log_returns = np.log(df['Close'] / df['Close'].shift(1)).dropna()
# 로그 수익률은 그냥 더하면 누적이 된다
total_log = log_returns.sum()
print(f"단순 수익률로 환산: {np.exp(total_log) - 1:.2%}")
로그 수익률의 합에 exp를 씌우면 누적 단순 수익률과 정확히 일치합니다. 통계 분석(평균, 분산, 정규성 검정)을 할 때는 로그 수익률이 다루기 편해서 학계 논문은 대부분 로그 수익률을 씁니다. 일상적인 백테스트에서는 단순 수익률의 cumprod 방식이면 충분하고, 둘을 섞어 쓰지만 않으면 됩니다.
연평균 수익률, CAGR
“3년 동안 40% 벌었다”는 말은 1년 기준으로 얼마인지 감이 안 옵니다. 그래서 기간이 다른 성과를 비교할 때는 CAGR(연평균 복리 수익률)로 환산합니다.
CAGR = (최종 자산 / 초기 자산)^(1/연수) - 1
n_years = (df.index[-1] - df.index[0]).days / 365.25
final_value = (1 + returns).prod()
cagr = final_value ** (1 / n_years) - 1
print(f"CAGR: {cagr:.2%}")
3년에 40%면 CAGR로 약 11.9%입니다. 어떤 전략의 성과든 CAGR로 환산해 보는 습관을 들이면 과장된 숫자에 속을 일이 줄어듭니다.
산술평균의 함정: 변동성 손실
“평균 수익률이 플러스면 돈을 번다”는 착각도 짚고 갑니다. 2년짜리 극단적인 예를 봅시다.
- 1년 차 +50%, 2년 차 -40%: 산술평균은 (50 - 40) / 2 = +5%
- 실제 계좌: 1.50 × 0.60 = 0.90, 즉 -10%
평균은 플러스인데 계좌는 마이너스입니다. 수익률이 출렁일수록 산술평균과 실제 복리 수익률의 격차가 커지는데, 이를 변동성 손실(volatility drag)이라고 부릅니다. 같은 평균 수익률이라면 덜 출렁이는 쪽의 실제 성과가 더 좋습니다. 저변동성 팩터나 자산 배분이 힘을 발휘하는 수학적 이유가 바로 이것입니다.
# 산술평균 vs 실제 복리(기하평균) 비교
arith_mean = returns.mean() * 252 # 연환산 산술평균
geo_mean = (1 + returns).prod() ** (252 / len(returns)) - 1 # 연환산 복리
print(f"산술평균(연): {arith_mean:.2%} / 복리(연): {geo_mean:.2%}")
둘의 차이가 클수록 그 자산은 출렁임이 심하다는 뜻입니다.
기간 환산: 일별·월별·연간
수익률을 다른 기간 단위로 바꾸는 관례도 알아두면 편합니다. 1년은 보통 거래일 252일, 12개월로 놓고 계산합니다.
# 일별 수익률 → 월별 수익률 (그 달의 복리 누적)
monthly_returns = (1 + returns).resample('ME').prod() - 1
# 월별 수익률 → 연환산 (12개월 복리)
annualized = (1 + monthly_returns.mean()) ** 12 - 1
주의: returns.mean() * 252처럼 단순히 곱하는 연환산은 근사치일 뿐이고(위에서 본 변동성 손실이 무시됨), 정확한 값은 복리로 계산해야 합니다. 관행상 통계 지표(변동성, 샤프비율)에는 곱셈 연환산을 쓰지만, 성과 보고에는 복리 기준을 쓰는 것이 정직합니다.
자주 하는 실수 정리
- 수익률을 더해서 누적하기 — 반드시 (1+r)을 곱해야 합니다. 특히 손실 구간이 있으면 오차가 커집니다.
- 단순 수익률과 로그 수익률 섞어 쓰기 — 한 백테스트 안에서는 하나로 통일해야 합니다.
- 기간이 다른 수익률을 그대로 비교하기 — 6개월 수익률과 2년 수익률은 CAGR로 환산한 뒤 비교합니다.
- 평균 수익률로 성과를 말하기 — 일별 수익률의 산술평균이 양수여도 누적 수익률은 마이너스일 수 있습니다(변동성 손실). 성과는 누적 수익률과 CAGR로 말해야 합니다.
정리
- 수익률의 누적은 덧셈이 아니라 (1+r)의 곱셈이다.
- 통계 분석에는 로그 수익률, 성과 보고에는 단순 수익률 기반 누적 수익률과 CAGR를 쓴다.
- pandas에서는
pct_change(),cumprod(),prod()세 가지면 기본 계산이 끝난다.
다음 두 글은 코드를 잠시 내려놓고 개념을 정리합니다. 먼저 백테스트가 정확히 무엇인지, 그다음 모멘텀이 무엇인지를 다룬 뒤, 그 개념들을 실제 코드 30줄짜리 백테스트로 구현합니다. 여기서 계산한 누적 수익률 곡선이 전략 평가의 기준이 됩니다.
이 글은 학습과 정보 공유 목적으로 작성되었으며, 특정 종목이나 상품에 대한 투자 권유가 아닙니다. 투자의 판단과 책임은 투자자 본인에게 있습니다.