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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/safety-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:

- name: Compile safety modules
run: |
python -m py_compile core/order_executor.py core/paper_preflight.py core/scheduler.py core/live_gate.py
python -m py_compile core/order_executor.py core/paper_preflight.py core/scheduler.py core/live_gate.py core/target_weight_rotation.py tools/target_weight_rotation_pilot.py tools/paper_pilot_control.py

- name: Run safety regression tests
env:
Expand All @@ -50,6 +50,8 @@ jobs:
tests/test_paper_runtime.py \
tests/test_paper_preflight.py \
tests/test_paper_pilot.py::TestPilotSchedulerIntegration \
tests/test_target_weight_rotation.py \
tests/test_target_weight_paper_adapter.py \
tests/test_live_gate.py \
tests/test_paper_evidence.py \
tests/test_promotion_engine.py \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Paper Evidence 체계 — `core/paper_evidence.py` 일별 22개 지표 자동
현재 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/research_candidate_sweep.py`, `tools/target_weight_rotation_pilot.py`. 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가 없고, 주문 제출 직전 실제 paper position이 계획 입력 장부 `position_quantities_before`와 일치하고, 유동성 preflight와 pre-trade risk 검증을 통과하고, 모든 계획 주문이 성공하며, 성공 주문 결과와 당일 `TradeHistory` fill 집계의 종목/방향/수량이 계획과 일치하고, 실행 후 실제 paper position 전체가 리밸런싱 후 `target_quantities_after` 장부와 일치하고 계획 밖 양수 포지션이 없을 때만 `pilot_paper`로 수집됩니다. 같은 날짜 evidence가 이미 있으면 기존 canonical record도 `pilot_paper`/authorized/target-weight complete/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, 차단 사유, 실행 명령, 승격 증거 조건을 고정합니다. 공식 `reports/paper_experiment_manifest.json`도 기존 scoring 60영업일 실험과 target-weight capped paper pilot을 분리해 기록합니다.
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`로 묶어 매일 운영 판단을 한 번에 확인하게 합니다. 공식 `reports/paper_experiment_manifest.json`도 기존 scoring 60영업일 실험과 target-weight capped paper pilot을 분리해 기록합니다.

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()`를 다시 통과해야 하며, SELL/exit 경로는 포지션 정리를 위해 계속 허용한다. pilot evidence freshness는 달력일이 아니라 주말과 한국장 휴장일을 제외한 영업일 기준으로 계산합니다.

Expand Down
1 change: 1 addition & 0 deletions docs/PROJECT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ full paper 신규 BUY는 preflight status artifact와 runtime state가 모두
| ✅ **target-weight shadow proof 추가** | dry-run에서 `--record-shadow-evidence`를 켜면 `append_shadow_plan_evidence()`가 non-promotable `shadow_bootstrap` record를 남김. `execution_backed=False`, excess=null이라 promotion은 오염하지 않고, launch readiness의 clean final day만 채운다. `--shadow-days 3` 또는 `--shadow-start-date/--shadow-end-date`로 여러 날짜 shadow bootstrap을 한 번에 누적할 수 있고, `--shadow-days N`은 휴장/데이터 공백으로 같은 거래일에 매핑되면 과거 평일을 추가 스캔해 N개 고유 resolved trade_day 충족을 목표로 한다. 목표 미달 또는 날짜별 실패가 있으면 CLI는 non-zero로 종료해 불완전한 증거가 자동화에서 성공으로 처리되지 않게 한다. 같은 실행에서 launch readiness JSON/MD와 pilot runbook을 생성하며 session/batch artifact에는 기본 cap preview, plan 기반 최소/추천 cap, enable 명령, launch artifact 경로를 기록 |
| ✅ **target-weight readiness audit 추가** | `tools/target_weight_rotation_pilot.py --readiness-audit`로 주문 제출, shadow evidence 기록, pilot session 저장 없이 capped pilot 직전 상태를 JSON artifact와 Markdown 운영 리포트로 점검. clean shadow/launch readiness, active pilot auth와 cap validation, 추천 cap, 중복 session idempotency, 실행 전 position drift, 유동성 preflight, 비용 반영 pre-trade risk, 다음 조치(`cap approval` 또는 `execute capped paper pilot`)를 함께 판정한다. Markdown 리포트는 shadow 수집/audit 재실행/추천 cap 승인/capped paper 실행 명령을 함께 남기며, cap 승인 준비가 안 된 blocker는 non-zero 종료로 자동화가 진행하지 못하게 한다 |
| ✅ **target-weight 60영업일 pilot manifest 추가** | `tools/target_weight_rotation_pilot.py --readiness-audit`가 60영업일 `pilot_paper` 증거 누적 기준, 추천 cap, 차단 사유, 실행 명령을 담은 `target_weight_paper_experiment_manifest_*.json`을 함께 생성한다. `reports/paper_experiment_manifest.json`에도 기존 scoring 실험과 별도 target-weight capped paper pilot 기준을 기록해 운영 흐름을 분리한다 |
| ✅ **target-weight daily ops summary 추가** | `tools/target_weight_rotation_pilot.py --daily-ops-summary`가 readiness audit, 60영업일 pilot evidence 진행률, 유동성/pre-trade risk 상태, 다음 운영 명령을 `target_weight_daily_ops_summary_*.json/.md`로 묶어 남긴다. 주문·증거·pilot session을 쓰지 않는 no-order summary라 장전/장마감 운영 확인에 안전하게 사용할 수 있다 |
| ✅ **pilot freshness 영업일 기준 보정** | `check_pilot_entry()`와 launch readiness가 evidence stale 여부를 달력일이 아니라 한국장 영업일 기준으로 계산한다. 긴 주말·휴장일 이후 정상적인 paper/pilot 흐름이 불필요하게 차단되지 않도록 `evidence_stale_unit=business_days`를 함께 남긴다 |
| ✅ **target-weight pilot enable guard 추가** | `tools/paper_pilot_control.py --enable`이 target-weight 후보를 승인할 때 pilot auth 기록 전에 readiness audit을 재실행하고, 운영자가 요청한 cap이 현재 plan/launch readiness/유동성 preflight/비용 반영 pre-trade risk를 만족할 때만 승인한다. stale plan이나 추천 cap 미충족 상태에서 auth만 먼저 열리는 경로를 차단한다 |
| ✅ **target-weight cap validation artifact 추가** | `--execute`가 pilot cap validation에서 막혀도 예외로 조기 종료하지 않고 session JSON artifact에 차단 사유, skipped orders, evidence block reason을 남긴다. 주문/체결/증거 수집/runtime pilot session 저장은 하지 않아 cap 조정 후 재점검할 수 있다 |
Expand Down
9 changes: 9 additions & 0 deletions reports/daily_ops_checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
- [ ] MDD 확인: 누적 ___% (15% 접근 시 주의)
- [ ] 손절/익절/트레일링 스탑 발동 건수: __건

### Target-weight pilot 전용

- [ ] `python tools/target_weight_rotation_pilot.py --daily-ops-summary` 실행
- [ ] Summary status 확인: BLOCKED / READY_TO_ENABLE_CAPS / READY_TO_EXECUTE
- [ ] Verified pilot days 확인: ____ / 60
- [ ] Invalid execution days가 0인지 확인
- [ ] Liquidity / Pre-trade risk가 PASS인지 확인
- [ ] 다음 명령이 cap 승인인지, capped paper 실행인지 기록

### 기록

```
Expand Down
140 changes: 140 additions & 0 deletions tests/test_target_weight_paper_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,146 @@ def test_build_target_weight_experiment_manifest_freezes_pilot_flow():
assert manifest["no_order_safety"]["orders_submitted"] is False


