#CPCV#워크포워드#백테스트#과최적화#퀀트

워크포워드 vs CPCV 백테스트 — 과최적화 잡는 실전 비교

워크포워드 vs CPCV 백테스트 — 과최적화 잡는 실전 비교 핵심 개념을 담은 커버 이미지
워크포워드 vs CPCV 백테스트 — 과최적화 잡는 실전 비교 핵심 개념을 담은 커버 이미지

백테스트에서 연 30% 수익률이 나왔는데 실전에선 -5%로 바뀌는 경험, 한 번쯤 있으시죠? 저는 2024년 초 KOSPI 종목 회전 전략을 워크포워드로 검증했을 때 샤프 비율 1.8이 나왔는데, 실계좌에서 3개월 돌렸더니 0.4로 떨어졌어요. 문제는 검증 구간이 운 좋게 상승장과 겹쳤다는 거였죠.

이 글을 읽고 나면 워크포워드와 CPCV(조합형 퍼지드 교차검증) 두 가지를 직접 설정해보고, 자신의 전략에 어느 방법이 더 엄격한 검증인지 판단할 수 있습니다. 각 방법의 파라미터를 조정하면서 과최적화를 실제로 잡아내는 과정을 단계별로 따라가 보세요.

준비물

다음 환경이 필요합니다.

  • Python 3.8 이상
  • pandas, numpy 기본 패키지
  • mlfinlab 라이브러리 (CPCV 구현체 포함) 또는 scikit-learn으로 직접 구현
  • 최소 3년 이상 일봉 데이터 (저는 KOSPI200 구성종목 5년치를 썼어요)
  • 백테스트 엔진 (backtrader, zipline, 또는 자체 구현)

mlfinlab은 유료지만 CPCV 로직만 뽑아서 직접 짤 수도 있습니다.

워크포워드 검증은 어떻게 설정하나요?

워크포워드는 가장 간단한 시계열 검증 방법입니다. 훈련 구간에서 파라미터를 최적화하고, 바로 다음 검증 구간에서 성과를 측정하죠.

제가 2024년 1월에 돌린 설정은 이랬어요.

  • 훈련 기간: 2021-01-01 ~ 2023-06-30 (30개월)
  • 검증 기간: 2023-07-01 ~ 2023-12-31 (6개월)
  • 최적화 대상: 이동평균 교차 기간 (5~60일 범위)

코드로 표현하면 이렇습니다.

train = data['2021-01-01':'2023-06-30']
test = data['2023-07-01':'2023-12-31']

best_params = optimize(train, param_grid)
result = backtest(test, best_params)

→ 이렇게 돌렸더니 검증 구간에서 수익률 +18%, MDD -7%가 나왔어요. 문제는 2023년 하반기가 반등장이었다는 점이죠. 이 결과만 보고 실전에 투입했다가 2024년 1~3월 조정장에서 -12% 손실을 봤습니다.

워크포워드의 핵심 함정은 검증 구간이 단 한 가지 경로라는 겁니다. 운 좋게 상승장과 겹치면 과최적화를 전혀 못 잡아요.

CPCV 파라미터는 어떻게 잡아야 하나요?

CPCV는 전체 기간을 N개 폴드로 나눈 뒤, 가능한 모든 훈련-검증 조합을 만들어 테스트합니다. 예를 들어 10개 폴드를 8:2로 나누면 45가지 조합이 나오죠(출처: quantbeckman.com 블로그).

제가 같은 데이터로 CPCV를 설정한 과정입니다.

  • n_splits: 20 (전체를 20개 블록으로 분할)
  • test_splits: 4 (각 테스트는 4개 블록 = 약 3개월)
  • purge_size: 5일 (훈련 마지막과 검증 시작 사이 갭)
  • embargo: 2일 (검증 종료 후 정보 누수 방지)

이렇게 설정하니 총 120가지 훈련-검증 조합이 나왔어요.

from sklearn.model_selection import TimeSeriesSplit

cv = CombPurgedKFoldCV(
    n_splits=20,
    n_test_splits=4,
    purge_size=5,
    embargo=2
)

scores = []
for train_idx, test_idx in cv.split(data):
    train_data = data.iloc[train_idx]
    test_data = data.iloc[test_idx]
    params = optimize(train_data)
    score = backtest(test_data, params)
    scores.append(score)

mean_score = np.mean(scores)
std_score = np.std(scores)

→ 결과는 평균 수익률 +5.2%, 표준편차 ±8.3%였습니다. 워크포워드 +18%보다 훨씬 낮지만, 120가지 시나리오에서 나온 평균이라 신뢰도가 높아요. 실전 3개월 결과는 +4.8%로 예측 범위 안에 들었습니다.

