from __future__ import annotations import argparse import json from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_JSON = ROOT / "GatherTradingData.json" DEFAULT_PERF = ROOT / "Temp" / "perf_recovery_harness_v1.json" DEFAULT_DQ = ROOT / "Temp" / "data_integrity_100_lock_v1.json" def _load(path: Path) -> dict[str, Any]: if not path.exists(): return {} try: obj = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return obj if isinstance(obj, dict) else {} def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--json", default=str(DEFAULT_JSON)) ap.add_argument("--perf", default=str(DEFAULT_PERF)) ap.add_argument("--dq", default=str(DEFAULT_DQ)) args = ap.parse_args() jp = Path(args.json) pp = Path(args.perf) dp = Path(args.dq) if not jp.is_absolute(): jp = ROOT / jp if not pp.is_absolute(): pp = ROOT / pp if not dp.is_absolute(): dp = ROOT / dp payload = _load(jp) perf = _load(pp) dq = _load(dp) data = payload.get("data") if isinstance(payload.get("data"), dict) else {} hctx = data.get("_harness_context") if isinstance(data.get("_harness_context"), dict) else {} perf_gate = str(perf.get("gate") or "DATA_MISSING") dq_gate = str(dq.get("gate") or "DATA_MISSING") reasons = perf.get("reasons") if isinstance(perf.get("reasons"), list) else [] metrics = perf.get("metrics") if isinstance(perf.get("metrics"), dict) else {} overrides = { "formula_id": "PERF_RECOVERY_OVERRIDES_V1", "active": False, "perf_gate": perf_gate, "dq_gate": dq_gate, "reason_codes": reasons, "buy": { "force_watch_only": False, "pilot_threshold_uplift": 0, "max_new_positions": None, }, "sell": { "min_rebound_wait_ratio": 0.5, "force_no_full_dump_without_emergency": True, "value_damage_cap_pct": 10.0, }, } if perf_gate == "FAIL": overrides["active"] = True # 뒷북/설거지 국면에서는 신규 진입 문턱 강화 overrides["buy"]["force_watch_only"] = True overrides["buy"]["pilot_threshold_uplift"] = 10 overrides["buy"]["max_new_positions"] = 0 # 매도는 반등대기 비중 최소화 하한 유지 overrides["sell"]["min_rebound_wait_ratio"] = 0.6 if dq_gate != "PASS_100": overrides["active"] = True # 데이터 완전성 미달 시 신규 매수 잠금 overrides["buy"]["force_watch_only"] = True overrides["buy"]["max_new_positions"] = 0 # 하네스 컨텍스트에 주입 (LLM 자유 해석 금지용) hctx["perf_recovery_overrides_json"] = json.dumps(overrides, ensure_ascii=False) hctx["perf_recovery_overrides_lock"] = True hctx["perf_recovery_gate"] = "ACTIVE" if overrides["active"] else "PASS" hctx["perf_recovery_watch_miss_rate"] = metrics.get("watch_miss_rate") hctx["perf_recovery_t20_pass_rate"] = metrics.get("t20_pass_rate") hctx["perf_recovery_value_damage"] = metrics.get("rebound_sell_value_damage") data["_harness_context"] = hctx payload["data"] = data jp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps({ "formula_id": "PERF_RECOVERY_OVERRIDES_V1", "applied": True, "active": overrides["active"], "perf_gate": perf_gate, "dq_gate": dq_gate, "buy_force_watch_only": overrides["buy"]["force_watch_only"], "max_new_positions": overrides["buy"]["max_new_positions"], "min_rebound_wait_ratio": overrides["sell"]["min_rebound_wait_ratio"], }, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())