📊 sunnypoint
개발일지

정직해진 백테스트 데이터: factor_store 전수 감사와 ADR 0008 구현

5분 읽기

팩터 저장소의 치명적 결함과 데이터의 진실

Phase 2a로 백테스트 가능한 데이터를 구축하는 작업을 시작했고, 단 하루 만에 마무리할 수 있었다. 그 과정에서 가장 큰 소득은 factor_store에 대한 전수 감사를 통해 그동안 숨어있던 치명적인 결함 세 가지를 직접 눈으로 확인한 것이었다. 이 치명적인 문제들을 해결하기 위해 ADR 0008을 작성하여 설계를 확정하고, 계획했던 체크리스트 9개 항목의 구현부터 데이터 백필, 가동까지 막힘없이 끝냈다. 관련 작업들은 커밋 0248610d로 설계를 기록하고, 17b80c4e로 구현을 마쳤으며, 최종적으로 문서화까지 정리해 두었다.

그동안 돌려왔던 백테스트가 사실상 무효였다는 사실은 꽤나 뼈아픈 수확이었다. 가장 먼저 발견한 결함은 모멘텀 계산에서의 look-ahead bias였다. _momentum() 함수가 기준일(as_of)을 무시하고 항상 가장 최신의 종가를 가져다 쓰고 있었다. 리밸런싱을 결정하는 시점에 미래 데이터를 가져다 계산을 해버린 셈이다. 게다가 유니버스 데이터를 갱신할 때 과거 기록을 덮어쓰는 바람에, 과거 스크리닝이 무조건 오늘날까지 생존해 있는 종목들 중에서만 선택되는 심각한 생존편향이 존재했다. 이미 상장폐지된 종목들은 OHLCV 파일조차 남아있지 않았다. 수정주가 역시 증분 방식으로 데이터를 추가할 때 주식분할이나 병합 같은 이벤트가 소급 적용되지 않아 한 파일 안에 서로 다른 수정 기준이 어지럽게 뒤섞여 있었다. 설상가상으로 합성 봉 문제나, 데이터 저장소가 지난 6월 17일부터 무려 27일 동안이나 멈춰 있었음에도 품질 감시 수단이 없어서 아무도 모르고 방치되어 있었던 사실까지 밝혀졌다.

라이브 실측과 백테스트 데이터의 재구축

문제를 명확히 정의한 후, pykrx 라이브러리와 KRX 로그인을 연동하여 과거 시점의 데이터를 검증해 보았다. 다행히 과거 특정 시점의 유니버스를 문제없이 조회할 수 있었고, 실제로 2023년 6월 19일 기준으로 KOSPI 952종목 중 43종목이 지금은 상장폐지된 상태라는 점을 구체적으로 확인했다. 상장폐지된 종목들의 과거 주가 역시 정상적으로 조회되었고, 시장별로 요청 한번에 전 종목의 OHLCV와 시가총액 스냅샷을 긁어올 수 있는 방법도 찾아냈다.

이를 바탕으로 설계한 9가지 체크리스트를 하나씩 지워나갔다. 일별로 유니버스 스냅샷을 저장하는 universe_history.parquet를 구축하고, 기준일에 맞는 유니버스를 제공하는 단일 조회 API를 만들었다. 여기에 상장폐지된 종목들을 포함해 총 7,021종목에 달하는 이름 매핑 정보를 정리했다. 이어서 상폐 종목을 포함한 2,976종목에 대해 2023년 6월 19일부터의 OHLCV 전체 데이터를 수정주가 기준으로 정렬해 다시 백필했다. 비어 있거나 실패한 데이터 없이 약 16분 만에 완벽히 채워 넣을 수 있었다.

또한 기존에 종목당 개별적으로 요청을 보내느라 9분 넘게 걸리던 일별 증분 수집 방식을 스냅샷 요청 두 번으로 줄여 초 단위로 단축했다. 여기에 등락률 역산으로 주식 분할이나 병합을 감지해 문제 종목만 자동으로 다시 내려받는 방식을 적용했다. 모멘텀과 유니버스, 펀더멘털의 기준 시점도 완벽하게 교정했다. 백테스트 시에는 상장폐지 시 마지막 관측가로 청산하고 거래 비용을 왕복 30bp로 설정하여 현실성을 더했다. 여기에 KRX의 일별 데이터 계보를 소급 구축하고 DART 공시일을 기준으로 분기 재무 정보를 불러오는 스키마도 마련했다.

마지막으로 신선도와 유니버스 연속성을 검증하는 5종의 품질 게이트를 배치해 실패 시 프로세스를 즉시 종료하도록 설계하고, 매일 6시 5분에 감시자가 SKILL.md 를 갱신하도록 구성했다.

백업 장애 트러블슈팅과 정직해진 데이터의 결과

구현을 마친 후 정기 검증을 하던 중 예기치 못한 사고가 발생했다. 밤 9시 43분에 시작된 백업 과정에서 pg_dump가 NAS 연결 순단 문제로 인해 소켓이 끊어지지 않고 2.6시간 동안 블록되는 현상이 일어난 것이다. launchd 스케줄러 특성상 같은 작업이 실행 중이면 다음 실행을 생략하기 때문에, 이대로 두면 아침 5시 50분의 핵심 데이터 수집이 통째로 날아갈 위기였다. 수동으로 pg_dump 프로세스를 강제 종료하고 체인을 재개하여 다행히 남은 수집 단계를 정상화했다. 재발 방지를 위한 타임아웃 처리는 조만간 해결해야 할 숙제다.

모든 데이터가 바로잡힌 후, 2024년 6월 28일 기준 유니버스에 지금은 사라진 상폐 종목 109개가 제대로 꽂혀 있는 것을 보며 생존편향이 완전히 차단되었음을 확인했다. 개선된 환경에서 6개월 모멘텀 상위 20개 종목을 매월 리밸런싱하는 백테스트를 돌려보았다. 결과는 2년 동안 누적 수익률 -62.7%, 월 승률 33%라는 처참한 수치였다. 만약 과거처럼 look-ahead 편향이 껴있었다면 아주 그럴듯한 거짓 수익률이 나왔을 텐데, 정직해진 데이터는 시장의 냉혹함을 있는 그대로 보여주었다. 단순한 급등주 추격이 한국 소형주 시장에서는 통하지 않는다는 사실을 입증한 셈이며, 앞으로 이 튼튼한 토대 위에서 낙폭과대 저점 매수 가설 같은 다음 백로그 과제들을 하나씩 검증해 나가려 한다.

다음 작업으로는 수정주가 통일과 캘린더 게이트라는 선행 조건이 해결되었으니, 구축해 둔 환경을 기반으로 LEAN 한국 데이터 어댑터를 설계할 계획이다.

오늘의 소감

먼가 주식거래 데이터 정리를 하는게 액셀로 하면 간단한데.. 여러 증권사 데이터를 통합 관리 하려고 하니 생각보다 간단하지 않다. 상폐된 데이터도 따로 처리해주어야 하고.. 한국의 상폐종목들..이 많다

#개발일지

의견 나누기

💬 GitHub Discussions 댓글 위젯 영역 (Giscus 활성화 예정)