From db1c3f9ea146ab83bd06ffbc07315f219c97ddf1 Mon Sep 17 00:00:00 2001 From: Quant Trader Date: Thu, 27 Aug 2026 10:20:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=8B=AC=EB=A0=A5=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=20=EB=8F=84=EA=B5=AC=EA=B0=80=20=EC=9E=A5=EC=A4=91=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EC=97=90=EC=84=9C=20=EC=98=A4=EB=8A=98=EC=9D=84=20?= =?UTF-8?q?=ED=9C=B4=EC=9E=A5=EC=9C=BC=EB=A1=9C=20=EC=98=A4=ED=8C=90?= =?UTF-8?q?=ED=95=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 머지 직후 오늘 날짜까지 넣고 돌려 보니 2026-08-27을 휴장 오류로 잡았다. 장중(10:16)이라 지수 봉이 아직 안 올라왔을 뿐인데 '데이터 없음'을 '휴장'으로 읽었다. 데이터가 아직 없는 최근 날은 휴장이 아니라 모름이다. 확인 범위를 지수의 마지막 봉 날짜까지로 자르고, 어디까지 확인했는지 출력에 적는다. 없는 사실을 오류로 보고하지 않는 게 이 도구의 존재 이유다. 매일 오탐이 하나씩 뜨면 진짜 달력 오류가 묻힌다 — 이 저장소가 경보 위생으로 다섯 번 데인 그 패턴이다. --- tests/test_snapshot_backfill.py | 30 +++++++++++++++++++++++++----- tools/verify_trading_calendar.py | 17 +++++++++++++++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/tests/test_snapshot_backfill.py b/tests/test_snapshot_backfill.py index ecf3ec57..3d625b01 100644 --- a/tests/test_snapshot_backfill.py +++ b/tests/test_snapshot_backfill.py @@ -210,11 +210,31 @@ def test_verifier_reports_mismatch(self, monkeypatch): """검증 도구가 불일치를 실제로 잡아내는가(달력이 맞아도 로직은 살아 있어야).""" import tools.verify_trading_calendar as v - # 시장은 6/1만 열렸다고 가정 — 나머지 평일은 전부 wrong_open이어야 한다 - monkeypatch.setattr(v, "market_open_days", lambda s, e, symbol=None: {date(2026, 6, 1)}) - r = v.compare(date(2026, 6, 1), date(2026, 6, 5)) - assert date(2026, 6, 1) not in r["wrong_open"] - assert date(2026, 6, 2) in r["wrong_open"] + # 6/8·6/10만 열렸다고 가정하면 그 사이 평일 6/9가 wrong_open으로 잡혀야 한다. + # (6/3은 실제 휴장일로 등록돼 있어 대조 대상에서 제외 — 순수 로직만 본다) + monkeypatch.setattr( + v, "market_open_days", + lambda s, e, symbol=None: {date(2026, 6, 8), date(2026, 6, 10)}, + ) + r = v.compare(date(2026, 6, 8), date(2026, 6, 12)) + assert date(2026, 6, 9) in r["wrong_open"] + assert date(2026, 6, 8) not in r["wrong_open"] + assert date(2026, 6, 10) not in r["wrong_open"] + + def test_verifier_ignores_days_beyond_last_known_bar(self, monkeypatch): + """데이터가 아직 안 올라온 최근 날은 '휴장'이 아니라 '모름'이다. + + 장중에 돌리면 오늘 봉이 없어서 오늘이 휴장으로 오판됐다(실제 오탐). + """ + import tools.verify_trading_calendar as v + + monkeypatch.setattr( + v, "market_open_days", + lambda s, e, symbol=None: {date(2026, 6, 1), date(2026, 6, 2)}, + ) + r = v.compare(date(2026, 6, 1), date(2026, 6, 30)) + assert r["verified_through"] == date(2026, 6, 2) + assert r["wrong_open"] == [], "데이터 없는 미래 날짜를 휴장 오류로 보고했다" def test_verifier_does_not_report_when_data_unavailable(self, monkeypatch): """데이터 소스 장애를 달력 오류로 둔갑시키지 않는다.""" diff --git a/tools/verify_trading_calendar.py b/tools/verify_trading_calendar.py index 276616a7..b1e88f85 100644 --- a/tools/verify_trading_calendar.py +++ b/tools/verify_trading_calendar.py @@ -60,6 +60,13 @@ def compare(start: date, end: date) -> dict: if opened is None: return {"error": "시장 데이터를 가져오지 못해 대조 불가", "wrong_open": [], "wrong_closed": []} + # 데이터가 아직 안 올라온 최근 날은 '휴장'이 아니라 '모름'이다. 장중에 돌리면 + # 오늘 봉이 없어서 오늘이 휴장으로 오판된다 — 확인 범위를 지수의 마지막 봉까지로 + # 자른다. 없는 사실을 오류로 보고하지 않는 게 이 도구의 존재 이유다. + last_known = max(opened) + if end > last_known: + end = last_known + th = TradingHours(Config.get()) wrong_open: list[date] = [] wrong_closed: list[date] = [] @@ -77,7 +84,12 @@ def compare(start: date, end: date) -> dict: wrong_closed.append(d) d += timedelta(days=1) - return {"wrong_open": wrong_open, "wrong_closed": wrong_closed, "checked": checked} + return { + "wrong_open": wrong_open, + "wrong_closed": wrong_closed, + "checked": checked, + "verified_through": end, + } def main() -> int: @@ -96,7 +108,8 @@ def main() -> int: return 0 # 데이터 소스 장애를 달력 오류로 보고하지 않는다 wo, wc = result["wrong_open"], result["wrong_closed"] - print(f" 평일 {result['checked']}일 확인") + through = result.get("verified_through") + print(f" 평일 {result['checked']}일 확인 (~{through} 까지 — 지수 마지막 봉 기준)") if wo: print(f"\n ❌ 거래일로 판정했지만 실제 휴장 ({len(wo)}일) — 커버리지 분모 과대")