fix: 스냅샷 커버리지 미달의 진짜 원인은 결측이 아니라 거래일 달력이었다 - #459
Merged
Conversation
승격 기준인 커버리지 95%가 두 트랙 다 미달로 나와 결측 복구를 파다가, 큰 쪽이 결측이 아니라는 걸 알았다. config/holidays.yaml에 2026-07-17이 빠져 있었다. 그 날은 휴장인데 거래일로 집계되니 분모만 부풀어 실제보다 낮게 보였다. kr_diversified_hold 96.3% → 94.5% kr_pocket 96.9% → 93.9% 한 줄이 두 트랙을 기준 아래로 끌어내린 것이다. 근거는 KOSPI 지수와 005930·105560· 069500 전부 그날 봉이 없고 07-16·07-20은 있다는 것. pykrx 영업일 API가 이 환경에서 깨져 있어(RangeIndex) 자동 갱신이 조용히 놓쳤다. 달력을 시장 데이터와 대조하는 도구를 만들어 전 구간을 돌렸더니 7건이 더 나왔다. 설 연휴가 2025년 날짜(01-27~29)로 들어가 있었고 실제 2026년 설(02-16~18)과 근로자의 날(05-01), 지방선거(06-03)가 빠져 있었다. 01-27~29는 실제로 개장했다. 전부 바로잡아 2026년 전 구간이 시장 데이터와 일치한다. 달력 오류는 양방향으로 조용히 해롭다. 휴장일을 거래일로 오인하면 분모가 부풀어 없는 결측을 쫓게 되고, 거래일을 휴장일로 오인하면 진짜 결측이 집계에서 사라진다. 뒤쪽이 더 위험하다. 그래서 달력을 신뢰하지 않고 지수 데이터로 직접 확인한다. 진짜 결측은 2일이었다(06-26, 08-18). 원장 재생으로 복원한다: - 포지션·현금은 trade_history + cash_flows 재생으로 정확히 복원된다. 실측 81일치를 재구성해 대조하니 현금은 전부 일치했다. - NAV는 정확히 복원 못 한다. 기존 스냅샷은 10:07 장중 마크인데 그 시점 틱이 없다. 시가·종가 중간값이 실측에 가장 가까웠다(평균 오차 0.45~0.53%, 시가 0.6%, 종가 0.8%). - 그래서 복원분은 reconstructed=True로 표시하고 평가가 실측과 나눠 표기한다. 커버리지 게이트는 '시스템이 실제로 돌았는가'를 보는 장치라, 보정분이 실측인 척하면 게이트가 목적을 잃는다. 실측이 나중에 들어오면 표시를 걷어낸다. - 가격을 못 구하는 날은 채우지 않는다(가짜 NAV 방지). 앞으로의 결측은 사이클이 스스로 메운다. _nav_attribution_date가 '오늘이 거래일이면 오늘'로 귀속해서 어제 사이클이 안 돌면 그 하루는 영원히 비었다 — 08-18 결측이 08-19~08-26 내내 남은 이유다. 이제 매매 전에 최근 10일 내 결측을 보충한다. 소급 한도를 짧게 잡은 건 며칠짜리 사고는 메우되 장기 중단은 공백으로 드러나야 하기 때문이다. 결과: 두 트랙 다 커버리지 100%(실측 96%/97% + 복원 2일/1일), 이슈 0건. 달력 교정만으로도 이미 기준을 넘었고 복원은 여유분이다. 달력이 바뀌며 기존 테스트 2건의 기대값이 어긋났다. 둘 다 로직 회귀가 아니라 달력 정정 결과라 기대값을 고쳤다. 연휴 stale 테스트는 1/27~29(실제 개장)를 쓰고 있어서 실제 설 연휴(2/16~18) 구간으로 옮겼다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
시작점
승격 기준인 스냅샷 커버리지 95%가 두 트랙 다 미달로 나왔다. 결측을 어떻게 복구할지 파다가, 큰 쪽이 결측이 아니라는 걸 알았다.
진짜 원인 — 달력 한 줄
config/holidays.yaml에2026-07-17이 빠져 있었다. 휴장일인데 거래일로 집계되니 분모만 부풀어 실제보다 낮게 보였다.한 줄이 두 트랙을 기준 아래로 끌어내렸다. 근거는 KOSPI 지수와 005930·105560·069500 전부 그날 봉이 없고 07-16·07-20은 있다는 것. pykrx 영업일 API가 이 환경에서 깨져 있어(
RangeIndex오류) 자동 갱신이 조용히 놓쳤다.대조 도구를 만들었더니 7건이 더 나왔다
tools/verify_trading_calendar.py— 달력 판정을 KOSPI 지수 데이터와 대조한다.설 연휴가 2025년 날짜로 들어가 있었다. 지금은 2026년 전 구간(평일 170일)이 시장 데이터와 일치한다.
달력 오류는 양방향으로 조용히 해롭다. 휴장일을 거래일로 오인하면 분모가 부풀어 없는 결측을 쫓게 되고, 거래일을 휴장일로 오인하면 진짜 결측이 집계에서 사라진다. 뒤쪽이 더 위험하다. 그래서 달력을 신뢰하지 않고 지수 데이터로 직접 확인하는 쪽을 택했다.
진짜 결측 2일은 원장 재생으로 복원
core/snapshot_backfill.py— 06-26, 08-18.정확히 복원되는 것: 포지션과 현금.
trade_history+cash_flows재생이라 근사가 아니다. 실측 81일치를 재구성해 대조하니 현금은 전부 일치했다.근사에 그치는 것: NAV. 기존 스냅샷이 10:07 장중 마크인데 그 시점 틱이 남아 있지 않다. 어떤 방식으로도 정확히는 못 맞춘다.
중간값을 쓴다. 그리고 복원분은
reconstructed=True로 표시하고 평가가 실측과 나눠 표기한다.커버리지 게이트는 '시스템이 실제로 돌았는가'를 보는 장치다. 보정분이 실측인 척하면 게이트가 목적을 잃는다. 실측이 나중에 들어오면 표시를 걷어낸다. 가격을 못 구하는 날은 채우지 않는다.
앞으로의 결측은 사이클이 스스로 메운다
_nav_attribution_date가 '오늘이 거래일이면 오늘'로 귀속해서, 어제 사이클이 안 돌면 그 하루는 영원히 비었다. 08-18 결측이 08-19~08-26 내내 남은 이유다.이제 매매 전에 최근 10일 내 결측을 보충한다. 소급 한도를 짧게 잡은 건 며칠짜리 사고는 메우되 장기 중단은 공백으로 드러나야 하기 때문이다.
결과
달력 교정만으로 이미 기준을 넘었고 복원은 여유분이다.
CI 환경 재현(
config/settings.yaml치움) 전체 스위트 1982개 통과.달력이 바뀌며 기존 테스트 2건의 기대값이 어긋났다. 둘 다 로직 회귀가 아니라 달력 정정 결과라 기대값을 고쳤다. 연휴 stale 테스트는 1/27
29(실제 개장)를 쓰고 있어서 실제 설 연휴(2/1618) 구간으로 옮겼다.