"""回填期期到期组的 settle_index(及可选内在价值成交)。 用法: $env:DEPLOY_PASS='...' $env:REPAIR_GROUP_ID='G-20260810-01' # 可选: $env:SETTLE_INDEX='1877.8' 不设则用币安 ETHUSDT 1m 收盘 python scripts/repair_oo_settle_index.py """ from __future__ import annotations import json import os import sys import urllib.request HOST = os.environ.get("DEPLOY_HOST", "47.236.184.99") USER = os.environ.get("DEPLOY_USER", "root") PASSWORD = os.environ.get("DEPLOY_PASS", "") ROOT = os.environ.get("DEPLOY_ROOT", "/opt/eth_hedge_sim") DB = f"{ROOT}/backend/data/hedge.db" GROUP = os.environ.get("REPAIR_GROUP_ID", "G-20260810-01") SETTLE = os.environ.get("SETTLE_INDEX", "").strip() REPAIR_FILLS = os.environ.get("REPAIR_FILLS", "1").strip() not in ("0", "false", "no") def _eth_close(ms: int) -> float | None: minute = (int(ms) // 60_000) * 60_000 url = ( "https://api.binance.com/api/v3/klines" f"?symbol=ETHUSDT&interval=1m&startTime={minute}&limit=1" ) with urllib.request.urlopen(url, timeout=10) as resp: rows = json.loads(resp.read().decode()) if not rows: return None return float(rows[0][4]) REMOTE = r''' import sqlite3 DB = %(db)r GROUP = %(group)r SETTLE = %(settle)s REPAIR_FILLS = %(repair_fills)s con = sqlite3.connect(DB) con.row_factory = sqlite3.Row g = con.execute("SELECT * FROM groups WHERE group_id=?", (GROUP,)).fetchone() if not g: print("missing", GROUP) raise SystemExit(1) if str(g["close_reason"] or "") != "expiry": print("not expiry", GROUP) raise SystemExit(1) settle = float(SETTLE) strike = float(g["strike"] or 0) strike2 = float(g["strike2"] or 0) if g["strike2"] is not None else None side = str(g["option_side"] or "call").lower() side2 = str(g["option2_side"] or "put").lower() def intrinsic(side, spot, k): if k is None: return None if side in ("call", "c"): return max(spot - k, 0.0) if side in ("put", "p"): return max(k - spot, 0.0) return 0.0 c_iv = intrinsic(side, settle, strike) p_iv = intrinsic(side2, settle, strike2) if strike2 is not None else None con.execute("BEGIN") con.execute( "UPDATE groups SET settle_index_px=?, note=COALESCE(note,'') || ? WHERE group_id=?", (settle, f" | settle_backfill={settle}", GROUP), ) updated = [] if REPAIR_FILLS: for leg, iv in (("option", c_iv), ("option2", p_iv)): if iv is None: continue row = con.execute( "SELECT id, qty_eth, fill_px FROM fills WHERE group_id=? AND leg=? AND action='close'", (GROUP, leg), ).fetchone() if not row: continue old_px = float(row["fill_px"] or 0) tol = max(0.5, abs(iv) * 0.05) if abs(old_px - iv) <= tol: continue qty = float(row["qty_eth"] or 0) con.execute( "UPDATE fills SET base_px=?, fill_px=?, notional=?, slip=0 WHERE id=?", (iv, iv, iv * qty, row["id"]), ) updated.append((leg, old_px, iv, qty)) # 重算 realized fills = list(con.execute("SELECT * FROM fills WHERE group_id=?", (GROUP,))) opt = {} for f in fills: key = (f["leg"], f["action"]) opt[key] = f net = 0.0 fees = 0.0 for leg in ("option", "option2"): o = opt.get((leg, "open")) c = opt.get((leg, "close")) if not o or not c: continue qty = float(o["qty_eth"] or c["qty_eth"] or 0) net += (float(c["fill_px"]) - float(o["fill_px"])) * qty fees += float(o["fee"] or 0) + float(c["fee"] or 0) net_after = net - fees con.execute( "UPDATE groups SET realized_pnl=? WHERE group_id=?", (net_after, GROUP), ) con.commit() print({ "group": GROUP, "settle": settle, "call_iv": c_iv, "put_iv": p_iv, "fills_updated": updated, "realized_pnl": net_after, }) ''' def main() -> int: if not PASSWORD: print("Set DEPLOY_PASS", file=sys.stderr) return 2 import paramiko settle = SETTLE if not settle: # peek close_at from remote first client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(HOST, username=USER, password=PASSWORD, timeout=30) _, stdout, _ = client.exec_command( f"sqlite3 {DB} \"SELECT close_at_ms FROM groups WHERE group_id='{GROUP}'\"" ) raw = stdout.read().decode().strip() client.close() if not raw: print("cannot read close_at_ms", file=sys.stderr) return 1 px = _eth_close(int(raw)) if px is None: print("cannot fetch ETHUSDT close", file=sys.stderr) return 1 settle = f"{px:.4f}" print("using public ETHUSDT close", settle) body = REMOTE % { "db": DB, "group": GROUP, "settle": settle, "repair_fills": "True" if REPAIR_FILLS else "False", } client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(HOST, username=USER, password=PASSWORD, timeout=30) _, stdout, stderr = client.exec_command("python3 - <<'PY'\n" + body + "\nPY") print(stdout.read().decode("utf-8", "replace")) err = stderr.read().decode("utf-8", "replace") if err: print(err, file=sys.stderr) code = stdout.channel.recv_exit_status() client.close() return code if __name__ == "__main__": raise SystemExit(main())