def test_summarize_target_weight_evidence_progress_counts_verified_days(monkeypatch):
from tools.target_weight_rotation_pilot import summarize_target_weight_evidence_progress

strategy = "target_weight_candidate"
params_hash = "hash"
records = [
{
"date": "2026-04-08",
"strategy": strategy,
"execution_backed": False,
"evidence_mode": "shadow_bootstrap",
"session_mode": "shadow_bootstrap",
},
{
"date": "2026-04-09",
"strategy": strategy,
"execution_backed": True,
"evidence_mode": "pilot_paper",
"session_mode": "pilot_paper",
"pilot_authorized": True,
"pilot_caps_snapshot": {
"target_weight_plan": {
"candidate_id": strategy,
"trade_day": "2026-04-09",
"params_hash": params_hash,
},
"target_weight_execution": {
"complete": True,
"params_hash": params_hash,
"liquidity_complete": True,
"pre_trade_risk_complete": True,
"order_result_complete": True,
"fill_complete": True,
"position_reconciliation": {"complete": True},
},
},
},
{
"date": "2026-04-10",
"strategy": strategy,
"execution_backed": True,
"evidence_mode": "pilot_paper",
"session_mode": "pilot_paper",
"pilot_authorized": True,
"pilot_caps_snapshot": {
"target_weight_plan": {
"candidate_id": strategy,
"trade_day": "2026-04-10",
"params_hash": params_hash,
},
},
},
]
monkeypatch.setattr(
"core.paper_evidence.get_canonical_records",
lambda candidate_id: records if candidate_id == strategy else [],
)

