#!/usr/bin/env python3 """ build_calibration_priority_v1.py ─────────────────────────────────────────────────────────────────────────────── P4 확장: alpha_feedback_loop_v2.json → calibration_registry.yaml 보정 제안 연결 alpha_feedback_loop_v2.json의 recommended_adjustments 를 읽어 calibration_registry.yaml의 해당 임계값과 연결한 보정 우선순위 리포트를 생성한다. 출력: Temp/calibration_priority_v1.json - 보정 우선순위 목록 (feedback 신호 기반) - 각 임계값의 현재 상태(EXPERT_PRIOR/샘플 수)와 권장 조치 - alpha_feedback_loop 미포착(miss5_count) 신호와의 연결 사용법: python tools/build_calibration_priority_v1.py """ from __future__ import annotations import json import sys from pathlib import Path import yaml ROOT = Path(__file__).resolve().parent.parent AFL = ROOT / "Temp" / "alpha_feedback_loop_v2.json" REG = ROOT / "spec" / "calibration_registry.yaml" OUTPUT = ROOT / "Temp" / "calibration_priority_v1.json" if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"): sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1) # alpha_feedback 요인 → 관련 calibration_registry ID 매핑 FACTOR_TO_REGISTRY: dict[str, list[str]] = { "antithesis_balance": [ "ALEG_V2_GATE1_BLOCK_PCT", "ALEG_V2_GATE2_BLOCK_PCT", "DSD_V1_CONFIRMED_WS", ], "passive_signal_quality": [ "ALEG_V2_GATE1_BLOCK_PCT", # 뒷박 차단 임계 — timing=None 진입 허용 과다 "ALEG_V2_GATE1_WAIT_PCT", # PULLBACK_WAIT 경계 "K2_REBOUND_TRIGGER_ATR_MULT", # 반등 트리거 — 타이밍 조건 ], "active_signal_confidence": [ "ALEG_V2_GATE1_BLOCK_PCT", "ALEG_V2_GATE2_BLOCK_PCT", "DSD_V1_SIG1_WEIGHT", "DSD_V1_SIG2_WEIGHT", ], "k2_rebound_efficiency": [ "K2_SPLIT_RATIO", "K2_REBOUND_TRIGGER_ATR_MULT", "K2_DEADLINE_DAYS", "SCR_V4_EFFICIENCY_DAMAGE_PENALTY_COEFF", ], # CAPITAL_STYLE_ALLOCATION_V1 — 투자성향별 가중치 보정 # passive_signal_quality miss5_count=51 → 단타/단기 신호 가중치 재보정 필요 "passive_signal_quality_style": [ "CSA_SCALP_W_TECHNICAL", # 단타에서 기술지표 과도 의존 여부 확인 "CSA_SCALP_W_SMARTMONEY", "CSA_SWING_W_TECHNICAL", "CSA_SWING_W_SMARTMONEY", "CSA_TECH_RSI_OVERSOLD", # RSI<35 임계 최적화 "CSA_TECH_DISPARITY_PULLBACK", # 눌림목 3% 임계 최적화 ], "conviction_calibration": [ "CSA_POSITION_PCT_HIGH_CONVICTION", # 80점 임계 → 실측 분포 기반 조정 "CSA_POSITION_PCT_STRONG", # 65점 임계 "CSA_POSITION_PCT_MODERATE", # 50점 임계 "CSA_POSITION_PCT_PILOT", # 35점 임계 ], } def load_json(p: Path) -> dict: if not p.exists(): return {} return json.loads(p.read_text(encoding="utf-8")) def load_registry(p: Path) -> dict[str, dict]: if not p.exists(): return {} data = yaml.safe_load(p.read_text(encoding="utf-8")) return {t["id"]: t for t in data.get("thresholds", []) if "id" in t} def main() -> int: afl_data = load_json(AFL) reg_index = load_registry(REG) sep = "=" * 70 print(sep) print(" 알파 피드백 루프 → 보정 우선순위 연결기 (CALIB-PRIORITY-V1)") print(sep) adjustments = afl_data.get("recommended_adjustments", []) cases_analyzed = afl_data.get("cases_analyzed", 0) miss5_count = 0 for adj in adjustments: if adj.get("factor") == "passive_signal_quality": miss5_count = int(adj.get("miss5_count", 0)) print(f"\n [alpha_feedback_loop_v2] cases_analyzed={cases_analyzed}") print(f" miss5_count(5%+ 급등 미포착)={miss5_count}건 → passive_signal_quality 개선 필요") priority_list: list[dict] = [] for adj in adjustments: factor = adj.get("factor", "") action = adj.get("action", "") rationale = adj.get("rationale", "") reg_ids = FACTOR_TO_REGISTRY.get(factor, []) for rid in reg_ids: reg_entry = reg_index.get(rid) if not reg_entry: continue source = reg_entry.get("source", "EXPERT_PRIOR") sample_n = int(reg_entry.get("sample_n", 0) or 0) value = reg_entry.get("value") formula = reg_entry.get("owner_formula", "") # 보정 우선도 점수: miss5_count 기여 + 미보정 가중 urgency = 0 if factor == "passive_signal_quality": urgency += miss5_count # miss가 많을수록 높은 urgency if source == "EXPERT_PRIOR": urgency += 10 if sample_n == 0: urgency += 5 priority_list.append({ "calibration_id": rid, "current_value": value, "owner_formula": formula, "source": source, "sample_n": sample_n, "linked_factor": factor, "alpha_action": action, "urgency_score": urgency, "calibration_path": ( ( "표본 30건 이상 확보 후 PROVISIONAL 승격 → " if sample_n >= 30 else f"표본 {30 - sample_n}건 추가 수집 후 PROVISIONAL 승격 → " ) + "실측 T+5 승률 기반 최적값 backtest → CALIBRATED 확정" ), "rationale": rationale[:200] if rationale else "", }) # 중복 제거 (같은 rid, 높은 urgency 유지) seen: dict[str, dict] = {} for p in priority_list: rid = p["calibration_id"] if rid not in seen or p["urgency_score"] > seen[rid]["urgency_score"]: seen[rid] = p priority_list = sorted(seen.values(), key=lambda x: -x["urgency_score"]) print(f"\n [보정 우선순위 TOP-10]") print(f" {'순위':<4} {'ID':<45} {'값':>7} {'샘플':>5} {'긴급도':>6}") print(f" {'-'*4} {'-'*45} {'-'*7} {'-'*5} {'-'*6}") for rank, item in enumerate(priority_list[:10], 1): print( f" {rank:<4} {item['calibration_id']:<45} " f"{str(item['current_value']):>7} {item['sample_n']:>5} {item['urgency_score']:>6}" ) print(f"\n [보정 로드맵]") print(f" Step 1 (즉시): 표본 누적 — 매 거래일 T+5 결과 자동 수집") print(f" Step 2 (30건 후): ALEG_V2_GATE1_BLOCK_PCT 3.0% → 실측 최적값으로 PROVISIONAL 승격") print(f" Step 3 (50건 후): DSD_V1 가중치 logistic regression 최적화") print(f" Step 4 (100건 후): K2_SPLIT_RATIO backtest 비교 → CALIBRATED 확정") print(f" miss5_count={miss5_count}건 → passive_signal_quality 개선이 T+5 35.86%→50%+ 핵심") result = { "status": "CALIBRATION_PRIORITY_OK", "cases_analyzed": cases_analyzed, "miss5_count": miss5_count, "priority_count": len(priority_list), "priority_list": priority_list, "roadmap": { "step1": "표본 누적 — 매 거래일 T+5 결과 자동 수집", "step2": "30건 후: ALEG_V2_GATE1_BLOCK_PCT 3.0% → 실측 최적값 PROVISIONAL 승격", "step3": "50건 후: DSD_V1 가중치 logistic regression 최적화", "step4": "100건 후: K2_SPLIT_RATIO 30/70~60/40 backtest → CALIBRATED", }, "target_improvement": { "current_t5_pct": 35.86, "target_t5_pct": 55.0, "key_lever": "passive_signal_quality (miss5_count=51건 개선)", }, } OUTPUT.parent.mkdir(parents=True, exist_ok=True) OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") print(f"\n → 결과 저장: {OUTPUT}") print(f" CALIBRATION_PRIORITY_OK\n") return 0 if __name__ == "__main__": raise SystemExit(main())