"""FINAL_JUDGMENT_GATE_V1 판단 결정론 계층 — 모든 게이트·신호 JSON + _harness_context를 읽어 종목별 단일 action_verdict를 결정론으로 산출한다. action_verdict ∈ {BUY_PILOT, HOLD, TRIM, SELL, WATCH, BLOCKED} 매도 판단 우선순위: 1. SELL : stop_breach=BREACH OR sell_waterfall stage_label ∈ {EMERGENCY_FULL, DISTRIBUTION_EXIT} 2. TRIM : sell_waterfall stage_label ∈ {URGENT_TRIM, TRIM_ONLY} 3. TP_TRIM: tp_trigger=TRIGGERED (미래 확장 예약) 매수 판단 AND-11 조건 (전부 PASS여야 BUY_PILOT): J01. anti_late_entry(alpha_lead buy_permission_state ≠ BLOCKED) J02. distribution(anti_distribution_state ≠ BLOCK_BUY) J03. breakout(Breakout_Gate ≠ WAIT OR lead_entry_state ≠ BLOCKED_LATE_CHASE) J04. smart_money_liquidity(gate_status ≠ BLOCK_BUY) J05. liquidity(execution_mode ≠ FROZEN) J06. macro(primary_gate ∉ {AVOID_NEW_BUY, HEDGE}) J07. fundamental(grade ∈ {A,B,C} — ETF 제외) J08. heat_gate(heat_gate_status ≠ BLOCK_NEW_BUY) J09. cash_floor(cash_floor_status == PASS) J10. position_count(position_count_gate ≠ POSITION_COUNT_BLOCK) J11. single_position_weight(single_position_weight_gate ≠ OVERWEIGHT_TRIM) harness_key 부재 → DATA_MISSING (silent PASS 금지) effective_confidence = round(raw_confidence × (0.4 + 0.6 × invest_quality_score/100), 1) """ from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any def _ensure_utf8_stdio() -> None: 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) if sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"): sys.stderr = open(sys.stderr.fileno(), mode="w", encoding="utf-8", buffering=1) ROOT = Path(__file__).resolve().parents[1] DEFAULT_JSON = ROOT / "GatherTradingData.json" DEFAULT_SM_GATE = ROOT / "Temp" / "smart_money_liquidity_gate_v1.json" DEFAULT_LIQUIDITY = ROOT / "Temp" / "liquidity_flow_signal_v1.json" DEFAULT_MACRO = ROOT / "Temp" / "macro_event_ticker_impact_v1.json" DEFAULT_FUNDAMENTAL = ROOT / "Temp" / "fundamental_multifactor_v3.json" DEFAULT_HORIZON = ROOT / "Temp" / "horizon_classification_v1.json" DEFAULT_SELL_WATERFALL = ROOT / "Temp" / "sell_waterfall_engine_v2.json" DEFAULT_DQ_RECONCILE = ROOT / "Temp" / "data_quality_reconciliation_v1.json" DEFAULT_OUT = ROOT / "Temp" / "final_judgment_gate_v1.json" # 매수 차단 집합 MACRO_BLOCK_NEW_BUY = {"AVOID_NEW_BUY", "HEDGE"} FUND_PASS_GRADES = {"A", "B", "C"} HEAT_BLOCK_VALUES = {"BLOCK_NEW_BUY"} CASH_PASS_VALUES = {"PASS"} POSITION_COUNT_BLOCK_VALUES = {"POSITION_COUNT_BLOCK"} SINGLE_WEIGHT_BLOCK_VALUES = {"OVERWEIGHT_TRIM", "OVERWEIGHT_BLOCK"} # 매도 waterfall stage 레이블 → verdict 매핑 SELL_STAGE_LABELS = {"EMERGENCY_FULL", "DISTRIBUTION_EXIT"} TRIM_STAGE_LABELS = {"URGENT_TRIM", "TRIM_ONLY"} # BUY AND-11 게이트 키 AND_GATE_KEYS = [ "J01_anti_late_entry", "J02_distribution", "J03_breakout", "J04_smart_money_liquidity", "J05_liquidity", "J06_macro", "J07_fundamental", "J08_heat_gate", "J09_cash_floor", "J10_position_count", "J11_single_position_weight", ] def _load_json(path: Path) -> dict[str, Any] | list: if not path.exists(): return {} try: obj = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return obj def _as_dict(obj: Any) -> dict[str, Any]: if isinstance(obj, dict): return obj return {} def _extract_harness_root(payload: dict[str, Any]) -> dict[str, Any]: h_apex = payload.get("hApex") data_apex = ((payload.get("data") or {}).get("_harness_context")) if isinstance(payload.get("data"), dict) else None if isinstance(h_apex, dict) and isinstance(data_apex, dict): merged = dict(data_apex) merged.update(h_apex) return merged if isinstance(h_apex, dict): return h_apex if isinstance(data_apex, dict): return data_apex return payload def _extract_data_feed(payload: dict[str, Any]) -> list[dict[str, Any]]: data_node = payload.get("data") or {} if isinstance(data_node, dict): feed = data_node.get("data_feed") if isinstance(feed, list): return feed return [] def _rows_by_ticker(obj: Any) -> dict[str, dict[str, Any]]: """JSON 산출물(list 또는 {rows:[]} dict)을 ticker→row dict로 변환.""" rows: list[dict[str, Any]] = [] if isinstance(obj, list): rows = [r for r in obj if isinstance(r, dict)] elif isinstance(obj, dict): for key in ("rows", "tickers", "data"): candidate = obj.get(key) if isinstance(candidate, list): rows = [r for r in candidate if isinstance(r, dict)] break return {str(r.get("ticker") or ""): r for r in rows if r.get("ticker")} def _parse_hctx_list(hctx: dict[str, Any], key: str) -> dict[str, dict[str, Any]]: val = hctx.get(key) if isinstance(val, str): try: val = json.loads(val) except Exception: return {} return _rows_by_ticker(val or {}) def _as_float(v: Any, default: float = 0.0) -> float: try: return float(v) except Exception: return default # ───────────────────────────────────────────── # BUY AND-11 게이트 평가 함수들 # ───────────────────────────────────────────── def _gate_result(gate_key: str, status: str, value: Any, detail: str = "") -> dict[str, Any]: return {"gate": gate_key, "status": status, "value": value, "detail": detail} def _eval_j01_anti_late_entry( alpha_row: dict[str, Any] | None, df_row: dict[str, Any], ) -> dict[str, Any]: """J01: anti_late_entry — alpha_lead buy_permission_state ≠ BLOCKED.""" if alpha_row is None: return _gate_result("J01_anti_late_entry", "DATA_MISSING", None, "alpha_lead_json row not found") bps = str(alpha_row.get("buy_permission_state") or "") passed = bps != "BLOCKED" return _gate_result( "J01_anti_late_entry", "PASS" if passed else "BLOCK", bps, str(alpha_row.get("lead_entry_state") or ""), ) def _eval_j02_distribution(dist_row: dict[str, Any] | None) -> dict[str, Any]: """J02: distribution — anti_distribution_state ≠ BLOCK_BUY.""" if dist_row is None: return _gate_result("J02_distribution", "DATA_MISSING", None, "distribution_risk_json row not found") ads = str(dist_row.get("anti_distribution_state") or "") passed = ads != "BLOCK_BUY" return _gate_result( "J02_distribution", "PASS" if passed else "BLOCK", ads, f"score={dist_row.get('distribution_risk_score')}", ) def _eval_j03_breakout( alpha_row: dict[str, Any] | None, df_row: dict[str, Any], ) -> dict[str, Any]: """J03: breakout — Breakout_Gate ≠ WAIT OR alpha_lead confirmed.""" breakout_gate = str(df_row.get("Breakout_Gate") or "MISSING") if alpha_row is not None: les = str(alpha_row.get("lead_entry_state") or "") confirmed = breakout_gate not in {"WAIT", "MISSING"} or les not in {"BLOCKED_LATE_CHASE", "WATCH"} else: confirmed = breakout_gate not in {"WAIT", "MISSING"} return _gate_result( "J03_breakout", "PASS" if confirmed else "BLOCK", breakout_gate, f"Breakout_Score={df_row.get('Breakout_Score')}", ) def _eval_j04_smart_money(sm_row: dict[str, Any] | None) -> dict[str, Any]: """J04: smart_money_liquidity gate_status ≠ BLOCK_BUY.""" if sm_row is None: return _gate_result("J04_smart_money_liquidity", "DATA_MISSING", None, "smart_money_liquidity_gate row not found") gs = str(sm_row.get("gate_status") or "") passed = gs != "BLOCK_BUY" return _gate_result("J04_smart_money_liquidity", "PASS" if passed else "BLOCK", gs) def _eval_j05_liquidity(lq_row: dict[str, Any] | None) -> dict[str, Any]: """J05: liquidity execution_mode ≠ FROZEN.""" if lq_row is None: return _gate_result("J05_liquidity", "DATA_MISSING", None, "liquidity_flow_signal row not found") em = str(lq_row.get("execution_mode") or "") passed = em != "FROZEN" return _gate_result("J05_liquidity", "PASS" if passed else "BLOCK", em) def _eval_j06_macro(macro_row: dict[str, Any] | None) -> dict[str, Any]: """J06: macro primary_gate ∉ {AVOID_NEW_BUY, HEDGE}.""" if macro_row is None: return _gate_result("J06_macro", "DATA_MISSING", None, "macro_event_ticker_impact row not found") pg = str(macro_row.get("primary_gate") or "") passed = pg not in MACRO_BLOCK_NEW_BUY return _gate_result("J06_macro", "PASS" if passed else "BLOCK", pg) def _eval_j07_fundamental(fund_row: dict[str, Any] | None, is_etf: bool) -> dict[str, Any]: """J07: fundamental grade ∈ {A,B,C} — ETF 제외.""" if is_etf: return _gate_result("J07_fundamental", "EXEMPT", "ETF", "ETF는 펀더멘털 게이트 면제") if fund_row is None: return _gate_result("J07_fundamental", "DATA_MISSING", None, "fundamental_multifactor row not found") grade = str(fund_row.get("grade") or "") passed = grade in FUND_PASS_GRADES return _gate_result("J07_fundamental", "PASS" if passed else "BLOCK", grade) def _eval_j08_heat(hctx: dict[str, Any]) -> dict[str, Any]: """J08: heat_gate_status ≠ BLOCK_NEW_BUY (포트폴리오 레벨).""" status = str(hctx.get("heat_gate_status") or "") if not status: return _gate_result("J08_heat_gate", "DATA_MISSING", None, "heat_gate_status not found") passed = status not in HEAT_BLOCK_VALUES return _gate_result("J08_heat_gate", "PASS" if passed else "BLOCK", status) def _eval_j09_cash_floor(hctx: dict[str, Any]) -> dict[str, Any]: """J09: cash_floor_status == PASS.""" status = str(hctx.get("cash_floor_status") or "") if not status: return _gate_result("J09_cash_floor", "DATA_MISSING", None, "cash_floor_status not found") passed = status in CASH_PASS_VALUES return _gate_result("J09_cash_floor", "PASS" if passed else "BLOCK", status) def _eval_j10_position_count(hctx: dict[str, Any]) -> dict[str, Any]: """J10: position_count_gate ≠ POSITION_COUNT_BLOCK.""" status = str(hctx.get("position_count_gate") or "") if not status: return _gate_result("J10_position_count", "DATA_MISSING", None, "position_count_gate not found") passed = status not in POSITION_COUNT_BLOCK_VALUES return _gate_result("J10_position_count", "PASS" if passed else "BLOCK", status) def _eval_j11_single_weight(hctx: dict[str, Any]) -> dict[str, Any]: """J11: single_position_weight_gate ≠ OVERWEIGHT (포트폴리오 레벨).""" status = str(hctx.get("single_position_weight_gate") or "") if not status: return _gate_result("J11_single_position_weight", "DATA_MISSING", None, "single_position_weight_gate not found") passed = status not in SINGLE_WEIGHT_BLOCK_VALUES return _gate_result("J11_single_position_weight", "PASS" if passed else "BLOCK", status) # ───────────────────────────────────────────── # 매도 신호 평가 # ───────────────────────────────────────────── def _eval_stop_breach(sb_row: dict[str, Any] | None) -> str: """BREACH → SELL, APPROACHING → WARN, else SAFE.""" if sb_row is None: return "SAFE" return str(sb_row.get("stop_breach_gate") or "SAFE") def _eval_tp_trigger(tp_row: dict[str, Any] | None) -> str: """TP 트리거 상태.""" if tp_row is None: return "NONE" return str(tp_row.get("tp_trigger_gate") or "NONE") def _eval_sell_waterfall(sw_row: dict[str, Any] | None) -> tuple[int, str]: """(stage, stage_label) — 미존재 시 (0, NONE).""" if sw_row is None: return 0, "NONE" stage = int(sw_row.get("stage") or 0) label = str(sw_row.get("stage_label") or "NONE") return stage, label # ───────────────────────────────────────────── # 종목별 판단 집약 # ───────────────────────────────────────────── def _compute_raw_confidence(and_trace: list[dict[str, Any]]) -> float: """AND-11 결과로부터 원시 신뢰도 산출 (PASS/EXEMPT=1.0, DATA_MISSING=0.5, BLOCK=0.0).""" if not and_trace: return 0.0 weights = {"PASS": 1.0, "EXEMPT": 1.0, "DATA_MISSING": 0.5, "BLOCK": 0.0} total = sum(weights.get(g["status"], 0.0) for g in and_trace) return round(total / len(and_trace) * 100.0, 1) def _compute_effective_confidence(raw: float, invest_quality_score: float) -> float: """effective_confidence = raw × (0.4 + 0.6 × iq/100).""" cap_factor = 0.4 + 0.6 * (invest_quality_score / 100.0) return round(raw * cap_factor, 1) def _evaluate_ticker( df_row: dict[str, Any], hctx: dict[str, Any], alpha_by_ticker: dict[str, dict], dist_by_ticker: dict[str, dict], sm_by_ticker: dict[str, dict], lq_by_ticker: dict[str, dict], macro_by_ticker: dict[str, dict], fund_by_ticker: dict[str, dict], horizon_by_ticker: dict[str, dict], sb_by_ticker: dict[str, dict], tp_by_ticker: dict[str, dict], sw_by_ticker: dict[str, dict], invest_quality_score: float, ) -> dict[str, Any]: ticker = str(df_row.get("Ticker") or "UNKNOWN") name = str(df_row.get("Name") or "") is_etf = str(df_row.get("SS001_Grade") or "") == "ETF" or ticker in {"0117V0", "494670", "471990", "091160"} # fund row로 is_etf 재확인 fund_row = fund_by_ticker.get(ticker) if fund_row is not None: is_etf = bool(fund_row.get("is_etf") or False) # AND-11 게이트 평가 and_trace: list[dict[str, Any]] = [ _eval_j01_anti_late_entry(alpha_by_ticker.get(ticker), df_row), _eval_j02_distribution(dist_by_ticker.get(ticker)), _eval_j03_breakout(alpha_by_ticker.get(ticker), df_row), _eval_j04_smart_money(sm_by_ticker.get(ticker)), _eval_j05_liquidity(lq_by_ticker.get(ticker)), _eval_j06_macro(macro_by_ticker.get(ticker)), _eval_j07_fundamental(fund_row, is_etf), _eval_j08_heat(hctx), _eval_j09_cash_floor(hctx), _eval_j10_position_count(hctx), _eval_j11_single_weight(hctx), ] blocking_gates = [g["gate"] for g in and_trace if g["status"] == "BLOCK"] data_missing_gates = [g["gate"] for g in and_trace if g["status"] == "DATA_MISSING"] # 매도 신호 평가 stop_breach_status = _eval_stop_breach(sb_by_ticker.get(ticker)) tp_status = _eval_tp_trigger(tp_by_ticker.get(ticker)) sw_stage, sw_label = _eval_sell_waterfall(sw_by_ticker.get(ticker)) # 호라이즌 horiz_row = horizon_by_ticker.get(ticker) horizon = str((horiz_row or {}).get("horizon") or "UNKNOWN") # 신뢰도 산출 raw_confidence = _compute_raw_confidence(and_trace) effective_confidence = _compute_effective_confidence(raw_confidence, invest_quality_score) # ── Verdict 결정 (우선순위 엄격 적용) ────────────────────────────────── verdict_reason: list[str] = [] if stop_breach_status == "BREACH": action_verdict = "SELL" verdict_reason.append(f"stop_breach=BREACH") elif sw_label in SELL_STAGE_LABELS: action_verdict = "SELL" verdict_reason.append(f"sell_waterfall={sw_label}(stage={sw_stage})") elif sw_label in TRIM_STAGE_LABELS and sw_stage > 0: action_verdict = "TRIM" verdict_reason.append(f"sell_waterfall={sw_label}(stage={sw_stage})") elif tp_status == "TRIGGERED": action_verdict = "TRIM" verdict_reason.append(f"tp_trigger=TRIGGERED") elif not blocking_gates and not data_missing_gates: action_verdict = "BUY_PILOT" verdict_reason.append("all_AND11_pass") elif blocking_gates: action_verdict = "BLOCKED" verdict_reason.extend([f"blocked_by:{g}" for g in blocking_gates[:3]]) elif data_missing_gates: action_verdict = "WATCH" verdict_reason.append(f"data_missing:{len(data_missing_gates)}gates") else: action_verdict = "HOLD" verdict_reason.append("no_active_signals") # stop_approaching → WATCH 강등(HOLD만 해당, SELL/TRIM 아닌 경우) if stop_breach_status == "APPROACHING" and action_verdict in {"HOLD", "BUY_PILOT"}: action_verdict = "WATCH" verdict_reason.append("stop_approaching") return { "ticker": ticker, "name": name, "action_verdict": action_verdict, "verdict_reason": verdict_reason, "raw_confidence": raw_confidence, "effective_confidence": effective_confidence, "invest_quality_cap_factor": round(0.4 + 0.6 * (invest_quality_score / 100.0), 3), "horizon": horizon, "is_etf": is_etf, "and_trace": and_trace, "blocking_gates": blocking_gates, "data_missing_gates": data_missing_gates, "sell_signals": { "stop_breach_gate": stop_breach_status, "tp_trigger_gate": tp_status, "sell_waterfall_stage": sw_stage, "sell_waterfall_label": sw_label, }, "formula_id": "FINAL_JUDGMENT_GATE_V1", } def main() -> int: _ensure_utf8_stdio() ap = argparse.ArgumentParser(description="FINAL_JUDGMENT_GATE_V1 — 판단 결정론 계층") ap.add_argument("--json", default=str(DEFAULT_JSON)) ap.add_argument("--sm-gate", default=str(DEFAULT_SM_GATE)) ap.add_argument("--liquidity", default=str(DEFAULT_LIQUIDITY)) ap.add_argument("--macro", default=str(DEFAULT_MACRO)) ap.add_argument("--fundamental", default=str(DEFAULT_FUNDAMENTAL)) ap.add_argument("--horizon", default=str(DEFAULT_HORIZON)) ap.add_argument("--sell-waterfall", default=str(DEFAULT_SELL_WATERFALL)) ap.add_argument("--dq-reconcile", default=str(DEFAULT_DQ_RECONCILE)) ap.add_argument("--out", default=str(DEFAULT_OUT)) args = ap.parse_args() def _rp(s: str) -> Path: p = Path(s) return p if p.is_absolute() else ROOT / p json_path = _rp(args.json) out_path = _rp(args.out) # ─── 데이터 로드 ───────────────────────────────────────────────────── main_data = _as_dict(_load_json(json_path)) hctx = _extract_harness_root(main_data) feed = _extract_data_feed(main_data) if not feed: print("ERROR: data_feed 비어 있음", file=sys.stderr) return 1 sm_gate = _as_dict(_load_json(_rp(args.sm_gate))) lq_data = _load_json(_rp(args.liquidity)) macro_data = _as_dict(_load_json(_rp(args.macro))) fund_data = _as_dict(_load_json(_rp(args.fundamental))) horizon_data = _as_dict(_load_json(_rp(args.horizon))) sw_data = _as_dict(_load_json(_rp(args.sell_waterfall))) dq_data = _as_dict(_load_json(_rp(args.dq_reconcile))) invest_quality_source_score = float(dq_data.get("investment_quality_score") or 0.0) invest_quality_cap_basis_score = float(dq_data.get("confidence_cap_basis_score") or invest_quality_source_score or 0.0) invest_quality_score = invest_quality_cap_basis_score or invest_quality_source_score # harness_context에서 per-ticker JSON 추출 alpha_by_ticker = _parse_hctx_list(hctx, "alpha_lead_json") dist_by_ticker = _parse_hctx_list(hctx, "distribution_risk_json") sb_by_ticker = _parse_hctx_list(hctx, "stop_breach_alert_json") tp_by_ticker = _parse_hctx_list(hctx, "tp_trigger_alert_json") # 외부 JSON 파일에서 per-ticker 추출 sm_by_ticker = _rows_by_ticker(sm_gate) lq_by_ticker = _rows_by_ticker(lq_data) macro_by_ticker = _rows_by_ticker(macro_data) fund_by_ticker = _rows_by_ticker(fund_data) horizon_by_ticker = _rows_by_ticker(horizon_data) sw_by_ticker = _rows_by_ticker(sw_data) # ─── 평가 실행 ──────────────────────────────────────────────────────── rows: list[dict[str, Any]] = [] verdict_counts: dict[str, int] = {} silent_pass_violations = 0 # 반드시 0이어야 함 for df_row in feed: result = _evaluate_ticker( df_row, hctx, alpha_by_ticker, dist_by_ticker, sm_by_ticker, lq_by_ticker, macro_by_ticker, fund_by_ticker, horizon_by_ticker, sb_by_ticker, tp_by_ticker, sw_by_ticker, invest_quality_score, ) rows.append(result) v = result["action_verdict"] verdict_counts[v] = verdict_counts.get(v, 0) + 1 # silent PASS 감시: DATA_MISSING이 있는데 BUY_PILOT이면 위반 if result["data_missing_gates"] and v == "BUY_PILOT": silent_pass_violations += 1 # 뒷박 후보 검증 (velocity≥3% OR distribution≥2.0인데 BUY_PILOT이면 위반) late_chase_buy_violations = [] for r in rows: if r["action_verdict"] != "BUY_PILOT": continue ticker = r["ticker"] df_row = next((x for x in feed if x.get("Ticker") == ticker), {}) ret5d = abs(float(df_row.get("Ret5D") or 0.0)) dist_r = dist_by_ticker.get(ticker) or {} dist_score = int(dist_r.get("distribution_risk_score") or 0) if ret5d >= 3.0 or dist_score >= 60: late_chase_buy_violations.append({"ticker": ticker, "ret5d": ret5d, "dist_score": dist_score}) coverage_pct = round(100.0 * len(rows) / max(1, len(feed)), 2) gate_ok = "PASS" if (silent_pass_violations == 0 and not late_chase_buy_violations) else "FAIL" out = { "formula_id": "FINAL_JUDGMENT_GATE_V1", "gate": gate_ok, "coverage_pct": coverage_pct, "ticker_count": len(rows), "invest_quality_score": invest_quality_score, "invest_quality_source_score": invest_quality_source_score, "invest_quality_cap_basis_score": invest_quality_cap_basis_score, "invest_quality_cap_factor": round(0.4 + 0.6 * (invest_quality_score / 100.0), 3), "verdict_counts": verdict_counts, "silent_pass_violations": silent_pass_violations, "late_chase_buy_violations": late_chase_buy_violations, "rows": rows, } out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") print("FINAL_JUDGMENT_GATE_V1") print(f" tickers: {len(rows)}/{len(feed)} (coverage={coverage_pct}%)") print(f" invest_quality_score: {invest_quality_score} (source={invest_quality_source_score}) → cap_factor={round(0.4+0.6*invest_quality_score/100,3)}") print(f" verdict_counts: {verdict_counts}") print(f" silent_pass_violations: {silent_pass_violations} (반드시 0)") print(f" late_chase_buy_violations: {len(late_chase_buy_violations)} (반드시 0)") print(f" gate: {gate_ok}") print() print(f" {'TICKER':<10} {'VERDICT':<12} {'RAW_CONF':>8} {'EFF_CONF':>8} BLOCKING_GATES") for r in rows: blocking = ",".join(r['blocking_gates'][:3]) if r['blocking_gates'] else "NONE" print(f" {r['ticker']:<10} {r['action_verdict']:<12} {r['raw_confidence']:>8.1f} {r['effective_confidence']:>8.1f} {blocking}") return 0 if gate_ok == "PASS" else 1 if __name__ == "__main__": raise SystemExit(main())