From 5fe40df820c3a7e0edfe42dce08297ccc1e8207b Mon Sep 17 00:00:00 2001 From: Quant Trader Date: Fri, 10 Jul 2026 12:13:39 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20README=20=EC=A0=84=EB=A9=B4=20=EA=B0=9C?= =?UTF-8?q?=ED=8E=B8=20=E2=80=94=20=EC=96=BC=EA=B5=B4=EC=9D=80=20=EC=A7=A7?= =?UTF-8?q?=EA=B2=8C,=20=EC=9D=B4=EB=A0=A5=EC=9D=80=20=EC=95=84=EC=B9=B4?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 486줄짜리 README가 사실상 연구 변경이력 덤프였다. 처음 보는 사람이 프로젝트가 뭘 하는지 파악하기 전에 2026-04월 sweep 로그부터 읽게 되는 구조. - README를 프로젝트 소개 중심으로 재작성: 정직한 결론(알파 없음 -> 구조로 수익), 실제 운영 대시보드 캡처, 시스템 구성/승격 파이프라인 mermaid 다이어그램 2개, 운영 트랙 현황 표(7/10 기준), 안전장치는 설계 원칙 3개 + 세부는 접기(details)로 정리 - 운영 상태 스냅샷 이력, 전략 상태/승격 체계, research sweep 기록, target-weight pilot 운영 상세는 docs/RESEARCH_LOG.md로 원문 그대로 이관 (재타이핑 없이 슬라이스 — 내용 유실 없음) - docs/images/dashboard-main.png: 실운영 대시보드 캡처(1440x960, 두 트랙 카드 + 수익률 추이) 검증: 코드펜스 짝수, 상대 링크 9개 전부 실재, mermaid 2블록 문법 표준 준수. --- README.md | 556 ++++++++------------------------- docs/RESEARCH_LOG.md | 227 ++++++++++++++ docs/images/dashboard-main.png | Bin 0 -> 137193 bytes 3 files changed, 353 insertions(+), 430 deletions(-) create mode 100644 docs/RESEARCH_LOG.md create mode 100644 docs/images/dashboard-main.png diff --git a/README.md b/README.md index 04b41dea..0b1b51a1 100644 --- a/README.md +++ b/README.md @@ -1,485 +1,181 @@ # QUANT TRADER -![Python](https://img.shields.io/badge/Python-3.11%20%7C%203.12-3776AB) +![Python](https://img.shields.io/badge/Python-3.11%20%7C%203.12-3776AB?logo=python&logoColor=white) +![Tests](https://img.shields.io/badge/tests-1%2C683%20passed-2EA043) ![Market](https://img.shields.io/badge/Market-KR%20stocks-0F766E) ![Mode](https://img.shields.io/badge/Modes-backtest%20%7C%20paper%20%7C%20live--gated-7C3AED) ![Focus](https://img.shields.io/badge/Focus-risk%20first-E11D48) -데이터 기반 알고리즘 트레이딩을 공부하면서 만든 Python 자동매매 실험실입니다. -지표·펀더멘털 기반 신호 생성부터 백테스트, 모의투자, 리스크 관리, 운영 대시보드, KIS API 연동까지 한 저장소에서 재현할 수 있게 구성했습니다. +한국 주식 자동매매를 **백테스트 → 페이퍼 트레이딩 → 실전 게이트**까지 한 저장소에서 굴리는 개인 프로젝트입니다. +데이터 수집, 전략 검증, 리스크 관리, 일일 자동 운영, 웹 대시보드, KIS API 연동까지 전부 재현 가능하게 구성했습니다. -> 이 프로젝트는 학습·연구용입니다. 투자 조언이 아니며, 실전 주문 경로는 여러 fail-closed gate를 통과해야만 열리도록 설계했습니다. +이 프로젝트가 다른 자동매매 저장소와 다른 점은 **결론이 정직하다는 것**입니다. -## Why This Repo Exists +- 능동 알파(시장 예측으로 초과수익)를 candidate family 단위로 수개월 체계적으로 탐색했고, 전부 승격 게이트를 넘지 못했습니다. 그 실패 기록 전체를 [연구 로그](docs/RESEARCH_LOG.md)로 남겼습니다. +- 그래서 수익은 예측이 아니라 **구조**에서 얻습니다 — 분산 베타 보유 + 유휴 현금의 이자 회수 + 비용 최소화(ETF 매도세 면제 반영) + 월 적립 복리. +- 안전이 항상 우선입니다. 실전 주문 경로는 여러 단계의 **fail-closed 게이트**를 전부 통과해야만 열립니다. -자동매매 프로젝트는 "수익률 그래프"보다 운영 안전성이 먼저라고 생각합니다. -`quant_trader`는 전략을 바로 실계좌로 보내지 않고, 아래 흐름을 강제합니다. +> 학습·연구용 프로젝트입니다. 투자 조언이 아닙니다. -1. 데이터 수집과 지표 계산 -2. 비용·슬리피지·look-ahead guard가 들어간 백테스트 -3. paper evidence와 runtime health 축적 -4. promotion gate 검증 -5. live 시작 전 KIS 연결, 잔고 동기화, current blockers 재확인 +## 운영 대시보드 -## Highlights +![운영 대시보드](docs/images/dashboard-main.png) -- 한국 주식 중심 백테스트, 포트폴리오 백테스트, 바스켓 리밸런싱 -- scoring, mean reversion, trend following, fundamental factor, relative strength rotation 전략 -- paper trading runtime state machine과 60영업일 evidence 기반 promotion workflow -- KIS REST/WebSocket 연동, 주문 중복 방지, 미체결 reconcile, emergency liquidation guard -- Discord 알림, SQLite 백업, web dashboard, operator health check -- pytest 기반 회귀 테스트와 다양한 research sweep 도구 - -## Quick Start +*실제 운영 화면 (2026-07-10). 트랙별 평가금·TWR 수익률·MDD·주식 배치율·보유 종목을 한눈에 보고, 월 적립 입금도 우측 상단 버튼으로 기록합니다. 입금은 시간가중수익률(TWR)로 중화되어 수익률을 왜곡하지 않습니다.* ```bash -python -m venv .venv -.venv\Scripts\activate -pip install -r requirements.txt - -# 사용 가능한 실행 모드 보기 -python main.py --mode guide +python main.py --mode dashboard # http://127.0.0.1:8080 +``` -# 단일 종목 백테스트 -python main.py --mode backtest --strategy scoring --symbol 005930 +## 시스템 구성 + +```mermaid +flowchart LR + FDR["FinanceDataReader
시세·지수·수정주가"] --> RB + KIS["KIS API
REST·WebSocket"] --> EX + RB["바스켓 리밸런서
drift 트리거 · 회전 상한
1일 1매매 가드"] --> RG + RG["리스크 가드
MDD·일손실 한도
유동성·중복주문 차단"] --> EX + EX["주문 실행기
paper 체결 모델
live는 gate 통과 시에만"] --> DB[("SQLite WAL
거래·포지션·스냅샷·입금")] + DB --> OBS["관측성
헬스체크 · 디스코드 알림
일일/주간 리포트"] + DB --> WEB["웹 대시보드
TWR · 입금 기록"] +``` -# 운영 헬스 점검 -python main.py --mode health +체결 반영은 원장 정합성을 보장합니다 — 포지션 저장이 실패하면 방금 저장한 매매 기록을 보상 롤백해 "현금만 차감된 반쪽 원장"이 남지 않고, 예외성 주문 실패는 디스코드 critical로 즉시 승격됩니다. -# 테스트 -pytest tests/ -q -``` +## 운영 트랙 (2026-07-10 기준) -## Good First Reads +| 트랙 | 구성 | 자본 | 진행 | +|------|------|------|------| +| **소액 적립** `kr_pocket` | KODEX 200 47.5% + CD금리 파킹 ETF 47.5% + 현금 5% | 30만 시작 + 월 10만 적립 | 페이퍼 1/60일 | +| **분산 대형주** `kr_diversified_hold` | 대형주 10종목 균등 buy&hold (저회전) | 1,000만 (페이퍼) | 23/60일 · 관찰용 | +| 단타 샌드박스 | 페이퍼 전용 (실돈 투입은 60일 게이트 통과 후 별도 결정) | — | 대기 | -| 문서 | 내용 | -|------|------| -| [`docs/PROFITABILITY_FINDINGS.md`](docs/PROFITABILITY_FINDINGS.md) | 수익성 정직 점검과 현재 결론 | -| [`docs/BASKET_PAPER_EVALUATION.md`](docs/BASKET_PAPER_EVALUATION.md) | 바스켓 paper→live 승격 기준 | -| [`docs/BASKET_LIVE_RUNBOOK.md`](docs/BASKET_LIVE_RUNBOOK.md) | 바스켓 live 전환 절차 | -| [`docs/PROJECT_GUIDE.md`](docs/PROJECT_GUIDE.md) | 파일 구조와 실행 흐름 | -| [`quant_trader_design.md`](quant_trader_design.md) | 아키텍처와 전략 설계 | +소액 트랙이 핵심입니다. 예측 없이 얻을 수 있는 것만 조합했습니다: -## English Summary +- **지수 절반**: KODEX 200 1주가 그 자체로 200종목 분산 — 소액에서 유일한 분산 수단 +- **파킹 절반**: 유휴 현금을 CD금리 누적형 ETF로 — 시장 위험 없이 연 3%대, 종전 "이자 0 현금"의 캐시 드래그 제거 +- **비용 정합**: 국내 상장 ETF 매도 거래세 면제를 체결 모델에도 반영 — 가짜 비용으로 승격 판정이 왜곡되지 않게 +- 낙폭에 민감한 설계: 위험자산을 총자산의 절반으로 고정해 MDD를 주식 100% 대비 절반 수준으로 억제 -`quant_trader` is a Python research project for risk-first algorithmic trading on Korean equities. It includes backtesting, paper trading, strategy validation, KIS API integration, portfolio rebalancing, operational health checks, and live-trading gates that fail closed unless the evidence package is current and consistent. - -> **현재 상태 (2026-06-10)**: -> - **바스켓 paper 운영 개시**: `kr_diversified_hold`(분산 대형주 buy&hold, 주식 80%/현금 20%) enabled — 일일 자동 사이클(리밸런싱 → NAV 스냅샷 → DB 백업 → 승격 진행률 보고)로 60영업일 트랙레코드 축적 중. `--mode health`가 사이클 끊김(스냅샷 4일+)을 자동 경고 -> - **paper→live 승격 기준 정의**(`docs/BASKET_PAPER_EVALUATION.md`): 60영업일·스냅샷 커버리지 ≥95%·dead-letter 0건·비용 드래그 ≤1%/년. 베타 전략이므로 "시장 초과수익"은 기준이 아님. `tools/basket_paper_evaluation.py`로 자동 판정(WAIT/PASS_CANDIDATE/FAIL_REVIEW) -> - **바스켓 전용 live gate 분기**: 기존 게이트가 바스켓에 신호 전략용 canonical promotion(벤치마크 초과수익 포함)을 요구해 영구 통과 불가였던 함정 해소 — paper 평가 PASS_CANDIDATE 기반으로 실제로 열리는 경로 확립. 전환 절차는 `docs/BASKET_LIVE_RUNBOOK.md`(모의서버 리허설 → 실계좌 소액 → 목표 자본) -> - live 바스켓 리밸런싱 실행 가능화: BUY가 "paper-only" 차단으로 항상 실패해 SELL-only 현금화가 되던 버그 수정(일반 매수와 동일한 집행 안전장치 + live gate 검사 승계), 회전율 예산 SELL 우선·부분 실행, KIS 잔고 미확인 시 사이징 fail-closed -> - 주문 응답 유실 시 재시도 래퍼가 재전송하던 잔여 구멍 폐쇄(`KISOrderResponseUnknown` → reconcile 대기), 손절가 하한 클램프, 히스터리시스 SELL 해제 부호 수정, 내장 지표 폴백 pandas-ta 패리티(RSI ~12pt 오차 정정) -> - 트랙레코드 도구가 현금 배분(`target_stock_weight`·`min_cash_ratio`)을 무시해 수익·MDD를 과대 보고하던 문제 정정 — 배포 바스켓 정직 수치(2021-12~2026-06): **CAGR +26.9%, Sharpe 1.09, MDD −21.4%** -> - 인자 없이 실행 시 백테스트 자동실행 대신 사용 가이드 출력(`--mode guide`), 운영 문서 v6.1 갱신 -> -> **이전 상태 (2026-06-01)**: -> - 적대적 코드 감사로 실거래 안전 버그 정리: KIS 주문이 응답 유실 시 재전송돼 이중 체결되던 경로를 `idempotent=False`로 차단(최대 1회 제출), 서킷 브레이커 재시도 루프 내 재확인, 429 Retry-After HTTP-date 크래시 가드 -> - 부분 익절이 매 모니터링 사이클마다 재발동되던 버그 수정(`Position.partial_tp_done` 영속화), 스케줄러 진입 예외 시 손절/익절 스킵 방지, 일일 손실 한도 기준값에 당일 스냅샷 사용 차단 -> - 라이브 게이트 fail-closed 보강: NaN/Inf 지표가 임계값 비교를 통과하던 구멍을 막아(`_as_float` 비유한값 → None) 손상된 지표로 라이브 승격되는 것을 차단 -> - 수익성 검증 정직화: canonical 유니버스 생존자 편향 차단 + Deflated/Probabilistic Sharpe + 진짜 OOS holdout(`--oos-holdout-split`)로 다중검정·선택 과적합 노출 -> - 지표 엔진 ADX +DI/-DI 매핑 오류, 백테스트 gross_return 비용 환원 오류, 거래량비율 inf 가드 수정 -> -> **이전 상태 (2026-05-14)**: -> - GitHub 원격 브랜치 정리 완료: 완료 브랜치 삭제, 활성 PR 브랜치만 유지 -> - 60영업일 Paper 실험 freeze pack 병합: `reports/experiment_freeze_pack.md`, 일/주간 ops checklist, stop condition 문서 추가 -> - Paper Evidence 런타임: v2 일별 자동 수집 → benchmark finalization → 날짜순 canonical evidence → promotion package → launch readiness -> - Paper Runtime State Machine: normal/degraded/frozen/blocked_insufficient_evidence 상태 자동 전환 + allowed_actions 제어 -> - Paper 운영 CLI는 pilot 제어/런타임 상태 액션 옵션을 한 번에 하나만 받도록 정리되어, 상충 액션 동시 지정 시 상태 변경 전에 fail-closed로 종료 -> - Paper Pilot Authorization: blocked 상태에서도 제한적 real paper 가능 (수동 승인 + 리스크 캡 + fail-closed/audited entry guard) -> - Paper 신규 진입 실행 경계 fail-closed: preflight 상태 누락/손상 또는 runtime 조회 실패 시 BUY 제출 전 차단, SELL 청산은 유지 -> - Paper 바스켓 리밸런싱 BUY 실행은 실제 존재하는 포트폴리오 자본/현금 조회 API를 사용하도록 고정되어 주문 실행 전 단계 오류를 회귀 테스트로 차단 -> - `--mode paper` 단발 실행은 설정 파일의 `trading.mode=live`가 남아 있어도 해당 실행을 paper로 고정해 실주문 경로 진입을 차단 -> - 장중 auto-entry 후보는 주문 직전 현재 데이터로 BUY 시그널을 재검증하며, 데이터/API/전략 계산 오류로 재검증에 실패하면 해당 루프 주문을 보류하고 후보를 다음 루프로 넘김 -> - `QUANT_AUTO_ENTRY` 해석 단일화: YAML hash와 resolved hash를 분리해 실험 설정 drift 감지 -> - Research sweep: 기존 top-20 all-family 후보 재검증도 `NO_ALPHA_CANDIDATE`; `pullback`, benchmark-relative momentum, risk-budget, cash-switch, benchmark-aware rotation, target-weight top-N rotation/score-floor 후보군과 exposure-matched benchmark 진단을 research-only로 추가 -> - target-weight 리스크 완화 top-200 sweep: 최상위 tolerance 후보도 수익/초과수익은 개선됐지만 MDD·회전율 게이트 미통과로 전 후보 `paper_only` -> - target-weight 저회전 top-200 sweep: 격월/분기 후보가 회전율은 낮췄지만 benchmark excess Sharpe와 MDD 게이트 미통과로 `NO_ALPHA_CANDIDATE` -> - target-weight 변동성 타깃 top-200 sweep: 최상위 후보는 초과수익이 양수였지만 전 후보가 MDD 게이트 미통과로 `KEEP_RESEARCH_ONLY` -> - target-weight 리스크 페널티 랭킹 top-200 sweep: 수익·초과수익은 개선됐지만 MDD·회전율 게이트 미통과로 `KEEP_RESEARCH_ONLY` -> - target-weight 손실방어 top-200 sweep: pdd8/floor25/cooldown1 후보가 MDD -19.56%, turnover/year 296.4%로 개선되어 research sweep 기준 `RUN_CANONICAL_EVALUATION` -> - target-weight 손실방어 canonical 평가: pdd8 후보는 benchmark excess -6.46%p, exp75 원본/tol3 후보는 turnover/year 1026.3%/1009.0%, exp75 tol4 후보는 turnover/year 986.5%로 회전율은 통과했지만 MDD -20.25%라 모두 `paper_only`; tol4 guard-only, rank-risk 강화, sector cap 2, corrcap85, corrpen 후보도 canonical 신규 승격 없음. 손실 후 재진입 제한 후보는 top-200에서 MDD 악화로 canonical 제외. 기존 risk-overlay 후보만 provisional 유지 -> - target-weight 변동성 예산 canonical 평가: top-200에서는 후보 2개가 통과했지만 canonical top-20에서는 MDD -20.82%/-20.89%로 `paper_only`; 초과수익·회전율은 개선됐으나 live/paper 신규 전환은 금지 -> - target-weight 포지션 손실 감산 canonical 평가: `tol5+sectorcap2+posloss8` 후보가 return +198.15%, MDD -17.18%, turnover/year 993.9%로 신규 `provisional_paper_candidate`; pilot plan은 rank penalty/sector cap/포지션 손실 감산과 paper evidence 기반 portfolio drawdown guard 상태를 재현하며, 명시 상태가 없으면 fail-closed 차단 -> - scoring: **paper_only** (관찰 가능하지만 Sharpe/PF/WF 안정성 미달) -> - rotation: **provisional_paper_candidate** (risk-adjusted 기준 통과, live alpha는 미확인) -> - target-weight risk overlay 후보: canonical bundle 기준 **provisional_paper_candidate** + 전용 paper/pilot adapter/shadow proof, 유동성/비용 pre-trade/pilot 승인/실행일/장 시간/가격 최신성 guard 추가. 리서치 백테스트는 직전 거래일 점수 → 다음 거래일 시가 체결 → 종가 평가 기준으로 보수화했으며, 기존 target-weight research artifact는 execution price mode 확인 또는 재생성 후 사용 (live 미연결) -> - target-weight capped pilot readiness: audit 시작 시 paper preflight를 먼저 갱신하고, Discord webhook 누락 또는 notifier 비정상 상태는 주문 전 `BLOCKED`로 확정 -> - live 보류 주문 복구는 KIS 미체결 목록에서 사라진 주문도 체결 상세가 확인될 때만 `RECONCILED`로 닫고, 조회 실패/불명확 상태는 열린 주문과 중복 차단을 유지 -> - live 체결 조회는 주문번호가 현재 주문과 일치하는 체결 row만 확정 체결로 인정 -> - KIS Circuit Breaker는 장애 후 HALF_OPEN 복구 상태에서 단일 probe만 허용하고 결과 확인 전 추가 요청을 차단 -> - 신규 BUY 주문 직전 유동성 재검증은 평균 거래량 누락/0도 fail-closed로 차단해 소형주·데이터 공백 주문을 막음 -> - 시장 국면 필터가 켜져 있는데 지수 데이터 조회·MA 계산이 불명확하면 `unknown` 국면으로 신규 BUY를 차단 -> - 갭업 추격매수 방지 가드는 최근 가격 조회 실패·데이터 부족도 신규 BUY 차단으로 처리 -> - 상관관계 리스크 확인은 가격 데이터 조회 실패·부족 시 신규 BUY를 차단 -> - 업종 비중 cap은 섹터 맵 조회 실패·매핑 누락 시 신규 BUY를 차단 -> - 주문/exit 판단 가격이 0·NaN·누락이면 BUY/SELL 제출과 손절/익절/블랙스완 판단을 보류하고 차단 이벤트를 기록 -> - live 바스켓 리밸런싱 주문은 CLI와 스케줄러 자동 체크 모두 바스켓별 `basket_rebalance:` canonical live gate/account/order tag 일치 없이는 실행 차단 -> - manual paper evidence single-day/backfill/finalize는 `backfill` provenance로 기록되어 승격 증거에서 제외 -> - shadow bootstrap collect/finalize는 `shadow_bootstrap` provenance로 기록되어 주문 기반 승격 증거와 분리 -> - live candidate: 없음. `--force-live` 제거, hard gate 우회 불가 - -## 주요 기능 - -- 백테스트 / 포트폴리오 백테스트 / 멀티전략 sleeve 비교 -- 모의투자 / 실전 매매 -- 전략 검증 / 성과 비교 / 파라미터 최적화 -- 스코어링 / 평균회귀 / 추세추종 / 펀더멘털 / 앙상블 전략 -- 거래량 돌파(C-4) / 상대강도 회전(C-5) / 2-sleeve 포트폴리오 (BV50/R50 **Paper 가동 중**) -- Paper Evidence 자동 수집 / Runtime State Machine / Pilot Authorization / Launch Readiness -- 리스크 관리, 알림, 바스켓 리밸런싱, 웹 대시보드 - -## 사용 환경 - -- Python 3.11 ~ 3.12 - -## 설치 +## 페이퍼 → 실전 승격 파이프라인 -```bash -pip install -r requirements.txt +```mermaid +flowchart LR + BT["백테스트
비용·슬리피지·look-ahead 가드"] --> PP["페이퍼 운영
60영업일 트랙레코드"] + PP -->|"스냅샷 커버리지 ≥95%
dead-letter 0건
비용 드래그 ≤1%/년"| PC["PASS_CANDIDATE"] + PC --> LG{"live gate
(fail-closed)"} + LG -->|"KIS 연결·잔고 동기화
blockers 통과 + 운영자 확인"| LIVE["실계좌 소액"] + LG -->|미충족| PP ``` -## 설정 - -실행 전에 설정 파일과 환경변수를 먼저 준비해야 합니다. +실계좌는 페이퍼 트랙레코드가 기준을 통과하기 전에는 **어떤 조합으로도 열리지 않습니다**. `--force-live` 같은 우회 플래그는 제거했습니다. -- `config/settings.yaml.example` → `config/settings.yaml` -- `.env.example` 참고 후 `.env` 작성 -- target-weight capped paper pilot은 Discord webhook이 필수입니다. `.env`의 `DISCORD_WEBHOOK_URL`을 채우고 `config/settings.yaml`의 `discord.enabled: true`를 유지하세요. -- `config/holidays.yaml`은 필요 시 갱신 가능 -- 미국 휴장일이 필요하면 `config/us_holidays.yaml` 추가 +## 일일 운영 (자동) -Paper schedule은 기본적으로 signal-only입니다. DB 모의 주문까지 실행하는 full paper는 환경변수로만 켭니다. +매 영업일 오전 10시에 한 사이클이 자동으로 돕니다: -```bash -QUANT_AUTO_ENTRY=true python main.py --mode schedule --strategy scoring +``` +리밸런싱 판단 → (필요 시) 주문 → NAV 스냅샷 → DB 백업 → 승격 진행률 리포트 → 디스코드 카드 ``` -`trading.auto_entry`의 YAML 기본값은 `false`로 두고, 60영업일 실험에서는 `QUANT_AUTO_ENTRY`에 따라 resolved hash가 달라지는지 확인합니다. live 모드는 별도 hard gate를 통과해야 하며, 환경변수만으로 실전 주문을 열 수 없습니다. - -## 실행 +- 스냅샷 결측은 당일 critical 경보 (KST 기준 영업일 판정) +- 금요일에는 백업 **복구 리허설**까지 자동 수행 — 백업이 실제로 복구되는지 매주 검증 +- 운영자 점검은 명령 하나면 됩니다: ```bash -# 단일 종목 백테스트 -python main.py --mode backtest --strategy scoring --symbol 005930 - -# 포트폴리오 백테스트 (gap/어닝/BlackSwan guard 포함) -python main.py --mode portfolio_backtest --strategy scoring --symbols 005930,000660 --start 2023-01-01 --end 2024-12-31 - -# 거래량 돌파 전략 (C-4) 포트폴리오 백테스트 -python main.py --mode portfolio_backtest --strategy breakout_volume --symbols 005930,000660,035720,051910 --start 2024-01-01 --end 2025-12-31 - -# 멀티전략 sleeve 비중 스윕 (C-5) -python scripts/c5_weight_sweep.py - -# paper trading 월간 리포트 (BV50/R50 guardrail 모니터링) -python scripts/c5_paper_monthly_report.py - -# paper trading 특정 기간 리포트 -python scripts/c5_paper_monthly_report.py 2026-04-01 2026-04-02 - -# 모의투자 -python main.py --mode paper --strategy scoring - -# 모의 스케줄 루프 -python main.py --mode schedule --strategy scoring +python main.py --mode health +# 종료코드 0=OK / 1=ATTENTION / 2=BLOCKED — 모니터링 스크립트에서 분기 가능 +# 승격 대기 같은 파이프라인의 '예상 상태'는 라벨로만 표시하고 경보로 올리지 않는다 +# — 매일 노란불이면 진짜 장애를 못 알아본다 +``` -# full paper 스케줄 루프 (DB 모의 주문 실행) -QUANT_AUTO_ENTRY=true python main.py --mode schedule --strategy scoring +## Quick Start -# Paper 운영 preflight / launch readiness -python tools/paper_preflight.py --strategy scoring --with-pilot-check -python tools/paper_launch_readiness.py --strategy scoring --generate-runbook +```bash +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt -# Target-weight capped pilot preflight / readiness audit -python tools/paper_preflight.py --strategy target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1 --with-pilot-check -python tools/target_weight_rotation_pilot.py --candidate-id target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1 --readiness-audit --allow-rerun +# 설정: config/settings.yaml.example → settings.yaml, .env.example → .env +# 디스코드 알림을 쓰려면 .env의 DISCORD_WEBHOOK_URL 설정 -# Paper evidence pipeline -python tools/run_paper_evidence_pipeline.py --strategy scoring --finalize --date YYYY-MM-DD -python tools/run_paper_evidence_pipeline.py --strategy scoring --generate-package +python main.py --mode guide # 실행 모드 안내 +python main.py --mode backtest --strategy scoring --symbol 005930 +python main.py --mode rebalance --dry-run # 바스켓 리밸런싱 (계획만) +python main.py --mode health # 운영 통합 헬스 점검 +python main.py --mode weekly_report # 주간 요약 (성과·귀속 분해) +python main.py --mode dashboard # 웹 대시보드 +pytest tests/ -q # 테스트 1,683개 +``` -# 실전 매매 (현재 모든 전략이 live 차단 상태 — live_candidate 승격 전까지 실행 불가) -# python main.py --mode live --strategy scoring --confirm-live +월 적립 기록은 대시보드의 **적립 입금** 버튼이 제일 편하고, CLI도 있습니다: -# 전략 검증 -python main.py --mode validate --strategy scoring --symbol 005930 --validation-years 5 +```bash +python tools/record_deposit.py --basket kr_pocket --amount 100000 +``` -# 성과 비교 -python main.py --mode compare --start 2025-01-01 --end 2025-03-19 --strategy scoring +## 안전장치 -# 파라미터 최적화 -python main.py --mode optimize --strategy scoring --include-weights --auto-correlation +실전 경험(과 사고)에서 나온 것들이라 목록이 깁니다. 설계 원칙은 세 가지입니다. -# 바스켓 리밸런싱 -python main.py --mode rebalance -python main.py --mode rebalance --dry-run -# config가 live일 때 실제 리밸런싱 주문은 ENABLE_LIVE_TRADING=true + --confirm-live + live gate 통과 필요 +1. **fail-closed** — 데이터 조회 실패, 상태 불명, 검증 불가면 주문하지 않는다 +2. **원장 정합** — 거래·포지션·현금이 어긋난 반쪽 상태를 남기지 않는다 (보상 롤백) +3. **경보 위생** — 파이프라인의 예상 상태는 라벨, 진짜 장애만 경보 (매일 울리는 경보는 없는 것과 같다) -# 웹 대시보드 -python main.py --mode dashboard -# 기본 바인드는 http://127.0.0.1:8080 입니다. -# 외부 공개가 필요할 때만 인증/reverse proxy 구성 후 --dashboard-host 0.0.0.0 을 명시하세요. +
+세부 안전장치 목록 펼치기 -# 운영 통합 헬스 점검 (전 전략 runtime + current_blockers 한눈에) -python main.py --mode health -# 종료코드 0=OK / 1=ATTENTION / 2=BLOCKED — 모니터링 스크립트에서 분기 가능. -# 주의: verdict는 '운영 건강' 기준이다. live 승격 게이트의 NO-GO(live 후보 없음 — -# 연구 결론상 정상 상태)는 BLOCKED로 합산하지 않고 헤드라인 라벨로만 보고한다. -# 게이트 artifact의 장애성 신호(부재/stale/표기 불일치)는 ATTENTION(1)으로 강등된다. -# 같은 점검을 tools/paper_runtime_status.py --health (--json) 으로도 실행할 수 있습니다. +- 포트폴리오 MDD·일손실 한도 도달 시 신규 매수 차단 (손절·청산 SELL은 유지) +- 미체결/중복 주문 방지 — live 미체결 조회 실패는 "미체결 있음"으로 간주 (fail-closed) +- 신규 매수 직전 유동성(평균 거래량·거래대금) 재검증, 누락 시 차단 +- 갭 리스크·상관관계·업종 비중 확인용 데이터 조회 실패 시 신규 매수 차단 +- 주문/청산 판단 가격이 0·NaN·누락이면 판단 보류 + 차단 이벤트 기록 +- 시장 국면 필터 데이터 불명 시 `unknown` 국면으로 신규 매수 차단 +- live 체결 확인 전 DB 반영 보류, 주문번호 불일치 시 반영 보류 +- live 시작 전 KIS 연결·잔고 동기화 실패 시 스케줄러 시작 차단 +- 체결 반영 원자성: 포지션 저장 실패 시 매매 기록 보상 롤백 (반쪽 원장 방지) +- 예외성 주문 실패는 ORDER_ERROR critical 이벤트 + 디스코드 즉시 알림 +- 1일 1매매 가드 — 중복 사이클이 회전 상한을 우회하지 못하게 +- 스키마 마이그레이션은 멱등·중단 재개 가능, 행수 검증 실패 시 원본 보존 +- DB 백업 (보존 14일) + 매주 금요일 복구 리허설 자동 수행 +- 웹 대시보드 쓰기는 입금 기록 하나뿐 (매매·설정 변경 불가), CSRF 방어 적용 +- 긴급 청산은 POST 전용 · 127.0.0.1 바인드 · 토큰 검증 기본 적용 -# 주간 요약 리포트 (성과 · 귀속 분해(실행/구성 격차) · 진행률 · 주간 이벤트 → 디스코드) -python main.py --mode weekly_report +
-# 바스켓 배포 점검 (계획 주문·예상 비용·활성화 절차) -python main.py --mode deploy_check +세부 파라미터는 `config/risk_params.yaml`, `config/baskets.yaml`에서 관리합니다. -# 바스켓 paper 운영 평가 (승격 판정 + 성과 귀속 분해) -.venv\Scripts\python.exe tools/basket_paper_evaluation.py +## 프로젝트 구조 -# 휴장일 갱신 -python main.py --update-holidays ``` - -Full paper 신규 BUY는 `reports/paper_runtime/preflight_status_{strategy}.json`이 존재하고, runtime state 조회가 성공하며, `entry` 허용 또는 현재 pilot authorization이 재검증될 때만 실행됩니다. preflight 누락/손상, runtime 조회 실패, critical fail은 주문 생성 전 차단되며 기존 포지션 SELL 청산은 차단하지 않습니다. -`python main.py --mode paper`는 설정 파일에 `trading.mode: live`가 남아 있어도 해당 실행을 paper로 고정합니다. 모의투자 명령이 설정 잔재 때문에 실전 주문 실행기로 초기화되는 것을 막기 위한 안전장치입니다. -장전/장중 auto-entry 후보는 주문 제출 직전에 최신 OHLCV와 전략 신호로 다시 확인합니다. 재조회 또는 신호 계산이 실패하면 stale 후보로 매수하지 않고 `ENTRY_REVALIDATION_BLOCK` 이벤트를 남긴 뒤 후보를 다음 루프까지 보류합니다. - -Target-weight capped pilot의 `--readiness-audit`는 주문 가능 여부를 판정하기 전에 `paper_preflight`를 먼저 갱신하고 그 결과를 `preflight_refresh` artifact에 남깁니다. `notifier: Discord webhook 미설정` 또는 notifier health 비정상 상태가 나오면 pilot authorization이나 cap이 맞아도 실행 전 `BLOCKED`로 유지되므로, `.env`의 `DISCORD_WEBHOOK_URL` 설정 후 preflight와 readiness audit을 다시 돌려야 합니다. - -실전 매매는 `ENABLE_LIVE_TRADING=true` + `--confirm-live` + 전략 상태 레지스트리의 `live_candidate` 허용 + 현재 commit/config와 일치하는 canonical promotion bundle + `reports/current_blockers.json`의 `go_live=true` 및 `live_candidates` 포함 + 내부 `strategy`가 현재 전략명과 정확히 일치하는 `ELIGIBLE` paper evidence package가 모두 필요합니다. 운영자가 전략 상태를 live 미허용으로 낮추거나 current blockers가 최신 promotion blocker summary와 맞지 않으면 canonical live gate가 통과해도 실전 시작은 fail-closed로 차단됩니다. promotion engine과 live gate는 same-universe/cash-adjusted paper excess가 모두 양수인지 확인하며, live gate는 `promotion_result.json` 표기를 그대로 믿지 않고 같은 산출물과 evidence를 승격 엔진으로 재계산해 현재 규칙에서도 `live_candidate`인지 다시 확인합니다. paper evidence package는 package payload hash와 원본 daily evidence JSONL의 source record hash도 재검증하므로 패키지 요약값이나 원본 record가 따로 바뀌면 live gate가 차단합니다. live 스케줄러 시작 전 KIS 연결 검증과 KIS↔DB 잔고 동기화도 반드시 통과해야 하며, 실패하면 경고로 넘기지 않고 시작을 차단합니다. 또한 `OrderExecutor`를 직접 생성해 live BUY를 호출해도 `live_gate_validated=True`로 생성된 실행 경로가 아니면 KIS 주문 제출 전에 차단됩니다. live 주문 전 미체결 조회 실패는 미체결 없음으로 해석하지 않고 중복 주문 방지를 위해 차단하며, live 긴급 청산도 KIS 현재가가 검증되지 않으면 평균단가 지정가 fallback 매도를 실행하지 않습니다. -수동 single-day/backfill/finalize evidence CLI는 운영 보정·품질 점검 용도이며 `backfill` provenance로 기록됩니다. 실제 scheduler/pilot 세션에서 수집된 `real_paper`/`pilot_paper` record만 승격 증거로 사용할 수 있습니다. -Shadow bootstrap collect/finalize CLI도 주문 제출 없이 `shadow_bootstrap` provenance로 기록되며, 빈 이전일을 finalize하면서 새 record가 생성되어도 승격 증거로 계산하지 않습니다. -현재 모든 전략은 `provisional_paper_candidate` 또는 `disabled` 상태이며, **live 모드는 차단**되어 있습니다. -`reports/approved_strategies.json`와 오래된 `validation_walkforward_*.json` 파일은 더 이상 live 근거가 아닙니다. `--force-live` 플래그는 제거되었으며, 어떤 조합으로도 hard gate를 우회할 수 없습니다. legacy `approved_strategies.json`가 깨져 있거나 스키마가 맞지 않으면 paper runtime도 fail-closed로 신규 entry/shadow 수집을 닫고 exit/finalize 같은 안전 작업만 허용합니다. - -## 리스크 관리 - -기본적인 안전장치는 넣어두었습니다. - -- look-ahead 완화 백테스트 -- 포지션 수 / 자금 비중 제한 -- 미체결 / 중복 주문 방지 (live 미체결 조회 실패 시 주문 보류, 호환 bool API도 미체결 있음으로 fail-closed) -- 신규 BUY 주문 직전 평균 거래량 누락 또는 거래대금 하한 미달 시 차단 -- 갭 리스크 확인용 최근 가격 조회 실패 또는 데이터 부족 시 신규 BUY 차단 -- 상관관계 리스크 확인용 가격 데이터 조회 실패 또는 보유 종목 데이터 부족 시 신규 BUY 차단 -- 업종 비중 cap 확인용 섹터 맵 조회 실패 또는 대상/보유 종목 매핑 누락 시 신규 BUY 차단 -- 주문/손절/익절/블랙스완 판단용 현재가가 없거나 0/NaN이면 주문·청산 판단 보류 및 `PRICE_DATA_BLOCK` 기록 -- live 주문 체결 확인 전 DB 거래·포지션 반영 보류 -- live 체결 조회 결과의 주문번호가 현재 주문과 다르면 DB 거래·포지션 반영 보류 -- live 재시작 시 브로커 미체결 목록에서 사라진 보류 주문도 체결 상세 확인 전에는 열린 주문으로 유지 -- live 시작 전 KIS 연결 / 잔고 동기화 실패 시 스케줄러 시작 차단 -- paper evidence package payload hash / source record hash 불일치 시 live gate 차단, `--check-only`는 invalid package를 구조화된 WARN으로 노출하고 `--paper-evidence-quarantine-invalid`로 운영 입력 경로 밖에 격리 가능 -- live 신규 BUY는 gate 통과 상태가 전달된 `OrderExecutor`에서만 KIS 주문 제출 허용 -- live 바스켓 리밸런싱 주문은 CLI 운영자 확인 또는 live 스케줄러 검증 상태에서 바스켓별 canonical live gate, account_key/order strategy tag 일치, KIS↔DB 포지션 동기화 통과 후에만 실행 -- auto-entry 후보 시그널 재검증 실패 시 신규 BUY 보류 -- live 긴급 청산 전 KIS-only 보유 포지션을 DB에 먼저 반영 -- live 긴급 청산 현재가 조회 실패 시 평균단가 지정가 fallback 매도 차단 -- HTTP 긴급 청산 트리거 live 실행은 별도 확인 환경변수 없이는 차단 -- HTTP 긴급 청산은 개별 매도 실패가 있으면 실패 응답으로 노출 -- HTTP 긴급 청산 실행은 POST 전용이며 query token 인증은 기본 비활성 -- HTTP 긴급 청산 서버는 기본적으로 127.0.0.1에만 바인드 -- HTTP 긴급 청산 토큰은 기본 최소 16자와 placeholder 차단 검증 적용 -- 긴급 청산 결과는 성공/실패 summary를 통합 알림으로 전파 -- paper/live 현금 정산은 실제 체결가 기준이며 슬리피지를 현금 흐름에서 중복 차감하지 않음 -- 성과 열화 시 진입 제한 -- 시장 국면 / 블랙스완 대응 -- 단일종목/포트폴리오 백테스트 gap/어닝/BlackSwan 이벤트 guard -- 백테스트 / research sweep universe 20일 평균 거래대금 사전 필터 -- DB 백업 / 잔고 크로스체크 / 긴급 청산 -- 알림 채널 fallback - -세부 설정은 `config/risk_params.yaml`, `config/strategies.yaml`, `config/settings.yaml`에서 관리합니다. - -## 테스트 - -```bash -pytest tests/ -q +quant_trader/ +├── main.py # 모드 라우터 (backtest/rebalance/health/dashboard/...) +├── config/ # 설정 (baskets, risk_params, strategies, settings) +├── core/ # 리밸런서·리스크·주문 실행·헬스·관측성·paper 런타임 +├── strategies/ # 전략 (scoring, rotation 등 — 현재 전부 연구 보관) +├── backtest/ # 백테스터 (비용·슬리피지·이벤트 가드 반영) +├── database/ # SQLite 모델·리포지토리·마이그레이션·백업 +├── monitoring/ # 로깅·디스코드·웹 대시보드 +├── api/ # KIS REST·WebSocket +├── tools/ # 운영 도구 (입금 기록·평가·트랙 재시작·시뮬레이터) +├── scripts/ # 검증 스크립트 (OOS·sleeve 비교·리포트) +├── deploy/ # (선택) Oracle Cloud ARM 상시 구동 (systemd) +├── tests/ # 1,683개 (외부 API는 모킹, DB는 격리) +└── docs/ # 설계·운영 문서 (+ images/ 스크린샷) ``` -외부 API나 웹소켓이 필요한 부분은 모킹해서 테스트합니다. - -## 프로젝트 구조 - -* `config/` — 설정 -* `core/` — 데이터, 지표, 신호, 리스크, 주문, 스케줄러, 알림, paper evidence/runtime/pilot/preflight -* `strategies/` — 전략 (scoring, breakout_volume, relative_strength_rotation 등) -* `scripts/` — 검증 스크립트 (C-4 OOS, C-5 sleeve 비교/비중 스윕/필터 테스트/rolling WF/paper 리포트) -* `tools/` — Paper 운영 도구와 research sweep (evidence pipeline, pilot control, bootstrap, preflight, launch readiness, candidate sweep, target-weight pilot) -* `api/` — KIS REST·웹소켓 -* `backtest/` — 백테스트, 검증, 최적화, 비교 -* `database/` — 모델·백업 -* `monitoring/` — 로깅, 알림, 대시보드, 청산 트리거 -* `tests/` — 테스트 -* `docs/` — 문서 -* `deploy/` — (선택) Oracle Cloud ARM 서버 상시 구동(systemd, logrotate) - ## 문서 | 문서 | 내용 | |------|------| -| [`docs/PROFITABILITY_FINDINGS.md`](docs/PROFITABILITY_FINDINGS.md) | **수익성 정직 점검 (먼저 읽을 것)** — 능동 alpha는 없음, 분산 대형주 buy&hold가 현실적 고수익 경로 | -| [`docs/PAPER_TO_LIVE_RUNBOOK.md`](docs/PAPER_TO_LIVE_RUNBOOK.md) | Paper→Live 검증된 운영 경로 + 정직한 caveat | -| [`docs/PROJECT_GUIDE.md`](docs/PROJECT_GUIDE.md) | 파일 역할, 모드별 흐름, 설정 요약, 실전 전 체크리스트 | -| [`quant_trader_design.md`](quant_trader_design.md) | 아키텍처, 지표·전략·리스크 설계, 검증 관점, 로드맵 | -| [`BACKTEST_IMPROVEMENT.md`](docs/BACKTEST_IMPROVEMENT.md) | 백테스트 신뢰성 개선 내역, 알려진 한계, 추가 과제 | -| [`deploy/README.md`](deploy/README.md) | Oracle Cloud Free Tier ARM 배포·systemd 상시 구동 가이드 | -| [`reports/strategy_promotion_policy.md`](reports/strategy_promotion_policy.md) | 전략 승격 정량 기준표 | -| [`reports/live_gate_policy.md`](reports/live_gate_policy.md) | Live 진입 canonical/evidence hard gate | -| [`reports/paper_runbook.md`](reports/paper_runbook.md) | Paper Trading 운영 가이드 | -| [`reports/experiment_freeze_pack.md`](reports/experiment_freeze_pack.md) | 60영업일 Paper 실험 동결 기준, hash, 실행 모드 | -| [`reports/daily_ops_checklist.md`](reports/daily_ops_checklist.md) | 일일 Paper 운영 체크리스트 | -| [`reports/weekly_ops_checklist.md`](reports/weekly_ops_checklist.md) | 주간 Paper 운영 체크리스트 | -| [`reports/experiment_stop_conditions.md`](reports/experiment_stop_conditions.md) | 실험 중단·동결·재개 조건 | - -## 전략 상태 - -승격 규칙 v3 — `core/promotion_engine.py`에서 metrics 기반 자동 판정. `tools/evaluate_and_promote.py --canonical`로 재현하며, canonical 평가 산출물에는 종목군 구성, 데이터 범위, 수집 오류를 바탕으로 만든 `data_snapshot_hash`를 남긴다. live gate와 승격 산출물 로더는 이 해시, 데이터 범위, 수집 오류, 평가 실패 상태를 다시 검증하고, live 진입 직전에 promotion engine 판정을 재계산해 손상되거나 현재 규칙과 어긋난 산출물 사용을 차단한다. -Research candidate sweep — `tools/research_candidate_sweep.py --quick --candidate-family all`로 promotion과 분리된 rotation/momentum/breakout/pullback/benchmark-relative/risk-budget/cash-switch/benchmark-aware rotation/target-weight top-N rotation 후보 랭킹 artifact를 생성. Raw EW B&H gate는 유지하되, defensive/cash-heavy 후보 해석을 위해 평균 노출률과 exposure-matched B&H excess도 진단값으로 기록합니다. 벤치마크 universe 일부 종목이라도 OHLCV 수집·기간 검증에 실패하면 `INSUFFICIENT_BENCHMARK_DATA`로 fail-closed 차단하고, 결측 종목과 커버리지 비율을 artifact/Markdown에 남깁니다. `--top-n 200`처럼 넓은 유니버스 검증을 요청하면 기본 후보 스캔 범위를 `max(100, top_n*2)`로 넓히며, 필요 시 `--universe-scan-limit`으로 명시 조정할 수 있습니다. 기존 best 후보만 빠르게 재검증할 때는 `--candidate-id`를 반복 또는 쉼표 구분으로 넘겨 선택한 후보만 평가합니다. top-200 follow-up처럼 MDD/회전율 완화 후보만 비교할 때는 `--candidate-family target_weight_risk_relief`로 risk-off/tolerance/exposure 축소 후보군을 바로 실행하고, 리밸런싱 빈도 완화 후보만 볼 때는 `--candidate-family target_weight_turnover_relief`로 격월/분기 후보군을 실행합니다. 손실 구간 노출을 더 직접 낮추는 후보는 `--candidate-family target_weight_volatility_target`로 benchmark 실현 변동성 타깃과 낙폭 floor를 적용해 검증합니다. 종목 선택 자체를 보수화하는 후속 비교는 `--candidate-family target_weight_downside_rank_relief`로 낙폭·하방변동성 페널티를 랭킹 점수에 반영해 검증합니다. 랭킹 페널티 기반 후보의 교체 빈도를 줄이는 후속 비교는 `--candidate-family target_weight_churn_relief`로 격월 리밸런싱과 리밸런싱당 신규 편입 수 상한(`max_new_targets_per_rebalance`)을 결합해 검증합니다. 포트폴리오 손실 구간 자체를 줄이는 후속 비교는 `--candidate-family target_weight_drawdown_guard`로 직전 평가 NAV 기반 drawdown guard와 재진입 cooldown을 결합해 검증합니다. 선정 종목의 목표 금액 자체를 변동성 예산형으로 조정하는 후속 비교는 `--candidate-family target_weight_volatility_budget`로 역변동성 sleeve weighting을 적용합니다. target-weight 후보 평가는 같은 sweep 안에서 OHLCV fetch cache를 공유해 동일 종목·기간 반복 수집을 줄이고, cache hit/unique fetch 진단값을 artifact와 Markdown에 남깁니다. sweep artifact와 Markdown에는 선택 방식, 요청 top-N, 스캔 한도, 후보 필터, 유동성 필터 전후 종목 수, 후보별 탈락 사유와 반복 병목 요약을 남겨 실제 검증 범위와 게이트 병목을 확인합니다. target-weight 후보는 `min_score_floor_pct`로 약한 초과 모멘텀 슬롯을 현금으로 남기고, `hold_rank_buffer`와 `max_new_targets_per_rebalance`로 랭킹 흔들림에 따른 불필요한 교체와 신규 편입 수를 줄이며, `target_allocation_mode=inverse_volatility`로 선택 종목별 목표 금액을 rolling 변동성의 역수 기준으로 나누고, `portfolio_drawdown_guard_trigger_pct`/`portfolio_drawdown_guard_exposure`/`portfolio_drawdown_guard_cooldown_rebalances`로 포트폴리오 낙폭 후 목표 노출과 재진입 속도를 제한하고, `market_exposure_mode=benchmark_risk`로 KS11 SMA/낙폭/변동성 risk-off 구간의 부분 노출 축소를 검증합니다. canonical risk-overlay 후보에는 `target_tolerance_pct=3/5` turnover-aware 변형도 포함해 작은 리밸런싱 생략이 비용과 성과에 주는 영향을 비교합니다. target-weight 리서치 백테스트는 직전 거래일 점수로 다음 거래일 시가에 리밸런싱하고 일말 평가는 종가로 하며, 전일 기준 20일 평균 거래량을 비용 계산에 넘겨 동적 슬리피지와 participation 진단값을 남깁니다. -Paper Evidence 체계 — `core/paper_evidence.py` v2 일별 22개 지표 자동 수집, `core/paper_runtime.py` entry gate, `core/paper_pilot.py` launch readiness/pilot auth 판정. scheduler는 v2 collector만 canonical 증거로 기록하며, legacy `core/evidence_collector.py`는 import 호환용 deprecated no-op입니다. 승격 패키지는 `execution_backed=True`와 `real_paper`/`pilot_paper` 출처가 명시된 기록만 승격 증거로 인정하고, 패키지 내부 `strategy`가 현재 전략명과 정확히 일치하지 않으면 승격/라이브 증거로 쓰지 않아 예전 형식·수작업 기록 오염을 차단한다. - -2026-04-29 all-family quick sweep: 5종목(`005930,000660,035720,051910,068270`)에서 rotation/momentum/breakout 후보 14개를 비교했지만 모두 benchmark excess return/Sharpe를 통과하지 못해 `NO_ALPHA_CANDIDATE`로 판정. 이 결과만으로 canonical promotion이나 paper/live 승격은 진행하지 않습니다. - -2026-04-30 top-20 all-family quick sweep: canonical liquidity universe 20종목에서 동일 후보 14개를 재검증했지만 `NO_ALPHA_CANDIDATE` 유지. best=`momentum_factor_120d`는 return +118.56%, Sharpe 0.79였으나 benchmark excess=-30.83%p, MDD=-40.08%로 승격 불가. 다음 연구는 단순 후보 확장이 아니라 benchmark를 이기는 새로운 alpha 후보군 설계로 전환합니다. - -2026-04-30 follow-up: 기존 전략 중 외부 재무 데이터 의존이 없는 `trend_pullback`을 `pullback` candidate family로 추가했습니다. 또한 기존 실패 원인(절대수익은 높지만 benchmark에 뒤처짐)을 직접 겨냥하기 위해 `momentum_factor`에 KS11 대비 초과 모멘텀/변동성 게이트 옵션을 추가하고 `benchmark_relative` candidate family로 노출했습니다. `all` sweep은 이제 rotation/momentum/breakout/pullback/benchmark-relative 후보군을 함께 평가합니다. - -2026-04-30 5-symbol smoke sweep: 신규 `benchmark_relative` 3개와 `pullback` 4개 모두 `NO_ALPHA_CANDIDATE`. best=`benchmark_relative_momentum_60d` return +4.13%, excess=-169.50%p; best pullback=`trend_pullback_aggressive` return +3.04%, excess=-170.59%p. 두 후보군은 계속 research-only입니다. - -2026-04-30 follow-up: 신호 필터만 추가하는 방향이 약하다고 판단해 `risk_budget` candidate family를 추가했습니다. 동일 신호를 집중형/균형형/방어형 exposure budget으로 나눠 평가하고, 각 후보 artifact에 적용된 `diversification` 설정을 기록합니다. - -2026-04-30 risk-budget smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best return=`risk_budget_momentum_120d_concentrated` +11.40%, excess=-162.23%p, MDD=-32.28%; best risk-adjusted=`risk_budget_rotation_slow_defensive` +10.91%, excess=-162.72%p, MDD=-6.41%. 방어형 exposure는 낙폭을 줄였지만 alpha 자체는 아직 없습니다. - -2026-04-30 follow-up: 방어형 exposure만으로 benchmark를 이기지 못해 `relative_strength_rotation`에 `market_filter_exit` 옵션을 추가하고 `cash_switch` candidate family를 추가했습니다. KS11이 이동평균 아래로 내려가면 신규 진입 차단을 넘어 기존 포지션도 현금화하는 구조입니다. - -2026-04-30 cash-switch smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`cash_switch_rotation_slow_defensive` return +1.87%, excess=-171.76%p, Sharpe=-0.40, MDD=-11.78%. 현금 전환은 손실 방어에는 일부 유효했지만 benchmark 대비 alpha는 만들지 못해 research-only로 유지합니다. +| [PROFITABILITY_FINDINGS](docs/PROFITABILITY_FINDINGS.md) | **수익성 정직 점검 (먼저 읽을 것)** — 알파 탐색 결론 | +| [POCKET_TRACK_PLAN](docs/POCKET_TRACK_PLAN.md) | 소액 적립 트랙 설계 — 기대치·구성·입금·게이트 | +| [BASKET_PAPER_EVALUATION](docs/BASKET_PAPER_EVALUATION.md) | 페이퍼→실전 승격 기준과 자동 판정 | +| [BASKET_LIVE_RUNBOOK](docs/BASKET_LIVE_RUNBOOK.md) | 실전 전환 절차 (모의서버 리허설 → 소액 → 목표 자본) | +| [PROJECT_GUIDE](docs/PROJECT_GUIDE.md) | 파일 역할·모드별 흐름·실전 전 체크리스트 | +| [RESEARCH_LOG](docs/RESEARCH_LOG.md) | 연구·운영 상세 이력 아카이브 (알파 탐색 실패 기록 포함) | +| [quant_trader_design](quant_trader_design.md) | 아키텍처·전략·리스크 설계 | -2026-04-30 follow-up: `research_candidate_sweep`에 exposure-matched benchmark diagnostics를 추가했습니다. cash-switch 후보의 평균 노출은 8.4~10.0%에 불과했고 exposure-matched excess도 -7.87%p~-0.36%p로 음수였습니다. 즉 raw benchmark gap은 낮은 노출 영향이 크지만, 같은 노출로 비교해도 신호 edge가 아직 없어 다음 연구는 단순 현금화보다 benchmark-aware 랭킹/부분 헤지/노출 유지형 alpha 설계가 우선입니다. - -2026-04-30 follow-up: `relative_strength_rotation`에 benchmark-aware ranked mode를 추가했습니다. `score_mode=benchmark_excess`는 종목 60/120d 복합 모멘텀에서 KS11 복합 모멘텀을 차감해 랭킹하고, `rank_entry_mode=dense_ranked`/`exit_rebalance_mode=score_floor`로 절대 추세 필터 때문에 과도하게 현금화되는 문제를 research-only로 분리 검증합니다. - -2026-05-08 follow-up: `backtest/portfolio_backtester.py`에도 gap-up 신규 매수 차단, gap-down `GAP_DOWN` 청산, 어닝 윈도우 신규 매수 차단, BlackSwan 청산·쿨다운·recovery 사이징을 반영했습니다. 단일종목/포트폴리오 백테스트가 같은 리스크 이벤트 전제로 비교되도록 `gap_*`, `earnings_*`, `blackswan_*` 진단 카운터와 회귀 테스트를 보강했습니다. - -2026-05-08 follow-up: `WatchlistManager.liquidity_filter_report()`를 공통 진단 API로 분리하고, 포트폴리오 백테스트와 `tools/research_candidate_sweep.py`가 평가 시작일 기준 20일 평균 거래대금 하한 미만 또는 strict 모드 데이터 누락 종목을 universe에서 먼저 제외하도록 보강했습니다. research artifact에는 입력 universe, 필터 통과 universe, 제외 종목/사유가 함께 남습니다. - -2026-05-08 follow-up: `PortfolioBacktester`도 단일종목 백테스터처럼 종목별 20일 평균 거래량을 `RiskManager.calculate_transaction_costs()`에 전달해 동적 슬리피지 배수를 반영합니다. 포트폴리오 거래 기록에는 `participation_rate`, `slippage_multiplier`, `slippage_cost`가 남아 거래비용 과소추정을 점검할 수 있습니다. - -2026-05-12 follow-up: 단일/포트폴리오 백테스트 리포트에 비용 전/후 성과 비교를 자동 노출합니다. `backtest.cost_impact`가 수수료·세금·슬리피지를 표준 집계해 비용 차감 전 추정 수익률, 비용 드래그(bp), 비용/순손익, cost impact status를 남기며, 비용 때문에 gross profit이 net loss로 뒤집히거나 비용이 순이익을 초과하면 운영 검토 신호로 표시합니다. - -2026-05-08 follow-up: `tools/research_candidate_sweep.py`의 target-weight 리서치 백테스트도 OHLCV `volume`으로 종목별 20일 평균 거래량을 계산해 매수·매도 비용에 전달합니다. target-weight trade와 metrics에는 `avg_daily_volume`, `participation_rate`, `slippage_multiplier`, `slippage_cost_total`이 남아 high-turnover 후보의 비용 과소추정을 더 빨리 확인할 수 있습니다. - -2026-05-12 follow-up: target-weight research sweep에 canonical risk-overlay 후보의 `target_tolerance_pct=3/5` 변형을 추가했습니다. 리서치 metrics에는 `rebalance_tolerance_pct`, `rebalance_tolerance_skipped_trades`, `rebalance_tolerance_skipped_notional`을 남겨 넓은 tolerance가 실제로 turnover를 줄였는지 artifact에서 바로 확인할 수 있습니다. - -2026-05-08 follow-up: `research_candidate_sweep`의 EW B&H 벤치마크가 일부 종목 결측 상태에서 전체 capital 대비 낮게 계산되어 후보 초과수익이 과대평가되는 경로를 차단했습니다. 벤치마크 입력 universe 전체가 수집·검증되지 않으면 excess gate와 decision action은 `INSUFFICIENT_BENCHMARK_DATA`로 고정됩니다. - -2026-05-08 follow-up: `OrderExecutor` live BUY/SELL은 주문 ACK 이후 체결가·체결수량 확인이 되지 않거나 부분체결만 확인되면 더 이상 예상가 기준 전량 FILLED로 처리하지 않습니다. 이때 `success=False`는 브로커 주문 부재가 아니라 `order_pending=True`/`requires_reconcile=True`인 장부 반영 보류 상태이며, KIS 잔고 대조 전 DB 포지션·거래 기록 오염을 막습니다. - -2026-05-11 follow-up: paper BUY 수량·손절·익절·트레일링 기준을 예상 체결가로 보수화하고, paper SELL도 매수처럼 모델 슬리피지를 체결가에 반영합니다. `TradeHistory.price`는 실제 체결가로 보고, `get_trade_cash_summary()`는 슬리피지를 진단값으로 집계하되 현금 흐름에서는 수수료·세금만 별도 차감해 체결가에 이미 들어간 비용이 중복 반영되지 않게 했습니다. - -2026-05-08 follow-up: KIS 미체결 조회 실패를 더 이상 “미체결 없음”으로 해석하지 않습니다. live BUY/SELL은 주문 전 미체결 조회가 실패하거나 응답 형식이 불명확하면 `live_unfilled_check.checked=False`로 주문을 보류하고, 재시작 복구에서도 KIS 미체결 조회 실패를 별도 critical 알림으로 드러냅니다. - -2026-05-14 follow-up: 호환용 `has_unfilled_orders()`도 미체결 조회 실패를 `False`로 감추지 않고 미체결 있음으로 간주합니다. live 신규 주문이 상태형 API를 우회하는 레거시 경로로 들어와도 중복 주문 위험은 fail-closed로 처리됩니다. - -2026-05-14 follow-up: `OrderExecutor` live BUY 직접 호출 우회를 막았습니다. `run_live_trading()` 또는 live 리밸런싱처럼 readiness gate를 통과한 경로만 `live_gate_validated=True`를 전달하며, 기본값은 fail-closed라 수동 스크립트/콘솔에서 executor를 직접 만들어도 신규 BUY는 KIS 주문 전에 차단됩니다. SELL은 긴급 청산 안전성을 위해 기존 실행 경로를 유지합니다. - -2026-05-14 follow-up: 바스켓 리밸런싱 paper BUY 실행 경로를 복구했습니다. `BasketRebalancer.execute()`가 없는 포트폴리오 메서드를 호출하던 부분을 `get_current_capital()`/`get_available_cash()` 기준으로 정리했고, 실제 주문 실행 전 자본·현금 값이 `OrderExecutor.execute_buy_quantity()`에 전달되는지 회귀 테스트로 고정했습니다. - -2026-05-14 follow-up: 시장 국면 필터를 fail-closed로 보강했습니다. `market_regime_filter=true` 상태에서 지수 데이터 조회 실패, 빈 데이터, MA 계산 실패가 발생하면 더 이상 bullish 기본값으로 넘기지 않고 `regime=unknown`, `allow_buys=false`, `position_scale=0.0`으로 신규 BUY를 차단합니다. 직접 `OrderExecutor.execute_buy()`를 호출하는 경로도 같은 결과를 받으면 주문 전 중단합니다. - -2026-05-14 follow-up: KIS Circuit Breaker의 `HALF_OPEN` 복구 상태를 단일 probe 방식으로 제한했습니다. OPEN 쿨다운 후 첫 요청 하나만 통과시키고, `on_success()` 또는 `on_failure()`가 들어오기 전까지 추가 요청은 차단해 장애 회복 직후 요청이 한꺼번에 풀리지 않게 했습니다. - -2026-05-14 follow-up: paper promotion evidence package에 `package_integrity.payload_hash`와 `source_records.records_hash`를 추가했습니다. live gate는 패키지 payload hash, 원본 daily evidence JSONL에서 재계산한 source record hash, record count/date 범위를 모두 비교해 패키지 요약값과 원본 evidence가 따로 변경된 상태를 live 전환 근거로 쓰지 않습니다. - -2026-05-14 follow-up: live 긴급 청산은 KIS 현재가가 없거나 0 이하이면 해당 종목을 실패 상세로 기록하고 `execute_sell`을 호출하지 않습니다. 평균단가를 현재 시장가처럼 사용한 지정가 매도 fallback을 막아 급락장 미체결 위험을 운영 알림으로 드러냅니다. - -2026-05-08 follow-up: target-weight 리서치 백테스트의 리밸런싱 체결 기준을 당일 종가에서 다음 거래일 시가로 보수화했습니다. 랭킹과 risk-off 판단은 직전 거래일 종가까지만 사용하고, 리밸런싱 체결은 해당 거래일 원본 `open`, 일말 평가는 `close`로 분리합니다. 신규 top-N 매수 후보의 리밸런싱일 `open`이 누락되면 `target_weight_research_execution_price_missing`으로 중단하고, 이미 보유한 종목의 `open`이 없으면 해당 리밸런싱을 거래 없이 skip 진단으로 남깁니다. 동적 슬리피지의 20일 평균 거래량도 당일 거래량을 포함하지 않도록 1거래일 지연합니다. - -2026-04-30 benchmark-aware rotation smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`benchmark_aware_rotation_60_120_balanced` return=+21.65%, Sharpe=0.50, avg exposure=24.1%였지만 raw excess=-151.98%p, exposure-matched excess=-16.05%p라 promotion 미진행. fast `40_100_dense`는 exposure-matched excess=+2.04%p였으나 raw excess=-163.35%p라 다음 연구 힌트로만 기록합니다. 다음 방향은 sparse BUY/SELL 신호를 넘어 monthly top-N 목표비중 리밸런싱을 별도 백테스터로 검증하는 것입니다. - -2026-04-30 target-weight top-N rotation smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`target_weight_rotation_top3_40_100_excess` return=+128.44%, Sharpe=1.13, avg exposure=85.3%로 노출 부족 문제는 해결했지만 raw excess=-45.19%p, exposure-matched excess=-14.82%p라 promotion 미진행. 이 결과는 sparse 신호가 병목이었음을 확인했지만, 동일 유니버스 B&H를 이기는 alpha는 아직 아니라서 다음 연구는 더 넓은 유니버스와 부분 hedge/상대강도 필터 개선입니다. - -2026-04-30 canonical top-20 target-weight full sweep: 기존 `target_weight_rotation_top5_60_120_floor0_hold3`는 return=+278.57%, raw excess=+129.18%p, exposure-matched excess=+150.88%p, Sharpe=1.65, WF positive/Sh+ 100%, turnover/year=807.8%였지만 MDD=-28.25%로 `paper_only`. benchmark-risk overlay 추가 후 best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+210.24%, raw excess=+60.85%p, exposure-matched excess=+130.96%p, Sharpe=1.60, PF=5.73, MDD=-19.24%, turnover/year=858.0%, WF positive/Sh+ 100%, risk-off rebalance=38.9%로 처음 `provisional_paper_candidate`에 도달했습니다. 후속으로 `tools/evaluate_and_promote.py --canonical`이 이 후보를 canonical promotion bundle에 재현하도록 연결했고, `promotion_result.json`에서도 동일 후보가 `provisional_paper_candidate`로 로드됩니다. 추가로 `tools/target_weight_rotation_pilot.py`가 portfolio-level 목표비중 plan을 만들고 pilot cap을 검증한 뒤 paper-only exact-quantity 주문을 낼 수 있게 했습니다. dry-run은 `--record-shadow-evidence`로 non-promotable `shadow_bootstrap` evidence를 남기고, 같은 실행에서 launch readiness JSON/MD와 pilot runbook을 생성합니다. session artifact에는 기본 cap preview, plan 기반 최소/추천 pilot cap, enable 명령, launch artifact 경로가 함께 기록됩니다. live 자동운영은 여전히 금지입니다. - -2026-05-11 follow-up: target-weight canonical을 next-open 체결과 결측 진단 기준으로 재검증했습니다. `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+171.20%, PF=4.24, Sharpe=1.41, MDD=-19.90%, WF positive=100%, WF Sharpe+=83.3%로 `provisional_paper_candidate`를 유지했습니다. 이번 산출물은 stale score 후보 2개 제외, held stale valuation 21일, 보유 종목 시가 누락에 따른 리밸런싱 skip 1회를 metrics에 남기므로 paper pilot에서는 체결 가능성과 stale valuation 빈도를 함께 감시해야 합니다. - -2026-05-12 top-200 target-weight follow-up: `--top-n 200 --candidate-id target_weight_rotation_top5_60_120_floor0_hold3_risk60_35` full sweep을 실행했습니다. canonical liquidity 200개 중 유동성 필터 통과 164개, benchmark coverage 100%에서 return=+110.39%, raw excess=+78.50%p, exposure-matched excess=+90.25%p, Sharpe=0.85, PF=2.06, WF positive=83.3%, WF Sharpe+=100%였지만 MDD=-25.79%, turnover/year=1097.1%로 provisional 게이트를 넘지 못해 `paper_only`입니다. 이 결과를 기준으로 alpha 존재 여부보다 drawdown과 turnover를 동시에 낮추는 리스크 완화 후보군 검증으로 이어갔습니다. - -2026-05-12 리스크 완화 top-200 follow-up: `--candidate-family target_weight_risk_relief --top-n 200` full sweep에서 10개 후보를 비교했습니다. 최상위 후보 `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5`는 return=+125.61%, raw excess=+93.72%p, exposure-matched excess=+104.53%p, avg exposure=68.5%, Sharpe=0.91, PF=2.19, WF positive=83.3%, WF Sharpe+=100%로 기존 단일 후보보다 수익성은 개선됐습니다. 다만 전체 후보가 MDD -25.27%~-32.14%, turnover/year 1027.2%~1344.3% 구간에 있어 `mdd < -20`와 `turnover_per_year >= 1000` 병목을 넘지 못했고, 판정은 `KEEP_RESEARCH_ONLY`입니다. sweep data fetch cache는 unique_fetches=1155, cache_hits=10395로 동작을 확인했습니다. 다음 연구는 리밸런싱 빈도 자체를 낮추는 격월/분기 리밸런싱, 변동성 타깃, 낙폭 차단, 회전율 패널티 랭킹을 우선합니다. - -2026-05-12 저회전 top-200 follow-up: `--candidate-family target_weight_turnover_relief --top-n 200` full sweep에서 격월/분기 리밸런싱 후보 6개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk90_35_bimonthly`는 return=+85.72%, raw excess=+53.83%p, exposure-matched excess=+68.63%p, Sharpe=0.73, PF=1.86, turnover/year=616.7%였습니다. 분기 후보의 turnover/year는 456.1~476.9%까지 내려갔지만, 전 후보가 benchmark excess Sharpe<=0 및 MDD -25.35%~-35.12%로 막혀 판정은 `NO_ALPHA_CANDIDATE`입니다. 즉 단순 리밸런싱 빈도 축소는 회전율 병목은 완화하지만 위험조정 alpha와 낙폭 문제를 해결하지 못했고, 이 판단을 바탕으로 변동성 타깃과 낙폭 차단을 실제 포지션 크기 산식에 넣는 후보군을 이어서 검증했습니다. - -2026-05-12 변동성 타깃 top-200 follow-up: `--candidate-family target_weight_volatility_target --top-n 200` full sweep에서 benchmark 실현 변동성 타깃과 drawdown floor 후보 6개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_vol16_dd8_floor35`는 return=+105.18%, raw excess=+73.29%p, exposure-matched excess=+91.35%p, avg exposure=71.7%, Sharpe=0.81, PF=2.03, turnover/year=958.0%였습니다. 다만 전 후보가 MDD -24.26%~-31.04%로 provisional MDD 게이트를 통과하지 못했고, 5개 후보는 benchmark excess Sharpe도 0 이하라 판정은 `KEEP_RESEARCH_ONLY`입니다. 변동성 기반 노출 축소만으로는 낙폭 병목이 충분히 풀리지 않았으므로 다음 연구는 목표 노출 산식보다 종목 선별 랭킹에 낙폭·하방변동성·상관/업종 집중 페널티를 넣는 방향으로 전환합니다. - -2026-05-12 리스크 페널티 랭킹 top-200 follow-up: `--candidate-family target_weight_downside_rank_relief --top-n 200` full sweep에서 낙폭·하방변동성 페널티 후보 5개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60`는 return=+132.51%, raw excess=+100.62%p, exposure-matched excess=+111.43%p, Sharpe=0.94, PF=2.21, WF positive/Sh+=100%로 기존 리스크 완화 후보보다 수익성은 개선됐습니다. 다만 전 후보가 MDD -25.20%~-33.07%, turnover/year 1143.8%~1366.0%로 막혀 판정은 `KEEP_RESEARCH_ONLY`입니다. 즉 downside rank penalty는 alpha 후보성을 강화했지만, 종목 교체 빈도를 낮추지 못했습니다. 다음 연구는 이 랭킹 페널티를 격월 리밸런싱, 더 넓은 tolerance, 월별 신규 편입 수 제한 같은 churn control과 결합하는 방향입니다. - -2026-05-12 follow-up: `target_weight_churn_relief` 후보군을 추가했습니다. 직전 best였던 downside rank penalty 후보를 기준으로 `max_new_targets_per_rebalance` 신규 편입 상한, 격월 리밸런싱, 넓은 tolerance 조합을 별도 family로 분리해 top-200 full sweep에서 turnover/year와 MDD 병목이 실제로 완화되는지 확인할 수 있게 했습니다. - -2026-05-12 churn control top-200 follow-up: `--candidate-family target_weight_churn_relief --top-n 200` full sweep에서 후보 5개를 검증했고 판정은 `KEEP_RESEARCH_ONLY`입니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60_maxnew2`는 return=+118.89%, raw excess=+87.00%p, exposure-matched excess=+97.61%p, Sharpe=0.89, PF=2.04, WF positive/Sh+=100%였지만 MDD=-26.95%, turnover/year=1034.2%로 provisional 게이트를 넘지 못했습니다. `maxnew1`과 격월 후보는 turnover/year를 423.0~674.4%까지 낮췄지만 benchmark excess Sharpe와 MDD가 악화됐습니다. 다음 연구는 단순 교체 제한보다 포트폴리오 drawdown guard, 재진입 cooldown, 상관/업종 집중도 페널티처럼 손실 구간 자체를 줄이는 구조가 우선입니다. - -2026-05-12 follow-up: `target_weight_drawdown_guard` 후보군을 추가했습니다. 직전 평가 NAV 기준 포트폴리오 낙폭이 임계값을 넘으면 다음 리밸런싱 목표 노출을 floor까지 낮추고, 회복 직후에도 지정 리밸런싱 횟수만큼 cooldown을 유지합니다. 다음 검증은 `--candidate-family target_weight_drawdown_guard --top-n 200` full sweep으로 MDD가 실제로 낮아지는지 확인합니다. - -2026-05-12 손실방어 top-200 follow-up: `--candidate-family target_weight_drawdown_guard --top-n 200` full sweep에서 후보 5개를 검증했고 판정은 `RUN_CANONICAL_EVALUATION`입니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60_pdd8_floor25_cd1`는 return=+99.53%, raw excess=+67.64%p, exposure-matched excess=+83.07%p, avg exposure=56.5%, Sharpe=1.02, PF=4.88, MDD=-19.56%, turnover/year=296.4%, WF positive/Sh+=66.7%였습니다. 포트폴리오 guard는 trigger 17회, guard rebalance 20회(74.1%)로 동작했습니다. 두 번째 후보 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_pdd10_floor40_cd1`도 provisional 조건을 통과했지만 turnover/year=893.4%로 비용 민감도가 더 큽니다. 나머지 3개는 MDD -20% 미만 또는 benchmark excess Sharpe<=0로 `paper_only`입니다. 다음 단계는 eligible 2개 후보를 canonical promotion evaluation에 올려 같은 기준에서 재현성을 확인하는 것이며, 이 artifact만으로 paper/live 전환은 하지 않습니다. - -2026-05-13 손실방어 canonical follow-up: `tools/evaluate_and_promote.py --canonical`에 eligible 2개 손실방어 후보를 포함해 재평가했습니다. 기존 `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+171.20%, benchmark excess=+21.81%p, Sharpe=1.41, PF=4.24, MDD=-19.90%, turnover/year=794.0%, WF positive=100%, WF Sharpe+=83.3%로 `provisional_paper_candidate`를 유지했습니다. pdd8/floor25 후보는 return=+142.93%, Sharpe=1.43, PF=7.69, MDD=-15.89%, turnover/year=530.8%로 방어력은 좋아졌지만 canonical benchmark excess=-6.46%p라 `paper_only`입니다. exp75/rankrisk90/pdd10/floor40 후보는 return=+180.40%, benchmark excess=+31.01%p, Sharpe=1.49, PF=4.85, MDD=-19.24%, WF positive/Sh+=100%였지만 turnover/year=1026.3%로 게이트를 넘겨 `paper_only`입니다. 다음 연구는 exp75 계열에 tolerance 또는 신규 편입 제한을 결합해 초과수익과 WF 안정성은 유지하면서 turnover/year를 1000% 아래로 낮추는 방향입니다. - -2026-05-13 exp75 회전율 완화 follow-up: `target_weight_drawdown_guard` family에 exp75/rankrisk90/pdd10/floor40 기반 `tol3`, `tol4`, `tol5`, `maxnew2`, `tol3_maxnew2` 변형을 추가했습니다. `--candidate-family target_weight_drawdown_guard --top-n 200` 재검증에서는 후보 10개 중 4개가 canonical 평가 대상으로 남았고, 새 `tol4` 후보는 return=+81.19%, raw excess=+49.30%p, exposure-matched excess=+66.89%p, MDD=-19.33%, turnover/year=886.7%, WF positive/Sh+=100%로 research 기준을 통과했습니다. canonical 재평가에서는 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol4_pdd10_floor40_cd1`가 return=+183.02%, benchmark excess=+33.63%p, Sharpe=1.50, PF=4.82, turnover/year=986.5%로 회전율 게이트는 통과했지만 MDD=-20.25%가 -20% 제한을 0.25%p 초과해 `paper_only`입니다. tol3는 turnover/year=1009.0%, 원본은 1026.3%로 각각 회전율 미달입니다. 다음 연구는 tol4의 낮아진 회전율을 유지하면서 drawdown floor/cooldown 또는 ranking risk penalty를 더 보수화해 MDD를 -20% 안쪽으로 되돌리는 방향이며, 현재 live/paper 전환 대상은 기존 risk-overlay 후보만 유지합니다. - -2026-05-13 tol4 낙폭 완화 follow-up: tol4 기반으로 `pdd10_floor35_cd1`, `pdd8_floor40_cd1`, `pdd8_floor35_cd1`, `pdd10_floor40_cd2` 네 변형을 추가해 `target_weight_drawdown_guard --top-n 200` full sweep을 다시 실행했습니다. 후보 14개 중 신규 변형은 canonical eligible에 추가되지 않았습니다. `pdd10_floor35`는 turnover/year=829.8%로 낮아졌지만 MDD=-20.20%, `pdd8_floor35`도 MDD=-20.20%라 실패했고, `pdd8_floor40`과 `cooldown2`는 MDD/turnover는 통과권이었지만 benchmark excess Sharpe<=0으로 제외됐습니다. 결론적으로 guard trigger/floor/cooldown만 더 조이는 방식은 alpha 또는 MDD 중 하나를 잃기 쉬우므로, 다음 후보는 종목 선택 단계의 downside rank penalty/상관·업종 집중도 페널티나 비용 민감도 기준을 함께 조정하는 쪽이 우선입니다. - -2026-05-13 rank-risk 보강 follow-up: exp75 계열에 `rankrisk90_dd75`, `rankrisk120`을 추가하고 tol4+pdd10/floor40 guard와 결합했습니다. `target_weight_drawdown_guard --top-n 200` 후보 16개 재검증에서는 `rankrisk120_tol4`가 return=+84.41%, raw excess=+52.52%p, MDD=-19.30%, turnover/year=919.4%, `rankrisk90_dd75_tol4`가 return=+82.56%, raw excess=+50.67%p, MDD=-19.54%, turnover/year=896.0%로 기존 tol4보다 좋아져 canonical 평가 대상에 포함됐습니다. 하지만 canonical top-20 재평가에서는 각각 return=+125.69%/+125.99%, turnover/year=927.9%/947.6%였지만 MDD=-20.74%/-20.76%와 benchmark excess=-23.70%p/-23.40%p로 `paper_only`입니다. 패널티 강도만 키우면 top-200에서는 좋아져도 canonical 대형주 구간 alpha가 꺾이므로, 다음은 sector/correlation cap처럼 분산 제약을 선택 단계에 직접 넣는 쪽을 우선합니다. - -2026-05-13 sector cap follow-up: FDR `KRX` 목록에 업종 컬럼이 없는 환경에서도 `get_sector_map()`이 KRX KIND 상장법인 목록(2,766개 업종 매핑)으로 fallback하도록 보강하고, `target_weight_drawdown_guard --top-n 200`에 `sectorcap1/2` 후보를 추가했습니다. `sectorcap2`는 실제 섹터 맵 적용 기준으로 `rankrisk120_tol4_sectorcap2` return=+85.49%, raw excess=+53.60%p, MDD=-19.94%, turnover/year=941.5%, `rankrisk90_tol4_sectorcap2` return=+82.57%, raw excess=+50.68%p, MDD=-19.59%, turnover/year=909.3%로 canonical 평가 대상에 포함됐습니다. canonical top-20에서는 `rankrisk90_tol4_sectorcap2`가 return=+185.39%, benchmark excess=+36.00%p, turnover/year=981.0%로 원본 tol4보다 좋아졌지만 MDD=-20.22%라 `paper_only`입니다. `rankrisk120_tol4_sectorcap2`는 sector cap 적용 후에도 canonical 결과가 기존과 같아 benchmark excess=-23.70%p, MDD=-20.74%로 제외했습니다. 다음 후보는 섹터 수량 cap만으로 부족한 낙폭 0.22%p를 줄이기 위해 상관도 cap, 월별 손실 이후 재진입 제한, 변동성 예산형 target selection을 결합하는 쪽입니다. - -2026-05-18 sector map cache follow-up: `DataCollector.get_sector_map()`은 FDR/KRX KIND/pykrx 업종 매핑을 성공적으로 읽으면 `reports/sector_map_cache.json`에 schema v1 캐시를 저장하고, 실시간 소스가 모두 비어 있으면 해당 캐시를 fallback으로 사용합니다. 최신 캐시는 KRX KIND 기준 2,766개 종목이며, target-weight sector cap plan은 캐시가 있더라도 planning universe 전체 섹터가 매핑되지 않으면 `target_weight_sector_map_incomplete`로 fail-closed 차단합니다. KRX KIND가 우선주 섹터를 직접 주지 않는 경우에는 `005935` → `005930`처럼 같은 5자리 stem의 보통주 섹터를 추론하고 diagnostics의 `sector_map_inferred_symbols`에 기록합니다. no-order `--daily-ops-summary`/`--readiness-audit`는 sector map blocker뿐 아니라 FDR/yfinance/KIS fallback 비활성화로 인한 가격 수집 실패도 traceback 대신 `BLOCKED` failure artifact로 남깁니다. - -2026-05-13 correlation cap follow-up: target selection 단계에 `max_pairwise_correlation`, `correlation_lookback_days`, `correlation_min_periods`를 추가해 이미 선택된 종목과의 과거 수익률 상관이 임계값을 넘는 후보를 건너뛰도록 했습니다. `target_weight_drawdown_guard --top-n 200`에서는 `corrcap85`와 `sectorcap2_corrcap85`가 canonical 평가 대상에 포함됐고 관측 최대 선택 상관도는 0.8147로 제한됐지만, return/MDD/turnover는 각각 기존 tol4 및 sectorcap2와 동일했습니다. `corrcap80`은 관측 상관도를 0.7675까지 낮췄지만 return=+70.22%, raw excess=+38.33%, MDD=-19.33%, turnover/year=908.3%에서 benchmark excess Sharpe<=0으로 제외됐습니다. canonical top-20에서도 `corrcap85`는 MDD=-20.25%, `sectorcap2_corrcap85`는 MDD=-20.22%로 기존 후보와 같은 `paper_only`입니다. 결론적으로 0.85 단순 pairwise cap은 너무 느슨하고, 0.80 단독 cap은 alpha를 깎으므로 다음은 상관도 penalty를 랭킹 점수에 직접 섞거나 변동성 예산/손실 후 재진입 제한과 결합해야 합니다. - -2026-05-13 correlation rank penalty follow-up: 단순 hard cap 대신 리밸런싱 시점의 `score_row`에 평균 양의 상관도 기반 `correlation_rank_penalty_weight`를 차감하는 후보를 추가했습니다. top-200 `target_weight_drawdown_guard`에서는 `sectorcap2_corrpen05`가 return=+84.22%, raw excess=+52.33%p, MDD=-19.59%, turnover/year=912.5%, `corrpen10`이 return=+83.50%, raw excess=+51.61%p, MDD=-19.96%, turnover/year=892.3%, `corrpen05`가 return=+82.82%, raw excess=+50.93%p, MDD=-19.33%, turnover/year=889.9%로 모두 canonical 후보에 포함됐습니다. 하지만 canonical top-20에서는 `corrpen05` return=+141.09%, benchmark excess=-8.30%p, MDD=-20.25%, `corrpen10` return=+128.43%, benchmark excess=-20.96%p, MDD=-20.25%, `sectorcap2_corrpen05` return=+143.11%, benchmark excess=-6.28%p, MDD=-20.22%로 모두 `paper_only`입니다. 결론적으로 평균 상관도 점수 차감은 top-200에서는 분산 개선 신호가 있었지만 대형주 canonical alpha를 크게 깎아 운영 승격에는 부적합합니다. 다음은 점수 차감보다 손실 후 재진입 제한이나 변동성 예산형 target selection처럼 낙폭을 직접 겨냥하는 제약을 우선합니다. - -2026-05-13 loss re-entry guard follow-up: 직전 실행 리밸런스 후 평가 NAV 대비 손실률이 `loss_reentry_guard_trigger_pct`를 넘으면 지정 cooldown 동안 신규 편입 수를 `loss_reentry_guard_max_new_targets`로 낮추는 옵션을 추가했습니다. `target_weight_drawdown_guard --top-n 200`에 `reentry4_maxnew0_cd1`, `reentry3_maxnew0_cd1`, `sectorcap2_reentry4_maxnew0_cd1` 후보를 추가했지만, 손실 직후 새 종목 편입을 막는 방식은 회전율만 낮추고 MDD를 악화시켰습니다. `sectorcap2_reentry4`는 return=+84.68%, raw excess=+52.79%p, turnover/year=793.8%였지만 MDD=-22.34%, `reentry4`는 return=+57.71%, MDD=-27.29%, `reentry3`은 return=+28.93%, MDD=-35.30%로 모두 `paper_only`라 canonical 평가 대상에서 제외했습니다. 다음은 보유 종목 고착을 만드는 신규 편입 차단보다, 종목별 변동성 예산이나 포지션별 손절/감산처럼 손실 원인을 직접 줄이는 제약이 우선입니다. - -2026-05-13 position loss reduction follow-up: 신규 편입을 막아 손실 종목을 고착시키는 대신, 직전 확정 종가 기준 보유 종목 손실률이 `position_loss_reduce_trigger_pct`를 넘으면 다음 리밸런싱 목표 금액을 `position_loss_reduce_target_fraction`까지 감산하는 옵션을 추가했습니다. 감산 매도는 기존 비용/PnL/turnover 경로를 그대로 타며, 리밸런싱 tolerance 때문에 위험 감산이 생략되지 않도록 분리했습니다. 실데이터 `target_weight_drawdown_guard --top-n 200` full sweep에서는 `sectorcap2_posloss8` 후보가 return=+105.79%, raw excess=+73.90%p, MDD=-18.96%, Sharpe=1.00으로 통과했지만 canonical에서는 turnover/year=1006.2%로 `paper_only`였습니다. 후속으로 `tol5`를 결합하자 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1`가 top-200 return=+112.16%, raw excess=+80.27%p, MDD=-19.23%, Sharpe=1.04로 통과했고, canonical top-20에서도 return=+198.15%, benchmark excess=+48.76%p, Sharpe=1.57, PF=5.58, MDD=-17.18%, turnover/year=993.9%, WF positive/Sh+=100%로 신규 `provisional_paper_candidate`가 됐습니다. 단 live는 여전히 60일 paper evidence와 target-weight verified proof가 없어 차단됩니다. `core.target_weight_rotation.build_target_weight_plan()`은 paper/pilot plan에서도 `rank_penalty_mode=downside_risk`, `max_targets_per_sector`, `position_loss_reduce_*`, `portfolio_drawdown_guard_*`를 적용하고, sector map 누락 또는 drawdown guard 명시 상태 누락은 fail-closed로 막습니다. pilot adapter는 기존 paper evidence의 total NAV, peak, guard cooldown을 상태로 복원하며 evidence snapshot에도 guard 결과를 남겨 다음 리밸런싱의 cooldown을 이어갑니다. - -2026-05-13 volatility budget target selection follow-up: 선택 종목은 유지하되 목표 금액을 동일가중이 아니라 rolling 실현 변동성의 역수로 배분하는 `target_allocation_mode=inverse_volatility`를 target-weight research backtester에 추가했습니다. `allocation_vol_lookback_days`, `allocation_vol_min_periods`, `allocation_vol_floor_pct`, `allocation_max_sleeve_weight_pct`를 artifact metrics에 남깁니다. 실데이터 `target_weight_volatility_budget --top-n 200` full sweep에서는 canonical liquidity 200개 중 유동성 통과 164개, benchmark coverage 100%에서 best=`target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol4_pdd10_floor40_cd1_volbudget60_cap35`가 return=+78.77%, raw excess=+46.88%p, exposure-matched excess=+63.79%p, avg exposure=55.2%, Sharpe=0.81, PF=1.96, MDD=-18.96%, trades=107로 `provisional_paper_candidate`가 됐습니다. sectorcap2+volbudget60 후보도 return=+77.18%, raw excess=+45.29%p, MDD=-19.19%로 통과했고, volbudget90 후보는 benchmark excess Sharpe<=0 병목이라 제외했습니다. 하지만 `tools/evaluate_and_promote.py --canonical` 재평가에서는 volbudget60 후보가 return=+170.73%, benchmark excess=+21.34%p, Sharpe=1.44, PF=4.53, turnover/year=982.6%, WF positive/Sh+=100%였지만 MDD=-20.82%로 `paper_only`입니다. sectorcap2+volbudget60도 return=+173.80%, benchmark excess=+24.41%p, Sharpe=1.46, PF=4.68, turnover/year=978.1%, WF positive/Sh+=100%였지만 MDD=-20.89%라 `paper_only`입니다. 변동성 예산은 회전율과 초과수익을 유지했지만 대형주 canonical MDD가 더 악화됐으므로 paper/live 전환 대상은 아니며, research-only 배분 로직이므로 향후 통과 후보가 다시 생기면 paper pilot 전 `core.target_weight_rotation` plan builder에 같은 배분 산식을 이식하고 params_hash 일치까지 검증해야 합니다. 다음 연구는 목표비중 배분보다 포지션별 손실 감산 또는 더 보수적인 drawdown guard를 결합해 MDD를 -20% 안쪽으로 안정화하는 방향입니다. - -2026-05-13 research empty-universe guard: canonical universe 목록 조회 실패, 데이터 수집 실패, strict 유동성 필터 때문에 research universe가 0개가 되면 후보별 0% 성과를 만들지 않고 benchmark/candidate 평가를 건너뛰도록 보강했습니다. artifact와 Markdown에는 `INSUFFICIENT_BENCHMARK_DATA`, `empty_universe_reason`, `selection_error`, `skipped_due_to_data`가 남아 데이터 문제를 전략 성과로 오해하지 않게 합니다. - -| 전략 | 상태 | Ret% | PF | WF P% | WF Sh+% | Paper Status | -|------|------|------|-----|-------|---------|--------------| -| relative_strength_rotation | **provisional_paper_candidate** | +18.09 | 1.62 | 100 | 83.3 | — | -| target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1 | **provisional_paper_candidate** (default capped pilot) | +198.15 | 5.58 | 100 | 100 | 전용 paper/pilot adapter 기본 후보, 60영업일 verified pilot evidence 필요 | -| target_weight_rotation_top5_60_120_floor0_hold3_risk60_35 | **provisional_paper_candidate** (previous default) | +171.20 | 4.24 | 100 | 83.3 | 이전 기본 후보, 관찰 유지 | -| scoring | **paper_only** | +11.22 | 1.07 | 83.3 | 50.0 | risk-adjusted alpha 미달 | -| breakout_volume | disabled (research_only) | -13.31 | 0.79 | 0 | 0 | — | -| mean_reversion | disabled (research_only) | -8.36 | 0.85 | 33.3 | 0 | — | -| trend_following | disabled (research_only) | -6.94 | 0.67 | 16.7 | 0 | — | -| ensemble | disabled (research_only) | — | — | 0 | 0 | — | - -## 주의 - -실전 투입 전에는 백테스트, 검증, 모의투자를 충분히 거친 뒤 사용하는 것을 권장합니다. -현재 scoring은 관찰용 paper_only로 강등되었습니다. 신규 우선 실험은 risk-adjusted 기준을 통과한 후보만 대상으로 하며, Paper Evidence 체계로 승격/강등 근거를 자동 수집합니다. -Paper 운영 도구: `tools/run_paper_evidence_pipeline.py` (backfill/finalize/package), `tools/paper_preflight.py`, `tools/paper_launch_readiness.py`, `tools/paper_pilot_control.py`, `tools/paper_runtime_status.py`, `tools/research_candidate_sweep.py`, `tools/target_weight_rotation_pilot.py`. `tools/paper_pilot_control.py`와 `tools/paper_runtime_status.py`의 상태 변경/조회 액션 옵션은 한 번에 하나만 지정합니다. 예를 들어 활성화와 비활성화처럼 서로 다른 액션을 같은 명령에 섞으면 파일 기록이나 런타임 상태 조회를 진행하지 않고 즉시 실패합니다. `tools/paper_preflight.py`와 `tools/paper_launch_readiness.py`도 점검 대상은 `--strategy` 또는 `--all` 중 하나만 허용하며, 두 옵션을 함께 넘기면 DB 초기화나 운영 artifact 생성 전에 즉시 실패합니다. preflight CLI는 overall fail 또는 entry 차단 결과를 non-zero로 반환하고, launch readiness CLI는 READY가 아니면 non-zero로 종료합니다. readiness 리포트만 만들고 싶을 때만 `--report-only`를 사용합니다. `tools/paper_bootstrap.py`는 빈 watchlist, 빈/역전 날짜 범위, 요청일 canonical evidence 미생성 상태를 incomplete로 보고 non-zero 종료합니다. target-weight 후보는 capped pilot 승인 전 `tools/target_weight_rotation_pilot.py --record-shadow-evidence`, `--shadow-days 3`, 또는 `--shadow-start-date YYYY-MM-DD --shadow-end-date YYYY-MM-DD`로 dry-run plan artifact, shadow readiness evidence, launch readiness artifact, plan 기반 cap 추천과 enable 명령이 포함된 pilot runbook을 먼저 누적합니다. 이후 `--readiness-audit`로 주문 제출이나 shadow/pilot evidence 기록 없이 clean shadow, launch readiness, active pilot caps, 중복 세션, 실행일/장 시간, 실행 전 포지션 드리프트, 종목/벤치마크 가격 최신성, 추천 cap 충족 여부, 최근 평균 거래대금 대비 주문 비율, 비용 반영 후 현금/투자비중 한도를 JSON artifact와 Markdown 운영 리포트로 점검합니다. 유동성 preflight는 기본적으로 주문별 20일 평균 거래대금의 5% 초과를 차단하며 `--max-order-adv-pct`로 조정할 수 있습니다. pre-trade risk 검증은 주문별 수수료/세금/동적 슬리피지를 반영한 예상 체결가로 현금 부족, 최소 현금비중, 총투자비중, 종목별 비중, 최대 보유 종목 수 위반을 실행 전에 차단합니다. Markdown 리포트에는 shadow 수집, audit 재실행, 추천 cap 승인, capped paper 실행 명령이 함께 기록됩니다. `--shadow-days N`은 휴장/데이터 공백으로 같은 거래일에 매핑되는 경우 과거 평일을 추가 스캔해 N개 고유 resolved trade_day 충족을 목표로 하며, 목표 미달이나 날짜별 실패가 있으면 CLI가 non-zero로 종료해 자동화가 불완전한 증거를 성공으로 처리하지 못하게 합니다. 실행형 pilot evidence는 같은 candidate/trade_day의 기존 pilot session artifact가 없고, `run_pilot` 점검과 `execute_plan` 주문 제출 직전 재확인에서 실제 paper position이 계획 입력 장부 `position_quantities_before`와 모두 일치하며, `plan.trade_day`가 KST 실행일과 일치하고 KRX 정규장 주문 가능 시간인 경우에만 진행됩니다. 이후 유동성 preflight와 pre-trade risk 검증을 통과하고, 모든 계획 주문이 성공하며, 성공 주문 결과와 당일 `TradeHistory` fill 집계의 종목/방향/수량이 계획과 일치하고, 실행 후 실제 paper position 전체가 리밸런싱 후 `target_quantities_after` 장부와 일치하고 계획 밖 양수 포지션이 없을 때만 `pilot_paper`로 수집됩니다. 같은 날짜 evidence가 이미 있으면 최신 canonical record도 `pilot_paper`/authorized/target-weight complete/execution-market-session-allowed/liquidity-complete/pre-trade-risk-complete/fill-complete 조건을 통과해야 재사용합니다. 중복 실행은 기본 차단하며 운영자가 명시적으로 재시도해야 할 때만 `--allow-rerun`을 사용합니다. 부분 실행/중단/중복 실행/주문 결과 불일치/체결 기록 불일치/기존 evidence 검증 실패/실행일 불일치/장 시간 외 실행/실행 전 포지션 드리프트/유동성 preflight 실패/pre-trade risk 실패/실행 후 포지션 불일치는 execution-backed 승격 증거에서 제외됩니다. - -Target-weight 60영업일 pilot manifest: `--readiness-audit`는 운영 리포트와 함께 `target_weight_paper_experiment_manifest_*.json`을 생성해 후보 snapshot, 추천 cap, 차단 사유, 실행 명령, 데이터 품질 진단, 승격 증거 조건을 고정합니다. `--daily-ops-summary`는 readiness, risk check, 데이터 품질, 실행일 일치 여부, 장 시간 주문 가능 여부, verified pilot day 진행률, 다음 실행 명령을 `target_weight_daily_ops_summary_*.json/.md`로 묶어 매일 운영 판단을 한 번에 확인하게 합니다. 당일 `pilot_paper` 증거가 이미 기록됐거나 복구 보존된 경우에는 summary 자체에도 `not_before_date`와 `premature_run_guard=target_weight_future_as_of_date_blocked`를 남겨 `current_blockers.json`이 늦게 갱신돼도 다음 KRX 영업일 전 재실행을 막습니다. 두 no-order 명령은 sector map, stale benchmark, 가격 데이터, FDR/yfinance/KIS fallback 비활성화 같은 plan 생성 blocker로 막혀도 traceback 대신 `target_weight_daily_ops_summary_failure_*.json/.md` 또는 `target_weight_readiness_audit_failure_*.json/.md`를 남기며, 해당 failure artifact에는 `status=BLOCKED`, 원인, 재실행 명령, `orders_submitted=false`가 고정됩니다. `READY_TO_EXECUTE`와 `WAITING_FOR_MARKET_SESSION`은 실행일/장 시간 점검과 pilot authorization snapshot이 실제 `checked=True`로 통과한 경우에만 표시하며, 미점검 항목은 `NOT CHECKED`로 남겨 cap 승인 준비와 실행 준비를 분리합니다. daily ops/readiness/manifest의 실행 명령은 `READY_TO_EXECUTE`가 아닐 때 `# blocked:` 주석 명령으로 고정해 cap 승인 전이나 blocker 해소 전 paper 실행을 운영 리포트가 유도하지 않게 합니다. `reports/current_blockers.json`도 schema v3부터 `operator_runbook`과 next action별 `command`/`order_safety`를 포함해 `check-only → daily ops summary → readiness audit → READY_TO_EXECUTE 당일 capped paper evidence 수집` 순서를 고정합니다. 최신 daily ops summary가 없으면 current blockers의 1순위는 먼저 daily ops summary 생성이고, 최신 summary가 있으면 shadow 3일 수집, Discord test preflight, 추천 cap 승인, READY_TO_EXECUTE 실행 중 현재 상태에 맞는 명령으로 자동 정렬됩니다. Discord webhook 미설정 blocker는 `DISCORD_WEBHOOK_URL` 설정이 먼저 필요한 액션으로 표시하고, READY 상태가 아닌 최신 summary의 실행 명령은 current blockers에서도 `# blocked:`로만 노출됩니다. preflight와 pilot runbook도 target-weight 후보에서는 generic paper evidence pipeline 대신 `target_weight_rotation_pilot.py --shadow-days`, `--daily-ops-summary`, `--readiness-audit`, Discord `--send-test-notification` 경로를 우선 안내합니다. 공식 `reports/paper_experiment_manifest.json`도 기존 scoring 60영업일 실험과 target-weight capped paper pilot을 분리해 기록합니다. - -Target-weight cash override 정책: `--cash`는 dry-run 계획 검토와 shadow bootstrap 보조 입력에만 사용합니다. `--readiness-audit`, `--daily-ops-summary`, `--execute`, `--collect-evidence`에서는 실제 paper 계좌 현금과 실행 직전 포지션을 기준으로만 판단하며, `--cash`가 같이 들어오면 CLI와 함수 호출 모두 `target_weight_cash_override_blocked`로 fail-closed 차단합니다. `--collect-evidence`는 반드시 `--execute`와 함께 사용해야 하며, 단독 증거 수집 요청은 `target_weight_collect_evidence_requires_execute`로 차단합니다. - -Generic paper entry도 동일한 실행 경계 원칙을 따른다. `main.py --mode paper`, scheduler auto-entry, fixed-quantity paper BUY 모두 preflight status와 runtime state를 주문 생성 전에 확인하고, 확인 실패는 fail-closed로 차단한다. blocked runtime에서 pilot authorization이 활성화되어도 `check_pilot_entry()`를 다시 통과해야 하며, pilot 총노출 cap은 현재 노출뿐 아니라 이번 주문 후 예상 노출까지 기준으로 검사한다. SELL/exit 경로는 포지션 정리를 위해 계속 허용한다. 운영 손실 한도도 같은 원칙을 따른다. `risk_params.yaml:drawdown.max_portfolio_mdd` 또는 `max_daily_loss`에 닿으면 신규 BUY만 fail-closed로 차단하고, 손절·트레일링·수동 청산 SELL은 계속 열어 둔다. pilot evidence freshness는 달력일이 아니라 주말과 한국장 휴장일을 제외한 영업일 기준으로 계산합니다. - -Target-weight pilot 승인/재시도 보강: `tools/paper_pilot_control.py --enable`은 `reports/promotion/run_metadata.json`의 canonical `strategy_specs[].base_strategy=target_weight_rotation`을 우선 기준으로 target-weight 후보를 식별하고, 기존 `target_weight_*` 접두어는 호환 fallback으로만 사용합니다. pilot auth를 쓰기 전 target-weight readiness audit을 다시 실행해 운영자가 요청한 cap이 현재 plan, launch readiness, 유동성 preflight, 비용 반영 pre-trade risk를 만족하는지 검증합니다. 이때 요청 cap은 audit의 `cap_recommendation.suggested_caps`와 정확히 일치해야 하므로, 통과 가능한 더 큰 cap으로 pilot을 여는 경로도 차단됩니다. 승인 auth에는 당시 plan의 `trade_day`, `as_of_date`, `params_hash`, targets, 시작/목표 수량 snapshot을 함께 저장해 cap 승인과 실행 계획을 묶습니다. 유동성 diagnostics가 없으면 fail-closed로 차단하고, 이미 주문이 완료된 same-candidate/trade-day 세션은 `--allow-rerun`을 줘도 재실행하지 않습니다. `--execute`는 주문 제출 전 같은 candidate/trade_day의 atomic `.lock`을 선점하며, 이미 in-progress lock이 있으면 주문·포지션 조회·증거 수집 전에 차단합니다. `--allow-rerun`도 이전 세션이 주문 제출에 도달했거나 실패/중단 흔적이 있으면 재실행하지 않고, 주문 제출 전 단계에서 막힌 불완전 세션 복구에만 사용합니다. - -Target-weight 실행 차단 기록: `--execute`가 pilot cap validation에서 막히면 주문·체결·증거 수집 없이 session JSON artifact에 차단 사유를 남깁니다. runtime pilot session은 쓰지 않아 cap을 고친 뒤 같은 거래일 계획을 다시 점검할 수 있습니다. 또한 `plan.trade_day`와 KST 기준 실제 실행일이 다르거나, 현재 시간이 KRX 정규장 주문 가능 시간이 아니거나, 활성 pilot auth의 승인 snapshot이 현재 plan의 `params_hash`/trade day/targets/수량 장부와 다르면 주문, idempotency, 포지션 조회, 체결 대조, session 저장, 승격 증거 수집 전에 fail-closed로 차단합니다. target-weight 전용 실행 어댑터는 후보명 접두어와 무관하게 승인 snapshot을 요구합니다. no-order `--readiness-audit`와 `--daily-ops-summary`도 실행일 check, 장 시간 check, 승인 snapshot check를 blocker로 표시해 오래되었거나 장 외 실행 명령을 READY 상태로 노출하지 않습니다. pilot entry가 막혀 아직 승인 snapshot이나 장 시간 검사가 실행되지 않은 경우 Markdown과 JSON risk snapshot은 `NOT CHECKED`로 표시해 미검사를 통과로 오해하지 않게 합니다. - -Target-weight 가격 최신성 guard: 목표비중 plan 생성 시 종목별 마지막 실제 종가 날짜(`price_last_dates`)와 벤치마크 최신 날짜(`benchmark_last_date`)를 diagnostics에 남깁니다. 운영 주문 계획에서는 ffill로 보정된 낡은 종목 가격이 `trade_day` 최신 가격처럼 쓰이면 `target_weight_stale_price_data`로 차단하고, `benchmark_excess` 점수나 benchmark risk overlay에 필요한 벤치마크 가격이 score day보다 오래되면 `target_weight_benchmark_price_stale`로 plan 생성 자체를 중단합니다. `--readiness-audit`와 `--daily-ops-summary`는 같은 diagnostics를 `data_quality_check`/Data Quality 섹션으로 다시 노출해 가격 최신성 진단 누락, stale 종목, 벤치마크 지연을 READY 상태로 숨기지 않습니다. research target-weight backtest는 원본 close/open panel을 보존하되, score day 실제 종가가 없는 종목은 후보에서 제외하고 `stale_score_symbols_*`로 기록합니다. 보유 종목의 일별 stale 평가는 `held_stale_valuation_*`로 남기며, 보유 종목의 리밸런싱일 시가가 없으면 거래 없이 `missing_held_open_*`/`skipped_rebalance_missing_held_open_count`로 기록합니다. 벤치마크 stale과 신규 top-N 매수 후보의 시가 누락은 계속 fail-closed로 중단해 stale 가격이나 당일 종가 체결 착시가 pilot 흐름에 섞이지 않게 합니다. - -Target-weight daily ops 실행 최신성 guard: `READY_TO_EXECUTE`, `READY_TO_ENABLE_CAPS`, `WAITING_FOR_MARKET_SESSION` summary는 `trade_day`가 현재 KST 날짜와 맞아야 할 뿐 아니라 `generated_at`도 현재일 30분 이내여야 합니다. 누락, 형식 오류, 미래 시각, 30분 초과 요약은 `paper_pilot_control.py --status`와 current blockers runbook에서 실행/승인 명령을 `# blocked:`로 바꾸고 daily ops summary 재생성을 요구합니다. - -Earnings filter fail-closed: `trading.skip_earnings_days`가 0보다 크면 신규 BUY 전에 yfinance와 DART 순서로 실적일을 확인합니다. 두 소스 모두 실적일을 확인하지 못하면 기본 `trading.earnings_filter_unknown_policy=block` 정책으로 매수를 차단합니다. 조회 불가 상태를 감수하고 운용하려면 `earnings_filter_unknown_policy: "allow"`를 명시해야 합니다. - -Paper trade quality gate: `tools/paper_trade_quality_report.py`는 체결 품질이 review이거나 선택 기간의 `TradeHistory`가 0건이면 기본 non-zero로 종료합니다. 체결 0건 리포트만 생성하려면 `--allow-no-trades`를 명시합니다. promotion package도 같은 기간 `trade_quality_status=no_trades`를 `fill_quality_no_trades` blocker로 반영하고, `execution_session_id`/`order_id`가 비어 있거나 evidence의 `total_trades` 및 BUY/SELL 합계가 `TradeHistory`와 다르면 `fill_quality_execution_link_missing`/`fill_quality_trade_count_mismatch`/`fill_quality_trade_action_mismatch`로 차단합니다. `TradeHistory.action`이 BUY/SELL이 아닌 경우도 `fill_quality_unknown_trade_action`으로 fail-closed 처리합니다. promotion engine 역시 live 후보 판정에서 `paper_trade_quality_status=ok`를 재확인합니다. - -Target-weight 승격 증거 보강: target-weight 계열 전략은 일반 `execution_backed=True` paper record만으로 promotion evidence day를 채우지 않습니다. `pilot_paper`/authorized record가 target-weight plan과 execution proof를 포함하고, record date와 plan trade day가 일치하며, `execution_trade_day_allowed=True`, `execution_market_session_allowed=True`, `pilot_authorization_snapshot_allowed=True`, liquidity/pre-trade risk/order result/fill/position reconciliation complete 및 plan/execution params hash 일치를 만족한 날만 승격 카운트에 들어갑니다. 60영업일 전체 verified pilot evidence는 하나의 params hash로 고정되어야 하며, promotion package 생성과 promotion/live gate는 canonical `strategy_specs`의 `base_strategy=target_weight_rotation`을 우선 기준으로 target-weight 후보를 식별합니다. paper evidence package도 canonical params hash와 pilot proof가 맞지 않으면 `BLOCKED`로 남기며, live gate도 canonical metadata의 params hash와 paper evidence params hash가 다르면 target-weight live 전환을 차단합니다. +## English Summary -Target-weight finalize 성과 진단: `--finalize-pilot-evidence`가 `total_value`/`daily_return` 미확정으로 막히면 finalize report에 source record 보유 성과 필드, portfolio metrics probe 수행 여부, probe에서 확인된 필드, 최종 누락 필드를 기록합니다. current blockers와 `paper_pilot_control.py --status`도 이 정보를 노출해, 성과 snapshot 대기인지 benchmark 대기인지 운영자가 구분할 수 있게 합니다. `daily_return`/`portfolio_value`/benchmark excess 누락처럼 finalize로 promotable record가 될 수 있는 사유는 repair보다 finalize를 먼저 안내합니다. +`quant_trader` is a risk-first algorithmic trading project for Korean equities: backtesting, paper trading with a 60-trading-day promotion track record, KIS API integration, TWR-based deposit accounting, and live-trading gates that fail closed. After months of systematic alpha research concluded honestly in "no edge at this scale", the deployed strategy is structural: diversified beta + parking idle cash in a CD-rate ETF + cost minimization + monthly contributions. diff --git a/docs/RESEARCH_LOG.md b/docs/RESEARCH_LOG.md new file mode 100644 index 00000000..e936c598 --- /dev/null +++ b/docs/RESEARCH_LOG.md @@ -0,0 +1,227 @@ +# 연구·운영 로그 (아카이브) + +README에 쌓이던 상세 이력을 이 파일로 옮겼다. 이 문서는 시간순 기록 원본이라 길고, +요약된 현재 결론은 [README](../README.md)와 +[PROFITABILITY_FINDINGS](PROFITABILITY_FINDINGS.md)를 먼저 보는 편이 낫다. + +- 능동 alpha 탐색은 2026-04~05에 걸쳐 candidate family 단위로 체계적으로 수행했고, + 전부 승격 게이트(벤치마크 초과·MDD·회전율)를 넘지 못했다 — 그 실패 기록 전체가 여기 있다. +- 2026-06 이후 운영은 분산 베타(바스켓) + 소액 적립 트랙으로 전환됐다. + +--- + +## 운영 상태 스냅샷 (README 이관분, 최신이 위) + +> **현재 상태 (2026-06-10)**: +> - **바스켓 paper 운영 개시**: `kr_diversified_hold`(분산 대형주 buy&hold, 주식 80%/현금 20%) enabled — 일일 자동 사이클(리밸런싱 → NAV 스냅샷 → DB 백업 → 승격 진행률 보고)로 60영업일 트랙레코드 축적 중. `--mode health`가 사이클 끊김(스냅샷 4일+)을 자동 경고 +> - **paper→live 승격 기준 정의**(`docs/BASKET_PAPER_EVALUATION.md`): 60영업일·스냅샷 커버리지 ≥95%·dead-letter 0건·비용 드래그 ≤1%/년. 베타 전략이므로 "시장 초과수익"은 기준이 아님. `tools/basket_paper_evaluation.py`로 자동 판정(WAIT/PASS_CANDIDATE/FAIL_REVIEW) +> - **바스켓 전용 live gate 분기**: 기존 게이트가 바스켓에 신호 전략용 canonical promotion(벤치마크 초과수익 포함)을 요구해 영구 통과 불가였던 함정 해소 — paper 평가 PASS_CANDIDATE 기반으로 실제로 열리는 경로 확립. 전환 절차는 `docs/BASKET_LIVE_RUNBOOK.md`(모의서버 리허설 → 실계좌 소액 → 목표 자본) +> - live 바스켓 리밸런싱 실행 가능화: BUY가 "paper-only" 차단으로 항상 실패해 SELL-only 현금화가 되던 버그 수정(일반 매수와 동일한 집행 안전장치 + live gate 검사 승계), 회전율 예산 SELL 우선·부분 실행, KIS 잔고 미확인 시 사이징 fail-closed +> - 주문 응답 유실 시 재시도 래퍼가 재전송하던 잔여 구멍 폐쇄(`KISOrderResponseUnknown` → reconcile 대기), 손절가 하한 클램프, 히스터리시스 SELL 해제 부호 수정, 내장 지표 폴백 pandas-ta 패리티(RSI ~12pt 오차 정정) +> - 트랙레코드 도구가 현금 배분(`target_stock_weight`·`min_cash_ratio`)을 무시해 수익·MDD를 과대 보고하던 문제 정정 — 배포 바스켓 정직 수치(2021-12~2026-06): **CAGR +26.9%, Sharpe 1.09, MDD −21.4%** +> - 인자 없이 실행 시 백테스트 자동실행 대신 사용 가이드 출력(`--mode guide`), 운영 문서 v6.1 갱신 +> +> **이전 상태 (2026-06-01)**: +> - 적대적 코드 감사로 실거래 안전 버그 정리: KIS 주문이 응답 유실 시 재전송돼 이중 체결되던 경로를 `idempotent=False`로 차단(최대 1회 제출), 서킷 브레이커 재시도 루프 내 재확인, 429 Retry-After HTTP-date 크래시 가드 +> - 부분 익절이 매 모니터링 사이클마다 재발동되던 버그 수정(`Position.partial_tp_done` 영속화), 스케줄러 진입 예외 시 손절/익절 스킵 방지, 일일 손실 한도 기준값에 당일 스냅샷 사용 차단 +> - 라이브 게이트 fail-closed 보강: NaN/Inf 지표가 임계값 비교를 통과하던 구멍을 막아(`_as_float` 비유한값 → None) 손상된 지표로 라이브 승격되는 것을 차단 +> - 수익성 검증 정직화: canonical 유니버스 생존자 편향 차단 + Deflated/Probabilistic Sharpe + 진짜 OOS holdout(`--oos-holdout-split`)로 다중검정·선택 과적합 노출 +> - 지표 엔진 ADX +DI/-DI 매핑 오류, 백테스트 gross_return 비용 환원 오류, 거래량비율 inf 가드 수정 +> +> **이전 상태 (2026-05-14)**: +> - GitHub 원격 브랜치 정리 완료: 완료 브랜치 삭제, 활성 PR 브랜치만 유지 +> - 60영업일 Paper 실험 freeze pack 병합: `reports/experiment_freeze_pack.md`, 일/주간 ops checklist, stop condition 문서 추가 +> - Paper Evidence 런타임: v2 일별 자동 수집 → benchmark finalization → 날짜순 canonical evidence → promotion package → launch readiness +> - Paper Runtime State Machine: normal/degraded/frozen/blocked_insufficient_evidence 상태 자동 전환 + allowed_actions 제어 +> - Paper 운영 CLI는 pilot 제어/런타임 상태 액션 옵션을 한 번에 하나만 받도록 정리되어, 상충 액션 동시 지정 시 상태 변경 전에 fail-closed로 종료 +> - Paper Pilot Authorization: blocked 상태에서도 제한적 real paper 가능 (수동 승인 + 리스크 캡 + fail-closed/audited entry guard) +> - Paper 신규 진입 실행 경계 fail-closed: preflight 상태 누락/손상 또는 runtime 조회 실패 시 BUY 제출 전 차단, SELL 청산은 유지 +> - Paper 바스켓 리밸런싱 BUY 실행은 실제 존재하는 포트폴리오 자본/현금 조회 API를 사용하도록 고정되어 주문 실행 전 단계 오류를 회귀 테스트로 차단 +> - `--mode paper` 단발 실행은 설정 파일의 `trading.mode=live`가 남아 있어도 해당 실행을 paper로 고정해 실주문 경로 진입을 차단 +> - 장중 auto-entry 후보는 주문 직전 현재 데이터로 BUY 시그널을 재검증하며, 데이터/API/전략 계산 오류로 재검증에 실패하면 해당 루프 주문을 보류하고 후보를 다음 루프로 넘김 +> - `QUANT_AUTO_ENTRY` 해석 단일화: YAML hash와 resolved hash를 분리해 실험 설정 drift 감지 +> - Research sweep: 기존 top-20 all-family 후보 재검증도 `NO_ALPHA_CANDIDATE`; `pullback`, benchmark-relative momentum, risk-budget, cash-switch, benchmark-aware rotation, target-weight top-N rotation/score-floor 후보군과 exposure-matched benchmark 진단을 research-only로 추가 +> - target-weight 리스크 완화 top-200 sweep: 최상위 tolerance 후보도 수익/초과수익은 개선됐지만 MDD·회전율 게이트 미통과로 전 후보 `paper_only` +> - target-weight 저회전 top-200 sweep: 격월/분기 후보가 회전율은 낮췄지만 benchmark excess Sharpe와 MDD 게이트 미통과로 `NO_ALPHA_CANDIDATE` +> - target-weight 변동성 타깃 top-200 sweep: 최상위 후보는 초과수익이 양수였지만 전 후보가 MDD 게이트 미통과로 `KEEP_RESEARCH_ONLY` +> - target-weight 리스크 페널티 랭킹 top-200 sweep: 수익·초과수익은 개선됐지만 MDD·회전율 게이트 미통과로 `KEEP_RESEARCH_ONLY` +> - target-weight 손실방어 top-200 sweep: pdd8/floor25/cooldown1 후보가 MDD -19.56%, turnover/year 296.4%로 개선되어 research sweep 기준 `RUN_CANONICAL_EVALUATION` +> - target-weight 손실방어 canonical 평가: pdd8 후보는 benchmark excess -6.46%p, exp75 원본/tol3 후보는 turnover/year 1026.3%/1009.0%, exp75 tol4 후보는 turnover/year 986.5%로 회전율은 통과했지만 MDD -20.25%라 모두 `paper_only`; tol4 guard-only, rank-risk 강화, sector cap 2, corrcap85, corrpen 후보도 canonical 신규 승격 없음. 손실 후 재진입 제한 후보는 top-200에서 MDD 악화로 canonical 제외. 기존 risk-overlay 후보만 provisional 유지 +> - target-weight 변동성 예산 canonical 평가: top-200에서는 후보 2개가 통과했지만 canonical top-20에서는 MDD -20.82%/-20.89%로 `paper_only`; 초과수익·회전율은 개선됐으나 live/paper 신규 전환은 금지 +> - target-weight 포지션 손실 감산 canonical 평가: `tol5+sectorcap2+posloss8` 후보가 return +198.15%, MDD -17.18%, turnover/year 993.9%로 신규 `provisional_paper_candidate`; pilot plan은 rank penalty/sector cap/포지션 손실 감산과 paper evidence 기반 portfolio drawdown guard 상태를 재현하며, 명시 상태가 없으면 fail-closed 차단 +> - scoring: **paper_only** (관찰 가능하지만 Sharpe/PF/WF 안정성 미달) +> - rotation: **provisional_paper_candidate** (risk-adjusted 기준 통과, live alpha는 미확인) +> - target-weight risk overlay 후보: canonical bundle 기준 **provisional_paper_candidate** + 전용 paper/pilot adapter/shadow proof, 유동성/비용 pre-trade/pilot 승인/실행일/장 시간/가격 최신성 guard 추가. 리서치 백테스트는 직전 거래일 점수 → 다음 거래일 시가 체결 → 종가 평가 기준으로 보수화했으며, 기존 target-weight research artifact는 execution price mode 확인 또는 재생성 후 사용 (live 미연결) +> - target-weight capped pilot readiness: audit 시작 시 paper preflight를 먼저 갱신하고, Discord webhook 누락 또는 notifier 비정상 상태는 주문 전 `BLOCKED`로 확정 +> - live 보류 주문 복구는 KIS 미체결 목록에서 사라진 주문도 체결 상세가 확인될 때만 `RECONCILED`로 닫고, 조회 실패/불명확 상태는 열린 주문과 중복 차단을 유지 +> - live 체결 조회는 주문번호가 현재 주문과 일치하는 체결 row만 확정 체결로 인정 +> - KIS Circuit Breaker는 장애 후 HALF_OPEN 복구 상태에서 단일 probe만 허용하고 결과 확인 전 추가 요청을 차단 +> - 신규 BUY 주문 직전 유동성 재검증은 평균 거래량 누락/0도 fail-closed로 차단해 소형주·데이터 공백 주문을 막음 +> - 시장 국면 필터가 켜져 있는데 지수 데이터 조회·MA 계산이 불명확하면 `unknown` 국면으로 신규 BUY를 차단 +> - 갭업 추격매수 방지 가드는 최근 가격 조회 실패·데이터 부족도 신규 BUY 차단으로 처리 +> - 상관관계 리스크 확인은 가격 데이터 조회 실패·부족 시 신규 BUY를 차단 +> - 업종 비중 cap은 섹터 맵 조회 실패·매핑 누락 시 신규 BUY를 차단 +> - 주문/exit 판단 가격이 0·NaN·누락이면 BUY/SELL 제출과 손절/익절/블랙스완 판단을 보류하고 차단 이벤트를 기록 +> - live 바스켓 리밸런싱 주문은 CLI와 스케줄러 자동 체크 모두 바스켓별 `basket_rebalance:` canonical live gate/account/order tag 일치 없이는 실행 차단 +> - manual paper evidence single-day/backfill/finalize는 `backfill` provenance로 기록되어 승격 증거에서 제외 +> - shadow bootstrap collect/finalize는 `shadow_bootstrap` provenance로 기록되어 주문 기반 승격 증거와 분리 +> - live candidate: 없음. `--force-live` 제거, hard gate 우회 불가 + +--- + +## 전략 상태·승격 체계·research sweep 이력 + +## 전략 상태 + +승격 규칙 v3 — `core/promotion_engine.py`에서 metrics 기반 자동 판정. `tools/evaluate_and_promote.py --canonical`로 재현하며, canonical 평가 산출물에는 종목군 구성, 데이터 범위, 수집 오류를 바탕으로 만든 `data_snapshot_hash`를 남긴다. live gate와 승격 산출물 로더는 이 해시, 데이터 범위, 수집 오류, 평가 실패 상태를 다시 검증하고, live 진입 직전에 promotion engine 판정을 재계산해 손상되거나 현재 규칙과 어긋난 산출물 사용을 차단한다. +Research candidate sweep — `tools/research_candidate_sweep.py --quick --candidate-family all`로 promotion과 분리된 rotation/momentum/breakout/pullback/benchmark-relative/risk-budget/cash-switch/benchmark-aware rotation/target-weight top-N rotation 후보 랭킹 artifact를 생성. Raw EW B&H gate는 유지하되, defensive/cash-heavy 후보 해석을 위해 평균 노출률과 exposure-matched B&H excess도 진단값으로 기록합니다. 벤치마크 universe 일부 종목이라도 OHLCV 수집·기간 검증에 실패하면 `INSUFFICIENT_BENCHMARK_DATA`로 fail-closed 차단하고, 결측 종목과 커버리지 비율을 artifact/Markdown에 남깁니다. `--top-n 200`처럼 넓은 유니버스 검증을 요청하면 기본 후보 스캔 범위를 `max(100, top_n*2)`로 넓히며, 필요 시 `--universe-scan-limit`으로 명시 조정할 수 있습니다. 기존 best 후보만 빠르게 재검증할 때는 `--candidate-id`를 반복 또는 쉼표 구분으로 넘겨 선택한 후보만 평가합니다. top-200 follow-up처럼 MDD/회전율 완화 후보만 비교할 때는 `--candidate-family target_weight_risk_relief`로 risk-off/tolerance/exposure 축소 후보군을 바로 실행하고, 리밸런싱 빈도 완화 후보만 볼 때는 `--candidate-family target_weight_turnover_relief`로 격월/분기 후보군을 실행합니다. 손실 구간 노출을 더 직접 낮추는 후보는 `--candidate-family target_weight_volatility_target`로 benchmark 실현 변동성 타깃과 낙폭 floor를 적용해 검증합니다. 종목 선택 자체를 보수화하는 후속 비교는 `--candidate-family target_weight_downside_rank_relief`로 낙폭·하방변동성 페널티를 랭킹 점수에 반영해 검증합니다. 랭킹 페널티 기반 후보의 교체 빈도를 줄이는 후속 비교는 `--candidate-family target_weight_churn_relief`로 격월 리밸런싱과 리밸런싱당 신규 편입 수 상한(`max_new_targets_per_rebalance`)을 결합해 검증합니다. 포트폴리오 손실 구간 자체를 줄이는 후속 비교는 `--candidate-family target_weight_drawdown_guard`로 직전 평가 NAV 기반 drawdown guard와 재진입 cooldown을 결합해 검증합니다. 선정 종목의 목표 금액 자체를 변동성 예산형으로 조정하는 후속 비교는 `--candidate-family target_weight_volatility_budget`로 역변동성 sleeve weighting을 적용합니다. target-weight 후보 평가는 같은 sweep 안에서 OHLCV fetch cache를 공유해 동일 종목·기간 반복 수집을 줄이고, cache hit/unique fetch 진단값을 artifact와 Markdown에 남깁니다. sweep artifact와 Markdown에는 선택 방식, 요청 top-N, 스캔 한도, 후보 필터, 유동성 필터 전후 종목 수, 후보별 탈락 사유와 반복 병목 요약을 남겨 실제 검증 범위와 게이트 병목을 확인합니다. target-weight 후보는 `min_score_floor_pct`로 약한 초과 모멘텀 슬롯을 현금으로 남기고, `hold_rank_buffer`와 `max_new_targets_per_rebalance`로 랭킹 흔들림에 따른 불필요한 교체와 신규 편입 수를 줄이며, `target_allocation_mode=inverse_volatility`로 선택 종목별 목표 금액을 rolling 변동성의 역수 기준으로 나누고, `portfolio_drawdown_guard_trigger_pct`/`portfolio_drawdown_guard_exposure`/`portfolio_drawdown_guard_cooldown_rebalances`로 포트폴리오 낙폭 후 목표 노출과 재진입 속도를 제한하고, `market_exposure_mode=benchmark_risk`로 KS11 SMA/낙폭/변동성 risk-off 구간의 부분 노출 축소를 검증합니다. canonical risk-overlay 후보에는 `target_tolerance_pct=3/5` turnover-aware 변형도 포함해 작은 리밸런싱 생략이 비용과 성과에 주는 영향을 비교합니다. target-weight 리서치 백테스트는 직전 거래일 점수로 다음 거래일 시가에 리밸런싱하고 일말 평가는 종가로 하며, 전일 기준 20일 평균 거래량을 비용 계산에 넘겨 동적 슬리피지와 participation 진단값을 남깁니다. +Paper Evidence 체계 — `core/paper_evidence.py` v2 일별 22개 지표 자동 수집, `core/paper_runtime.py` entry gate, `core/paper_pilot.py` launch readiness/pilot auth 판정. scheduler는 v2 collector만 canonical 증거로 기록하며, legacy `core/evidence_collector.py`는 import 호환용 deprecated no-op입니다. 승격 패키지는 `execution_backed=True`와 `real_paper`/`pilot_paper` 출처가 명시된 기록만 승격 증거로 인정하고, 패키지 내부 `strategy`가 현재 전략명과 정확히 일치하지 않으면 승격/라이브 증거로 쓰지 않아 예전 형식·수작업 기록 오염을 차단한다. + +2026-04-29 all-family quick sweep: 5종목(`005930,000660,035720,051910,068270`)에서 rotation/momentum/breakout 후보 14개를 비교했지만 모두 benchmark excess return/Sharpe를 통과하지 못해 `NO_ALPHA_CANDIDATE`로 판정. 이 결과만으로 canonical promotion이나 paper/live 승격은 진행하지 않습니다. + +2026-04-30 top-20 all-family quick sweep: canonical liquidity universe 20종목에서 동일 후보 14개를 재검증했지만 `NO_ALPHA_CANDIDATE` 유지. best=`momentum_factor_120d`는 return +118.56%, Sharpe 0.79였으나 benchmark excess=-30.83%p, MDD=-40.08%로 승격 불가. 다음 연구는 단순 후보 확장이 아니라 benchmark를 이기는 새로운 alpha 후보군 설계로 전환합니다. + +2026-04-30 follow-up: 기존 전략 중 외부 재무 데이터 의존이 없는 `trend_pullback`을 `pullback` candidate family로 추가했습니다. 또한 기존 실패 원인(절대수익은 높지만 benchmark에 뒤처짐)을 직접 겨냥하기 위해 `momentum_factor`에 KS11 대비 초과 모멘텀/변동성 게이트 옵션을 추가하고 `benchmark_relative` candidate family로 노출했습니다. `all` sweep은 이제 rotation/momentum/breakout/pullback/benchmark-relative 후보군을 함께 평가합니다. + +2026-04-30 5-symbol smoke sweep: 신규 `benchmark_relative` 3개와 `pullback` 4개 모두 `NO_ALPHA_CANDIDATE`. best=`benchmark_relative_momentum_60d` return +4.13%, excess=-169.50%p; best pullback=`trend_pullback_aggressive` return +3.04%, excess=-170.59%p. 두 후보군은 계속 research-only입니다. + +2026-04-30 follow-up: 신호 필터만 추가하는 방향이 약하다고 판단해 `risk_budget` candidate family를 추가했습니다. 동일 신호를 집중형/균형형/방어형 exposure budget으로 나눠 평가하고, 각 후보 artifact에 적용된 `diversification` 설정을 기록합니다. + +2026-04-30 risk-budget smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best return=`risk_budget_momentum_120d_concentrated` +11.40%, excess=-162.23%p, MDD=-32.28%; best risk-adjusted=`risk_budget_rotation_slow_defensive` +10.91%, excess=-162.72%p, MDD=-6.41%. 방어형 exposure는 낙폭을 줄였지만 alpha 자체는 아직 없습니다. + +2026-04-30 follow-up: 방어형 exposure만으로 benchmark를 이기지 못해 `relative_strength_rotation`에 `market_filter_exit` 옵션을 추가하고 `cash_switch` candidate family를 추가했습니다. KS11이 이동평균 아래로 내려가면 신규 진입 차단을 넘어 기존 포지션도 현금화하는 구조입니다. + +2026-04-30 cash-switch smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`cash_switch_rotation_slow_defensive` return +1.87%, excess=-171.76%p, Sharpe=-0.40, MDD=-11.78%. 현금 전환은 손실 방어에는 일부 유효했지만 benchmark 대비 alpha는 만들지 못해 research-only로 유지합니다. + +2026-04-30 follow-up: `research_candidate_sweep`에 exposure-matched benchmark diagnostics를 추가했습니다. cash-switch 후보의 평균 노출은 8.4~10.0%에 불과했고 exposure-matched excess도 -7.87%p~-0.36%p로 음수였습니다. 즉 raw benchmark gap은 낮은 노출 영향이 크지만, 같은 노출로 비교해도 신호 edge가 아직 없어 다음 연구는 단순 현금화보다 benchmark-aware 랭킹/부분 헤지/노출 유지형 alpha 설계가 우선입니다. + +2026-04-30 follow-up: `relative_strength_rotation`에 benchmark-aware ranked mode를 추가했습니다. `score_mode=benchmark_excess`는 종목 60/120d 복합 모멘텀에서 KS11 복합 모멘텀을 차감해 랭킹하고, `rank_entry_mode=dense_ranked`/`exit_rebalance_mode=score_floor`로 절대 추세 필터 때문에 과도하게 현금화되는 문제를 research-only로 분리 검증합니다. + +2026-05-08 follow-up: `backtest/portfolio_backtester.py`에도 gap-up 신규 매수 차단, gap-down `GAP_DOWN` 청산, 어닝 윈도우 신규 매수 차단, BlackSwan 청산·쿨다운·recovery 사이징을 반영했습니다. 단일종목/포트폴리오 백테스트가 같은 리스크 이벤트 전제로 비교되도록 `gap_*`, `earnings_*`, `blackswan_*` 진단 카운터와 회귀 테스트를 보강했습니다. + +2026-05-08 follow-up: `WatchlistManager.liquidity_filter_report()`를 공통 진단 API로 분리하고, 포트폴리오 백테스트와 `tools/research_candidate_sweep.py`가 평가 시작일 기준 20일 평균 거래대금 하한 미만 또는 strict 모드 데이터 누락 종목을 universe에서 먼저 제외하도록 보강했습니다. research artifact에는 입력 universe, 필터 통과 universe, 제외 종목/사유가 함께 남습니다. + +2026-05-08 follow-up: `PortfolioBacktester`도 단일종목 백테스터처럼 종목별 20일 평균 거래량을 `RiskManager.calculate_transaction_costs()`에 전달해 동적 슬리피지 배수를 반영합니다. 포트폴리오 거래 기록에는 `participation_rate`, `slippage_multiplier`, `slippage_cost`가 남아 거래비용 과소추정을 점검할 수 있습니다. + +2026-05-12 follow-up: 단일/포트폴리오 백테스트 리포트에 비용 전/후 성과 비교를 자동 노출합니다. `backtest.cost_impact`가 수수료·세금·슬리피지를 표준 집계해 비용 차감 전 추정 수익률, 비용 드래그(bp), 비용/순손익, cost impact status를 남기며, 비용 때문에 gross profit이 net loss로 뒤집히거나 비용이 순이익을 초과하면 운영 검토 신호로 표시합니다. + +2026-05-08 follow-up: `tools/research_candidate_sweep.py`의 target-weight 리서치 백테스트도 OHLCV `volume`으로 종목별 20일 평균 거래량을 계산해 매수·매도 비용에 전달합니다. target-weight trade와 metrics에는 `avg_daily_volume`, `participation_rate`, `slippage_multiplier`, `slippage_cost_total`이 남아 high-turnover 후보의 비용 과소추정을 더 빨리 확인할 수 있습니다. + +2026-05-12 follow-up: target-weight research sweep에 canonical risk-overlay 후보의 `target_tolerance_pct=3/5` 변형을 추가했습니다. 리서치 metrics에는 `rebalance_tolerance_pct`, `rebalance_tolerance_skipped_trades`, `rebalance_tolerance_skipped_notional`을 남겨 넓은 tolerance가 실제로 turnover를 줄였는지 artifact에서 바로 확인할 수 있습니다. + +2026-05-08 follow-up: `research_candidate_sweep`의 EW B&H 벤치마크가 일부 종목 결측 상태에서 전체 capital 대비 낮게 계산되어 후보 초과수익이 과대평가되는 경로를 차단했습니다. 벤치마크 입력 universe 전체가 수집·검증되지 않으면 excess gate와 decision action은 `INSUFFICIENT_BENCHMARK_DATA`로 고정됩니다. + +2026-05-08 follow-up: `OrderExecutor` live BUY/SELL은 주문 ACK 이후 체결가·체결수량 확인이 되지 않거나 부분체결만 확인되면 더 이상 예상가 기준 전량 FILLED로 처리하지 않습니다. 이때 `success=False`는 브로커 주문 부재가 아니라 `order_pending=True`/`requires_reconcile=True`인 장부 반영 보류 상태이며, KIS 잔고 대조 전 DB 포지션·거래 기록 오염을 막습니다. + +2026-05-11 follow-up: paper BUY 수량·손절·익절·트레일링 기준을 예상 체결가로 보수화하고, paper SELL도 매수처럼 모델 슬리피지를 체결가에 반영합니다. `TradeHistory.price`는 실제 체결가로 보고, `get_trade_cash_summary()`는 슬리피지를 진단값으로 집계하되 현금 흐름에서는 수수료·세금만 별도 차감해 체결가에 이미 들어간 비용이 중복 반영되지 않게 했습니다. + +2026-05-08 follow-up: KIS 미체결 조회 실패를 더 이상 “미체결 없음”으로 해석하지 않습니다. live BUY/SELL은 주문 전 미체결 조회가 실패하거나 응답 형식이 불명확하면 `live_unfilled_check.checked=False`로 주문을 보류하고, 재시작 복구에서도 KIS 미체결 조회 실패를 별도 critical 알림으로 드러냅니다. + +2026-05-14 follow-up: 호환용 `has_unfilled_orders()`도 미체결 조회 실패를 `False`로 감추지 않고 미체결 있음으로 간주합니다. live 신규 주문이 상태형 API를 우회하는 레거시 경로로 들어와도 중복 주문 위험은 fail-closed로 처리됩니다. + +2026-05-14 follow-up: `OrderExecutor` live BUY 직접 호출 우회를 막았습니다. `run_live_trading()` 또는 live 리밸런싱처럼 readiness gate를 통과한 경로만 `live_gate_validated=True`를 전달하며, 기본값은 fail-closed라 수동 스크립트/콘솔에서 executor를 직접 만들어도 신규 BUY는 KIS 주문 전에 차단됩니다. SELL은 긴급 청산 안전성을 위해 기존 실행 경로를 유지합니다. + +2026-05-14 follow-up: 바스켓 리밸런싱 paper BUY 실행 경로를 복구했습니다. `BasketRebalancer.execute()`가 없는 포트폴리오 메서드를 호출하던 부분을 `get_current_capital()`/`get_available_cash()` 기준으로 정리했고, 실제 주문 실행 전 자본·현금 값이 `OrderExecutor.execute_buy_quantity()`에 전달되는지 회귀 테스트로 고정했습니다. + +2026-05-14 follow-up: 시장 국면 필터를 fail-closed로 보강했습니다. `market_regime_filter=true` 상태에서 지수 데이터 조회 실패, 빈 데이터, MA 계산 실패가 발생하면 더 이상 bullish 기본값으로 넘기지 않고 `regime=unknown`, `allow_buys=false`, `position_scale=0.0`으로 신규 BUY를 차단합니다. 직접 `OrderExecutor.execute_buy()`를 호출하는 경로도 같은 결과를 받으면 주문 전 중단합니다. + +2026-05-14 follow-up: KIS Circuit Breaker의 `HALF_OPEN` 복구 상태를 단일 probe 방식으로 제한했습니다. OPEN 쿨다운 후 첫 요청 하나만 통과시키고, `on_success()` 또는 `on_failure()`가 들어오기 전까지 추가 요청은 차단해 장애 회복 직후 요청이 한꺼번에 풀리지 않게 했습니다. + +2026-05-14 follow-up: paper promotion evidence package에 `package_integrity.payload_hash`와 `source_records.records_hash`를 추가했습니다. live gate는 패키지 payload hash, 원본 daily evidence JSONL에서 재계산한 source record hash, record count/date 범위를 모두 비교해 패키지 요약값과 원본 evidence가 따로 변경된 상태를 live 전환 근거로 쓰지 않습니다. + +2026-05-14 follow-up: live 긴급 청산은 KIS 현재가가 없거나 0 이하이면 해당 종목을 실패 상세로 기록하고 `execute_sell`을 호출하지 않습니다. 평균단가를 현재 시장가처럼 사용한 지정가 매도 fallback을 막아 급락장 미체결 위험을 운영 알림으로 드러냅니다. + +2026-05-08 follow-up: target-weight 리서치 백테스트의 리밸런싱 체결 기준을 당일 종가에서 다음 거래일 시가로 보수화했습니다. 랭킹과 risk-off 판단은 직전 거래일 종가까지만 사용하고, 리밸런싱 체결은 해당 거래일 원본 `open`, 일말 평가는 `close`로 분리합니다. 신규 top-N 매수 후보의 리밸런싱일 `open`이 누락되면 `target_weight_research_execution_price_missing`으로 중단하고, 이미 보유한 종목의 `open`이 없으면 해당 리밸런싱을 거래 없이 skip 진단으로 남깁니다. 동적 슬리피지의 20일 평균 거래량도 당일 거래량을 포함하지 않도록 1거래일 지연합니다. + +2026-04-30 benchmark-aware rotation smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`benchmark_aware_rotation_60_120_balanced` return=+21.65%, Sharpe=0.50, avg exposure=24.1%였지만 raw excess=-151.98%p, exposure-matched excess=-16.05%p라 promotion 미진행. fast `40_100_dense`는 exposure-matched excess=+2.04%p였으나 raw excess=-163.35%p라 다음 연구 힌트로만 기록합니다. 다음 방향은 sparse BUY/SELL 신호를 넘어 monthly top-N 목표비중 리밸런싱을 별도 백테스터로 검증하는 것입니다. + +2026-04-30 target-weight top-N rotation smoke sweep: 5종목 기준 `NO_ALPHA_CANDIDATE`. best=`target_weight_rotation_top3_40_100_excess` return=+128.44%, Sharpe=1.13, avg exposure=85.3%로 노출 부족 문제는 해결했지만 raw excess=-45.19%p, exposure-matched excess=-14.82%p라 promotion 미진행. 이 결과는 sparse 신호가 병목이었음을 확인했지만, 동일 유니버스 B&H를 이기는 alpha는 아직 아니라서 다음 연구는 더 넓은 유니버스와 부분 hedge/상대강도 필터 개선입니다. + +2026-04-30 canonical top-20 target-weight full sweep: 기존 `target_weight_rotation_top5_60_120_floor0_hold3`는 return=+278.57%, raw excess=+129.18%p, exposure-matched excess=+150.88%p, Sharpe=1.65, WF positive/Sh+ 100%, turnover/year=807.8%였지만 MDD=-28.25%로 `paper_only`. benchmark-risk overlay 추가 후 best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+210.24%, raw excess=+60.85%p, exposure-matched excess=+130.96%p, Sharpe=1.60, PF=5.73, MDD=-19.24%, turnover/year=858.0%, WF positive/Sh+ 100%, risk-off rebalance=38.9%로 처음 `provisional_paper_candidate`에 도달했습니다. 후속으로 `tools/evaluate_and_promote.py --canonical`이 이 후보를 canonical promotion bundle에 재현하도록 연결했고, `promotion_result.json`에서도 동일 후보가 `provisional_paper_candidate`로 로드됩니다. 추가로 `tools/target_weight_rotation_pilot.py`가 portfolio-level 목표비중 plan을 만들고 pilot cap을 검증한 뒤 paper-only exact-quantity 주문을 낼 수 있게 했습니다. dry-run은 `--record-shadow-evidence`로 non-promotable `shadow_bootstrap` evidence를 남기고, 같은 실행에서 launch readiness JSON/MD와 pilot runbook을 생성합니다. session artifact에는 기본 cap preview, plan 기반 최소/추천 pilot cap, enable 명령, launch artifact 경로가 함께 기록됩니다. live 자동운영은 여전히 금지입니다. + +2026-05-11 follow-up: target-weight canonical을 next-open 체결과 결측 진단 기준으로 재검증했습니다. `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+171.20%, PF=4.24, Sharpe=1.41, MDD=-19.90%, WF positive=100%, WF Sharpe+=83.3%로 `provisional_paper_candidate`를 유지했습니다. 이번 산출물은 stale score 후보 2개 제외, held stale valuation 21일, 보유 종목 시가 누락에 따른 리밸런싱 skip 1회를 metrics에 남기므로 paper pilot에서는 체결 가능성과 stale valuation 빈도를 함께 감시해야 합니다. + +2026-05-12 top-200 target-weight follow-up: `--top-n 200 --candidate-id target_weight_rotation_top5_60_120_floor0_hold3_risk60_35` full sweep을 실행했습니다. canonical liquidity 200개 중 유동성 필터 통과 164개, benchmark coverage 100%에서 return=+110.39%, raw excess=+78.50%p, exposure-matched excess=+90.25%p, Sharpe=0.85, PF=2.06, WF positive=83.3%, WF Sharpe+=100%였지만 MDD=-25.79%, turnover/year=1097.1%로 provisional 게이트를 넘지 못해 `paper_only`입니다. 이 결과를 기준으로 alpha 존재 여부보다 drawdown과 turnover를 동시에 낮추는 리스크 완화 후보군 검증으로 이어갔습니다. + +2026-05-12 리스크 완화 top-200 follow-up: `--candidate-family target_weight_risk_relief --top-n 200` full sweep에서 10개 후보를 비교했습니다. 최상위 후보 `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5`는 return=+125.61%, raw excess=+93.72%p, exposure-matched excess=+104.53%p, avg exposure=68.5%, Sharpe=0.91, PF=2.19, WF positive=83.3%, WF Sharpe+=100%로 기존 단일 후보보다 수익성은 개선됐습니다. 다만 전체 후보가 MDD -25.27%~-32.14%, turnover/year 1027.2%~1344.3% 구간에 있어 `mdd < -20`와 `turnover_per_year >= 1000` 병목을 넘지 못했고, 판정은 `KEEP_RESEARCH_ONLY`입니다. sweep data fetch cache는 unique_fetches=1155, cache_hits=10395로 동작을 확인했습니다. 다음 연구는 리밸런싱 빈도 자체를 낮추는 격월/분기 리밸런싱, 변동성 타깃, 낙폭 차단, 회전율 패널티 랭킹을 우선합니다. + +2026-05-12 저회전 top-200 follow-up: `--candidate-family target_weight_turnover_relief --top-n 200` full sweep에서 격월/분기 리밸런싱 후보 6개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk90_35_bimonthly`는 return=+85.72%, raw excess=+53.83%p, exposure-matched excess=+68.63%p, Sharpe=0.73, PF=1.86, turnover/year=616.7%였습니다. 분기 후보의 turnover/year는 456.1~476.9%까지 내려갔지만, 전 후보가 benchmark excess Sharpe<=0 및 MDD -25.35%~-35.12%로 막혀 판정은 `NO_ALPHA_CANDIDATE`입니다. 즉 단순 리밸런싱 빈도 축소는 회전율 병목은 완화하지만 위험조정 alpha와 낙폭 문제를 해결하지 못했고, 이 판단을 바탕으로 변동성 타깃과 낙폭 차단을 실제 포지션 크기 산식에 넣는 후보군을 이어서 검증했습니다. + +2026-05-12 변동성 타깃 top-200 follow-up: `--candidate-family target_weight_volatility_target --top-n 200` full sweep에서 benchmark 실현 변동성 타깃과 drawdown floor 후보 6개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_vol16_dd8_floor35`는 return=+105.18%, raw excess=+73.29%p, exposure-matched excess=+91.35%p, avg exposure=71.7%, Sharpe=0.81, PF=2.03, turnover/year=958.0%였습니다. 다만 전 후보가 MDD -24.26%~-31.04%로 provisional MDD 게이트를 통과하지 못했고, 5개 후보는 benchmark excess Sharpe도 0 이하라 판정은 `KEEP_RESEARCH_ONLY`입니다. 변동성 기반 노출 축소만으로는 낙폭 병목이 충분히 풀리지 않았으므로 다음 연구는 목표 노출 산식보다 종목 선별 랭킹에 낙폭·하방변동성·상관/업종 집중 페널티를 넣는 방향으로 전환합니다. + +2026-05-12 리스크 페널티 랭킹 top-200 follow-up: `--candidate-family target_weight_downside_rank_relief --top-n 200` full sweep에서 낙폭·하방변동성 페널티 후보 5개를 검증했습니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60`는 return=+132.51%, raw excess=+100.62%p, exposure-matched excess=+111.43%p, Sharpe=0.94, PF=2.21, WF positive/Sh+=100%로 기존 리스크 완화 후보보다 수익성은 개선됐습니다. 다만 전 후보가 MDD -25.20%~-33.07%, turnover/year 1143.8%~1366.0%로 막혀 판정은 `KEEP_RESEARCH_ONLY`입니다. 즉 downside rank penalty는 alpha 후보성을 강화했지만, 종목 교체 빈도를 낮추지 못했습니다. 다음 연구는 이 랭킹 페널티를 격월 리밸런싱, 더 넓은 tolerance, 월별 신규 편입 수 제한 같은 churn control과 결합하는 방향입니다. + +2026-05-12 follow-up: `target_weight_churn_relief` 후보군을 추가했습니다. 직전 best였던 downside rank penalty 후보를 기준으로 `max_new_targets_per_rebalance` 신규 편입 상한, 격월 리밸런싱, 넓은 tolerance 조합을 별도 family로 분리해 top-200 full sweep에서 turnover/year와 MDD 병목이 실제로 완화되는지 확인할 수 있게 했습니다. + +2026-05-12 churn control top-200 follow-up: `--candidate-family target_weight_churn_relief --top-n 200` full sweep에서 후보 5개를 검증했고 판정은 `KEEP_RESEARCH_ONLY`입니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60_maxnew2`는 return=+118.89%, raw excess=+87.00%p, exposure-matched excess=+97.61%p, Sharpe=0.89, PF=2.04, WF positive/Sh+=100%였지만 MDD=-26.95%, turnover/year=1034.2%로 provisional 게이트를 넘지 못했습니다. `maxnew1`과 격월 후보는 turnover/year를 423.0~674.4%까지 낮췄지만 benchmark excess Sharpe와 MDD가 악화됐습니다. 다음 연구는 단순 교체 제한보다 포트폴리오 drawdown guard, 재진입 cooldown, 상관/업종 집중도 페널티처럼 손실 구간 자체를 줄이는 구조가 우선입니다. + +2026-05-12 follow-up: `target_weight_drawdown_guard` 후보군을 추가했습니다. 직전 평가 NAV 기준 포트폴리오 낙폭이 임계값을 넘으면 다음 리밸런싱 목표 노출을 floor까지 낮추고, 회복 직후에도 지정 리밸런싱 횟수만큼 cooldown을 유지합니다. 다음 검증은 `--candidate-family target_weight_drawdown_guard --top-n 200` full sweep으로 MDD가 실제로 낮아지는지 확인합니다. + +2026-05-12 손실방어 top-200 follow-up: `--candidate-family target_weight_drawdown_guard --top-n 200` full sweep에서 후보 5개를 검증했고 판정은 `RUN_CANONICAL_EVALUATION`입니다. best=`target_weight_rotation_top5_60_120_floor0_hold3_risk60_35_tol5_rankrisk60_pdd8_floor25_cd1`는 return=+99.53%, raw excess=+67.64%p, exposure-matched excess=+83.07%p, avg exposure=56.5%, Sharpe=1.02, PF=4.88, MDD=-19.56%, turnover/year=296.4%, WF positive/Sh+=66.7%였습니다. 포트폴리오 guard는 trigger 17회, guard rebalance 20회(74.1%)로 동작했습니다. 두 번째 후보 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_pdd10_floor40_cd1`도 provisional 조건을 통과했지만 turnover/year=893.4%로 비용 민감도가 더 큽니다. 나머지 3개는 MDD -20% 미만 또는 benchmark excess Sharpe<=0로 `paper_only`입니다. 다음 단계는 eligible 2개 후보를 canonical promotion evaluation에 올려 같은 기준에서 재현성을 확인하는 것이며, 이 artifact만으로 paper/live 전환은 하지 않습니다. + +2026-05-13 손실방어 canonical follow-up: `tools/evaluate_and_promote.py --canonical`에 eligible 2개 손실방어 후보를 포함해 재평가했습니다. 기존 `target_weight_rotation_top5_60_120_floor0_hold3_risk60_35`는 return=+171.20%, benchmark excess=+21.81%p, Sharpe=1.41, PF=4.24, MDD=-19.90%, turnover/year=794.0%, WF positive=100%, WF Sharpe+=83.3%로 `provisional_paper_candidate`를 유지했습니다. pdd8/floor25 후보는 return=+142.93%, Sharpe=1.43, PF=7.69, MDD=-15.89%, turnover/year=530.8%로 방어력은 좋아졌지만 canonical benchmark excess=-6.46%p라 `paper_only`입니다. exp75/rankrisk90/pdd10/floor40 후보는 return=+180.40%, benchmark excess=+31.01%p, Sharpe=1.49, PF=4.85, MDD=-19.24%, WF positive/Sh+=100%였지만 turnover/year=1026.3%로 게이트를 넘겨 `paper_only`입니다. 다음 연구는 exp75 계열에 tolerance 또는 신규 편입 제한을 결합해 초과수익과 WF 안정성은 유지하면서 turnover/year를 1000% 아래로 낮추는 방향입니다. + +2026-05-13 exp75 회전율 완화 follow-up: `target_weight_drawdown_guard` family에 exp75/rankrisk90/pdd10/floor40 기반 `tol3`, `tol4`, `tol5`, `maxnew2`, `tol3_maxnew2` 변형을 추가했습니다. `--candidate-family target_weight_drawdown_guard --top-n 200` 재검증에서는 후보 10개 중 4개가 canonical 평가 대상으로 남았고, 새 `tol4` 후보는 return=+81.19%, raw excess=+49.30%p, exposure-matched excess=+66.89%p, MDD=-19.33%, turnover/year=886.7%, WF positive/Sh+=100%로 research 기준을 통과했습니다. canonical 재평가에서는 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol4_pdd10_floor40_cd1`가 return=+183.02%, benchmark excess=+33.63%p, Sharpe=1.50, PF=4.82, turnover/year=986.5%로 회전율 게이트는 통과했지만 MDD=-20.25%가 -20% 제한을 0.25%p 초과해 `paper_only`입니다. tol3는 turnover/year=1009.0%, 원본은 1026.3%로 각각 회전율 미달입니다. 다음 연구는 tol4의 낮아진 회전율을 유지하면서 drawdown floor/cooldown 또는 ranking risk penalty를 더 보수화해 MDD를 -20% 안쪽으로 되돌리는 방향이며, 현재 live/paper 전환 대상은 기존 risk-overlay 후보만 유지합니다. + +2026-05-13 tol4 낙폭 완화 follow-up: tol4 기반으로 `pdd10_floor35_cd1`, `pdd8_floor40_cd1`, `pdd8_floor35_cd1`, `pdd10_floor40_cd2` 네 변형을 추가해 `target_weight_drawdown_guard --top-n 200` full sweep을 다시 실행했습니다. 후보 14개 중 신규 변형은 canonical eligible에 추가되지 않았습니다. `pdd10_floor35`는 turnover/year=829.8%로 낮아졌지만 MDD=-20.20%, `pdd8_floor35`도 MDD=-20.20%라 실패했고, `pdd8_floor40`과 `cooldown2`는 MDD/turnover는 통과권이었지만 benchmark excess Sharpe<=0으로 제외됐습니다. 결론적으로 guard trigger/floor/cooldown만 더 조이는 방식은 alpha 또는 MDD 중 하나를 잃기 쉬우므로, 다음 후보는 종목 선택 단계의 downside rank penalty/상관·업종 집중도 페널티나 비용 민감도 기준을 함께 조정하는 쪽이 우선입니다. + +2026-05-13 rank-risk 보강 follow-up: exp75 계열에 `rankrisk90_dd75`, `rankrisk120`을 추가하고 tol4+pdd10/floor40 guard와 결합했습니다. `target_weight_drawdown_guard --top-n 200` 후보 16개 재검증에서는 `rankrisk120_tol4`가 return=+84.41%, raw excess=+52.52%p, MDD=-19.30%, turnover/year=919.4%, `rankrisk90_dd75_tol4`가 return=+82.56%, raw excess=+50.67%p, MDD=-19.54%, turnover/year=896.0%로 기존 tol4보다 좋아져 canonical 평가 대상에 포함됐습니다. 하지만 canonical top-20 재평가에서는 각각 return=+125.69%/+125.99%, turnover/year=927.9%/947.6%였지만 MDD=-20.74%/-20.76%와 benchmark excess=-23.70%p/-23.40%p로 `paper_only`입니다. 패널티 강도만 키우면 top-200에서는 좋아져도 canonical 대형주 구간 alpha가 꺾이므로, 다음은 sector/correlation cap처럼 분산 제약을 선택 단계에 직접 넣는 쪽을 우선합니다. + +2026-05-13 sector cap follow-up: FDR `KRX` 목록에 업종 컬럼이 없는 환경에서도 `get_sector_map()`이 KRX KIND 상장법인 목록(2,766개 업종 매핑)으로 fallback하도록 보강하고, `target_weight_drawdown_guard --top-n 200`에 `sectorcap1/2` 후보를 추가했습니다. `sectorcap2`는 실제 섹터 맵 적용 기준으로 `rankrisk120_tol4_sectorcap2` return=+85.49%, raw excess=+53.60%p, MDD=-19.94%, turnover/year=941.5%, `rankrisk90_tol4_sectorcap2` return=+82.57%, raw excess=+50.68%p, MDD=-19.59%, turnover/year=909.3%로 canonical 평가 대상에 포함됐습니다. canonical top-20에서는 `rankrisk90_tol4_sectorcap2`가 return=+185.39%, benchmark excess=+36.00%p, turnover/year=981.0%로 원본 tol4보다 좋아졌지만 MDD=-20.22%라 `paper_only`입니다. `rankrisk120_tol4_sectorcap2`는 sector cap 적용 후에도 canonical 결과가 기존과 같아 benchmark excess=-23.70%p, MDD=-20.74%로 제외했습니다. 다음 후보는 섹터 수량 cap만으로 부족한 낙폭 0.22%p를 줄이기 위해 상관도 cap, 월별 손실 이후 재진입 제한, 변동성 예산형 target selection을 결합하는 쪽입니다. + +2026-05-18 sector map cache follow-up: `DataCollector.get_sector_map()`은 FDR/KRX KIND/pykrx 업종 매핑을 성공적으로 읽으면 `reports/sector_map_cache.json`에 schema v1 캐시를 저장하고, 실시간 소스가 모두 비어 있으면 해당 캐시를 fallback으로 사용합니다. 최신 캐시는 KRX KIND 기준 2,766개 종목이며, target-weight sector cap plan은 캐시가 있더라도 planning universe 전체 섹터가 매핑되지 않으면 `target_weight_sector_map_incomplete`로 fail-closed 차단합니다. KRX KIND가 우선주 섹터를 직접 주지 않는 경우에는 `005935` → `005930`처럼 같은 5자리 stem의 보통주 섹터를 추론하고 diagnostics의 `sector_map_inferred_symbols`에 기록합니다. no-order `--daily-ops-summary`/`--readiness-audit`는 sector map blocker뿐 아니라 FDR/yfinance/KIS fallback 비활성화로 인한 가격 수집 실패도 traceback 대신 `BLOCKED` failure artifact로 남깁니다. + +2026-05-13 correlation cap follow-up: target selection 단계에 `max_pairwise_correlation`, `correlation_lookback_days`, `correlation_min_periods`를 추가해 이미 선택된 종목과의 과거 수익률 상관이 임계값을 넘는 후보를 건너뛰도록 했습니다. `target_weight_drawdown_guard --top-n 200`에서는 `corrcap85`와 `sectorcap2_corrcap85`가 canonical 평가 대상에 포함됐고 관측 최대 선택 상관도는 0.8147로 제한됐지만, return/MDD/turnover는 각각 기존 tol4 및 sectorcap2와 동일했습니다. `corrcap80`은 관측 상관도를 0.7675까지 낮췄지만 return=+70.22%, raw excess=+38.33%, MDD=-19.33%, turnover/year=908.3%에서 benchmark excess Sharpe<=0으로 제외됐습니다. canonical top-20에서도 `corrcap85`는 MDD=-20.25%, `sectorcap2_corrcap85`는 MDD=-20.22%로 기존 후보와 같은 `paper_only`입니다. 결론적으로 0.85 단순 pairwise cap은 너무 느슨하고, 0.80 단독 cap은 alpha를 깎으므로 다음은 상관도 penalty를 랭킹 점수에 직접 섞거나 변동성 예산/손실 후 재진입 제한과 결합해야 합니다. + +2026-05-13 correlation rank penalty follow-up: 단순 hard cap 대신 리밸런싱 시점의 `score_row`에 평균 양의 상관도 기반 `correlation_rank_penalty_weight`를 차감하는 후보를 추가했습니다. top-200 `target_weight_drawdown_guard`에서는 `sectorcap2_corrpen05`가 return=+84.22%, raw excess=+52.33%p, MDD=-19.59%, turnover/year=912.5%, `corrpen10`이 return=+83.50%, raw excess=+51.61%p, MDD=-19.96%, turnover/year=892.3%, `corrpen05`가 return=+82.82%, raw excess=+50.93%p, MDD=-19.33%, turnover/year=889.9%로 모두 canonical 후보에 포함됐습니다. 하지만 canonical top-20에서는 `corrpen05` return=+141.09%, benchmark excess=-8.30%p, MDD=-20.25%, `corrpen10` return=+128.43%, benchmark excess=-20.96%p, MDD=-20.25%, `sectorcap2_corrpen05` return=+143.11%, benchmark excess=-6.28%p, MDD=-20.22%로 모두 `paper_only`입니다. 결론적으로 평균 상관도 점수 차감은 top-200에서는 분산 개선 신호가 있었지만 대형주 canonical alpha를 크게 깎아 운영 승격에는 부적합합니다. 다음은 점수 차감보다 손실 후 재진입 제한이나 변동성 예산형 target selection처럼 낙폭을 직접 겨냥하는 제약을 우선합니다. + +2026-05-13 loss re-entry guard follow-up: 직전 실행 리밸런스 후 평가 NAV 대비 손실률이 `loss_reentry_guard_trigger_pct`를 넘으면 지정 cooldown 동안 신규 편입 수를 `loss_reentry_guard_max_new_targets`로 낮추는 옵션을 추가했습니다. `target_weight_drawdown_guard --top-n 200`에 `reentry4_maxnew0_cd1`, `reentry3_maxnew0_cd1`, `sectorcap2_reentry4_maxnew0_cd1` 후보를 추가했지만, 손실 직후 새 종목 편입을 막는 방식은 회전율만 낮추고 MDD를 악화시켰습니다. `sectorcap2_reentry4`는 return=+84.68%, raw excess=+52.79%p, turnover/year=793.8%였지만 MDD=-22.34%, `reentry4`는 return=+57.71%, MDD=-27.29%, `reentry3`은 return=+28.93%, MDD=-35.30%로 모두 `paper_only`라 canonical 평가 대상에서 제외했습니다. 다음은 보유 종목 고착을 만드는 신규 편입 차단보다, 종목별 변동성 예산이나 포지션별 손절/감산처럼 손실 원인을 직접 줄이는 제약이 우선입니다. + +2026-05-13 position loss reduction follow-up: 신규 편입을 막아 손실 종목을 고착시키는 대신, 직전 확정 종가 기준 보유 종목 손실률이 `position_loss_reduce_trigger_pct`를 넘으면 다음 리밸런싱 목표 금액을 `position_loss_reduce_target_fraction`까지 감산하는 옵션을 추가했습니다. 감산 매도는 기존 비용/PnL/turnover 경로를 그대로 타며, 리밸런싱 tolerance 때문에 위험 감산이 생략되지 않도록 분리했습니다. 실데이터 `target_weight_drawdown_guard --top-n 200` full sweep에서는 `sectorcap2_posloss8` 후보가 return=+105.79%, raw excess=+73.90%p, MDD=-18.96%, Sharpe=1.00으로 통과했지만 canonical에서는 turnover/year=1006.2%로 `paper_only`였습니다. 후속으로 `tol5`를 결합하자 `target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1`가 top-200 return=+112.16%, raw excess=+80.27%p, MDD=-19.23%, Sharpe=1.04로 통과했고, canonical top-20에서도 return=+198.15%, benchmark excess=+48.76%p, Sharpe=1.57, PF=5.58, MDD=-17.18%, turnover/year=993.9%, WF positive/Sh+=100%로 신규 `provisional_paper_candidate`가 됐습니다. 단 live는 여전히 60일 paper evidence와 target-weight verified proof가 없어 차단됩니다. `core.target_weight_rotation.build_target_weight_plan()`은 paper/pilot plan에서도 `rank_penalty_mode=downside_risk`, `max_targets_per_sector`, `position_loss_reduce_*`, `portfolio_drawdown_guard_*`를 적용하고, sector map 누락 또는 drawdown guard 명시 상태 누락은 fail-closed로 막습니다. pilot adapter는 기존 paper evidence의 total NAV, peak, guard cooldown을 상태로 복원하며 evidence snapshot에도 guard 결과를 남겨 다음 리밸런싱의 cooldown을 이어갑니다. + +2026-05-13 volatility budget target selection follow-up: 선택 종목은 유지하되 목표 금액을 동일가중이 아니라 rolling 실현 변동성의 역수로 배분하는 `target_allocation_mode=inverse_volatility`를 target-weight research backtester에 추가했습니다. `allocation_vol_lookback_days`, `allocation_vol_min_periods`, `allocation_vol_floor_pct`, `allocation_max_sleeve_weight_pct`를 artifact metrics에 남깁니다. 실데이터 `target_weight_volatility_budget --top-n 200` full sweep에서는 canonical liquidity 200개 중 유동성 통과 164개, benchmark coverage 100%에서 best=`target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol4_pdd10_floor40_cd1_volbudget60_cap35`가 return=+78.77%, raw excess=+46.88%p, exposure-matched excess=+63.79%p, avg exposure=55.2%, Sharpe=0.81, PF=1.96, MDD=-18.96%, trades=107로 `provisional_paper_candidate`가 됐습니다. sectorcap2+volbudget60 후보도 return=+77.18%, raw excess=+45.29%p, MDD=-19.19%로 통과했고, volbudget90 후보는 benchmark excess Sharpe<=0 병목이라 제외했습니다. 하지만 `tools/evaluate_and_promote.py --canonical` 재평가에서는 volbudget60 후보가 return=+170.73%, benchmark excess=+21.34%p, Sharpe=1.44, PF=4.53, turnover/year=982.6%, WF positive/Sh+=100%였지만 MDD=-20.82%로 `paper_only`입니다. sectorcap2+volbudget60도 return=+173.80%, benchmark excess=+24.41%p, Sharpe=1.46, PF=4.68, turnover/year=978.1%, WF positive/Sh+=100%였지만 MDD=-20.89%라 `paper_only`입니다. 변동성 예산은 회전율과 초과수익을 유지했지만 대형주 canonical MDD가 더 악화됐으므로 paper/live 전환 대상은 아니며, research-only 배분 로직이므로 향후 통과 후보가 다시 생기면 paper pilot 전 `core.target_weight_rotation` plan builder에 같은 배분 산식을 이식하고 params_hash 일치까지 검증해야 합니다. 다음 연구는 목표비중 배분보다 포지션별 손실 감산 또는 더 보수적인 drawdown guard를 결합해 MDD를 -20% 안쪽으로 안정화하는 방향입니다. + +2026-05-13 research empty-universe guard: canonical universe 목록 조회 실패, 데이터 수집 실패, strict 유동성 필터 때문에 research universe가 0개가 되면 후보별 0% 성과를 만들지 않고 benchmark/candidate 평가를 건너뛰도록 보강했습니다. artifact와 Markdown에는 `INSUFFICIENT_BENCHMARK_DATA`, `empty_universe_reason`, `selection_error`, `skipped_due_to_data`가 남아 데이터 문제를 전략 성과로 오해하지 않게 합니다. + +| 전략 | 상태 | Ret% | PF | WF P% | WF Sh+% | Paper Status | +|------|------|------|-----|-------|---------|--------------| +| relative_strength_rotation | **provisional_paper_candidate** | +18.09 | 1.62 | 100 | 83.3 | — | +| target_weight_rotation_top5_60_120_floor0_exp75_rankrisk90_tol5_sectorcap2_posloss8_frac50_pdd10_floor40_cd1 | **provisional_paper_candidate** (default capped pilot) | +198.15 | 5.58 | 100 | 100 | 전용 paper/pilot adapter 기본 후보, 60영업일 verified pilot evidence 필요 | +| target_weight_rotation_top5_60_120_floor0_hold3_risk60_35 | **provisional_paper_candidate** (previous default) | +171.20 | 4.24 | 100 | 83.3 | 이전 기본 후보, 관찰 유지 | +| scoring | **paper_only** | +11.22 | 1.07 | 83.3 | 50.0 | risk-adjusted alpha 미달 | +| breakout_volume | disabled (research_only) | -13.31 | 0.79 | 0 | 0 | — | +| mean_reversion | disabled (research_only) | -8.36 | 0.85 | 33.3 | 0 | — | +| trend_following | disabled (research_only) | -6.94 | 0.67 | 16.7 | 0 | — | +| ensemble | disabled (research_only) | — | — | 0 | 0 | — | + +--- + +## Paper/Pilot 운영 상세 (target-weight capped pilot 등) + +## 주의 + +실전 투입 전에는 백테스트, 검증, 모의투자를 충분히 거친 뒤 사용하는 것을 권장합니다. +현재 scoring은 관찰용 paper_only로 강등되었습니다. 신규 우선 실험은 risk-adjusted 기준을 통과한 후보만 대상으로 하며, Paper Evidence 체계로 승격/강등 근거를 자동 수집합니다. +Paper 운영 도구: `tools/run_paper_evidence_pipeline.py` (backfill/finalize/package), `tools/paper_preflight.py`, `tools/paper_launch_readiness.py`, `tools/paper_pilot_control.py`, `tools/paper_runtime_status.py`, `tools/research_candidate_sweep.py`, `tools/target_weight_rotation_pilot.py`. `tools/paper_pilot_control.py`와 `tools/paper_runtime_status.py`의 상태 변경/조회 액션 옵션은 한 번에 하나만 지정합니다. 예를 들어 활성화와 비활성화처럼 서로 다른 액션을 같은 명령에 섞으면 파일 기록이나 런타임 상태 조회를 진행하지 않고 즉시 실패합니다. `tools/paper_preflight.py`와 `tools/paper_launch_readiness.py`도 점검 대상은 `--strategy` 또는 `--all` 중 하나만 허용하며, 두 옵션을 함께 넘기면 DB 초기화나 운영 artifact 생성 전에 즉시 실패합니다. preflight CLI는 overall fail 또는 entry 차단 결과를 non-zero로 반환하고, launch readiness CLI는 READY가 아니면 non-zero로 종료합니다. readiness 리포트만 만들고 싶을 때만 `--report-only`를 사용합니다. `tools/paper_bootstrap.py`는 빈 watchlist, 빈/역전 날짜 범위, 요청일 canonical evidence 미생성 상태를 incomplete로 보고 non-zero 종료합니다. target-weight 후보는 capped pilot 승인 전 `tools/target_weight_rotation_pilot.py --record-shadow-evidence`, `--shadow-days 3`, 또는 `--shadow-start-date YYYY-MM-DD --shadow-end-date YYYY-MM-DD`로 dry-run plan artifact, shadow readiness evidence, launch readiness artifact, plan 기반 cap 추천과 enable 명령이 포함된 pilot runbook을 먼저 누적합니다. 이후 `--readiness-audit`로 주문 제출이나 shadow/pilot evidence 기록 없이 clean shadow, launch readiness, active pilot caps, 중복 세션, 실행일/장 시간, 실행 전 포지션 드리프트, 종목/벤치마크 가격 최신성, 추천 cap 충족 여부, 최근 평균 거래대금 대비 주문 비율, 비용 반영 후 현금/투자비중 한도를 JSON artifact와 Markdown 운영 리포트로 점검합니다. 유동성 preflight는 기본적으로 주문별 20일 평균 거래대금의 5% 초과를 차단하며 `--max-order-adv-pct`로 조정할 수 있습니다. pre-trade risk 검증은 주문별 수수료/세금/동적 슬리피지를 반영한 예상 체결가로 현금 부족, 최소 현금비중, 총투자비중, 종목별 비중, 최대 보유 종목 수 위반을 실행 전에 차단합니다. Markdown 리포트에는 shadow 수집, audit 재실행, 추천 cap 승인, capped paper 실행 명령이 함께 기록됩니다. `--shadow-days N`은 휴장/데이터 공백으로 같은 거래일에 매핑되는 경우 과거 평일을 추가 스캔해 N개 고유 resolved trade_day 충족을 목표로 하며, 목표 미달이나 날짜별 실패가 있으면 CLI가 non-zero로 종료해 자동화가 불완전한 증거를 성공으로 처리하지 못하게 합니다. 실행형 pilot evidence는 같은 candidate/trade_day의 기존 pilot session artifact가 없고, `run_pilot` 점검과 `execute_plan` 주문 제출 직전 재확인에서 실제 paper position이 계획 입력 장부 `position_quantities_before`와 모두 일치하며, `plan.trade_day`가 KST 실행일과 일치하고 KRX 정규장 주문 가능 시간인 경우에만 진행됩니다. 이후 유동성 preflight와 pre-trade risk 검증을 통과하고, 모든 계획 주문이 성공하며, 성공 주문 결과와 당일 `TradeHistory` fill 집계의 종목/방향/수량이 계획과 일치하고, 실행 후 실제 paper position 전체가 리밸런싱 후 `target_quantities_after` 장부와 일치하고 계획 밖 양수 포지션이 없을 때만 `pilot_paper`로 수집됩니다. 같은 날짜 evidence가 이미 있으면 최신 canonical record도 `pilot_paper`/authorized/target-weight complete/execution-market-session-allowed/liquidity-complete/pre-trade-risk-complete/fill-complete 조건을 통과해야 재사용합니다. 중복 실행은 기본 차단하며 운영자가 명시적으로 재시도해야 할 때만 `--allow-rerun`을 사용합니다. 부분 실행/중단/중복 실행/주문 결과 불일치/체결 기록 불일치/기존 evidence 검증 실패/실행일 불일치/장 시간 외 실행/실행 전 포지션 드리프트/유동성 preflight 실패/pre-trade risk 실패/실행 후 포지션 불일치는 execution-backed 승격 증거에서 제외됩니다. + +Target-weight 60영업일 pilot manifest: `--readiness-audit`는 운영 리포트와 함께 `target_weight_paper_experiment_manifest_*.json`을 생성해 후보 snapshot, 추천 cap, 차단 사유, 실행 명령, 데이터 품질 진단, 승격 증거 조건을 고정합니다. `--daily-ops-summary`는 readiness, risk check, 데이터 품질, 실행일 일치 여부, 장 시간 주문 가능 여부, verified pilot day 진행률, 다음 실행 명령을 `target_weight_daily_ops_summary_*.json/.md`로 묶어 매일 운영 판단을 한 번에 확인하게 합니다. 당일 `pilot_paper` 증거가 이미 기록됐거나 복구 보존된 경우에는 summary 자체에도 `not_before_date`와 `premature_run_guard=target_weight_future_as_of_date_blocked`를 남겨 `current_blockers.json`이 늦게 갱신돼도 다음 KRX 영업일 전 재실행을 막습니다. 두 no-order 명령은 sector map, stale benchmark, 가격 데이터, FDR/yfinance/KIS fallback 비활성화 같은 plan 생성 blocker로 막혀도 traceback 대신 `target_weight_daily_ops_summary_failure_*.json/.md` 또는 `target_weight_readiness_audit_failure_*.json/.md`를 남기며, 해당 failure artifact에는 `status=BLOCKED`, 원인, 재실행 명령, `orders_submitted=false`가 고정됩니다. `READY_TO_EXECUTE`와 `WAITING_FOR_MARKET_SESSION`은 실행일/장 시간 점검과 pilot authorization snapshot이 실제 `checked=True`로 통과한 경우에만 표시하며, 미점검 항목은 `NOT CHECKED`로 남겨 cap 승인 준비와 실행 준비를 분리합니다. daily ops/readiness/manifest의 실행 명령은 `READY_TO_EXECUTE`가 아닐 때 `# blocked:` 주석 명령으로 고정해 cap 승인 전이나 blocker 해소 전 paper 실행을 운영 리포트가 유도하지 않게 합니다. `reports/current_blockers.json`도 schema v3부터 `operator_runbook`과 next action별 `command`/`order_safety`를 포함해 `check-only → daily ops summary → readiness audit → READY_TO_EXECUTE 당일 capped paper evidence 수집` 순서를 고정합니다. 최신 daily ops summary가 없으면 current blockers의 1순위는 먼저 daily ops summary 생성이고, 최신 summary가 있으면 shadow 3일 수집, Discord test preflight, 추천 cap 승인, READY_TO_EXECUTE 실행 중 현재 상태에 맞는 명령으로 자동 정렬됩니다. Discord webhook 미설정 blocker는 `DISCORD_WEBHOOK_URL` 설정이 먼저 필요한 액션으로 표시하고, READY 상태가 아닌 최신 summary의 실행 명령은 current blockers에서도 `# blocked:`로만 노출됩니다. preflight와 pilot runbook도 target-weight 후보에서는 generic paper evidence pipeline 대신 `target_weight_rotation_pilot.py --shadow-days`, `--daily-ops-summary`, `--readiness-audit`, Discord `--send-test-notification` 경로를 우선 안내합니다. 공식 `reports/paper_experiment_manifest.json`도 기존 scoring 60영업일 실험과 target-weight capped paper pilot을 분리해 기록합니다. + +Target-weight cash override 정책: `--cash`는 dry-run 계획 검토와 shadow bootstrap 보조 입력에만 사용합니다. `--readiness-audit`, `--daily-ops-summary`, `--execute`, `--collect-evidence`에서는 실제 paper 계좌 현금과 실행 직전 포지션을 기준으로만 판단하며, `--cash`가 같이 들어오면 CLI와 함수 호출 모두 `target_weight_cash_override_blocked`로 fail-closed 차단합니다. `--collect-evidence`는 반드시 `--execute`와 함께 사용해야 하며, 단독 증거 수집 요청은 `target_weight_collect_evidence_requires_execute`로 차단합니다. + +Generic paper entry도 동일한 실행 경계 원칙을 따른다. `main.py --mode paper`, scheduler auto-entry, fixed-quantity paper BUY 모두 preflight status와 runtime state를 주문 생성 전에 확인하고, 확인 실패는 fail-closed로 차단한다. blocked runtime에서 pilot authorization이 활성화되어도 `check_pilot_entry()`를 다시 통과해야 하며, pilot 총노출 cap은 현재 노출뿐 아니라 이번 주문 후 예상 노출까지 기준으로 검사한다. SELL/exit 경로는 포지션 정리를 위해 계속 허용한다. 운영 손실 한도도 같은 원칙을 따른다. `risk_params.yaml:drawdown.max_portfolio_mdd` 또는 `max_daily_loss`에 닿으면 신규 BUY만 fail-closed로 차단하고, 손절·트레일링·수동 청산 SELL은 계속 열어 둔다. pilot evidence freshness는 달력일이 아니라 주말과 한국장 휴장일을 제외한 영업일 기준으로 계산합니다. + +Target-weight pilot 승인/재시도 보강: `tools/paper_pilot_control.py --enable`은 `reports/promotion/run_metadata.json`의 canonical `strategy_specs[].base_strategy=target_weight_rotation`을 우선 기준으로 target-weight 후보를 식별하고, 기존 `target_weight_*` 접두어는 호환 fallback으로만 사용합니다. pilot auth를 쓰기 전 target-weight readiness audit을 다시 실행해 운영자가 요청한 cap이 현재 plan, launch readiness, 유동성 preflight, 비용 반영 pre-trade risk를 만족하는지 검증합니다. 이때 요청 cap은 audit의 `cap_recommendation.suggested_caps`와 정확히 일치해야 하므로, 통과 가능한 더 큰 cap으로 pilot을 여는 경로도 차단됩니다. 승인 auth에는 당시 plan의 `trade_day`, `as_of_date`, `params_hash`, targets, 시작/목표 수량 snapshot을 함께 저장해 cap 승인과 실행 계획을 묶습니다. 유동성 diagnostics가 없으면 fail-closed로 차단하고, 이미 주문이 완료된 same-candidate/trade-day 세션은 `--allow-rerun`을 줘도 재실행하지 않습니다. `--execute`는 주문 제출 전 같은 candidate/trade_day의 atomic `.lock`을 선점하며, 이미 in-progress lock이 있으면 주문·포지션 조회·증거 수집 전에 차단합니다. `--allow-rerun`도 이전 세션이 주문 제출에 도달했거나 실패/중단 흔적이 있으면 재실행하지 않고, 주문 제출 전 단계에서 막힌 불완전 세션 복구에만 사용합니다. + +Target-weight 실행 차단 기록: `--execute`가 pilot cap validation에서 막히면 주문·체결·증거 수집 없이 session JSON artifact에 차단 사유를 남깁니다. runtime pilot session은 쓰지 않아 cap을 고친 뒤 같은 거래일 계획을 다시 점검할 수 있습니다. 또한 `plan.trade_day`와 KST 기준 실제 실행일이 다르거나, 현재 시간이 KRX 정규장 주문 가능 시간이 아니거나, 활성 pilot auth의 승인 snapshot이 현재 plan의 `params_hash`/trade day/targets/수량 장부와 다르면 주문, idempotency, 포지션 조회, 체결 대조, session 저장, 승격 증거 수집 전에 fail-closed로 차단합니다. target-weight 전용 실행 어댑터는 후보명 접두어와 무관하게 승인 snapshot을 요구합니다. no-order `--readiness-audit`와 `--daily-ops-summary`도 실행일 check, 장 시간 check, 승인 snapshot check를 blocker로 표시해 오래되었거나 장 외 실행 명령을 READY 상태로 노출하지 않습니다. pilot entry가 막혀 아직 승인 snapshot이나 장 시간 검사가 실행되지 않은 경우 Markdown과 JSON risk snapshot은 `NOT CHECKED`로 표시해 미검사를 통과로 오해하지 않게 합니다. + +Target-weight 가격 최신성 guard: 목표비중 plan 생성 시 종목별 마지막 실제 종가 날짜(`price_last_dates`)와 벤치마크 최신 날짜(`benchmark_last_date`)를 diagnostics에 남깁니다. 운영 주문 계획에서는 ffill로 보정된 낡은 종목 가격이 `trade_day` 최신 가격처럼 쓰이면 `target_weight_stale_price_data`로 차단하고, `benchmark_excess` 점수나 benchmark risk overlay에 필요한 벤치마크 가격이 score day보다 오래되면 `target_weight_benchmark_price_stale`로 plan 생성 자체를 중단합니다. `--readiness-audit`와 `--daily-ops-summary`는 같은 diagnostics를 `data_quality_check`/Data Quality 섹션으로 다시 노출해 가격 최신성 진단 누락, stale 종목, 벤치마크 지연을 READY 상태로 숨기지 않습니다. research target-weight backtest는 원본 close/open panel을 보존하되, score day 실제 종가가 없는 종목은 후보에서 제외하고 `stale_score_symbols_*`로 기록합니다. 보유 종목의 일별 stale 평가는 `held_stale_valuation_*`로 남기며, 보유 종목의 리밸런싱일 시가가 없으면 거래 없이 `missing_held_open_*`/`skipped_rebalance_missing_held_open_count`로 기록합니다. 벤치마크 stale과 신규 top-N 매수 후보의 시가 누락은 계속 fail-closed로 중단해 stale 가격이나 당일 종가 체결 착시가 pilot 흐름에 섞이지 않게 합니다. + +Target-weight daily ops 실행 최신성 guard: `READY_TO_EXECUTE`, `READY_TO_ENABLE_CAPS`, `WAITING_FOR_MARKET_SESSION` summary는 `trade_day`가 현재 KST 날짜와 맞아야 할 뿐 아니라 `generated_at`도 현재일 30분 이내여야 합니다. 누락, 형식 오류, 미래 시각, 30분 초과 요약은 `paper_pilot_control.py --status`와 current blockers runbook에서 실행/승인 명령을 `# blocked:`로 바꾸고 daily ops summary 재생성을 요구합니다. + +Earnings filter fail-closed: `trading.skip_earnings_days`가 0보다 크면 신규 BUY 전에 yfinance와 DART 순서로 실적일을 확인합니다. 두 소스 모두 실적일을 확인하지 못하면 기본 `trading.earnings_filter_unknown_policy=block` 정책으로 매수를 차단합니다. 조회 불가 상태를 감수하고 운용하려면 `earnings_filter_unknown_policy: "allow"`를 명시해야 합니다. + +Paper trade quality gate: `tools/paper_trade_quality_report.py`는 체결 품질이 review이거나 선택 기간의 `TradeHistory`가 0건이면 기본 non-zero로 종료합니다. 체결 0건 리포트만 생성하려면 `--allow-no-trades`를 명시합니다. promotion package도 같은 기간 `trade_quality_status=no_trades`를 `fill_quality_no_trades` blocker로 반영하고, `execution_session_id`/`order_id`가 비어 있거나 evidence의 `total_trades` 및 BUY/SELL 합계가 `TradeHistory`와 다르면 `fill_quality_execution_link_missing`/`fill_quality_trade_count_mismatch`/`fill_quality_trade_action_mismatch`로 차단합니다. `TradeHistory.action`이 BUY/SELL이 아닌 경우도 `fill_quality_unknown_trade_action`으로 fail-closed 처리합니다. promotion engine 역시 live 후보 판정에서 `paper_trade_quality_status=ok`를 재확인합니다. + +Target-weight 승격 증거 보강: target-weight 계열 전략은 일반 `execution_backed=True` paper record만으로 promotion evidence day를 채우지 않습니다. `pilot_paper`/authorized record가 target-weight plan과 execution proof를 포함하고, record date와 plan trade day가 일치하며, `execution_trade_day_allowed=True`, `execution_market_session_allowed=True`, `pilot_authorization_snapshot_allowed=True`, liquidity/pre-trade risk/order result/fill/position reconciliation complete 및 plan/execution params hash 일치를 만족한 날만 승격 카운트에 들어갑니다. 60영업일 전체 verified pilot evidence는 하나의 params hash로 고정되어야 하며, promotion package 생성과 promotion/live gate는 canonical `strategy_specs`의 `base_strategy=target_weight_rotation`을 우선 기준으로 target-weight 후보를 식별합니다. paper evidence package도 canonical params hash와 pilot proof가 맞지 않으면 `BLOCKED`로 남기며, live gate도 canonical metadata의 params hash와 paper evidence params hash가 다르면 target-weight live 전환을 차단합니다. + +Target-weight finalize 성과 진단: `--finalize-pilot-evidence`가 `total_value`/`daily_return` 미확정으로 막히면 finalize report에 source record 보유 성과 필드, portfolio metrics probe 수행 여부, probe에서 확인된 필드, 최종 누락 필드를 기록합니다. current blockers와 `paper_pilot_control.py --status`도 이 정보를 노출해, 성과 snapshot 대기인지 benchmark 대기인지 운영자가 구분할 수 있게 합니다. `daily_return`/`portfolio_value`/benchmark excess 누락처럼 finalize로 promotable record가 될 수 있는 사유는 repair보다 finalize를 먼저 안내합니다. diff --git a/docs/images/dashboard-main.png b/docs/images/dashboard-main.png new file mode 100644 index 0000000000000000000000000000000000000000..6e68b4af7ab9ed3da7c1a15e5845c39b480797be GIT binary patch literal 137193 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