요약
- look-ahead 편향: 그때는 알 수 없던 데이터로 과거 신호를 계산하는 것. 시험 문제를 미리 보고 푸는 것과 같습니다.
- 시점 일치(point-in-time): 그 시점에 실제로 공개·확정돼 있던 데이터만 쓰는 원칙.
- 이 둘을 구분하지 않으면 백테스트 수익률은 부풀려진 허상이 됩니다.
1. look-ahead가 어떻게 들어오는가
사례 1: 재무제표 — 공시일 vs 결산일
- 12월 결산법인의 2023년 실적 확정 공시: 2024년 3~4월
- 백테스트에서 "2023년 12월 말 시점에 2023년 순이익을 안다"면 → look-ahead
- 시점 일치: 2024년 3월 공시 전까지는 2022년 실적만 사용 가능
사례 2: 지수 구성 변경 — 발표일 vs 적용일
- 코스피200 정기 변경: 6월/12월 둘째 주 금요일 발표 → 익월 두 번째 금요일 적용
- 발표일 전에 변경 종목을 알 수 없음. 적용일 전에 매매하면 look-ahead
사례 3: 분할·합병·감자 — 효력 발생일
- 주식 분할 비율은 주주총회 결의일 이후에나 확정
- 분할 전 가격으로 분할 후 수량을 계산하면 look-ahead
사례 4: 수정주가 — 배당락일 vs 권리락일
- 수정주가는 과거 모든 시점을 소급 수정하지만, 당시 투자자는 수정주가를 볼 수 없었음
- 배당락일 전에는 배당을 받을 권리가 주가에 반영돼 있음 → 수정주가 그대로 쓰면 look-ahead
2. 시점 일치 데이터로 막는 방법
| 데이터 종류 | 시점 일치 기준 | 실수하기 쉬운 포인트 |
|---|---|---|
| 재무제표 | 공시일(또는 접수일) 기준 | 결산일·기말일 기준 사용 |
| 지수 구성 | 적용일 기준 | 발표일·공시일 기준 사용 |
| 기업 행동 | 효력 발생일 기준 | 결의일·공시일 기준 사용 |
| 수정주가 | 권리락일 전날 종가 기준 | 단순 수정주가 전체 사용 |
| 애널리스트 추정치 | 추정치 발표일 기준 | 목표주가·투자의견 변경일 기준 사용 |
3. 이 사이트에서 어떻게 적용하는가
각 전략 스냅샷의 provenance 필드에 기록:
{
"provenance": {
"kind": "backtest-replay",
"reviewStatus": "reviewed",
"pointInTime": true,
"dataSource": "KOSPI/KOSDAQ 일별 시세 + 공시일 기준 재무데이터",
"limitations": [
"재무데이터는 공시일 기준 근사치 적용",
"지수 구성 변경은 적용일 기준 반영"
]
}
}
pointInTime: true인 경우에만 시점 일치 원칙을 지켰다고 표시합니다.
4. 직접 점검 체크리스트
자신의 백테스트 코드에서 다음을 확인하세요:
- 재무데이터 조회 시 공시일 이후만 사용했는가
- 지수 구성 종목 리스트를 적용일 기준으로 썼는가
- 수정주가를 쓸 때 권리락일 전날 종가로 환산했는가
- 기업 행동(분할·합병·감자)을 효력 발생일에 맞췄는가
- 신호 계산 시점에 존재하지 않던 데이터를 쓰지 않았는가
하나라도 "아니요"면 해당 백테스트에는 look-ahead가 있을 수 있습니다.
5. 공시일 한 칸이 신호를 어떻게 바꾸는지 — 합성 예시
아래 달력은 특정 회사의 실적이 아닙니다. 결산일과 공시일을 섞으면 신호가 왜 예뻐지는지 보여 주는 합성 예시입니다. 이름을 붙이지 않은 이유이기도 합니다.
12월에 사업연도가 끝나는 회사를 생각하겠습니다. 그 해의 이익은 다음 해 3월 20일에야 공시된다고 가정합니다. 주가는 매달 말의 값만 있습니다. 규칙은 "직전 결산 이익이 그 전 해보다 늘었으면 다음 달에 보유한다"입니다. 규칙은 단순하고, 단순한 만큼 날짜 한 칸에 결과가 좌우됩니다.
틀린 달력은 12월 31일에 그 해 이익을 이미 안다고 둡니다. 1월과 2월의 보유 여부가 3월에야 공개된 숫자로 결정됩니다. 당시의 사람은 그 숫자를 볼 수 없었습니다. 백테스트만 그 숫자를 보고 1월에 사고 2월에 팝니다. 이익이 늘었던 해에는 1~2월 주가가 이미 그 소식을 반영하기 시작한 뒤일 수 있습니다. 규칙을 그 구간에 넣으면, 소식을 미리 안 사람만 얻을 수 있는 구간을 수익으로 셉니다. 이것이 look-ahead입니다. 시험지를 걷기 전에 답안을 보고 푸는 것과 구조가 같습니다.
맞는 달력은 3월 20일 이전의 모든 달에 전전년도 이익만 씁니다. 3월 말 보유분부터 방금 공시된 이익을 씁니다. 같은 규칙, 같은 주가, 다른 정보 집합입니다. 두 달력의 차이만 떼어 보면 1월과 2월의 보유가 통째로 달라질 수 있습니다. 연간 성적의 상당 부분이 바로 그 두 달에 몰려 있다면, 규칙의 실력이 아니라 공시 전 정보를 쓴 대가가 성적의 본체입니다. 차이를 보지 않고 "이익이 늘면 산다"만 말하면, 어느 달력으로 돌렸는지가 문장에서 사라집니다.
지수 구성도 같은 달력 문제입니다. 변경이 발표된 날과 그 변경이 지수에 들어가는 날은 보통 다릅니다. 발표일에 편입 예정 이름을 이미 보유한 것으로 처리하면, 발표와 적용 사이의 가격 움직임을 규칙의 수익으로 가져옵니다. 당시에는 발표를 보고 적용일까지 시간이 있었습니다. 그 시간을 0으로 두면 실행할 수 없었던 체결이 됩니다. 적용일 당일에 편입하는 것으로 두면, 적어도 "그때 지수에 들어 있던 것"과 보유가 맞습니다. 발표일 기준인지 적용일 기준인지를 적지 않은 백테스트는 둘 중 어느 쪽인지 독자가 알 수 없습니다.
수정주가는 더 조용히 섞입니다. 배당이나 분할이 있은 뒤, 과거 가격을 오늘의 기준으로 다시 그리는 열이 수정주가입니다. 연구 막바지에 그 열 하나만 쓰면 장기 수익률의 연속성은 좋아집니다. 그러나 배당락 전날의 사람은 수정된 과거가 아니라 당시의 호가를 보고 체결했습니다. 권리락 전날 종가와 그 다음 날의 조정된 가격을 한 시계열로 그대로 잇고, 락 당일에 보유 수량이 바뀌는 사건을 따로 적지 않으면, 락 구간의 수익률이 이중으로 들어가거나 빠집니다. 시점 일치는 "수정주가를 쓰지 말라"가 아닙니다. 사건이 효력을 낸 날에만 수량과 가격을 바꾸고, 그 전에는 그 사건이 없다고 두는 것입니다.
점검 목록을 달력으로 바꾸면 다섯 줄입니다.
- 재무 숫자의 사용 시작일은 결산일인가, 공시일인가. 결산일이면 그 사이 구간은 look-ahead 후보입니다.
- 지수 구성 명단은 발표일인가, 적용일인가. 발표일이면 적용 전까지의 보유는 실행 불가능한 보유입니다.
- 분할과 합병은 결의일인가, 효력 발생일인가. 결의일에는 비율이 아직 바뀔 수 있습니다.
- 수정주가 열을 쓸 때 권리락일의 수량 변화가 별도 사건으로 남아 있는가.
- 신호를 계산한 날에 그 입력 파일이 이미 존재했는가. 파일을 나중에 받아 과거로 덮어썼다면 날짜가 맞아도 정보는 미래입니다.
이 사이트의 스냅샷이 pointInTime을 참이라고 적는 것은 이 다섯 줄을 지켰다는 운영자의 표시입니다. 표시가 있다고 해서 근사치가 사라지지는 않습니다. 재무 공시 시각을 분 단위로 갖고 있지 않으면 그날의 장중 체결은 여전히 애매합니다. 한계 칸에 "공시일 기준 근사"라고 적힌 기록은, 참이라는 표시와 그 근사를 같이 읽어야 합니다. 표시만 보고 미래 정보가 0이라고 단정하면 필드 이름을 면죄부로 쓰는 것입니다.
자신의 코드를 볼 때는 수익 곡선보다 입력 날짜의 최댓값을 먼저 출력합니다. 각 신호일에, 그 신호가 참조한 모든 열의 공개일이 신호일 이전인지 한 줄로 집계합니다. 한 건이라도 신호일 이후이면 그 달은 성적에서 분리합니다. 분리한 뒤에도 곡선이 비슷하면 날짜 실수가 결론을 만들지 않은 것입니다. 분리하자마자 곡선이 무너지면, 예쁘던 구간은 당시 알 수 없던 칸이었습니다.
연도별 재현과 walk-forward 글이 파라미터의 확정 시각을 다룬다면, 이 글은 그 파라미터에 들어가는 숫자의 공개 시각을 다룹니다. 둘 중 하나만 지켜도 나머지 하나에서 미래가 들어옵니다. 과적합 글은 그렇게 들어온 미래를 여러 번 시도해서 더 예쁜 쪽만 남기는 다음 단계입니다.