progress = summarize_target_weight_evidence_progress(strategy)

assert progress["verified_pilot_days"] == 1
assert progress["remaining_pilot_days"] == 59
assert progress["shadow_days"] == 1
assert progress["invalid_execution_days"] == 1
assert progress["invalid_reasons"]["missing_target_weight_execution"] == 1
assert progress["latest_verified_pilot_date"] == "2026-04-09"


def test_build_target_weight_daily_ops_summary_writes_operator_view(tmp_path):
from tools.target_weight_rotation_pilot import (
build_target_weight_daily_ops_summary,
build_target_weight_experiment_manifest,
recommend_pilot_caps,
write_target_weight_daily_ops_summary,
)

plan = _adapter_plan()
cap_recommendation = recommend_pilot_caps(plan)
audit = {
"candidate_id": plan.candidate_id,
"trade_day": plan.trade_day,
"ready_for_cap_approval": True,
"ready_for_capped_pilot": False,
"next_action": "enable pilot with suggested caps, then rerun readiness audit",
"blocking_reasons": ["pilot_authorization: no active capped pilot authorization"],
"warning_reasons": ["preview_caps: current preview blocked"],
"operator_commands": {
"collect_shadow_days": "python tools/target_weight_rotation_pilot.py --shadow-days 3",
"rerun_readiness_audit": "python tools/target_weight_rotation_pilot.py --readiness-audit",
"enable_suggested_caps": cap_recommendation["enable_command"],
"execute_capped_paper": "python tools/target_weight_rotation_pilot.py --execute --collect-evidence",
},
"plan_summary": {
"order_count": 3,
"target_position_count": 3,
"max_order_notional": 1_200_000.0,
"gross_exposure_after": 3_200_000.0,
},
"liquidity_check": {"complete": True, "reason": "target_weight_liquidity_preflight_passed"},
"pre_trade_risk_check": {"complete": True, "reason": "target_weight_pre_trade_risk_passed"},
}
manifest = build_target_weight_experiment_manifest(
plan=plan,
cap_recommendation=cap_recommendation,
readiness_audit=audit,
)
progress = {
"candidate_id": plan.candidate_id,
"target_days": 60,
"verified_pilot_days": 12,
"remaining_pilot_days": 48,
"progress_ratio": 0.2,
"shadow_days": 3,
"invalid_execution_days": 0,
"invalid_reasons": {},
"non_promotable_days": 0,
"total_canonical_records": 15,
"latest_record_date": "2026-04-10",
"latest_verified_pilot_date": "2026-04-10",
"latest_shadow_date": "2026-04-03",
"ready_for_promotion_day_count": False,
}

summary = build_target_weight_daily_ops_summary(
audit=audit,
experiment_manifest=manifest,
evidence_progress=progress,
)
json_path, md_path = write_target_weight_daily_ops_summary(summary, output_dir=tmp_path)
payload = json.loads(json_path.read_text(encoding="utf-8"))
report = md_path.read_text(encoding="utf-8")

assert summary["status"] == "READY_TO_ENABLE_CAPS"
assert len(summary["summary_hash"]) == 64
assert payload["evidence_progress"]["remaining_pilot_days"] == 48
assert payload["no_order_safety"]["summary_only"] is True
assert "Verified pilot days: 12/60" in report
assert "READY_TO_ENABLE_CAPS" in report


def test_assess_plan_liquidity_blocks_large_adv_order():
from tools.target_weight_rotation_pilot import assess_plan_liquidity

Expand Down
Loading
Loading