#!/usr/bin/env python3 """ validate_capital_style_allocation_v1.py ─────────────────────────────────────────────────────────────────────────────── CAPITAL_STYLE_ALLOCATION_V1 검증기 결정론 및 범위 체크: (1) conviction_score ∈ [0, 100] (2) recommended_pct ∈ {0.0, 1.5, 3.0, 5.0, 7.0} (3) 가중치 합 = 1.0 (각 스타일별) (4) LLM 계산 금지 확인 (meta.llm_computed=false) (5) 결정론 확인 (meta.deterministic=true) (6) 모든 종목에 4개 스타일 행 존재 (7) gate=PASS이면 errors=[] 출력 토큰: CAPITAL_ALLOC_OK / CAPITAL_ALLOC_FAIL """ from __future__ import annotations import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent INPUT = ROOT / "Temp" / "capital_style_allocation_v1.json" OUTPUT = ROOT / "Temp" / "capital_style_allocation_validation_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) VALID_STYLES = {"SCALP", "SWING", "MOMENTUM", "POSITION"} VALID_REC_PCTS = {0.0, 1.5, 3.0, 5.0, 7.0} WEIGHT_SUM_TOL = 0.001 def main() -> int: if not INPUT.exists(): print("CAPITAL_ALLOC_FAIL") print(f" MISSING: {INPUT}") print(" 먼저 build_capital_style_allocation_v1.py 실행 필요") return 1 data = json.loads(INPUT.read_text(encoding="utf-8")) violations: list[str] = [] warnings: list[str] = [] # (4) LLM 계산 금지 meta = data.get("meta", {}) if meta.get("llm_computed") is not False: violations.append("meta.llm_computed must be false") # (5) 결정론 if meta.get("deterministic") is not True: violations.append("meta.deterministic must be true") # gate 체크 gate = data.get("gate") if gate != "PASS": violations.append(f"gate={gate!r} (expected PASS)") errors_field = data.get("errors", []) if errors_field: violations.append(f"errors field non-empty: {errors_field[:3]}") # (3) 가중치 합 검증 weights = data.get("weights", {}) for style, w in weights.items(): total = sum(w.values()) if abs(total - 1.0) > WEIGHT_SUM_TOL: violations.append(f"weights[{style}] sum={total:.4f} != 1.0") rows = data.get("rows", []) if not rows: violations.append("rows is empty — no tickers processed") # 종목별 검증 for row in rows: ticker = row.get("ticker", "?") styles = row.get("styles", []) # (6) 4개 스타일 존재 style_names = {s.get("style") for s in styles} missing = VALID_STYLES - style_names if missing: violations.append(f"{ticker}: missing styles {missing}") for s in styles: sname = s.get("style", "?") conv = s.get("conviction_score") rpct = s.get("recommended_pct") # (1) conviction_score 범위 if conv is None or not isinstance(conv, (int, float)): violations.append(f"{ticker}.{sname}: conviction_score missing or non-numeric") elif not (0.0 <= float(conv) <= 100.0): violations.append(f"{ticker}.{sname}: conviction_score={conv} not in [0,100]") # (2) recommended_pct 유효값 if rpct is None: violations.append(f"{ticker}.{sname}: recommended_pct missing") elif float(rpct) not in VALID_REC_PCTS: violations.append(f"{ticker}.{sname}: recommended_pct={rpct} not in {VALID_REC_PCTS}") # 신호 분해 존재 sb = row.get("signal_breakdown", {}) for field in ("technical_score", "smart_money_score", "fundamental_score", "macro_event_score", "liquidity_modifier"): if field not in sb: warnings.append(f"{ticker}: signal_breakdown missing {field}") # ── 결과 ─────────────────────────────────────────────────────────────── status = "CAPITAL_ALLOC_OK" if not violations else "CAPITAL_ALLOC_FAIL" result = { "status": status, "violation_count": len(violations), "warning_count": len(warnings), "violations": violations, "warnings": warnings, "ticker_count": len(rows), "styles_checked": sorted(VALID_STYLES), "gate_from_build": gate, } OUTPUT.parent.mkdir(parents=True, exist_ok=True) OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") sep = "=" * 70 print(sep) print(" CAPITAL_STYLE_ALLOCATION_V1 검증기") print(sep) print(f" tickers={len(rows)} violations={len(violations)} warnings={len(warnings)}") if violations: print("\n [위반]:") for v in violations: print(f" {v}") if warnings: print(f"\n [경고] {len(warnings)}건 (허용)") print(f"\n → 저장: {OUTPUT}") print(f" {status}\n") return 0 if not violations else 1 if __name__ == "__main__": raise SystemExit(main())