from __future__ import annotations """SMART_CASH_RECOVERY_V6 — ADV-1 지침 충족 빌더. V5 위에서 추가 강화: 1. exec_mode=EXECUTE_REBOUND_ONLY 시 현재가 매도 금지, rebound_trigger_price 전사 2. numeric_generation_allowed=0 잠금 필드 추가 3. 결정론 체크섬(input_hash) — 동일 입력 = 동일 출력 보장 """ import argparse import hashlib import json import subprocess from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_JSON = ROOT / "GatherTradingData.json" DEFAULT_REBOUND = ROOT / "Temp" / "rebound_sell_efficiency_v1.json" DEFAULT_OUT = ROOT / "Temp" / "smart_cash_recovery_v6.json" TEMP_V5 = ROOT / "Temp" / "_smart_cash_recovery_v5_for_v6.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 _input_hash(jp: Path, rp: Path) -> str: h = hashlib.sha256() for p in (jp, rp): if p.exists(): h.update(p.read_bytes()) return h.hexdigest()[:16] def _enrich_row_v6(row: dict[str, Any], global_exec_allowed: bool) -> dict[str, Any]: """ADV-1 Step3/4 강제 적용 — rebound 전사 및 exec_mode 잠금.""" enriched = dict(row) exec_mode = str(row.get("exec_mode") or "") rebound_price = row.get("rebound_trigger_price") if exec_mode == "EXECUTE_REBOUND_ONLY": enriched["hts_order_type"] = "LIMIT_SELL" enriched["hts_limit_price"] = rebound_price enriched["hts_immediate_sell_blocked"] = True enriched["hts_block_reason"] = "EXECUTE_REBOUND_ONLY: 반등지정가만 허용" else: enriched["hts_order_type"] = row.get("sell_order_type", "LIMIT_SELL") enriched["hts_limit_price"] = row.get("sell_limit_price") or rebound_price enriched["hts_immediate_sell_blocked"] = False enriched["hts_block_reason"] = "" enriched["hts_candidate"] = global_exec_allowed and not enriched["hts_immediate_sell_blocked"] enriched["ledger_only"] = not enriched["hts_candidate"] return enriched def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--json", default=str(DEFAULT_JSON)) ap.add_argument("--rebound", default=str(DEFAULT_REBOUND)) ap.add_argument("--out", default=str(DEFAULT_OUT)) args = ap.parse_args() jp = Path(args.json) if Path(args.json).is_absolute() else ROOT / args.json rp = Path(args.rebound) if Path(args.rebound).is_absolute() else ROOT / args.rebound op = Path(args.out) if Path(args.out).is_absolute() else ROOT / args.out # 결정론 해시 ihash = _input_hash(jp, rp) # V5 빌드 cmd = [ "python", "tools/build_smart_cash_recovery_v5.py", "--json", str(jp), "--rebound", str(rp), "--out", str(TEMP_V5), ] proc = subprocess.run(cmd, cwd=str(ROOT), text=True, capture_output=True, encoding="utf-8", errors="replace") if proc.returncode != 0: print((proc.stdout or "") + (proc.stderr or "")) return proc.returncode v5 = _load(TEMP_V5) result = dict(v5) global_exec = bool(result.get("execution_allowed")) # V6: ADV-1 강화 — 각 행에 rebound 전사 + exec_mode 잠금 selected_combo = [ _enrich_row_v6(row, global_exec) for row in (result.get("selected_sell_combo") or []) if isinstance(row, dict) ] result["selected_sell_combo"] = selected_combo # ADV-1 필수 잠금 필드 result["formula_id"] = "SMART_CASH_RECOVERY_V6" result["upgraded_from"] = "SMART_CASH_RECOVERY_V5" result["numeric_generation_allowed"] = 0 # LLM 수치 생성 금지 result["input_hash"] = ihash # 결정론 체크섬 result["v6_enforced"] = { "rebound_trigger_price_wired": True, "exec_mode_lock": True, "numeric_generation_allowed": 0, "hts_candidate_rows": len([r for r in selected_combo if r.get("hts_candidate")]), "ledger_only_rows": len([r for r in selected_combo if r.get("ledger_only")]), "execute_rebound_only_rows": len([r for r in selected_combo if r.get("hts_immediate_sell_blocked")]), } op.parent.mkdir(parents=True, exist_ok=True) op.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") vd = result.get("value_damage_pct_avg", 999) covered = result.get("cash_shortfall_covered", False) print( f"SMART_CASH_RECOVERY_V6 input_hash={ihash} " f"execution_allowed={global_exec} value_damage={vd} covered={covered} " f"hts_rows={result['v6_enforced']['hts_candidate_rows']}" ) return 0 if __name__ == "__main__": raise SystemExit(main())