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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
> **현재 상태 (2026-05-08)**:
> - GitHub 원격 브랜치 정리 완료: 완료 브랜치 삭제, 활성 PR 브랜치만 유지
> - 60영업일 Paper 실험 freeze pack 병합: `reports/experiment_freeze_pack.md`, 일/주간 ops checklist, stop condition 문서 추가
> - Paper Evidence 런타임: 일별 자동 수집 → benchmark finalization → promotion package → launch readiness
> - Paper Evidence 런타임: 일별 자동 수집 → benchmark finalization → 날짜순 canonical evidence → promotion package → launch readiness
> - Paper Runtime State Machine: normal/degraded/frozen/blocked_insufficient_evidence 상태 자동 전환 + allowed_actions 제어
> - Paper Pilot Authorization: blocked 상태에서도 제한적 real paper 가능 (수동 승인 + 리스크 캡 + fail-closed/audited entry guard)
> - Paper 신규 진입 실행 경계 fail-closed: preflight 상태 누락/손상 또는 runtime 조회 실패 시 BUY 제출 전 차단, SELL 청산은 유지
Expand Down
25 changes: 9 additions & 16 deletions core/paper_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,11 @@ def _already_recorded(jsonl_path: Path, date_str: str, *, allow_provisional: boo
if not jsonl_path.exists():
return False
try:
with open(jsonl_path, "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines[-10:]:
line = line.strip()
if not line:
continue
try:
rec = json.loads(line)
if rec.get("date") == date_str:
if allow_provisional and rec.get("benchmark_status") == "provisional":
return False # provisional이면 finalize 가능
return True
except json.JSONDecodeError:
continue
for rec in reversed(_read_all_evidence(jsonl_path)):
if rec.get("date") == date_str:
if allow_provisional and rec.get("benchmark_status") == "provisional":
return False # provisional이면 finalize 가능
return True
except Exception:
pass
return False
Expand Down Expand Up @@ -231,15 +222,17 @@ def get_canonical_records(strategy: str) -> list[dict]:
all_records = _read_all_evidence(jsonl_path)
by_date: dict[str, dict] = {}
for r in all_records:
date = r["date"]
date = r.get("date")
if not date:
continue
prev = by_date.get(date)
if prev is not None:
prev_exec = prev.get("execution_backed", True)
curr_exec = r.get("execution_backed", True)
if prev_exec and not curr_exec:
continue
by_date[date] = r # later entry wins unless it would downgrade real paper to shadow
return list(by_date.values())
return [by_date[date] for date in sorted(by_date)]


# ═══════════════════════════════════════════════════════════════
Expand Down
2 changes: 2 additions & 0 deletions docs/BACKTEST_IMPROVEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
| **cash-only day 처리** (v5.1) | blocked/no-position 상태에서 당일 PortfolioSnapshot 없으면 daily_return=None → benchmark_status=failed → clean day 불인정 deadlock | **수정 완료** — 직전 snapshot + 거래 0건이면 daily_return=0.0 추론. 진짜 데이터 부재만 failed |
| **벤치마크 비용 미반영** (v5.0 수정) | `_buy_and_hold_metrics`에 거래비용 미적용 → 전략 alpha 0.2~0.5%p 과대평가 | **수정 완료** — commission/tax/slippage 반영 |
| **pilot guard fail-open 위험** | pilot authorization 이후 runtime/evidence/notifier/cap guard 오류가 예외 처리로 삼켜지면 제한 주문이 허용될 수 있음 | **수정 완료** — `check_pilot_entry()` 모든 blocked/allowed 결과 audit, guard 예외는 fail-closed block, notifier health missing/corrupt도 차단 |
| **paper evidence append 순서 의존** | backfill/shadow/finalize 기록이 JSONL 뒤쪽에 섞이면 최신 evidence, 최근 10일, promotion period가 append 순서 기준으로 왜곡될 수 있음 | **수정 완료** — canonical view를 날짜별 최신 record + 날짜순 반환으로 고정하고, 중복 확인은 전체 파일의 해당 날짜 최신 record를 조회 |
| **방어형 후보 raw benchmark 해석** | cash-switch처럼 평균 노출이 낮은 후보는 full B&H 대비 excess가 과도하게 나빠 보일 수 있음 | **진단 추가** — research sweep에 exposure-matched B&H return/sharpe/MDD/excess 기록. 단, promotion gate는 raw benchmark excess 유지 |
| **회전 전략의 sparse signal 한계** | 월간 상대강도 후보가 BUY/SELL 신호만 내면 목표 top-N을 지속적으로 채우지 못해 평균 노출이 낮게 측정될 수 있음 | **검증 완료** — target-weight top-N research backtester로 avg exposure 85%대까지 개선. 5종목 smoke는 raw excess 음수였지만 canonical top-20 full sweep은 alpha 후보 확인. `hold_rank_buffer` 적용 후 turnover gate 통과, `benchmark_risk` overlay 적용 후 best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`가 return=+210.24%, raw excess=+60.85%p, Sharpe=1.60, MDD=-19.24%, turnover/year=858.0%, WF positive/Sh+ 100%로 research sweep 기준 provisional gate 통과 |
| **target-weight 후보의 paper 연결 부재** | research-only evaluator에서 provisional 후보가 나와도 기존 canonical/paper 경로는 등록 전략만 평가 | **대부분 해결** — canonical bundle 재현 완료 + `core/target_weight_rotation.py`, `tools/target_weight_rotation_pilot.py`로 전용 paper/pilot adapter 추가. dry-run은 `--record-shadow-evidence`, `--shadow-days 3`, 또는 `--shadow-start-date/--shadow-end-date`로 non-promotable shadow readiness evidence, launch readiness artifact, pilot runbook을 남기고 cap preview와 plan 기반 최소/추천 cap + enable 명령으로 pilot 승인 전 캡 적합성을 확인. 이후 `--readiness-audit`가 주문 제출, evidence 기록, pilot session 저장 없이 clean shadow/launch readiness, active pilot auth와 cap validation, 중복 session idempotency, 실행일/장 시간, 실행 전 position drift, 추천 cap 충족 여부, 유동성 preflight, 비용 반영 pre-trade risk를 JSON artifact와 Markdown 운영 리포트로 판정한다. 유동성 preflight는 최근 20일 평균 거래대금 대비 주문 notional 비율을 계산하고 기본 5%(`--max-order-adv-pct`) 초과 주문은 readiness와 실행을 fail-closed 차단한다. pre-trade risk는 `RiskManager.calculate_transaction_costs()`의 수수료/세금/동적 슬리피지 예상 체결가를 재사용해 현금 부족, 최소 현금비중, 총투자비중, 종목별 비중, 보유 종목 수 위반을 주문 제출 전에 차단한다. Markdown 리포트는 shadow 수집, audit 재실행, 추천 cap 승인, capped paper 실행 명령을 함께 남긴다. `--shadow-days N`은 휴장/데이터 공백으로 같은 거래일에 매핑될 때 과거 평일을 추가 스캔해 N개 고유 resolved trade_day 충족을 목표로 하며, 목표 미달이나 날짜별 실패는 non-zero 종료로 fail-closed 처리한다. 실행형 `pilot_paper` evidence는 같은 candidate/trade_day의 기존 pilot session artifact가 없고, 주문 제출 직전 실제 paper position이 계획 입력 장부 `position_quantities_before`와 일치하고, KST 실행일과 KRX 정규장 주문 가능 시간을 통과하고, 유동성 preflight와 pre-trade risk를 통과하고, 계획 주문 전부 성공 및 주문 결과 payload와 당일 `TradeHistory` fill 집계가 plan과 일치하며, 실행 후 실제 paper position 전체가 리밸런싱 후 `target_quantities_after` 장부와 일치하고 계획 밖 양수 포지션이 없을 때만 수집한다. 같은 날짜가 이미 기록된 경우에도 기존 canonical evidence가 `pilot_paper`/authorized/execution-backed이고 target-weight plan hash와 complete/execution-market-session/liquidity/pre-trade-risk/order/fill/position 검증을 통과해야 재사용해 중복 실행/stale plan/장 외 실행/부분 실행/중단/주문 결과 불일치/체결 기록 불일치/기존 evidence 검증 실패/포지션 불일치일이 승격 증거로 섞이지 않게 한다. 명시 재시도는 `--allow-rerun`으로만 허용한다. 다음 과제는 shadow clean days 충족 후 정규장 capped pilot_paper execution-backed evidence 축적 |
Expand Down Expand Up @@ -129,6 +130,7 @@
| 주문 상태기계 | 높음 | **완료 — OrderStatus 9개 상태, FILLED 전 position 반영 없음, live/paper 회귀 테스트 green** |
| 벤치마크 거래비용 반영 | 높음 | **완료 — `_buy_and_hold_metrics`에 commission/tax/slippage 적용** |
| Paper Evidence 수집 체계 | 높음 | **완료 — `core/paper_evidence.py` 일별 22개 지표, 6 anomaly rule, 9 approval gate** |
| Paper Evidence canonical 정렬 | 높음 | 완료 — append-only JSONL에서 같은 날짜의 최신 record만 canonical로 유지하고 날짜순으로 반환. backfill/finalize/shadow append 순서와 무관하게 freshness, 최근 10일, promotion period가 실제 날짜 기준으로 계산됨 |
| Paper Runtime State Machine | 높음 | **완료 — `core/paper_runtime.py` 5개 상태(normal/degraded/frozen/blocked/research_disabled), schema quarantine** |
| Paper Pilot Authorization | 높음 | **완료 — `core/paper_pilot.py` launch readiness + pilot auth + 리스크 캡 + fail-closed/audited entry guard** |
| Paper Preflight Check | 높음 | **완료 — `core/paper_preflight.py` 운영 준비 상태 점검** |
Expand Down
9 changes: 5 additions & 4 deletions docs/PROJECT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ quant_trader/
| **data_validator.py** | OHLCV Null·NaN·음수 주가·거래량·타임스탬프 역전 등 검사. |
| **notifier.py** | 통합 알림 이중화. 1차 디스코드 → 2차 텔레그램 Bot API → 3차 이메일(SMTP). `critical=True` 시 모든 채널 동시 발송. `Scheduler`, `CircuitBreaker`, `main.py` 등 주요 모듈이 `DiscordBot` 대신 `Notifier` 사용. 알림 실패 5회 누적 시 점검 경고. |
| **strategy_diagnostics.py** | `DiagnosticLine` — 전략별 신호·점수 진단 라인 생성. 스케줄러·대시보드에서 전략 실행 현황 요약 시 사용. |
| **paper_evidence.py** | Paper Evidence 런타임 수집. `DailyEvidence` 데이터클래스, `collect_daily_evidence()`, `append_shadow_plan_evidence()`, `finalize_daily_evidence()`, `generate_promotion_package()`, 3종 benchmark excess (same_universe/exposure_matched/cash_adjusted), 6 anomaly rule (repeated_reject, phantom_position, stale_pending, duplicate_flood, reconcile, deep_drawdown), cash-only carry-forward (zero-return semantics). Shadow plan evidence는 `execution_backed=False`라 promotion에는 반영되지 않는다. |
| **paper_evidence.py** | Paper Evidence 런타임 수집. `DailyEvidence` 데이터클래스, `collect_daily_evidence()`, `append_shadow_plan_evidence()`, `finalize_daily_evidence()`, `generate_promotion_package()`, 3종 benchmark excess (same_universe/exposure_matched/cash_adjusted), 6 anomaly rule (repeated_reject, phantom_position, stale_pending, duplicate_flood, reconcile, deep_drawdown), cash-only carry-forward (zero-return semantics). Canonical view는 같은 날짜의 뒤쪽 최신 record를 유지한 뒤 날짜순으로 반환한다. Shadow plan evidence는 `execution_backed=False`라 promotion에는 반영되지 않는다. |
| **paper_runtime.py** | Paper Runtime State Machine. 5개 상태 (research_disabled/normal/degraded/frozen/blocked_insufficient_evidence), schema quarantine (legacy record 제외), allowed_actions (모든 상태에서 exit/cancel/reconcile/finalize/evidence/reporting 허용). `get_paper_runtime_state()`, `filter_runtime_eligible()`. |
| **paper_pilot.py** | Paper Pilot Authorization. `PilotAuthorization` 데이터클래스, `enable_pilot()`, `get_active_pilot()`, `check_pilot_prerequisites()`, `check_pilot_entry()`, `compute_launch_readiness()`, `generate_launch_readiness_artifact()`. launch readiness: clean_final_days ≥ 3 + evidence_fresh + benchmark_final_ratio ≥ 40% + notifier_ready. Pilot entry guard는 모든 allowed/blocked 결정을 audit하고 runtime/evidence/notifier/order/position/exposure guard 예외를 fail-closed로 차단한다. |
| **paper_preflight.py** | Paper 세션 전 운영 준비 상태 점검. runtime state, allowed_actions, evidence freshness, notifier health 등 확인. |
Expand Down Expand Up @@ -362,8 +362,8 @@ quant_trader/
| **test_strategy_validator.py** | 전략 검증(validate) 로직. |
| **test_trading_hours.py** | 장 시간·휴장일. |
| **test_paper_lifecycle.py** | Full paper lifecycle (BUY/SELL/Snapshot, 격리 DB truncate, 4/4 PASS). |
| **test_paper_evidence.py** | Paper Evidence 수집/검증 (52건): JSONL I/O, anomaly detection, benchmark missing, E2E replay 7일, cash-only zero-return deadlock regression, shadow evidence 분리, target-weight shadow plan evidence. |
| **test_paper_runtime.py** | Paper Runtime State Machine (45건): 상태 전이, schema quarantine, allowed_actions, auto-unfreeze. |
| **test_paper_evidence.py** | Paper Evidence 수집/검증 (55건): JSONL I/O, canonical 날짜 정렬/idempotency, promotion period, anomaly detection, benchmark missing, E2E replay 7일, cash-only zero-return deadlock regression, shadow evidence 분리, target-weight shadow plan evidence. |
| **test_paper_runtime.py** | Paper Runtime State Machine (46건): 상태 전이, canonical latest date, schema quarantine, allowed_actions, auto-unfreeze. |
| **test_paper_pilot.py** | Paper Pilot Authorization: pilot enable/disable, cap enforcement, launch readiness, preflight prerequisites, artifact-only target-weight 후보 eligibility, fail-closed/audited entry guard. |
| **test_paper_preflight.py** | Paper Preflight Check: 운영 준비 상태 점검 시나리오. |
| **test_promotion_engine.py** | Promotion 규칙: metrics 기반 자동 판정, threshold 경계. |
Expand Down Expand Up @@ -731,6 +731,7 @@ full paper 신규 BUY는 preflight status artifact와 runtime state가 모두
| ✅ **canonical 평가 입력 snapshot 추가** | `tools/evaluate_and_promote.py --canonical`이 선정 종목의 유동성 데이터 범위, 벤치마크 데이터 범위, 데이터 수집 오류, `data_snapshot_hash`를 `run_metadata.json`에 기록한다. 평가 예외는 단순 0% 성과와 구분해 `evaluation_status=failed`로 남긴다 |
| ✅ **live gate snapshot 검증 추가** | `core/live_gate.py`와 `core/promotion_engine.py`가 canonical `run_metadata.json`의 `data_snapshot_hash`, 재계산 hash, 종목군 데이터 범위, 수집 오류, 평가 오류 상태를 검증한다. 손상되거나 부분 실패한 승격 산출물은 live 진입과 산출물 기반 pilot eligibility 로드에서 fail-closed 처리한다 |
| ✅ **paper evidence 출처 검증 강화** | 60영업일 성과가 좋아도 `execution_backed=True`와 `real_paper`/`pilot_paper` 출처가 명시되지 않은 예전 형식·수작업 JSONL record는 승격 증거로 세지 않는다. 출처 없는 기록만 있으면 `no_execution_backed_evidence`와 `insufficient_days=0/60`으로 promotion package를 차단한다 |
| ✅ **paper evidence canonical 정렬 보강** | append-only JSONL에서 같은 날짜는 뒤쪽 최신 record를 유지하되, canonical view는 날짜순으로 반환한다. 중복 확인도 마지막 10줄이 아니라 전체 파일의 해당 날짜 최신 record를 확인해 backfill/shadow 기록이 뒤섞여도 freshness, 최근 10일, promotion period가 append 순서에 흔들리지 않게 한다 |
| ✅ **Zero-return Semantics** | cash-only/no-position day deadlock 해소 — daily_return=0.0 추론 |
| ✅ **scoring paper_only 강등** | Sharpe/PF/WF 안정성 미달. 관찰은 가능하지만 우선 pilot 후보 아님 |

Expand Down Expand Up @@ -859,4 +860,4 @@ full paper 신규 BUY는 preflight status artifact와 runtime state가 모두

> 📌 **상세 설계·지표 공식·전략 로직·시스템 진단**: `quant_trader_design.md`
> **문서 버전**: v5.3
> **최종 수정**: 2026-05-08 (target-weight 가격 최신성 guard 반영)
> **최종 수정**: 2026-05-08 (paper evidence canonical 정렬 보강)
Loading
Loading