#!/usr/bin/env python3 """ validate_harness_json.py harness_context 의도·결과 검증 JSON 경로: data.data._harness_context """ import json, pathlib, sys # Windows cp949 터미널 호환 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) JSON_PATH = pathlib.Path('GatherTradingData.json') SEP = '=' * 60 SEP2 = '-' * 100 def safe_json(val): if isinstance(val, str): try: return json.loads(val) except Exception: return val return val def main(): if not JSON_PATH.exists(): print(f'[ERROR] {JSON_PATH} not found') sys.exit(1) root = json.loads(JSON_PATH.read_text(encoding='utf-8')) # harness_context 탐색 hc = None hc_path = 'unknown' try: hc = root['data']['_harness_context'] hc_path = 'data._harness_context' except (KeyError, TypeError): pass if hc is None: for key in ['_harness_context', 'harness_context']: if key in root and isinstance(root[key], dict): hc = root[key]; hc_path = key; break if hc is None: print('[ERROR] harness_context 찾을 수 없음') print('root keys:', list(root.keys())[:10]) sys.exit(1) print(SEP) print(f'파일: {JSON_PATH.name}') print(f'harness_context 경로: {hc_path}') print(f'키 수: {len(hc)}') print(SEP) # ── 1. HARNESS META ────────────────────────────────────────────────────── print('\n[1] HARNESS META') for k in ['harness_version', 'computed_at', 'captured_at', 'intraday_lock', 'mrs_score']: v = hc.get(k, '(missing)') warn = '' if k == 'harness_version': if v == '2026-05-19-X4R1': warn = ' <- OK (최신 X4R1)' else: warn = f' <- [WARN] expected 2026-05-19-X4R1, got {v}' print(f' {k}: {v}{warn}') # ── 2. H1 GUARDS ──────────────────────────────────────────────────────── print('\n[2] H1 포트폴리오 가드 (QEH_AUDIT_BLOCK)') h1 = [ ('total_heat_pct', 'TOTAL_HEAT_V1 '), ('heat_gate_status', 'HEAT_GATE '), ('cash_floor_status', 'CASH_RATIOS_V1 '), ('cash_floor_min_pct', 'cash_floor_min_pct '), ('settlement_cash_pct', 'D+2 현금% '), ('immediate_cash_krw', 'immediate_cash_krw '), ('buy_power_krw', 'buy_power_krw '), ('total_asset_krw', 'total_asset_krw '), ('performance_label', 'bayesian_label '), ('performance_multiplier', 'bayesian_mult '), ] for k, label in h1: v = hc.get(k, '(missing)') print(f' [{label}] {v}') # ── 3. Alpha-Shield ────────────────────────────────────────────────────── print('\n[3] Alpha-Shield (X1/X3/W1~W4) [신규 2026-05-19-X1W1/X4R1]') as_keys = [ 'alpha_shield_lock', 'alpha_shield_critical_alert_count', 'alpha_shield_critical_alert_flag', 'alpha_shield_formula_ids', 'alpha_shield_computed_at', ] as_ok = True for k in as_keys: v = hc.get(k, '(missing)') flag = ' <- [FAIL] 누락' if v == '(missing)' else '' if v == '(missing)': as_ok = False print(f' {k}: {v}{flag}') alpha_raw = hc.get('alpha_shield_json') if alpha_raw is None: print(' alpha_shield_json: (missing) <- [FAIL]') as_ok = False else: holdings = safe_json(alpha_raw) if isinstance(holdings, list) and holdings: print(f'\n per-holding ({len(holdings)}종목):') cols = f' {"Ticker":<8} {"MRG_Gate":<22} {"RS_Status":<16} {"W1":<20} {"W2":<20} {"W3":<20} {"W4":<24} {"Fires"}' print(cols) print(' ' + '-' * 135) for h in holdings: t = h.get('ticker', '?') mrg = h.get('mrg_gate', '?') rs = h.get('rs_status', '?') w1 = h.get('w1_status', '?') w2 = h.get('w2_status', '?') w3 = h.get('w3_status', '?') w4 = h.get('w4_status', '?') fires = h.get('radar_fires', '?') crit = ' [CRITICAL]' if h.get('critical_alert') == 'CRITICAL_ALERT' else '' print(f' {t:<8} {mrg:<22} {rs:<16} {w1:<20} {w2:<20} {w3:<20} {w4:<24} {fires}{crit}') # deviation_ratio / rs_ratio 상세 print() print(f' {"Ticker":<8} {"deviation_ratio":<18} {"rs_ratio":<12} {"sector":<12} {"sector_rank":<12} {"flow_accel_ratio"}') print(' ' + '-' * 90) for h in holdings: t = h.get('ticker', '?') dr = h.get('deviation_ratio', 'N/A') rsr = h.get('rs_ratio', 'N/A') sec = h.get('sector', 'N/A') or 'N/A' srk = h.get('sector_rank', 'N/A') far = h.get('flow_accel_ratio', 'N/A') print(f' {t:<8} {str(dr):<18} {str(rsr):<12} {str(sec):<12} {str(srk):<12} {far}') else: print(f' alpha_shield_json: parse error or empty ({type(holdings).__name__})') as_ok = False print() print(' Alpha-Shield 전체 상태:', 'PASS' if as_ok else '[FAIL] 일부 누락') # ── 4. X4 Ratchet ──────────────────────────────────────────────────────── print('\n[4] X4 Ratchet -- prices_json [신규 2026-05-19-X4R1]') prices_raw = hc.get('prices_json') if prices_raw is None: print(' prices_json: (missing)') else: prices = safe_json(prices_raw) if isinstance(prices, list): has_ratchet = any('ratchet_applied' in p for p in prices) if not has_ratchet: print(' [FAIL] ratchet_applied 필드 없음 -- X4 미적용') else: print(f' {"Ticker":<8} {"stop_price":<12} {"avg_cost":<10} {"atr20":<8} {"ratchet_applied":<32} note[:50]') print(' ' + '-' * 120) for p in prices: if 'error' in p: print(f' {p.get("ticker","?"):<8} ERROR: {p.get("error")}') continue t = p.get('ticker', '?') sp = p.get('stop_price', '?') ac = p.get('avg_cost', '?') atr = p.get('atr20', '?') ra = p.get('ratchet_applied', '(missing)') note = str(p.get('ratchet_note', ''))[:50] flag = ' <- [RATCHET ON]' if 'RATCHET' in str(ra) and 'INACTIVE' not in str(ra) and 'PASS' not in str(ra) else '' print(f' {t:<8} {str(sp):<12} {str(ac):<10} {str(atr):<8} {str(ra):<32} {note}{flag}') else: print(f' prices_json type: {type(prices).__name__}') # ── 5. Gate 1d -- Decision Trace ───────────────────────────────────────── print('\n[5] Gate 1d (MEAN_REVERSION_GATE) -- decision_trace [신규 Gate]') traces_raw = hc.get('decision_trace_json') if traces_raw is None: print(' decision_trace_json: (missing)') else: traces = safe_json(traces_raw) if isinstance(traces, list): mrg = [t for t in traces if t.get('state') == 'MEAN_REVERSION_GATE'] if not mrg: print(' [INFO] MEAN_REVERSION_GATE trace 없음') print(' -- 모든 종목이 BUY 아니거나 데이터 부재 (정상 가능)') else: print(f' {"Ticker":<8} {"Result":<20} reason') print(' ' + '-' * 80) for t in mrg: print(f' {t.get("ticker","?"):<8} {t.get("result","?"):<20} {t.get("reason","?")}') else: print(f' decode error: {type(traces).__name__}') # ── 6. Sell Priority ───────────────────────────────────────────────────── print('\n[6] Sell Priority (H2)') sc_raw = hc.get('sell_candidates_json') if sc_raw: cands = safe_json(sc_raw) if isinstance(cands, list): print(f' {"Rank":<5} {"Ticker":<8} {"Tier":<5} {"Score":<7} {"Trim_Style":<14} reason[:65]') print(' ' + '-' * 110) for c in cands[:12]: print(f' {str(c.get("rank","?")):<5} {c.get("ticker","?"):<8} ' f'{str(c.get("tier","?")):<5} {str(c.get("score","?")):<7} ' f'{str(c.get("trim_style","?")):<14} {str(c.get("reason",""))[:65]}') # ── 7. H5 Decision Flow ─────────────────────────────────────────────────── print('\n[7] H5 Decision Flow') dec_raw = hc.get('decisions_json') if dec_raw: decs = safe_json(dec_raw) if isinstance(decs, list): print(f' {"Ticker":<8} {"base_action":<20} {"final_action":<20} {"gate_changed":<14} trace gates') print(' ' + '-' * 100) for d in decs: gates = [tr.get('gate','?') + ':' + tr.get('result','?') for tr in d.get('gate_trace', [])] gc = str(d.get('gate_changed', '?')) print(f' {d.get("ticker","?"):<8} {d.get("base_action","?"):<20} ' f'{d.get("final_action","?"):<20} {gc:<14} {", ".join(gates)}') print() print(SEP) print('검증 완료') if __name__ == '__main__': main()