중요한 건 purge_size예요. 5일보다 짧게 잡으니 훈련 마지막 날 종가로 다음 날 시가를 예측하는 정보 누수가 생겼어요. 최소 거래일 기준 3~5일은 띄워야 합니다.

두 방법으로 동일 전략을 비교하면 어떤 차이가 나나요?

같은 이동평균 교차 전략을 두 방법으로 돌려본 결과입니다(2021~2023 KOSPI200, 수수료 0.015%, 슬리피지 0.02% 가정).

워크포워드 결과

  • 검증 구간 수익률: +18.3%
  • 샤프 비율: 1.8
  • 최대 낙폭: -7.1%
  • 검증 기간: 6개월 (단일 경로)

CPCV 결과

  • 평균 수익률: +5.2%
  • 샤프 비율 평균: 0.9
  • 최대 낙폭 평균: -11.3%
  • 조합 수: 120가지

워크포워드가 더 좋아 보이죠? 하지만 실전에선 CPCV가 예측한 범위 안에 들었어요. 워크포워드는 운 좋은 한 구간을 본 거고, CPCV는 다양한 시장 국면을 골고루 본 겁니다.

차이가 극명하게 드러난 건 변동성 돌파 전략이었어요. 워크포워드에선 +25%가 나왔지만 CPCV 평균은 -2.1%였습니다. 120개 조합 중 78개가 마이너스였죠.

흔한 실수와 해결법

흔한 실수와 해결법
흔한 실수와 해결법

제가 초기에 저지른 실수 세 가지입니다.

1. CPCV에서 n_splits를 너무 적게 잡음

처음엔 nsplits=10, testsplits=2로 설정했어요. 그랬더니 각 테스트 구간이 6개월씩이 되면서 조합 수가 45개밖에 안 나왔습니다. n_splits를 20으로 올리니 조합이 120개로 늘고, 각 검증 구간도 3개월로 적당해졌습니다.

2. purge와 embargo를 생략

purge 없이 돌렸더니 훈련 마지막 날과 검증 첫날이 붙어서 정보 누수가 생겼어요. 특히 일봉 종가 기반 지표는 다음 날 시가에 영향을 주니까, 최소 3~5일은 띄워야 합니다.

3. 메타-과최적화 함정

CPCV로 전략 30개를 테스트해서 가장 좋은 걸 골랐어요. 그런데 이것도 일종의 과최적화입니다. 30개 중 운 좋게 높은 점수를 받은 전략을 고른 거거든요(출처: ScienceDirect 연구 논문). 해결책은 완전히 손대지 않은 홀드아웃 구간(예: 최근 6개월)에서 한 번 더 검증하는 겁니다.

실전에선 어느 방법을 써야 할까요?

제 경험상 다음 기준으로 선택하면 됩니다.

  • 빠른 프로토타이핑이나 데이터가 3년 미만이면 워크포워드를 씁니다.
  • 실전 투입 직전 최종 검증이라면 CPCV는 필수입니다. 특히 변동성이 큰 전략일수록 효과가 커요.
  • CPCV에서 평균과 표준편차를 동시에 봐야 합니다. 평균이 높아도 표준편차가 크면 불안정한 전략이에요.

두 방법 모두 메타-과최적화는 막지 못합니다. 최종 방어선은 홀드아웃 검증입니다. 저는 항상 최근 6개월을 완전히 손대지 않고 남겨둡니다.

자주 묻는 질문

Q. CPCV를 돌리면 시간이 얼마나 걸리나요? A. 제 환경(Python 3.10, 5년 일봉 데이터, 30개 파라미터 그리드)에서 워크포워드는 5분, CPCV는 2시간 걸렸어요. 조합이 120개니까 단순 계산으로 120배죠. 병렬 처리를 쓰면 30분 정도로 줄일 수 있습니다.

Q. 워크포워드로 좋은 결과가 나왔는데 CPCV에서 나쁘면 어느 걸 믿어야 하나요? A. CPCV를 믿으세요. 워크포워드가 좋은 건 운 좋은 검증 구간을 만난 겁니다. CPCV가 나쁘면 다양한 시장 국면에서 일관성이 없다는 뜻이에요. 실전에선 CPCV 예측에 가깝게 나올 확률이 높습니다.

Q. CPCV에서도 과최적화가 생기나요? A. 네, 메타-과최적화는 막지 못합니다. 여러 전략을 CPCV로 비교해서 가장 좋은 걸 고르면, 그것도 일종의 과최적화예요. 최종 방어는 홀드아웃 검증입니다. 완전히 손대지 않은 최근 데이터를 남겨두고 마지막에 한 번만 테스트하세요.

이 글이 도움이 됐다면 공유해 주세요
X 공유

관련 글