Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions config/holidays.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
# 한국 증시 휴장일 — 자동 갱신 (pykrx + fallback). 수동 편집 가능.
# 갱신: python main.py --update-holidays
#
# 2026-08-26 달력 전면 교정 (tools/verify_trading_calendar.py 가 시장 데이터와 대조해 적발):
# 추가: 02-16·02-17·02-18(설 연휴 — 2025년 날짜인 01-27~29가 대신 들어가 있었다),
# 05-01(근로자의 날), 06-03(지방선거), 07-17
# 제거: 01-27·01-28·01-29 — 이 날들은 실제로 개장했다
# 07-17이 빠져 있어 거래일로 판정됐고, 그만큼
# 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) 자동 갱신이 놓쳤다.
# 달력 오류는 양방향으로 조용히 해롭다 — 분모를 부풀리거나, 진짜 결측을 가린다.
# tools/verify_trading_calendar.py 로 시장 데이터와 대조할 것.

holidays:
- '2026-01-01'
- '2026-01-27'
- '2026-01-28'
- '2026-01-29'
- '2026-02-16'
- '2026-02-17'
- '2026-02-18'
- '2026-03-01'
- '2026-03-02'
- '2026-05-01'
- '2026-05-05'
- '2026-05-24'
- '2026-05-25'
- '2026-06-03'
- '2026-06-06'
- '2026-07-17'
- '2026-08-15'
- '2026-08-17'
- '2026-09-24'
Expand Down
24 changes: 23 additions & 1 deletion core/basket_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def evaluate_basket_paper_operation(
trading_days_total: int,
snapshot_days: int,
pending_failed_orders: int,
reconstructed_days: int = 0,
total_costs: float,
initial_capital: float,
nav_return_pct: float | None = None,
Expand All @@ -55,6 +56,8 @@ def evaluate_basket_paper_operation(
"""
trading_days_total = max(int(trading_days_total), 0)
snapshot_days = max(int(snapshot_days), 0)
reconstructed_days = max(0, min(int(reconstructed_days), snapshot_days))
measured_days = snapshot_days - reconstructed_days
issues: list[str] = []

# A. 운영 무결성
Expand Down Expand Up @@ -92,6 +95,11 @@ def evaluate_basket_paper_operation(
"today": str(today),
"progress_days": trading_days_total,
"snapshot_days": snapshot_days,
"reconstructed_days": reconstructed_days,
"measured_days": measured_days,
"measured_coverage": round(
(measured_days / trading_days_total) if trading_days_total > 0 else 0.0, 4
),
"min_trading_days": min_trading_days,
"progress_pct": min(1.0, trading_days_total / min_trading_days) if min_trading_days > 0 else 1.0,
"snapshot_coverage": round(coverage, 4),
Expand Down Expand Up @@ -122,7 +130,12 @@ def format_evaluation_report(result: dict[str, Any], basket_name: str = "") -> s
f" {result['progress_pct']:.0%})" if result["verdict"] == "WAIT" else ""
),
f" 운영 기간: {result['operation_start']} ~ {result['today']}",
f" 스냅샷 커버리지: {result['snapshot_coverage']:.0%}",
f" 스냅샷 커버리지: {result['snapshot_coverage']:.0%}"
+ (
f" (실측 {result['measured_days']}일 {result['measured_coverage']:.0%}"
f" + 사후 복원 {result['reconstructed_days']}일)"
if result.get("reconstructed_days") else ""
),
f" 비용 드래그: 누적 {result['cost_drag_cum']:.4%} | 연환산 {result['cost_drag_annualized']:.4%}"
+ (
" (기간 미충족 — 초기 매입 일회성 비용이 과장되므로 판정 미적용)"
Expand Down Expand Up @@ -414,8 +427,14 @@ def _d(v):

th = TradingHours(config)
snapshot_dates = {_d(s.date) for s in snaps}
# 사후 복원(reconstructed)된 날은 따로 센다. 커버리지 95% 게이트는 '시스템이 실제로
# 돌았는가'를 보는 장치라, 보정분이 실측인 척하면 게이트가 목적을 잃는다.
reconstructed_dates = {
_d(s.date) for s in snaps if bool(getattr(s, "reconstructed", False))
}
trading_days_total = 0
snapshot_days = 0
reconstructed_days = 0
d = operation_start
while d <= today:
if th.is_trading_day(datetime(d.year, d.month, d.day)):
Expand All @@ -426,6 +445,8 @@ def _d(v):
trading_days_total += 1
if d in snapshot_dates:
snapshot_days += 1
if d in reconstructed_dates:
reconstructed_days += 1
d += timedelta(days=1)

total_costs = sum(
Expand Down Expand Up @@ -487,6 +508,7 @@ def _d(v):
today=today,
trading_days_total=trading_days_total,
snapshot_days=snapshot_days,
reconstructed_days=reconstructed_days,
# dead-letter도 이 바스켓 계정 것만 집계 — 다른 전략의 잔여 실패 주문이
# 바스켓 승격을 막는 오판 방지(바스켓 자신의 실패는 여전히 fail-closed).
pending_failed_orders=len(get_pending_failed_orders(account_key=basket_key) or []),
Expand Down
Loading
Loading