/**
 * OKX 对冲计划 P0:行情 + 永期列表 / 期期 T + 情景测算 + 门禁.
 */
(function () {
  const root = document.getElementById("hedge-plan-root");
  if (!root) return;

  function flagOn(attr) {
    return root.getAttribute(attr) !== "0";
  }

  const showPerp = flagOn("data-show-perp");
  const showOo = flagOn("data-show-oo");

  function pickDefaultTab() {
    if (showPerp) return "perp_options";
    if (showOo) return "options_options";
    return "active";
  }

  const state = {
    tab: pickDefaultTab(),
    mode: showPerp ? "perp_options" : showOo ? "options_options" : "perp_options",
    underlying: root.getAttribute("data-default-underly") || "ETH",
    moneyFilter: root.getAttribute("data-option-primary") !== "0" ? "otm" : "itm",
    ooMoneyFilter: "atm_otm", // 期期锁定:平值+虚值
    ooRecommend: null, // atm_straddle | double_otm | null
    ooStrikeExpandAll: false, // 默认 Call/Put 各 3 档
    chain: null,
    selected: null,
    legA: null,
    legB: null,
    market: null,
    ooSheetsMode: "same_sheets",
    ooBiasSplitBy: "budget",
    ooBiasRatio: 0.7,
    ooCloseModeEnabled: root.getAttribute("data-oo-close-mode-enabled") !== "0",
    ooCloseMode: "close_all",
    direction: "long",
    optionPrimary: root.getAttribute("data-option-primary") !== "0",
    opLevTouched: false,
    opRatioTouched: false,
    tradingUsdc: null,
    fundingUsdc: null,
    tradeBudgetUsdc: null,
    budgetBuffer: (function () {
      const raw = root.getAttribute("data-budget-buffer");
      const n = raw != null && raw !== "" ? Number(raw) : NaN;
      return !Number.isNaN(n) && n > 0 ? n : 0.95;
    })(),
    previewOk: false,
    canStart: false,
    previewPlanType: null,
  };

  function getDirection() {
    return (state.direction || "long").toLowerCase() === "short" ? "short" : "long";
  }

  function syncPoDirUI() {
    const dir = getDirection();
    document.querySelectorAll(".hp-po-dir").forEach(function (b) {
      const on = (b.getAttribute("data-dir") || "") === dir;
      b.classList.toggle("is-selected", on);
      b.classList.toggle("active", on);
    });
  }

  function setDirection(dir, forceReload) {
    const next = (dir || "long").toLowerCase() === "short" ? "short" : "long";
    const changed = next !== getDirection();
    state.direction = next;
    syncPoDirUI();
    if (!changed && !forceReload) return;
    state.selected = null;
    if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
    void loadMarket().then(function () {
      renderListStrikes();
    });
  }

  function $(id) {
    return document.getElementById(id);
  }

  async function apiJson(url, opts) {
    const res = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
    const data = await res.json().catch(function () {
      return {};
    });
    if (!res.ok) throw new Error(data.msg || res.statusText || "请求失败");
    return data;
  }

  function fmt(v, d) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    return Number(v).toFixed(d == null ? 2 : d);
  }

  function fmtOptionPx(v, tickSz) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    const n = Number(v);
    const tick = Number(tickSz);
    if (!tickSz || Number.isNaN(tick) || tick <= 0) {
      return String(n).replace(/(\.\d*?[1-9])0+$/, "$1").replace(/\.0+$/, "");
    }
    let decimals = 0;
    if (tick < 1) decimals = Math.max(0, -Math.round(Math.log10(tick)));
    else if (String(tick).indexOf(".") >= 0) decimals = String(tick).split(".")[1].length;
    let s = n.toFixed(decimals);
    // 仅裁小数尾零;整数 tick(BTC=5)时绝不能把 1370 裁成 137
    if (decimals > 0) s = s.replace(/\.?0+$/, "");
    return s || "0";
  }

  /** 价格/流动性(张),价格按 tick_sz 对齐交易所精度. */
  function fmtPxSz(px, sz, estimated, tickSz) {
    if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
    let price = fmtOptionPx(px, tickSz);
    if (price === "—") return "—";
    if (estimated) price += "~";
    if (sz === null || sz === undefined || sz === "" || Number.isNaN(Number(sz))) return price;
    const s = Number(sz);
    const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
    return price + "/" + size;
  }

  function moneynessBadge(c) {
    const m = (c && c.moneyness) || "";
    const label = (c && c.moneyness_label) || "—";
    return '<span class="opt-moneyness opt-moneyness-' + m + '">' + label + "</span>";
  }

  function pnlClass(v) {
    if (v == null || v === "") return "";
    const n = Number(v);
    if (Number.isNaN(n)) return "";
    return n >= 0 ? "hp-pnl-pos" : "hp-pnl-neg";
  }

  function fmtPnlHtml(v, digits) {
    if (v == null || v === "" || Number.isNaN(Number(v))) return "—";
    const cls = pnlClass(v);
    return '<strong class="' + cls + '">' + fmt(v, digits) + "</strong>";
  }

  function fmtRr(v) {
    if (v == null || Number.isNaN(Number(v))) return "—";
    return fmt(v, 2) + ":1";
  }

  /** 列表/盯盘候选杠杆门槛(与后端 effective_min_opt_leverage 对齐). */
  function optionPrimaryMinLev() {
    const minLev = numInput("hp-opt-leverage", opMoneyKind() === "otm" ? 200 : 100);
    if (!(minLev > 0)) return 0;
    if (opMoneyKind() === "otm") return Math.max(minLev, 180);
    return minLev;
  }

  function optionPrimaryLevOk(c) {
    const idx = indexPx();
    const ask = Number(c && c.ask);
    const floor = optionPrimaryMinLev();
    if (!(floor > 0)) return true;
    if (!(idx > 0) || !(ask > 0)) return false;
    return idx / ask >= floor - 1e-9;
  }

  function matchesMoneyFilter(c) {
    const f = state.moneyFilter || "itm";
    const m = (c.moneyness || "").toLowerCase();
    if (!isOptionPrimary() && f === "otm") return false;
    // 列表:间隔+虚实值+杠杆门槛(达标才显示;启动盯盘后监控同样门槛)
    if (isOptionPrimary()) {
      const idx = indexPx();
      const interval = numInput("hp-strike-interval", 15);
      if (idx && interval > 0 && Math.abs(Number(c.strike) - idx) > interval + 1e-9) {
        return false;
      }
      if (!optionPrimaryLevOk(c)) return false;
    }
    if (f === "itm") return m === "itm" || m === "atm";
    if (f === "atm") return m === "atm";
    if (f === "otm") return m === "otm";
    return m === "itm" || m === "atm";
  }

  function matchesOoMoneyFilter(c) {
    // 期期:仅平值/虚值(禁实值)
    if (!c) return false;
    const m = (c.moneyness || "").toLowerCase();
    const f = state.ooMoneyFilter || "atm_otm";
    if (f === "atm") return m === "atm";
    if (f === "otm") return m === "otm";
    return m === "atm" || m === "otm";
  }

  function indexPx() {
    const fromChain = state.chain && Number(state.chain.index_px);
    if (fromChain && !Number.isNaN(fromChain) && fromChain > 0) return fromChain;
    const fromMkt = state.market && Number(state.market.index_px || state.market.mark);
    if (fromMkt && !Number.isNaN(fromMkt) && fromMkt > 0) return fromMkt;
    return null;
  }

  function currentContracts(expSelectId) {
    const exp = currentExp(expSelectId);
    return (exp && exp.contracts) || [];
  }

  function pickClosestItmAtm(contracts, want) {
    const idx = indexPx();
    if (!idx) return null;
    const list = (contracts || []).filter(function (c) {
      return (
        String(c.opt_type || "").toUpperCase() === want &&
        matchesMoneyFilter(c)
      );
    });
    if (!list.length) return null;
    list.sort(function (a, b) {
      return Math.abs(Number(a.strike) - idx) - Math.abs(Number(b.strike) - idx);
    });
    return list[0];
  }

  function pickOoTemplate(template) {
    const idx = indexPx();
    const contracts = currentContracts("hp-oo-exp-select");
    if (!idx || !contracts.length) return null;
    const preferOtm = template === "double_otm";
    function pickSide(want) {
      const list = contracts.filter(function (c) {
        if (String(c.opt_type || "").toUpperCase() !== want) return false;
        if (!matchesOoMoneyFilter(c)) return false;
        const m = (c.moneyness || "").toLowerCase();
        if (preferOtm) return m === "otm";
        return m === "atm" || m === "otm";
      });
      if (!list.length) return null;
      list.sort(function (a, b) {
        const ma = (a.moneyness || "").toLowerCase();
        const mb = (b.moneyness || "").toLowerCase();
        if (!preferOtm) {
          if (ma === "atm" && mb !== "atm") return -1;
          if (mb === "atm" && ma !== "atm") return 1;
        }
        return Math.abs(Number(a.strike) - idx) - Math.abs(Number(b.strike) - idx);
      });
      return list[0];
    }
    const call = pickSide("C");
    const put = pickSide("P");
    if (!call || !put) return null;
    return { call: call, put: put };
  }

  function isOptionPrimary() {
    return !!state.optionPrimary;
  }

  function optTypeForDirection(dir) {
    if (isOptionPrimary()) {
      return dir === "short" ? "P" : "C";
    }
    return dir === "short" ? "C" : "P";
  }

  function opMoneyKind() {
    const f = state.moneyFilter || "itm";
    if (f === "otm") return "otm";
    if (f === "atm") return "atm";
    return "itm";
  }

  function applyOpDefaultsFromMoney(force) {
    const kind = opMoneyKind();
    const levEl = $("hp-opt-leverage");
    const ratioEl = $("hp-opt-perp-ratio");
    if (levEl && (force || !state.opLevTouched)) {
      levEl.value = kind === "otm" ? "200" : "100";
    }
    if (ratioEl && (force || !state.opRatioTouched)) {
      ratioEl.value = kind === "otm" ? "4" : "2";
    }
  }

  function syncOptionPrimaryUI() {
    const on = isOptionPrimary();
    const ins = $("hp-po-fields-insurance");
    const op = $("hp-po-fields-option-primary");
    // 保险模式只显示开仓价/张数/止盈止损;以期权为主显示三组参数(env 切换,页内不可改)
    if (ins) {
      ins.classList.toggle("hidden", on);
      if (on) ins.setAttribute("hidden", "hidden");
      else ins.removeAttribute("hidden");
      ins.style.display = on ? "none" : "";
    }
    if (op) {
      op.classList.toggle("hidden", !on);
      if (!on) op.setAttribute("hidden", "hidden");
      else op.removeAttribute("hidden");
      op.style.display = on ? "" : "none";
    }
    const insMoney = $("hp-po-ins-money");
    if (insMoney) {
      if (on) {
        insMoney.setAttribute("hidden", "hidden");
        insMoney.classList.add("hidden");
      } else {
        insMoney.removeAttribute("hidden");
        insMoney.classList.remove("hidden");
      }
    }
    document.querySelectorAll(".hp-money-otm").forEach(function (otmBtn) {
      otmBtn.classList.toggle("hidden", !on);
      if (!on) otmBtn.setAttribute("hidden", "hidden");
      else otmBtn.removeAttribute("hidden");
    });
    if (!on && state.moneyFilter === "otm") {
      state.moneyFilter = "itm";
      syncMoneyUI();
    }
    if (on) applyOpDefaultsFromMoney(false);
    const badge = $("hp-po-mode-badge");
    if (badge) {
      badge.textContent = on ? "以期权为主" : "保险模式";
      badge.classList.toggle("is-insurance", !on);
    }
    const title = $("hp-po-card-title");
    if (title) title.textContent = "执行参数";
    const dirLong = document.querySelector('.hp-po-dir[data-dir="long"]');
    const dirShort = document.querySelector('.hp-po-dir[data-dir="short"]');
    if (dirLong) dirLong.title = on ? "做多=买Call+永续空" : "做多永续";
    if (dirShort) dirShort.title = on ? "做空=买Put+永续多" : "做空永续";
    syncPoActionBtn();
  }

  function hoursFromExpMs(expMs) {
    const n = Number(expMs);
    if (!n || Number.isNaN(n)) return null;
    const ms = n < 1e12 ? n * 1000 : n;
    return (ms - Date.now()) / 3600000;
  }

  function numInput(id, fallback) {
    const el = $(id);
    const n = Number(el && el.value);
    if (Number.isNaN(n)) return fallback;
    return n;
  }

  function computeOpSizing(ask, ctMult) {
    const budget = numInput("hp-premium-budget", 0);
    const ratio = numInput("hp-opt-perp-ratio", 2);
    const cs = Number((state.market && state.market.contract_size) || 0.01);
    const usable = budget * 0.95;
    const a = Number(ask || 0);
    const ct = Number(ctMult || 0.01);
    if (!(budget > 0) || !(a > 0) || !(ct > 0) || !(ratio > 0) || !(cs > 0)) {
      return null;
    }
    let eth = Math.floor((usable / a) * 100 + 1e-12) / 100;
    if (!(eth > 0)) return null;
    let sheets = Math.floor(eth / ct + 1e-12);
    if (!(sheets > 0)) return null;
    eth = Math.round(sheets * ct * 100) / 100;
    const perpEth = eth / ratio;
    const contracts = perpEth / cs;
    return {
      usable: usable,
      eth_qty: eth,
      sheets: sheets,
      contracts: contracts,
      premium_est: a * sheets * ct,
      ratio: ratio,
    };
  }

  function syncUnderlyingUI() {
    const uly = state.underlying || "ETH";
    document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
      const on = b.getAttribute("data-uly") === uly;
      b.classList.toggle("active", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
    const lab = $("hp-perp-uly-label");
    if (lab) lab.textContent = uly;
    const ooLab = $("hp-oo-uly-label");
    if (ooLab) ooLab.textContent = uly;
  }

  function syncMoneyUI() {
    const moneySel = $("hp-money-select");
    if (moneySel && isOptionPrimary()) {
      moneySel.value = state.moneyFilter === "otm" ? "otm" : state.moneyFilter === "atm" ? "atm" : "itm";
    }
    document.querySelectorAll(".hp-money-btn").forEach(function (b) {
      const on = b.getAttribute("data-money") === state.moneyFilter;
      b.classList.toggle("active", on);
      b.classList.toggle("is-selected", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
    document.querySelectorAll(".hp-oo-money-btn").forEach(function (b) {
      const on = b.getAttribute("data-oo-money") === state.ooMoneyFilter;
      b.classList.toggle("active", on);
      b.classList.toggle("is-selected", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
  }

  function syncPoActionBtn() {
    const btn = $("hp-preview-btn");
    if (!btn) return;
    if (isOptionPrimary()) {
      btn.textContent = "策略启动";
      btn.title = "按参数启动盯盘;杠杆/间隔达标后自动开仓(非现场开)";
    } else {
      btn.textContent = "计算";
      btn.title = "情景测算后再启动";
    }
  }

  function isPoOptionPrimaryPlan(p) {
    if (!p || p.plan_type !== "perp_options") return false;
    return p.option_primary == 1 || p.option_primary === true || Number(p.option_primary) === 1;
  }

  function setPoStrategyStatus(kind, planId) {
    const el = $("hp-po-strategy-status");
    if (!el) return;
    el.classList.remove("is-watching", "is-holding", "is-idle");
    if (!isOptionPrimary()) {
      el.textContent = "";
      return;
    }
    const idPart = planId ? " #" + planId : "";
    if (kind === "watching") {
      el.textContent = "盯盘中" + idPart;
      el.classList.add("is-watching");
    } else if (kind === "holding") {
      el.textContent = "持仓中" + idPart;
      el.classList.add("is-holding");
    } else {
      el.textContent = "未启动";
      el.classList.add("is-idle");
    }
  }

  async function refreshPoStrategyStatus() {
    const el = $("hp-po-strategy-status");
    if (!el) return;
    if (!isOptionPrimary()) {
      setPoStrategyStatus("", null);
      return;
    }
    try {
      const d = await apiJson("/api/hedge-plan/active");
      const uly = (state.underlying || "ETH").toUpperCase();
      const rows = (d.plans || []).filter(function (p) {
        return isPoOptionPrimaryPlan(p) && String(p.underlying || "").toUpperCase() === uly;
      });
      if (!rows.length) {
        setPoStrategyStatus("idle", null);
        return;
      }
      rows.sort(function (a, b) {
        return Number(b.id || 0) - Number(a.id || 0);
      });
      const p = rows[0];
      const st = String(p.status || "");
      if (st === "watching") setPoStrategyStatus("watching", p.id);
      else if (st === "active" || st === "opening" || st === "partial") setPoStrategyStatus("holding", p.id);
      else setPoStrategyStatus("idle", null);
    } catch (e) {
      /* ignore status refresh errors */
    }
  }

  function syncOoRecommendUI() {
    const cur = state.ooRecommend || "";
    document.querySelectorAll(".hp-oo-recommend-btn").forEach(function (b) {
      const on = (b.getAttribute("data-oo-rec") || "") === cur;
      b.classList.toggle("active", on);
      b.classList.toggle("is-selected", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
  }

  function syncOoExpandUI() {
    const btn = $("hp-oo-expand-all");
    if (!btn) return;
    const on = !!state.ooStrikeExpandAll;
    btn.classList.toggle("active", on);
    btn.classList.toggle("is-selected", on);
    btn.setAttribute("aria-pressed", on ? "true" : "false");
  }

  function calcStrikeAskLeverage(strike, ask) {
    const k = Number(strike);
    const a = Number(ask);
    if (!Number.isFinite(k) || k <= 0 || !Number.isFinite(a) || a <= 0) return "—";
    return (Math.round((k / a) * 10) / 10).toFixed(1) + "×";
  }

  /** 永期期权行情杠杆:指数÷卖一(与选约杠杆门一致). */
  function calcIndexAskLeverage(ask) {
    const idx = indexPx();
    const a = Number(ask);
    if (!(idx > 0) || !(a > 0)) return "—";
    return (Math.round((idx / a) * 10) / 10).toFixed(1) + "×";
  }

  function clearPoSelection() {
    state.selected = null;
    if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
    updatePremiumLine();
    updatePerpPnlHint();
  }

  /** 以期权为主:按类型/间隔自动匹配最近合约. */
  function autoMatchPoOption(force) {
    if (!isOptionPrimary()) return;
    const want = optTypeForDirection(getDirection());
    const sel = state.selected;
    const stillOk =
      !force &&
      sel &&
      String(sel.opt_type || "").toUpperCase() === want &&
      matchesMoneyFilter(sel);
    if (stillOk) return;
    const c = pickClosestItmAtm(currentContracts("hp-exp-select"), want);
    if (c) pickContract(c);
    else clearPoSelection();
  }

  function findAtmStrikeFromRows(rows, idx) {
    if (!rows.length) return null;
    if (idx == null || Number.isNaN(Number(idx))) return rows[0].strike;
    let best = rows[0].strike;
    let bestDist = Math.abs(Number(rows[0].strike) - Number(idx));
    rows.forEach(function (row) {
      const d = Math.abs(Number(row.strike) - Number(idx));
      if (d < bestDist || (d === bestDist && Number(row.strike) < Number(best))) {
        bestDist = d;
        best = row.strike;
      }
    });
    return best;
  }

  function sliceOoTRows(prepared, idx) {
    // prepared: [{strike, call, put, callOk, putOk}]
    if (state.ooStrikeExpandAll || !prepared.length) return prepared;
    const n = 3;
    const anchor = Number(idx) || Number(findAtmStrikeFromRows(prepared, idx)) || 0;
    function nearest(list, count) {
      return list
        .slice()
        .sort(function (a, b) {
          return Math.abs(Number(a.strike) - anchor) - Math.abs(Number(b.strike) - anchor);
        })
        .slice(0, count);
    }
    const byStrike = {};
    nearest(
      prepared.filter(function (r) {
        return r.callOk;
      }),
      n
    ).forEach(function (r) {
      byStrike[String(r.strike)] = r;
    });
    nearest(
      prepared.filter(function (r) {
        return r.putOk;
      }),
      n
    ).forEach(function (r) {
      byStrike[String(r.strike)] = r;
    });
    // 已选用腿始终保留,避免折叠后看不到选中项
    [state.legA, state.legB].forEach(function (leg) {
      if (!leg) return;
      const hit = prepared.find(function (r) {
        return (
          (r.call && r.call.inst_id === leg.inst_id) ||
          (r.put && r.put.inst_id === leg.inst_id)
        );
      });
      if (hit) byStrike[String(hit.strike)] = hit;
    });
    return Object.keys(byStrike)
      .map(function (k) {
        return byStrike[k];
      })
      .sort(function (a, b) {
        return Number(a.strike) - Number(b.strike);
      });
  }

  function syncTabUI() {
    let tab = state.tab || pickDefaultTab();
    if (tab === "perp_options" && !showPerp) tab = pickDefaultTab();
    if (tab === "options_options" && !showOo) tab = pickDefaultTab();
    state.tab = tab;
    document.querySelectorAll(".hp-tab").forEach(function (b) {
      const on = b.getAttribute("data-tab") === tab;
      b.classList.toggle("active", on);
      b.setAttribute("aria-selected", on ? "true" : "false");
    });
    ["perp_options", "options_options", "active", "history", "stats"].forEach(function (id) {
      const panel = $("hp-tab-" + id);
      if (!panel) return;
      const on = id === tab;
      panel.classList.toggle("hidden", !on);
      if (on) panel.removeAttribute("hidden");
      else panel.setAttribute("hidden", "");
    });
    if (tab === "perp_options" || tab === "options_options") {
      state.mode = tab;
    }
    const hint = $("hp-acct-hint");
    if (hint) {
      if (tab === "options_options") {
        hint.textContent = "期期双腿→期权账户";
      } else if (tab === "perp_options") {
        hint.textContent = "永续腿→合约账户 · 期权腿→期权账户";
      } else {
        hint.textContent = "进行中/历史含永期与期期;显示开关只影响新建测算 Tab";
      }
    }
  }

  function setGateLine(gates) {
    const el = $("hp-gate-line");
    if (!el) return;
    if (!gates) {
      el.textContent = "";
      return;
    }
    const parts = [
      "计仓:" + (gates.is_full_margin ? "全仓" : "非全仓"),
      "测算:" + (gates.can_preview ? "可" : "否"),
      "开仓:" + (gates.can_start ? "可" : "否"),
    ];
    if (gates.reasons && gates.reasons.length) parts.push(gates.reasons.join("; "));
    el.textContent = parts.join(" · ");
    state.canStart = !!gates.can_start;
    syncPreviewStartBtn();
  }

  function syncPreviewStartBtn() {
    const start = $("hp-preview-start");
    if (!start) return;
    start.disabled = !(state.previewOk && state.canStart);
  }

  function openPreviewModal() {
    const modal = $("hp-preview-modal");
    if (modal) modal.hidden = false;
  }

  function closePreviewModal() {
    const modal = $("hp-preview-modal");
    if (modal) modal.hidden = true;
  }

  function applyBudgetBuffer(raw) {
    if (raw == null || raw === "") return;
    const buf = Number(raw);
    if (Number.isNaN(buf) || buf <= 0) return;
    state.budgetBuffer = buf;
    const el = $("hp-oo-buf-ratio");
    if (el) el.textContent = fmt(buf, 2);
  }

  function setOptionsBalance(chain) {
    const acct = (chain && chain.options_account) || {};
    const label = (chain && chain.account_label) || acct.label || "期权账户";
    const tag = $("hp-opt-acct-tag");
    if (tag) tag.textContent = label;
    if (acct.trading_usdc != null && acct.trading_usdc !== "") {
      state.tradingUsdc = Number(acct.trading_usdc);
    }
    if (acct.funding_usdc != null && acct.funding_usdc !== "") {
      state.fundingUsdc = Number(acct.funding_usdc);
    }
    if (chain && chain.trade_budget_usdc != null && chain.trade_budget_usdc !== "") {
      state.tradeBudgetUsdc = Number(chain.trade_budget_usdc);
    }
    if (chain && chain.budget_buffer != null && chain.budget_buffer !== "") {
      applyBudgetBuffer(chain.budget_buffer);
    }
    const line =
      label +
      " · 交易 USDC " +
      fmt(acct.trading_usdc, 2) +
      " · 资金 USDC " +
      fmt(acct.funding_usdc, 2);
    const el = $("hp-opt-bal-line");
    if (el) el.textContent = line;
    const fundEl = $("hp-oo-funding-usdc");
    const tradeEl = $("hp-oo-trading-usdc");
    if (fundEl) fundEl.textContent = fmt(acct.funding_usdc, 2);
    if (tradeEl) tradeEl.textContent = fmt(acct.trading_usdc, 2);
    autoFillOoSheets();
  }

  function setOoXferMsg(text, kind) {
    const el = $("hp-oo-xfer-msg");
    if (!el) return;
    el.textContent = text || "";
    el.classList.toggle("is-err", kind === "err");
    el.classList.toggle("is-ok", kind === "ok");
    el.classList.toggle("muted", !kind);
  }

  function ooXferDir() {
    const sel = $("hp-oo-xfer-dir");
    return (sel && sel.value) || "funding_to_trading";
  }

  async function submitOoUsdcTransfer(amount) {
    const dir = ooXferDir();
    const from = dir === "trading_to_funding" ? "trading" : "funding";
    const to = dir === "trading_to_funding" ? "funding" : "trading";
    setOoXferMsg("划转中…", null);
    try {
      const d = await apiJson("/api/options/transfer", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ ccy: "USDC", from: from, to: to, amount: amount }),
      });
      if (!d.ok) {
        setOoXferMsg(d.msg || "划转失败", "err");
        return;
      }
      setOoXferMsg("划转成功", "ok");
      if ($("hp-oo-xfer-amount")) $("hp-oo-xfer-amount").value = "";
      await loadChain();
    } catch (e) {
      setOoXferMsg(e.message || "划转失败", "err");
    }
  }

  function resolveOoBudget() {
    const buf = state.budgetBuffer > 0 ? state.budgetBuffer : 0.95;
    const trading = state.tradingUsdc;
    const cap = state.tradeBudgetUsdc;
    let tradingCap = null;
    let tradeCap = null;
    if (trading != null && !Number.isNaN(Number(trading))) {
      tradingCap = Math.max(0, Number(trading) * buf);
    }
    if (cap != null && !Number.isNaN(Number(cap))) {
      tradeCap = Math.max(0, Number(cap));
    }
    if (tradingCap == null && tradeCap == null) {
      return { ok: false, budget: 0, tradingCap: null, tradeCap: null, buf: buf, msg: "缺少交易户余额与单笔预算" };
    }
    let budget = 0;
    if (tradingCap == null) budget = tradeCap;
    else if (tradeCap == null) budget = tradingCap;
    else budget = Math.min(tradingCap, tradeCap);
    budget = Math.floor(budget * 1e6 + 1e-12) / 1e6;
    return {
      ok: budget > 0,
      budget: budget,
      tradingCap: tradingCap,
      tradeCap: tradeCap,
      buf: buf,
      msg: budget > 0 ? "" : "可用预算为 0",
    };
  }

  function unitCost(c) {
    if (!c) return 0;
    const ask = Number(c.ask || 0);
    if (!(ask > 0)) return 0;
    return ask * Number(c.ct_mult || 0.01);
  }

  function capByDepth(n, askSz) {
    let out = Math.max(0, Math.floor(Number(n) || 0));
    if (askSz == null || askSz === "") return out;
    const d = Number(askSz);
    if (Number.isNaN(d)) return out;
    if (d <= 0) return 0;
    return Math.min(out, Math.floor(d + 1e-12));
  }

  function normalizeOptCP(ot) {
    const u = String(ot || "").toUpperCase();
    if (u.indexOf("C") === 0) return "C";
    if (u.indexOf("P") === 0) return "P";
    return "";
  }

  function ooSizeModeLabel(mode) {
    if (mode === "long_bias") return "做多";
    if (mode === "short_bias") return "做空";
    if (mode === "split_budget") return "均分";
    return "同张数";
  }

  function suggestOoSheetsLocal(budget, mode) {
    const costA = unitCost(state.legA);
    const costB = unitCost(state.legB);
    if (!(budget > 0)) {
      return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "可用预算为 0" };
    }
    if (!(costA > 0) || !(costB > 0)) {
      return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "缺少有效卖一价,无法建议张数" };
    }
    let ratio = Number(state.ooBiasRatio);
    if (!(ratio > 0) || !(ratio < 1)) ratio = 0.7;
    const splitBy = state.ooBiasSplitBy === "sheets" ? "sheets" : "budget";
    const pair = costA + costB;
    const nPair = pair > 0 ? Math.floor(budget / pair + 1e-12) : 0;
    const nSame = Math.min(
      capByDepth(nPair, state.legA && state.legA.ask_sz),
      capByDepth(nPair, state.legB && state.legB.ask_sz)
    );

    let nA = 0;
    let nB = 0;
    if (mode === "long_bias" || mode === "short_bias") {
      const aCP = normalizeOptCP(state.legA && state.legA.opt_type);
      const bCP = normalizeOptCP(state.legB && state.legB.opt_type);
      if (!((aCP === "C" && bCP === "P") || (aCP === "P" && bCP === "C"))) {
        return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "做多/做空需一腿 Call、一腿 Put" };
      }
      const callIsA = aCP === "C";
      const majorIsCall = mode === "long_bias";
      let nCall = 0;
      let nPut = 0;
      if (splitBy === "sheets") {
        // 总张数 = 同张数两侧合计(每腿 n → 共 2n),再按比例拆
        const total = nSame * 2;
        if (total < 2) {
          return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "同张数总规模不足 2,无法按比例拆分" };
        }
        let majorN = Math.round(total * ratio);
        majorN = Math.max(1, Math.min(majorN, total - 1));
        const minorN = total - majorN;
        nCall = majorIsCall ? majorN : minorN;
        nPut = majorIsCall ? minorN : majorN;
      } else {
        const majBudget = budget * ratio;
        const minBudget = budget * (1 - ratio);
        const costCall = callIsA ? costA : costB;
        const costPut = callIsA ? costB : costA;
        if (majorIsCall) {
          nCall = Math.floor(majBudget / costCall + 1e-12);
          nPut = Math.floor(minBudget / costPut + 1e-12);
        } else {
          nPut = Math.floor(majBudget / costPut + 1e-12);
          nCall = Math.floor(minBudget / costCall + 1e-12);
        }
      }
      nA = callIsA ? nCall : nPut;
      nB = callIsA ? nPut : nCall;
      nA = capByDepth(nA, state.legA && state.legA.ask_sz);
      nB = capByDepth(nB, state.legB && state.legB.ask_sz);
    } else if (mode === "split_budget") {
      const half = budget / 2;
      nA = Math.floor(half / costA + 1e-12);
      nB = Math.floor(half / costB + 1e-12);
      nA = capByDepth(nA, state.legA && state.legA.ask_sz);
      nB = capByDepth(nB, state.legB && state.legB.ask_sz);
    } else {
      nA = nSame;
      nB = nSame;
    }
    const premium = costA * nA + costB * nB;
    const ok = nA >= 1 && nB >= 1;
    return {
      sheetsA: nA,
      sheetsB: nB,
      premium: premium,
      ok: ok,
      msg: ok ? "" : "预算不够开 1+1(或卖一深度不足)",
    };
  }

  function syncOoSizeModeUI() {
    document.querySelectorAll(".hp-oo-size-mode").forEach(function (b) {
      const on = b.getAttribute("data-oo-size") === state.ooSheetsMode;
      b.classList.toggle("active", on);
      b.classList.toggle("is-selected", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
  }

  function syncOoCloseModeUI() {
    const enabled = !!state.ooCloseModeEnabled;
    const row = $("hp-oo-close-mode-row");
    if (row) row.classList.toggle("hidden", !enabled);
    if (!enabled) state.ooCloseMode = "hold_expiry";
    document.querySelectorAll(".hp-oo-close-mode").forEach(function (b) {
      const on = b.getAttribute("data-oo-close") === state.ooCloseMode;
      b.classList.toggle("active", on);
      b.classList.toggle("is-selected", on);
      b.setAttribute("aria-pressed", on ? "true" : "false");
    });
  }

  function updateOoBudgetLine(extra) {
    const line = $("hp-oo-budget-line");
    if (!line) return;
    const b = resolveOoBudget();
    const sizeLabel = ooSizeModeLabel(state.ooSheetsMode);
    const closeLabel = !state.ooCloseModeEnabled
      ? ""
      : state.ooCloseMode === "hold_expiry"
        ? "到期平"
        : "全平";
    const parts = ["可用 " + fmt(b.budget, 2) + "U", sizeLabel];
    if (closeLabel) parts.push(closeLabel);
    if (extra && extra.msg) parts.push(extra.msg);
    else if (b.msg) parts.push(b.msg);
    line.textContent = parts.join(" · ");
    line.title =
      "对冲预算=min(交易×" +
      fmt(b.buf, 2) +
      ", 单笔) · 缓冲 HEDGE_PLAN_BUDGET_BUFFER" +
      (b.tradingCap != null ? " · 交易×缓冲 " + fmt(b.tradingCap, 2) : "") +
      (b.tradeCap != null ? " · 单笔 " + fmt(b.tradeCap, 2) : "");
    line.classList.toggle("hp-oo-budget-warn", !!(extra && extra.msg) || !b.ok);
  }

  function autoFillOoSheets() {
    syncOoSizeModeUI();
    if (!state.legA || !state.legB) {
      updateOoBudgetLine(null);
      return;
    }
    const b = resolveOoBudget();
    const sug = suggestOoSheetsLocal(b.budget, state.ooSheetsMode);
    const a = $("hp-oo-sheets-a");
    const bb = $("hp-oo-sheets-b");
    if (a && !a.disabled) a.value = String(sug.sheetsA);
    if (bb && !bb.disabled) bb.value = String(sug.sheetsB);
    updateOoBudgetLine(sug.ok ? null : sug);
    updateOoPremiumLine();
  }

  async function loadGates() {
    try {
      const d = await apiJson("/api/hedge-plan/gates?plan_type=" + encodeURIComponent(state.mode));
      if (d.oo_close_mode_enabled != null) {
        state.ooCloseModeEnabled = !!d.oo_close_mode_enabled;
      }
      if (!state.ooCloseModeEnabled) {
        state.ooCloseMode = "hold_expiry";
      } else if (d.oo_close_mode_default && !state._ooCloseModeTouched) {
        state.ooCloseMode = d.oo_close_mode_default === "hold_expiry" ? "hold_expiry" : "close_all";
      }
      if (d.oo_bias_split_by != null) {
        state.ooBiasSplitBy = d.oo_bias_split_by === "sheets" ? "sheets" : "budget";
      }
      if (d.oo_bias_ratio != null && Number(d.oo_bias_ratio) > 0 && Number(d.oo_bias_ratio) < 1) {
        state.ooBiasRatio = Number(d.oo_bias_ratio);
      }
      if (d.budget_buffer != null) applyBudgetBuffer(d.budget_buffer);
      syncOoCloseModeUI();
      setGateLine(d);
      if (state.mode === "options_options") autoFillOoSheets();
    } catch (e) {
      setGateLine({ can_preview: false, can_start: false, reasons: [e.message], is_full_margin: false });
    }
  }

  async function loadMarket() {
    const dir = getDirection();
    const d = await apiJson(
      "/api/hedge-plan/market?base=" +
        encodeURIComponent(state.underlying) +
        "&direction=" +
        encodeURIComponent(dir) +
        "&option_primary=" +
        (isOptionPrimary() ? "1" : "0")
    );
    state.market = d;
    setGateLine(d.gates);
    const acctLabel = d.account_label || "合约账户";
    const tag = $("hp-perp-acct-tag");
    if (tag) tag.textContent = acctLabel;
    const amtPrec = d.amount_precision != null ? Number(d.amount_precision) : 4;
    const markEl = $("hp-po-mark");
    if (markEl) markEl.textContent = "标记 " + fmt(d.mark, 2);
    const quoteHtml =
      "可用 <strong>" +
      fmt(d.available_usdt, 2) +
      "</strong> USDT · 卖一 " +
      fmt(d.ask, 2) +
      " · 买一 " +
      fmt(d.bid, 2) +
      " · 面值 " +
      fmt(d.contract_size, 4) +
      " · 精度 " +
      amtPrec +
      " 位" +
      (d.perp_direction
        ? " · 永续方向 " + (d.perp_direction === "short" ? "空" : "多")
        : "");
    const q = $("hp-perp-quote");
    if (q) q.innerHTML = quoteHtml;
    const contractsInput = $("hp-contracts");
    if (contractsInput) {
      const step = amtPrec <= 0 ? "1" : String(Math.pow(10, -amtPrec));
      contractsInput.step = step;
    }
    const sz = $("hp-sizing-line");
    if (sz) {
      if (isOptionPrimary()) {
        const sized =
          state.selected &&
          computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
        if (sized) {
          sz.innerHTML =
            "执行预算 <strong>" +
            fmt(sized.usable, 2) +
            "</strong> · 期权 ETH <strong>" +
            fmt(sized.eth_qty, 2) +
            "</strong> / " +
            sized.sheets +
            " 张 · 永续 <strong>" +
            fmt(sized.contracts, amtPrec) +
            "</strong> 张";
        } else {
          sz.textContent = "填写权利金并选用期权后显示定仓(权利金×0.95,ETH两位小数)";
        }
      } else if (d.full_margin_sizing) {
        const s = d.full_margin_sizing;
        sz.innerHTML =
          "全仓建议 <strong>" +
          fmt(d.suggest_contracts, amtPrec) +
          "</strong> 张 · 保证金 " +
          fmt(s.margin_capital, 2) +
          " × " +
          s.leverage +
          "x · 名义 " +
          fmt(s.notional_value, 2) +
          " USDT";
      } else {
        sz.textContent = "非全仓时请手动填张数；永期开仓需全仓";
      }
    }
    const entry = $("hp-entry");
    if (!isOptionPrimary() && entry && d.entry_ref && !entry.value) entry.value = d.entry_ref;
    if (!isOptionPrimary() && contractsInput && d.suggest_contracts != null && !contractsInput.value) {
      contractsInput.value = fmt(d.suggest_contracts, amtPrec);
    }
    const label = $("hp-opt-type-label");
    if (label) label.textContent = d.suggested_opt_type === "C" ? "Call" : "Put";
    updatePerpPnlHint();
  }

  function updatePerpPnlHint() {
    const el = $("hp-perp-pnl-line");
    if (!el) return;
    if (isOptionPrimary()) {
      const n = numInput("hp-opt-target-pts", NaN);
      const m = numInput("hp-perp-target-pts", NaN);
      const k = state.selected && Number(state.selected.strike);
      if (!(k > 0) || (!(n >= 0) && !(m >= 0))) {
        el.innerHTML = '<span class="muted">选用期权并填目标点数后显示 K±N 出场参考</span>';
        return;
      }
      const dir = getDirection();
      const optT = n >= 0 ? (dir === "short" ? k - n : k + n) : null;
      const perpT = m >= 0 ? (dir === "short" ? k - m : k + m) : null;
      el.innerHTML =
        "期权目标指数 " +
        (optT != null ? fmt(optT, 2) : "—") +
        " · 永续目标指数 " +
        (perpT != null ? fmt(perpT, 2) : "—") +
        ' <span class="muted">(相对K;期权目标需买一且扣费净利&gt;0)</span>';
      return;
    }
    const entry = Number(($("hp-entry") && $("hp-entry").value) || NaN);
    const tp = Number(($("hp-tp") && $("hp-tp").value) || NaN);
    const sl = Number(($("hp-sl") && $("hp-sl").value) || NaN);
    const contracts = Number(($("hp-contracts") && $("hp-contracts").value) || NaN);
    const cs = Number((state.market && state.market.contract_size) || 0.01);
    const dir = getDirection();
    if (!(entry > 0) || !(contracts > 0) || !(cs > 0)) {
      el.innerHTML = '<span class="muted">填开仓价与张数后，输入止盈/止损可看盈亏</span>';
      return;
    }
    function pnlAt(exitPx) {
      const coins = contracts * cs;
      if (dir === "short") return (entry - exitPx) * coins;
      return (exitPx - entry) * coins;
    }
    function chip(lab, px) {
      if (!(px > 0)) {
        return '<span class="hp-po-chip muted">' + lab + " —</span>";
      }
      const p = pnlAt(px);
      const cls = p >= 0 ? "hp-pnl-pos" : "hp-pnl-neg";
      const sign = p >= 0 ? "+" : "";
      return (
        '<span class="hp-po-chip">' +
        lab +
        ' <strong class="' +
        cls +
        '">' +
        sign +
        fmt(p, 2) +
        "</strong></span>"
      );
    }
    el.innerHTML = chip("止盈", tp) + chip("止损", sl);
  }

  function fillExpSelect(sel, chain) {
    if (!sel) return;
    const prev = sel.value;
    const isPoSel = sel.id === "hp-exp-select";
    // 永期以期权为主:到期下拉按「最低剩余小时」过滤;期期下拉不过滤,保证明天到期可见
    const minH = isPoSel && isOptionPrimary() ? numInput("hp-min-hours", 36) : 0;
    sel.innerHTML = '<option value="">选择到期日</option>';
    let firstOk = null;
    let skippedNear = 0;
    (chain.expiries || []).forEach(function (e) {
      const h = hoursFromExpMs(e.exp_time);
      if (minH > 0 && h != null && h < minH) {
        skippedNear += 1;
        return;
      }
      const opt = document.createElement("option");
      opt.value = String(e.exp_time);
      const dt = new Date(Number(e.exp_time) < 1e12 ? Number(e.exp_time) * 1000 : Number(e.exp_time));
      opt.textContent = dt.toLocaleString() + (h != null ? " · " + fmt(h, 1) + "h" : "");
      sel.appendChild(opt);
      if (!firstOk) firstOk = e;
    });
    if (prev) sel.value = prev;
    if (!sel.value && firstOk) {
      sel.value = String(firstOk.exp_time);
    }
    if (isPoSel && skippedNear > 0 && minH > 0) {
      const tip = document.createElement("option");
      tip.disabled = true;
      tip.textContent = "(已隐藏 " + skippedNear + " 个不足 " + minH + "h 的到期 · 可改左侧最低剩余小时)";
      sel.appendChild(tip);
    }
  }

  async function loadChain() {
    // 拉完整链(按 CHAIN_MAX_DTE);永期「最低剩余小时」只在左侧到期下拉里过滤,不影响期期看到明天到期
    const url =
      "/api/hedge-plan/options-chain?underlying=" + encodeURIComponent(state.underlying);
    const d = await apiJson(url);
    state.chain = d;
    const idx = $("hp-index-line");
    if (idx) idx.textContent = "指数 " + fmt(d.index_px, 2);
    const ooIdx = $("hp-oo-index");
    if (ooIdx) ooIdx.textContent = "指数 " + fmt(d.index_px, 2);
    setOptionsBalance(d);
    fillExpSelect($("hp-exp-select"), d);
    fillExpSelect($("hp-oo-exp-select"), d);
    renderListStrikes();
    renderTStrikes();
    if (d.index_px) {
      // 盈亏比默认2,不随指数自动改写
      if ($("hp-profit-rr") && !$("hp-profit-rr").value) {
        $("hp-profit-rr").value = "2";
      }
    }
  }

  function currentExp(selectId) {
    const sel = $(selectId);
    const expMs = sel && sel.value;
    if (!expMs || !state.chain) return null;
    return (state.chain.expiries || []).find(function (e) {
      return String(e.exp_time) === String(expMs);
    });
  }

  function pickContract(c) {
    if (!c) return;
    const m = (c.moneyness || "").toLowerCase();
    if (!isOptionPrimary() && m === "otm") {
      alert("永期保险腿须为实值或平值,不可选虚值");
      return;
    }
    if (isOptionPrimary() && !matchesMoneyFilter(c)) {
      alert("不符合当前间隔/虚实值/杠杆门槛");
      return;
    }
    state.selected = c;
    const el = $("hp-sel-inst");
    if (el) el.textContent = c.inst_id;
    const tbody = $("hp-strike-tbody");
    if (tbody) {
      tbody.querySelectorAll(".opt-strike-row").forEach(function (r) {
        r.classList.toggle("opt-row-selected", r.getAttribute("data-inst") === c.inst_id);
      });
      tbody.querySelectorAll(".hp-pick").forEach(function (b) {
        b.classList.toggle("active", b.getAttribute("data-inst") === c.inst_id);
      });
    }
    updatePremiumLine();
    if (isOptionPrimary()) {
      const sized = computeOpSizing(c.ask, c.ct_mult || 0.01);
      if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
      void loadMarket();
      updatePerpPnlHint();
    }
  }

  function renderListStrikes() {
    const tbody = $("hp-strike-tbody");
    if (!tbody) return;
    const want = optTypeForDirection(getDirection());
    const exp = currentExp("hp-exp-select");
    const prevInst = state.selected && state.selected.inst_id;
    tbody.innerHTML = "";
    if (!exp) {
      tbody.innerHTML = '<tr><td colspan="6" class="muted">请选择到期日</td></tr>';
      if (isOptionPrimary()) clearPoSelection();
      return;
    }
    const sameType = (exp.contracts || []).filter(function (c) {
      return String(c.opt_type || "").toUpperCase() === want;
    });
    const list = sameType.filter(matchesMoneyFilter);
    if (!list.length) {
      const idx = indexPx();
      const interval = isOptionPrimary() ? numInput("hp-strike-interval", 15) : null;
      let hint = "无匹配合约";
      if (isOptionPrimary() && sameType.length) {
        hint =
          "无匹配" +
          (want === "C" ? "Call" : "Put") +
          "(已滤 " +
          sameType.length +
          " 档)·检查间隔" +
          (interval != null ? "≤" + interval : "") +
          "点/虚实值/杠杆≥" +
          optionPrimaryMinLev();
      } else if (!sameType.length) {
        hint =
          "该到期无 " +
          (want === "C" ? "Call" : "Put") +
          (idx ? " · 指数 " + fmt(idx, 2) : "") +
          " · 可换到期或点刷新链";
      }
      tbody.innerHTML = '<tr><td colspan="6" class="muted">' + hint + "</td></tr>";
      if (isOptionPrimary()) clearPoSelection();
      return;
    }
    list.forEach(function (c) {
      const tr = document.createElement("tr");
      tr.className = "opt-strike-row" + (c.moneyness ? " opt-row-" + c.moneyness : "");
      if (prevInst && c.inst_id === prevInst) tr.classList.add("opt-row-selected");
      tr.setAttribute("data-inst", c.inst_id);
      tr.innerHTML =
        "<td>" +
        c.strike +
        "</td><td>" +
        moneynessBadge(c) +
        '</td><td class="opt-chain-lev" title="指数÷卖一">' +
        calcIndexAskLeverage(c.ask) +
        '</td><td class="opt-px-sz">' +
        fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
        '</td><td class="opt-px-sz">' +
        fmtPxSz(c.bid, c.bid_sz, false, c.tick_sz) +
        '</td><td><button type="button" class="btn-secondary hp-pick' +
        (prevInst && c.inst_id === prevInst ? " active" : "") +
        '" data-inst="' +
        c.inst_id +
        '">选用</button></td>';
      tbody.appendChild(tr);
    });
    tbody.querySelectorAll(".hp-pick").forEach(function (btn) {
      btn.addEventListener("click", function () {
        const inst = btn.getAttribute("data-inst");
        const c = list.find(function (x) {
          return x.inst_id === inst;
        });
        pickContract(c);
      });
    });
    autoMatchPoOption(false);
  }

  function updatePremiumLine() {
    const line = $("hp-premium-line");
    if (!line || !state.selected) {
      if (line) line.textContent = "";
      return;
    }
    const sheets = Number(($("hp-sheets") && $("hp-sheets").value) || 1);
    const ct = Number(state.selected.ct_mult || 0.01);
    const ask = Number(state.selected.ask || 0);
    const prem = ask * sheets * ct;
    line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " USDC(期权账户)";
  }

  function buildStraddleRows(contracts) {
    const map = {};
    (contracts || []).forEach(function (c) {
      const key = String(c.strike);
      if (!map[key]) map[key] = { strike: c.strike, call: null, put: null };
      const o = (c.opt_type || "").toUpperCase();
      if (o === "C") map[key].call = c;
      else if (o === "P") map[key].put = c;
    });
    return Object.keys(map)
      .map(function (k) {
        return map[k];
      })
      .sort(function (a, b) {
        return Number(a.strike) - Number(b.strike);
      });
  }

  function selectedOoLegMap() {
    const map = {};
    if (state.legA && state.legA.inst_id) map[String(state.legA.inst_id)] = "A";
    if (state.legB && state.legB.inst_id) map[String(state.legB.inst_id)] = "B";
    return map;
  }

  function syncOoPickHighlight() {
    const tbody = $("hp-oo-tbody");
    if (!tbody) return;
    const map = selectedOoLegMap();
    tbody.querySelectorAll(".hp-oo-pick").forEach(function (btn) {
      const inst = String(btn.getAttribute("data-inst") || "");
      const leg = map[inst];
      const on = !!leg;
      btn.classList.toggle("is-selected", on);
      btn.classList.toggle("active", on);
      btn.setAttribute("aria-pressed", on ? "true" : "false");
      const side = String(btn.getAttribute("data-side") || "").toUpperCase();
      const base = side === "P" ? "Put" : "Call";
      btn.textContent = on ? "腿" + leg + " · " + base : base;
      btn.title = on ? "已选用为腿" + leg + "(再点其他合约可改选)" : "选用此 " + base;
    });
    tbody.querySelectorAll("tr").forEach(function (tr) {
      const callBtn = tr.querySelector('.hp-oo-pick[data-side="C"]');
      const putBtn = tr.querySelector('.hp-oo-pick[data-side="P"]');
      const callOn = !!(callBtn && map[String(callBtn.getAttribute("data-inst") || "")]);
      const putOn = !!(putBtn && map[String(putBtn.getAttribute("data-inst") || "")]);
      tr.classList.toggle("hp-oo-row-selected", callOn || putOn);
      tr.querySelectorAll("td.opt-t-call").forEach(function (td) {
        td.classList.toggle("hp-oo-side-selected", callOn);
      });
      if (callBtn && callBtn.parentElement) {
        callBtn.parentElement.classList.toggle("hp-oo-side-selected", callOn);
      }
      tr.querySelectorAll("td.opt-t-put").forEach(function (td) {
        td.classList.toggle("hp-oo-side-selected", putOn);
      });
      if (putBtn && putBtn.parentElement) {
        putBtn.parentElement.classList.toggle("hp-oo-side-selected", putOn);
      }
    });
  }

  function renderTStrikes() {
    const tbody = $("hp-oo-tbody");
    if (!tbody) return;
    const exp = currentExp("hp-oo-exp-select");
    const cols = 9;
    tbody.innerHTML = "";
    if (!exp) {
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">请选择到期日</td></tr>';
      return;
    }
    const prepared = [];
    buildStraddleRows(exp.contracts).forEach(function (row) {
      const callOk = !!(row.call && matchesOoMoneyFilter(row.call));
      const putOk = !!(row.put && matchesOoMoneyFilter(row.put));
      if (!callOk && !putOk) return;
      prepared.push({
        strike: row.strike,
        call: row.call,
        put: row.put,
        callOk: callOk,
        putOk: putOk,
      });
    });
    const rows = sliceOoTRows(prepared, indexPx());
    const selected = selectedOoLegMap();
    if (!rows.length) {
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">该筛选下暂无平值/虚值合约</td></tr>';
      return;
    }
    rows.forEach(function (row) {
      const tr = document.createElement("tr");
      const call = row.call;
      const put = row.put;
      const callOk = row.callOk;
      const putOk = row.putOk;
      const callAsk = callOk ? fmtPxSz(call.ask, call.ask_sz, call.ask_estimated, call.tick_sz) : "—";
      const putAsk = putOk ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated, put.tick_sz) : "—";
      const callLev = callOk ? calcStrikeAskLeverage(row.strike, call.ask) : "—";
      const putLev = putOk ? calcStrikeAskLeverage(row.strike, put.ask) : "—";
      const callLeg = callOk ? selected[String(call.inst_id)] : "";
      const putLeg = putOk ? selected[String(put.inst_id)] : "";
      tr.innerHTML =
        '<td class="opt-t-call opt-px-sz">' +
        callAsk +
        '</td><td class="opt-t-call opt-chain-lev" title="K÷卖一">' +
        callLev +
        '</td><td class="opt-t-call">' +
        (callOk ? moneynessBadge(call) : "—") +
        "</td><td>" +
        (callOk
          ? '<button type="button" class="btn-secondary hp-oo-pick' +
            (callLeg ? " is-selected active" : "") +
            '" data-side="C" data-inst="' +
            call.inst_id +
            '" aria-pressed="' +
            (callLeg ? "true" : "false") +
            '">' +
            (callLeg ? "腿" + callLeg + " · Call" : "Call") +
            "</button>"
          : "—") +
        '</td><td class="opt-t-strike"><strong>' +
        row.strike +
        '</strong></td><td class="opt-t-put">' +
        (putOk ? moneynessBadge(put) : "—") +
        '</td><td class="opt-t-put opt-px-sz">' +
        putAsk +
        '</td><td class="opt-t-put opt-chain-lev" title="K÷卖一">' +
        putLev +
        "</td><td>" +
        (putOk
          ? '<button type="button" class="btn-secondary hp-oo-pick' +
            (putLeg ? " is-selected active" : "") +
            '" data-side="P" data-inst="' +
            put.inst_id +
            '" aria-pressed="' +
            (putLeg ? "true" : "false") +
            '">' +
            (putLeg ? "腿" + putLeg + " · Put" : "Put") +
            "</button>"
          : "—") +
        "</td>";
      tbody.appendChild(tr);
    });
    if (!state.ooStrikeExpandAll && prepared.length > rows.length) {
      const hint = document.createElement("tr");
      hint.className = "opt-strike-hint-row";
      hint.innerHTML =
        '<td colspan="' +
        cols +
        '" class="muted opt-strike-hint">默认显示 Call/Put 各最近 3 档 · 点「显示全部」查看该到期更多行权价(若当前为「仅平值」会自动切到「平/虚」)</td>';
      tbody.appendChild(hint);
    } else if (state.ooStrikeExpandAll && state.ooMoneyFilter === "otm") {
      const hint = document.createElement("tr");
      hint.className = "opt-strike-hint-row";
      hint.innerHTML =
        '<td colspan="' +
        cols +
        '" class="muted opt-strike-hint">当前为「仅虚值」筛选 · 切到「平/虚」可看平值档</td>';
      tbody.appendChild(hint);
    }
    tbody.querySelectorAll(".hp-oo-pick").forEach(function (btn) {
      btn.addEventListener("click", function () {
        const inst = btn.getAttribute("data-inst");
        const exp2 = currentExp("hp-oo-exp-select");
        const c = (exp2.contracts || []).find(function (x) {
          return x.inst_id === inst;
        });
        if (!c || !matchesOoMoneyFilter(c)) {
          alert("期期仅可选平值或虚值,不可选实值");
          return;
        }
        if (!state.legA) state.legA = c;
        else if (!state.legB || state.legB.inst_id === state.legA.inst_id) state.legB = c;
        else {
          state.legA = c;
          state.legB = null;
        }
        state.ooRecommend = null;
        syncOoRecommendUI();
        renderOoLegs();
      });
    });
    syncOoPickHighlight();
  }

  function renderOoLegs() {
    function fill(tag, c, infoId, sheetsId) {
      const info = $(infoId);
      const sheets = $(sheetsId);
      if (!info) return;
      if (!c) {
        info.textContent = tag + ": 尚未选用";
        if (sheets) {
          sheets.disabled = true;
          sheets.value = "1";
        }
        return;
      }
      info.innerHTML =
        tag +
        ": <strong>" +
        c.opt_type +
        " K" +
        c.strike +
        "</strong> " +
        (c.moneyness_label || "") +
        " · 卖一 " +
        fmtPxSz(c.ask, c.ask_sz, c.ask_estimated, c.tick_sz) +
        " <code>" +
        c.inst_id +
        "</code>";
      if (sheets) sheets.disabled = false;
    }
    fill("腿A", state.legA, "hp-oo-leg-a-info", "hp-oo-sheets-a");
    fill("腿B", state.legB, "hp-oo-leg-b-info", "hp-oo-sheets-b");
    autoFillOoSheets();
    syncOoPickHighlight();
  }

  function ooSheets(id) {
    const n = Number(($(id) && $(id).value) || 1);
    return n > 0 ? n : 1;
  }

  function updateOoPremiumLine() {
    const line = $("hp-oo-prem-line");
    if (!line) return;
    if (!state.legA && !state.legB) {
      line.textContent = "";
      return;
    }
    function prem(c, sheets) {
      if (!c) return 0;
      return Number(c.ask || 0) * sheets * Number(c.ct_mult || 0.01);
    }
    const a = prem(state.legA, ooSheets("hp-oo-sheets-a"));
    const b = prem(state.legB, ooSheets("hp-oo-sheets-b"));
    line.textContent =
      "预估权利金 A " +
      fmt(a, 4) +
      " + B " +
      fmt(b, 4) +
      " ≈ " +
      fmt(a + b, 4) +
      " USDC";
  }

  function legPayload(c, sheets) {
    return {
      opt_type: c.opt_type,
      strike: c.strike,
      sheets: sheets,
      ct_mult: c.ct_mult || 0.01,
      ask: c.ask,
      inst_id: c.inst_id,
    };
  }

  function setUnderlying(uly, forceReload) {
    const next = (uly || "ETH").toUpperCase();
    const changed = next !== state.underlying;
    state.underlying = next;
    syncUnderlyingUI();
    if (!changed && !forceReload) return;
    state.selected = null;
    state.legA = null;
    state.legB = null;
    state.ooRecommend = null;
    syncOoRecommendUI();
    if ($("hp-entry")) $("hp-entry").value = "";
    if ($("hp-contracts")) $("hp-contracts").value = "";
    if ($("hp-tp")) $("hp-tp").value = "";
    if ($("hp-sl")) $("hp-sl").value = "";
    if ($("hp-profit-rr")) $("hp-profit-rr").value = "2";
    if ($("hp-sel-inst")) $("hp-sel-inst").textContent = "—";
    if ($("hp-premium-line")) $("hp-premium-line").textContent = "";
    if ($("hp-oo-sheets-a")) {
      $("hp-oo-sheets-a").value = "1";
      $("hp-oo-sheets-a").disabled = true;
    }
    if ($("hp-oo-sheets-b")) {
      $("hp-oo-sheets-b").value = "1";
      $("hp-oo-sheets-b").disabled = true;
    }
    renderOoLegs();
    void refreshAll();
  }

  async function runPreview() {
    const isOo = state.mode === "options_options";
    const tbody = $("hp-result-tbody");
    const summary = $("hp-preview-summary");
    const midTh = $("hp-preview-mid-th");
    const title = $("hp-preview-title");
    state.previewOk = false;
    state.previewPlanType = isOo ? "options_options" : "perp_options";
    syncPreviewStartBtn();
    if (midTh) midTh.textContent = isOo ? "腿盈亏" : "永续/腿盈亏";
    if (title) title.textContent = isOo ? "情景测算 · 期期" : "情景测算 · 永期";
    if (summary) summary.textContent = "";
    if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="muted">计算中…</td></tr>';
    openPreviewModal();
    try {
      let body;
      if (isOo) {
        if (!state.legA || !state.legB) throw new Error("请选用两条期权腿");
        if (!matchesOoMoneyFilter(state.legA) || !matchesOoMoneyFilter(state.legB)) {
          throw new Error("期期两腿须为平值或虚值,不可选实值");
        }
        const rr = Number(($("hp-profit-rr") && $("hp-profit-rr").value) || 0);
        if (!(rr > 0)) throw new Error("请填写盈亏比(须大于0,默认2)");
        body = {
          plan_type: "options_options",
          profit_rr: rr,
          index_px: indexPx() || 0,
          leg_a: legPayload(state.legA, ooSheets("hp-oo-sheets-a")),
          leg_b: legPayload(state.legB, ooSheets("hp-oo-sheets-b")),
        };
      } else {
        if (!state.selected) throw new Error("请选用期权腿");
        if (isOptionPrimary()) {
          const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
          if (!sized) throw new Error("请填写权利金并确认卖一有效");
          const optPts = numInput("hp-opt-target-pts", NaN);
          const perpPts = numInput("hp-perp-target-pts", NaN);
          if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
          const exp = currentExp("hp-exp-select");
          body = {
            plan_type: "perp_options",
            option_primary: true,
            direction: getDirection(),
            entry: indexPx() || Number((state.market && state.market.mark) || 0),
            contracts: sized.contracts,
            sheets: sized.sheets,
            contract_size: (state.market && state.market.contract_size) || 0.01,
            opt_type: state.selected.opt_type,
            strike: state.selected.strike,
            ct_mult: state.selected.ct_mult || 0.01,
            ask: state.selected.ask,
            index_px: indexPx() || 0,
            premium_budget: numInput("hp-premium-budget", 0),
            option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
            option_target_points: optPts,
            perp_target_points: perpPts,
            strike_interval: numInput("hp-strike-interval", 15),
            min_option_hours: numInput("hp-min-hours", 36),
            option_leverage: numInput("hp-opt-leverage", 100),
            leverage: numInput("hp-perp-leverage", 100),
            moneyness: opMoneyKind(),
            hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
          };
        } else {
          const mSel = (state.selected.moneyness || "").toLowerCase();
          if (mSel === "otm") throw new Error("永期保险腿须为实值或平值,不可选虚值");
          const entry = Number(($("hp-entry") && $("hp-entry").value) || 0);
          const tp = Number(($("hp-tp") && $("hp-tp").value) || 0);
          const sl = Number(($("hp-sl") && $("hp-sl").value) || 0);
          const contracts = Number(($("hp-contracts") && $("hp-contracts").value) || 0);
          const sheets = Number(($("hp-sheets") && $("hp-sheets").value) || 1);
          if (!entry || !tp || !sl || !contracts) throw new Error("请完整填写开仓/止盈/止损/张数");
          body = {
            plan_type: "perp_options",
            direction: getDirection(),
            entry: entry,
            tp: tp,
            sl: sl,
            contracts: contracts,
            contract_size: (state.market && state.market.contract_size) || 0.01,
            opt_type: state.selected.opt_type,
            strike: state.selected.strike,
            sheets: sheets,
            ct_mult: state.selected.ct_mult || 0.01,
            ask: state.selected.ask,
            index_px: indexPx() || entry,
          };
        }
      }
      const d = await apiJson("/api/hedge-plan/preview", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      setGateLine(d.gates);
      const s = d.summary || {};
      if (summary) {
        if (d.plan_type === "perp_options" && (d.option_primary || s.opt_target_total != null)) {
          const sz = d.sizing || {};
          summary.innerHTML =
            "期权目标净利 " +
            fmtPnlHtml(s.opt_target_total) +
            " · 永续目标净利 " +
            fmtPnlHtml(s.perp_target_total) +
            " · 保费 " +
            fmt(s.premium_paid) +
            (sz.eth_qty != null ? " · ETH " + fmt(sz.eth_qty, 2) : "") +
            (s.perp_direction ? " · 永续" + (s.perp_direction === "short" ? "空" : "多") : "");
        } else if (d.plan_type === "perp_options") {
          summary.innerHTML =
            "止盈合计 " +
            fmtPnlHtml(s.tp_total) +
            " · 止损合计 " +
            fmtPnlHtml(s.sl_total) +
            " · 保费 " +
            fmt(s.premium_paid) +
            (s.hedge_ratio_at_sl != null ? " · 止损对冲率 " + fmt(s.hedge_ratio_at_sl) + "%" : "");
        } else {
          const rrTarget = s.profit_rr != null ? s.profit_rr : null;
          let rrLine = "";
          if (rrTarget != null) {
            rrLine =
              " · 目标盈亏比 " +
              fmt(rrTarget, 2) +
              '<span class="muted">（盈利金额/总权利金）</span>';
          } else if (s.rr_at_up != null || s.rr_at_down != null) {
            rrLine =
              " · 盈亏比 上破 " +
              fmtRr(s.rr_at_up) +
              (s.at_target_down_total != null ? " / 下破 " + fmtRr(s.rr_at_down) : "") +
              '<span class="muted">（亏=全额保费 ' +
              fmt(s.rr_risk_premium != null ? s.rr_risk_premium : s.premium_paid) +
              "）</span>";
          }
          const aTot = s.at_rr_a_full_total != null ? s.at_rr_a_full_total : s.at_target_up_total;
          const bTot = s.at_rr_b_full_total != null ? s.at_rr_b_full_total : s.at_target_down_total;
          summary.innerHTML =
            (rrTarget != null ? "腿A达标 " : "上破 ") +
            fmtPnlHtml(aTot) +
            (bTot != null ? (rrTarget != null ? " · 腿B达标 " : " · 下破 ") + fmtPnlHtml(bTot) : "") +
            " · 到期现价 " +
            fmtPnlHtml(s.expiry_flat_total) +
            " · 保费 " +
            fmt(s.premium_paid) +
            rrLine +
            (s.expiry_is_loss ? " · 到期无盈利(记总亏损)" : "");
        }
      }
      if (!tbody) return;
      tbody.innerHTML = "";
      (d.scenarios || []).forEach(function (sc) {
        const tr = document.createElement("tr");
        let mid;
        if (sc.perp_pnl != null) {
          mid = "永续 " + fmtPnlHtml(sc.perp_pnl);
        } else {
          mid = "A " + fmtPnlHtml(sc.leg_a_pnl) + " / B " + fmtPnlHtml(sc.leg_b_pnl);
        }
        const optCol = sc.options_pnl != null ? fmtPnlHtml(sc.options_pnl) : "—";
        tr.innerHTML =
          "<td>" +
          (sc.label || sc.id) +
          "</td><td>" +
          fmt(sc.spot) +
          "</td><td>" +
          mid +
          "</td><td>" +
          optCol +
          "</td><td>" +
          fmtPnlHtml(sc.total) +
          "</td><td class=\"muted\">" +
          (sc.note || "") +
          "</td>";
        tbody.appendChild(tr);
      });
      state.previewOk = true;
      syncPreviewStartBtn();
    } catch (e) {
      state.previewOk = false;
      syncPreviewStartBtn();
      if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="err">' + (e.message || e) + "</td></tr>";
      if (summary) summary.textContent = "";
    }
  }

  function hardenAmountAutofill(ids) {
    (ids || []).forEach(function (id) {
      const el = $(id);
      if (!el) return;
      function wipe() {
        const v = String(el.value || "").trim();
        // 浏览器常把登录用户名(如 dekun)灌进数量框
        if (/^[a-z][a-z0-9._-]{1,31}$/i.test(v)) el.value = "";
      }
      wipe();
      const lockRo = id.indexOf("xfer") >= 0 || el.hasAttribute("readonly");
      if (lockRo) {
        el.setAttribute("readonly", "readonly");
        el.addEventListener("focus", function () {
          el.removeAttribute("readonly");
        });
        el.addEventListener("blur", function () {
          if (!el.value) el.setAttribute("readonly", "readonly");
        });
      }
      setTimeout(wipe, 200);
      setTimeout(wipe, 800);
      setTimeout(wipe, 2000);
    });
  }

  function bind() {
    document.querySelectorAll(".hp-tab").forEach(function (b) {
      b.addEventListener("click", function () {
        const next = b.getAttribute("data-tab") || pickDefaultTab();
        if (next === "perp_options" && !showPerp) return;
        if (next === "options_options" && !showOo) return;
        state.tab = next;
        syncTabUI();
        if (state.tab === "perp_options" || state.tab === "options_options") {
          void loadGates();
          if (state.tab === "perp_options") void refreshPoStrategyStatus();
        } else if (state.tab === "active") {
          void loadActivePlans();
        } else if (state.tab === "history") {
          void loadHistory();
        } else if (state.tab === "stats") {
          void loadStats();
        } else {
          const el = $("hp-gate-line");
          if (el) el.textContent = "";
        }
      });
    });
    document.querySelectorAll(".hp-uly-btn, .hp-uly-btn-oo").forEach(function (b) {
      b.addEventListener("click", function () {
        setUnderlying(b.getAttribute("data-uly") || "ETH", true);
      });
    });
    document.querySelectorAll(".hp-money-btn").forEach(function (b) {
      b.addEventListener("click", function () {
        const m = b.getAttribute("data-money") || "itm";
        if (m === "otm" && !isOptionPrimary()) {
          alert("永期保险腿仅允许实值或平值;请在 env 将 HEDGE_PLAN_OPTION_PRIMARY=true");
          return;
        }
        state.moneyFilter = m === "otm" ? "otm" : m === "atm" ? "atm" : "itm";
        if (isOptionPrimary()) applyOpDefaultsFromMoney(false);
        syncMoneyUI();
        renderListStrikes();
      });
    });
    if ($("hp-money-select")) {
      $("hp-money-select").addEventListener("change", function () {
        const m = $("hp-money-select").value || "otm";
        state.moneyFilter = m === "otm" ? "otm" : m === "atm" ? "atm" : "itm";
        state.opLevTouched = false;
        state.opRatioTouched = false;
        applyOpDefaultsFromMoney(true);
        syncMoneyUI();
        renderListStrikes();
      });
    }
    if ($("hp-opt-leverage")) {
      $("hp-opt-leverage").addEventListener("input", function () {
        state.opLevTouched = true;
        if (isOptionPrimary()) renderListStrikes();
      });
    }
    if ($("hp-opt-perp-ratio")) {
      $("hp-opt-perp-ratio").addEventListener("input", function () {
        state.opRatioTouched = true;
        if (state.selected) {
          const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
          if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
        }
        void loadMarket();
      });
    }
    let _poChainReloadTimer = null;
    function schedulePoChainReload() {
      if (_poChainReloadTimer) clearTimeout(_poChainReloadTimer);
      _poChainReloadTimer = setTimeout(function () {
        _poChainReloadTimer = null;
        void loadChain();
      }, 350);
    }
    ["hp-premium-budget", "hp-strike-interval", "hp-min-hours", "hp-opt-target-pts", "hp-perp-target-pts"].forEach(
      function (id) {
        const el = $(id);
        if (!el) return;
        el.addEventListener("input", function () {
          if (id === "hp-min-hours" || id === "hp-strike-interval") {
            schedulePoChainReload();
          }
          if (id === "hp-premium-budget") renderListStrikes();
          if (state.selected && id === "hp-premium-budget") {
            const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
            if (sized && $("hp-sheets")) $("hp-sheets").value = String(sized.sheets);
            void loadMarket();
          }
          updatePerpPnlHint();
        });
      }
    );
    document.querySelectorAll(".hp-oo-money-btn").forEach(function (b) {
      b.addEventListener("click", function () {
        const m = b.getAttribute("data-oo-money") || "atm_otm";
        if (m === "itm") {
          alert("期期两腿仅允许平值或虚值");
          return;
        }
        state.ooMoneyFilter = m;
        syncMoneyUI();
        renderTStrikes();
      });
    });
    if ($("hp-recommend-opt")) {
      $("hp-recommend-opt").addEventListener("click", function () {
        if (isOptionPrimary()) {
          autoMatchPoOption(true);
          if (!state.selected) {
            alert("当前选约条件下无匹配合约,请调整类型/间隔或换到期");
          }
          return;
        }
        const want = optTypeForDirection(getDirection());
        const c = pickClosestItmAtm(currentContracts("hp-exp-select"), want);
        if (!c) {
          alert("当前到期日无可用实值/平值合约,请换到期或刷新链");
          return;
        }
        pickContract(c);
      });
    }
    if ($("hp-oo-recommend-atm")) {
      $("hp-oo-recommend-atm").addEventListener("click", function () {
        const pair = pickOoTemplate("atm_straddle");
        if (!pair) {
          alert("无法推荐平值跨式,请确认到期日与链数据");
          return;
        }
        state.legA = pair.call;
        state.legB = pair.put;
        state.ooRecommend = "atm_straddle";
        syncOoRecommendUI();
        renderOoLegs();
        autoFillOoSheets();
        updateOoPremiumLine();
      });
    }
    if ($("hp-oo-recommend-otm")) {
      $("hp-oo-recommend-otm").addEventListener("click", function () {
        const pair = pickOoTemplate("double_otm");
        if (!pair) {
          alert("无法推荐双虚值,请确认到期日与链数据");
          return;
        }
        state.legA = pair.call;
        state.legB = pair.put;
        state.ooRecommend = "double_otm";
        syncOoRecommendUI();
        renderOoLegs();
        autoFillOoSheets();
        updateOoPremiumLine();
      });
    }
    document.querySelectorAll(".hp-po-dir").forEach(function (b) {
      b.addEventListener("click", function () {
        setDirection(b.getAttribute("data-dir") || "long", true);
      });
    });
    ["hp-entry", "hp-tp", "hp-sl", "hp-contracts"].forEach(function (id) {
      const el = $(id);
      if (el) el.addEventListener("input", updatePerpPnlHint);
    });
    syncOptionPrimaryUI();
    if ($("hp-refresh"))
      $("hp-refresh").addEventListener("click", function () {
        void refreshAll();
      });
    if ($("hp-load-chain"))
      $("hp-load-chain").addEventListener("click", function () {
        void loadChain();
      });
    if ($("hp-oo-load-chain"))
      $("hp-oo-load-chain").addEventListener("click", function () {
        void loadChain();
      });
    if ($("hp-oo-expand-all")) {
      $("hp-oo-expand-all").addEventListener("click", function () {
        state.ooStrikeExpandAll = !state.ooStrikeExpandAll;
        // 「仅平值」时本来就只有 1～3 档,展开几乎无变化;展开时自动切到平/虚以便看到全部可选档
        if (state.ooStrikeExpandAll && state.ooMoneyFilter === "atm") {
          state.ooMoneyFilter = "atm_otm";
          syncMoneyUI();
        }
        syncOoExpandUI();
        renderTStrikes();
      });
    }
    if ($("hp-exp-select")) $("hp-exp-select").addEventListener("change", renderListStrikes);
    if ($("hp-oo-exp-select")) $("hp-oo-exp-select").addEventListener("change", renderTStrikes);
    if ($("hp-sheets")) $("hp-sheets").addEventListener("input", updatePremiumLine);
    ["hp-oo-sheets-a", "hp-oo-sheets-b"].forEach(function (id) {
      const el = $(id);
      if (el) el.addEventListener("input", updateOoPremiumLine);
    });
    document.querySelectorAll(".hp-oo-size-mode").forEach(function (b) {
      b.addEventListener("click", function () {
        state.ooSheetsMode = b.getAttribute("data-oo-size") || "same_sheets";
        syncOoSizeModeUI();
        autoFillOoSheets();
        updateOoBudgetLine(null);
      });
    });
    document.querySelectorAll(".hp-oo-close-mode").forEach(function (b) {
      b.addEventListener("click", function () {
        if (!state.ooCloseModeEnabled) return;
        state.ooCloseMode = b.getAttribute("data-oo-close") || "close_all";
        state._ooCloseModeTouched = true;
        syncOoCloseModeUI();
        updateOoBudgetLine(null);
      });
    });
    if ($("hp-oo-xfer-btn")) {
      $("hp-oo-xfer-btn").addEventListener("click", function () {
        const amount = Number(($("hp-oo-xfer-amount") && $("hp-oo-xfer-amount").value) || 0);
        if (!(amount > 0)) {
          setOoXferMsg("请输入有效数量", "err");
          return;
        }
        void submitOoUsdcTransfer(amount);
      });
    }
    if ($("hp-oo-xfer-all")) {
      $("hp-oo-xfer-all").addEventListener("click", function () {
        const dir = ooXferDir();
        const max =
          dir === "trading_to_funding" ? Number(state.tradingUsdc || 0) : Number(state.fundingUsdc || 0);
        if (!(max > 0)) {
          setOoXferMsg("划出账户可用余额不足", "err");
          return;
        }
        const amt = Math.floor(max * 100) / 100;
        const label = dir === "trading_to_funding" ? "交易 → 资金" : "资金 → 交易";
        if (!window.confirm("确认全部划转?\n方向:" + label + "\n数量:" + amt + " USDC")) return;
        const amtEl = $("hp-oo-xfer-amount");
        if (amtEl) {
          amtEl.removeAttribute("readonly");
          amtEl.value = String(amt);
        }
        void submitOoUsdcTransfer(amt);
      });
    }
    hardenAmountAutofill([
      "hp-oo-xfer-amount",
      "hp-entry",
      "hp-contracts",
      "hp-tp",
      "hp-sl",
      "hp-sheets",
      "hp-profit-rr",
    ]);
    if ($("hp-preview-btn"))
      $("hp-preview-btn").addEventListener("click", function () {
        state.mode = "perp_options";
        if (isOptionPrimary()) void startOptionPrimaryWatch();
        else void runPreview();
      });
    if ($("hp-preview-btn-oo"))
      $("hp-preview-btn-oo").addEventListener("click", function () {
        state.mode = "options_options";
        void runPreview();
      });
    if ($("hp-preview-cancel")) $("hp-preview-cancel").addEventListener("click", closePreviewModal);
    if ($("hp-preview-cancel-x")) $("hp-preview-cancel-x").addEventListener("click", closePreviewModal);
    if ($("hp-preview-start"))
      $("hp-preview-start").addEventListener("click", function () {
        const planType = state.previewPlanType || state.mode;
        void startPlan(planType, true);
      });
    const previewModal = $("hp-preview-modal");
    if (previewModal) {
      previewModal.addEventListener("click", function (ev) {
        if (ev.target === previewModal) closePreviewModal();
      });
    }
    if ($("hp-detail-close")) $("hp-detail-close").addEventListener("click", closeModal);
    const modal = $("hp-detail-modal");
    if (modal) {
      modal.addEventListener("click", function (ev) {
        if (ev.target === modal) closeModal();
      });
    }
  }

  async function loadHistory() {
    const tbody = $("hp-history-tbody");
    if (!tbody) return;
    try {
      const d = await apiJson("/api/hedge-plan/history");
      const rows = d.plans || [];
      if (!rows.length) {
        tbody.innerHTML = '<tr><td colspan="10" class="muted">暂无已结束计划</td></tr>';
        return;
      }
      tbody.innerHTML = "";
      rows.forEach(function (p) {
        const tr = document.createElement("tr");
        const typeLabel = p.plan_type === "perp_options" ? "永期" : "期期";
        const contracts = p.contracts_summary || "—";
        const pnl = p.realized_pnl_total;
        const pnlCls =
          pnl == null || Number.isNaN(Number(pnl)) ? "" : Number(pnl) >= 0 ? "hp-pnl-pos" : "hp-pnl-neg";
        tr.innerHTML =
          "<td>#" +
          p.id +
          "</td><td>" +
          typeLabel +
          "</td><td>" +
          (p.underlying || "") +
          "</td><td class=\"hp-contracts-cell\" title=\"" +
          String(contracts).replace(/"/g, "&quot;") +
          "\"><code>" +
          contracts +
          "</code></td><td>" +
          (p.status || "") +
          '</td><td class="' +
          pnlCls +
          '">' +
          fmt(pnl) +
          "</td><td>" +
          reasonLabel(p.close_reason) +
          "</td><td>" +
          (p.opened_at || "—") +
          "</td><td>" +
          (p.closed_at || "—") +
          '</td><td class="hp-hist-actions">' +
          '<button type="button" class="btn-secondary hp-btn-detail" data-id="' +
          p.id +
          '">成交细节</button> ' +
          '<button type="button" class="btn-secondary hp-btn-del" data-id="' +
          p.id +
          '">删除</button>' +
          "</td>";
        tbody.appendChild(tr);
      });
      tbody.querySelectorAll(".hp-btn-detail").forEach(function (btn) {
        btn.addEventListener("click", function () {
          void showPlanDetail(Number(btn.getAttribute("data-id")));
        });
      });
      tbody.querySelectorAll(".hp-btn-del").forEach(function (btn) {
        btn.addEventListener("click", function () {
          void deletePlan(Number(btn.getAttribute("data-id")));
        });
      });
    } catch (e) {
      tbody.innerHTML = '<tr><td colspan="10" class="err">' + (e.message || e) + "</td></tr>";
    }
  }

  function activeTargetLabel(p) {
    if (p.plan_type === "perp_options" && (p.option_primary == 1 || p.option_primary === true || Number(p.option_primary) === 1)) {
      return (
        "期权K±" +
        fmt(p.option_target_points, 0) +
        " · 永续K±" +
        fmt(p.perp_target_points, 0)
      );
    }
    if (p.plan_type === "perp_options") {
      return "止盈 " + fmt(p.tp) + " · 止损 " + fmt(p.sl);
    }
    if (p.profit_rr != null && Number(p.profit_rr) > 0) {
      return "盈亏比 " + fmt(p.profit_rr, 2);
    }
    return "上破 " + fmt(p.target_price_up || p.target_price) + " · 下破 " + fmt(p.target_price_down || p.target_price);
  }

  function activeStatusLabel(p) {
    if ((p.status || "") === "watching") {
      return '<span class="hp-plan-watching">盯盘中</span>';
    }
    if ((p.status || "") === "partial") {
      return '<span class="hp-plan-partial">半腿待补</span>';
    }
    if ((p.status || "") === "opening") {
      return '<span class="hp-plan-active">开仓中</span>';
    }
    return '<span class="hp-plan-active">进行中</span>';
  }

  function completeLegButtonHtml(p) {
    if ((p.status || "") !== "partial") return "";
    const role = p.missing_leg || "";
    let label = "";
    if (role === "perp") label = "补开永续";
    else if (role === "option_b") label = "补开腿B";
    else if (role === "option_hedge" || role === "option_a") label = "补开期权";
    else return "";
    return (
      '<button type="button" class="primary hp-btn-complete" data-id="' +
      p.id +
      '" data-role="' +
      role +
      '">' +
      label +
      "</button> "
    );
  }

  async function completeMissingLeg(planId) {
    if (!window.confirm("确认补开缺失腿并真实下单?")) return;
    try {
      const d = await apiJson("/api/hedge-plan/" + planId + "/complete-leg", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({}),
      });
      alert("补开成功 #" + (d.plan_id || planId) + " · 已完全成交并进入进行中");
      void loadActivePlans();
      void loadGates();
    } catch (e) {
      alert(e.message || String(e));
    }
  }

  async function endActivePlan(planId) {
    if (
      !window.confirm(
        "确认结束计划 #" +
          planId +
          "？\n不会自动平仓；未成交/待补腿将标为未成交取消。\n已有持仓请自行平掉。"
      )
    ) {
      return;
    }
    try {
      const d = await apiJson("/api/hedge-plan/" + planId + "/end", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({}),
      });
      alert(d.msg || "计划已结束 #" + (d.plan_id || planId));
      void loadActivePlans();
      void loadHistory();
      void loadGates();
      void refreshPoStrategyStatus();
    } catch (e) {
      alert(e.message || String(e));
    }
  }

  function legStatusLabel(st) {
    const map = {
      open: "持仓中",
      pending: "待补/未成交",
      cancelled: "未成交取消",
      canceled: "未成交取消",
      closed: "已平仓",
    };
    return map[st] || st || "—";
  }

  async function loadActivePlans() {
    const tbody = $("hp-active-tbody");
    if (!tbody) return;
    try {
      const d = await apiJson("/api/hedge-plan/active");
      const rows = d.plans || [];
      if (!rows.length) {
        tbody.innerHTML = '<tr><td colspan="8" class="muted">暂无进行中的计划</td></tr>';
        return;
      }
      tbody.innerHTML = "";
      rows.forEach(function (p) {
        const tr = document.createElement("tr");
        const typeLabel = p.plan_type === "perp_options" ? "永期" : "期期";
        const contracts = p.contracts_summary || "—";
        tr.innerHTML =
          "<td>#" +
          p.id +
          "</td><td>" +
          typeLabel +
          "</td><td>" +
          (p.underlying || "") +
          "</td><td class=\"hp-contracts-cell\" title=\"" +
          String(contracts).replace(/"/g, "&quot;") +
          "\"><code>" +
          contracts +
          "</code></td><td>" +
          activeStatusLabel(p) +
          "</td><td>" +
          activeTargetLabel(p) +
          "</td><td>" +
          (p.opened_at || "—") +
          '</td><td class="hp-hist-actions">' +
          completeLegButtonHtml(p) +
          '<button type="button" class="btn-secondary hp-btn-detail" data-id="' +
          p.id +
          '">成交细节</button> ' +
          '<button type="button" class="btn-secondary hp-btn-end" data-id="' +
          p.id +
          '">结束计划</button></td>';
        tbody.appendChild(tr);
      });
      tbody.querySelectorAll(".hp-btn-detail").forEach(function (btn) {
        btn.addEventListener("click", function () {
          void showPlanDetail(Number(btn.getAttribute("data-id")));
        });
      });
      tbody.querySelectorAll(".hp-btn-complete").forEach(function (btn) {
        btn.addEventListener("click", function () {
          void completeMissingLeg(Number(btn.getAttribute("data-id")));
        });
      });
      tbody.querySelectorAll(".hp-btn-end").forEach(function (btn) {
        btn.addEventListener("click", function () {
          void endActivePlan(Number(btn.getAttribute("data-id")));
        });
      });
    } catch (e) {
      tbody.innerHTML = '<tr><td colspan="8" class="err">' + (e.message || e) + "</td></tr>";
    }
  }

  function reasonLabel(r) {
    const map = {
      perp_tp: "永续止盈",
      perp_sl: "永续止损",
      oo_expiry_loss: "期期到期亏损",
      oo_expiry_win: "期期到期盈利",
      target_win_leg: "期期平盈利腿",
      target_up_win_leg: "期期上破·平盈利腿",
      target_down_win_leg: "期期下破·平盈利腿",
      profit_rr_win_leg: "期期盈亏比达标·平盈利腿",
      oo_rest_closing: "期期残值平·清亏损腿中",
      oo_rest_closed: "期期残值平·两腿已平",
      orphaned_after_tp: "止盈后持有至到期",
      orphaned_option_expiry: "残腿到期",
      hold_to_expiry: "持有至到期",
      expiry: "到期",
      manual: "人工结束",
      manual_end: "人工结束",
      partial_fail: "半腿失败",
      cancelled: "已取消",
      unfilled: "未成交",
    };
    return map[r] || r || "—";
  }

  function roleLabel(role) {
    const map = {
      perp: "永续腿",
      option_hedge: "保险期权",
      option_a: "期期腿A",
      option_b: "期期腿B",
    };
    return map[role] || role || "—";
  }

  function closeModal() {
    const m = $("hp-detail-modal");
    if (m) m.hidden = true;
  }

  async function showPlanDetail(planId) {
    const modal = $("hp-detail-modal");
    const body = $("hp-detail-body");
    const title = $("hp-detail-title");
    if (!modal || !body) return;
    modal.hidden = false;
    body.innerHTML = '<p class="muted">加载中…</p>';
    if (title) title.textContent = "成交细节 #" + planId;
    try {
      const d = await apiJson("/api/hedge-plan/" + planId);
      const p = d.plan || {};
      const legs = d.legs || [];
      const typeLabel = p.plan_type === "perp_options" ? "永期对冲" : "期期对冲";
      let html = "";
      html += '<div class="hp-detail-summary">';
      html += "<div><span class=\"muted\">类型</span> " + typeLabel + "</div>";
      html += "<div><span class=\"muted\">标的</span> " + (p.underlying || "—");
      if (p.direction) html += " · " + (p.direction === "long" ? "做多" : "做空");
      html += "</div>";
      html += "<div><span class=\"muted\">状态</span> " + (p.status || "—") + " / " + reasonLabel(p.close_reason) + "</div>";
      html += "<div><span class=\"muted\">时间</span> " + (p.opened_at || "—") + " → " + (p.closed_at || "—") + "</div>";
      html +=
        "<div><span class=\"muted\">盈亏</span> 永续 " +
        fmt(p.realized_pnl_perp) +
        " · 期权 " +
        fmt(p.realized_pnl_options) +
        " · 合计 <strong class=\"" +
        (Number(p.realized_pnl_total) >= 0 ? "hp-pnl-pos" : "hp-pnl-neg") +
        '">' +
        fmt(p.realized_pnl_total) +
        "</strong> ≈U</div>";
      if (p.plan_type === "perp_options") {
        html +=
          "<div><span class=\"muted\">参考价</span> 开 " +
          fmt(p.entry_mark) +
          " · 止盈 " +
          fmt(p.tp) +
          " · 止损 " +
          fmt(p.sl) +
          " · 杠杆 " +
          fmt(p.leverage, 0) +
          "x · 张数 " +
          fmt(p.perp_size, 4) +
          "</div>";
      } else {
        if (p.profit_rr != null && Number(p.profit_rr) > 0) {
          html +=
            "<div><span class=\"muted\">盈亏比</span> " +
            fmt(p.profit_rr, 2) +
            " <span class=\"muted\">(盈利金额/总权利金)</span></div>";
        } else {
          html +=
            "<div><span class=\"muted\">目标价</span> 上破 " +
            fmt(p.target_price_up || p.target_price) +
            " · 下破 " +
            fmt(p.target_price_down || p.target_price) +
            "</div>";
        }
      }
      html +=
        "<div><span class=\"muted\">权利金合计</span> " +
        fmt(p.premium_total, 4) +
        " USDC</div>";
      html +=
        "<div><span class=\"muted\">合约摘要</span> <code>" +
        (d.contracts_summary || "—") +
        "</code></div>";
      html += "</div>";
      html += '<table class="options-strike-table hp-detail-legs"><thead><tr>';
      html +=
        "<th>角色</th><th>合约名称</th><th>方向/类型</th><th>数量</th><th>开仓价</th><th>权利金</th><th>状态</th><th>腿盈亏</th><th>成交号</th><th>平仓原因</th>";
      html += "</tr></thead><tbody>";
      if (!legs.length) {
        html += '<tr><td colspan="10" class="muted">无腿记录</td></tr>';
      } else {
        legs.forEach(function (leg) {
          const contract =
            leg.leg_role === "perp"
              ? leg.symbol || "—"
              : leg.inst_id || "—";
          const side =
            leg.leg_role === "perp"
              ? leg.side || "—"
              : (leg.opt_type || "") + (leg.strike != null ? " K" + fmt(leg.strike, 0) : "");
          html += "<tr>";
          html += "<td>" + roleLabel(leg.leg_role) + "</td>";
          html += "<td><code>" + contract + "</code></td>";
          html += "<td>" + side + "</td>";
          html += "<td>" + fmt(leg.size, leg.leg_role === "perp" ? 4 : 0) + "</td>";
          html += "<td>" + fmt(leg.avg_open, 4) + "</td>";
          html += "<td>" + (leg.premium != null ? fmt(leg.premium, 4) : "—") + "</td>";
          html += "<td>" + legStatusLabel(leg.status) + "</td>";
          html += "<td>" + fmt(leg.realized_pnl, 4) + "</td>";
          html += "<td><code class=\"hp-ord\">" + (leg.exchange_ord_id || "—") + "</code></td>";
          html += "<td>" + reasonLabel(leg.close_reason) + "</td>";
          html += "</tr>";
        });
      }
      html += "</tbody></table>";
      if (p.note) {
        html += '<p class="hp-detail-note"><span class="muted">备注</span> ' + String(p.note) + "</p>";
      }
      body.innerHTML = html;
    } catch (e) {
      body.innerHTML = '<p class="err">' + (e.message || e) + "</p>";
    }
  }

  async function deletePlan(planId) {
    if (!window.confirm("确认删除历史计划 #" + planId + "？此操作不可恢复。")) return;
    try {
      await apiJson("/api/hedge-plan/" + planId, { method: "DELETE" });
      await loadHistory();
      if (state.tab === "stats") await loadStats();
    } catch (e) {
      window.alert(e.message || String(e));
    }
  }

  function metricCard(title, m) {
    if (!m || !m.count) {
      return (
        '<div class="hp-stats-card"><h3>' +
        title +
        '</h3><p class="muted">暂无已结束样本</p></div>'
      );
    }
    const wr = m.win_rate == null ? "—" : (Number(m.win_rate) * 100).toFixed(1) + "%";
    let pf = "—";
    if (m.profit_factor_infinite) pf = "∞";
    else if (m.profit_factor != null) pf = fmt(m.profit_factor, 2);
    return (
      '<div class="hp-stats-card"><h3>' +
      title +
      "</h3><ul class=\"hp-stats-list\">" +
      "<li><span>笔数</span><strong>" +
      m.count +
      "</strong></li>" +
      "<li><span>胜率</span><strong>" +
      wr +
      "</strong> <span class=\"muted\">(" +
      m.wins +
      "/" +
      m.count +
      ")</span></li>" +
      "<li><span>净盈亏≈U</span><strong class=\"" +
      (Number(m.net_pnl) >= 0 ? "hp-pnl-pos" : "hp-pnl-neg") +
      '">' +
      fmt(m.net_pnl) +
      "</strong></li>" +
      "<li><span>盈亏比</span><strong>" +
      pf +
      "</strong> <span class=\"muted\">毛利/|毛亏|</span></li>" +
      "<li><span>最大盈利</span><strong class=\"hp-pnl-pos\">" +
      fmt(m.max_profit) +
      "</strong></li>" +
      "<li><span>最大亏损</span><strong class=\"hp-pnl-neg\">" +
      fmt(m.max_loss) +
      "</strong></li>" +
      "<li><span>最大回撤</span><strong>" +
      fmt(m.max_drawdown) +
      "</strong></li>" +
      "<li><span>平均保费</span><strong>" +
      fmt(m.avg_premium, 4) +
      "</strong></li>" +
      "</ul></div>"
    );
  }

  async function loadStats() {
    const box = $("hp-stats-box");
    if (!box) return;
    try {
      const d = await apiJson("/api/hedge-plan/stats");
      const by = d.by_type || {};
      let html = '<div class="hp-stats-grid">';
      html +=
        '<div class="hp-stats-card hp-stats-card--overview"><h3>总览</h3><p>活跃 <strong>' +
        (d.active || 0) +
        "</strong> · 已结 <strong>" +
        (d.closed_count || 0) +
        '</strong> · 合计 <strong class="' +
        (Number(d.closed_pnl_total) >= 0 ? "hp-pnl-pos" : "hp-pnl-neg") +
        '">' +
        fmt(d.closed_pnl_total) +
        "</strong> ≈U</p></div>";
      html += metricCard("永期对冲", by.perp_options);
      html += metricCard("期期对冲", by.options_options);
      html += "</div>";
      const poB = (by.perp_options && by.perp_options.buckets) || {};
      const ooB = (by.options_options && by.options_options.buckets) || {};
      if ((poB.tp && poB.tp.count) || (poB.sl && poB.sl.count) || (ooB.expiry_loss && ooB.expiry_loss.count)) {
        html += '<div class="hp-stats-grid hp-stats-grid--sub">';
        if (poB.tp && poB.tp.count) html += metricCard("永期·止盈桶", poB.tp);
        if (poB.sl && poB.sl.count) html += metricCard("永期·止损桶", poB.sl);
        if (ooB.expiry_loss && ooB.expiry_loss.count) html += metricCard("期期·到期亏损", ooB.expiry_loss);
        if (ooB.expiry_win && ooB.expiry_win.count) html += metricCard("期期·到期盈利", ooB.expiry_win);
        html += "</div>";
      }
      box.innerHTML = html;
    } catch (e) {
      box.textContent = e.message || String(e);
    }
  }

  async function startOptionPrimaryWatch() {
    try {
      const optPts = numInput("hp-opt-target-pts", NaN);
      const perpPts = numInput("hp-perp-target-pts", NaN);
      const prem = numInput("hp-premium-budget", 0);
      const optLev = numInput("hp-opt-leverage", 200);
      if (!(prem > 0)) throw new Error("请填写权利金预算");
      if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
      if (!(optLev > 0)) throw new Error("请填写期权杠杆门槛");
      if (!(state.market && state.market.exchange_symbol)) throw new Error("永续行情未就绪,请先刷新");
      if (!state.canStart) {
        throw new Error("当前不可启动(门禁未满足),请查看上方提示");
      }
      const msg =
        "确认启动盯盘?\n" +
        "类型 " +
        (opMoneyKind() === "otm" ? "虚值" : opMoneyKind() === "atm" ? "平值" : "实/平") +
        " · 间隔 " +
        numInput("hp-strike-interval", 15) +
        " · 杠杆≥" +
        optionPrimaryMinLev() +
        "\n达标后自动开仓(非现场立即开)";
      if (!window.confirm(msg)) return;
      const body = {
        plan_type: "perp_options",
        option_primary: true,
        watch_entry: 1,
        underlying: state.underlying,
        direction: getDirection(),
        exchange_symbol: state.market.exchange_symbol,
        contract_size: state.market.contract_size || 0.01,
        index_px: indexPx() || Number(state.market.mark || 0),
        entry: indexPx() || Number(state.market.mark || 0),
        premium_budget: prem,
        option_perp_ratio: numInput("hp-opt-perp-ratio", 4),
        option_target_points: optPts,
        perp_target_points: perpPts,
        strike_interval: numInput("hp-strike-interval", 15),
        min_option_hours: numInput("hp-min-hours", 36),
        option_leverage: optLev,
        leverage: numInput("hp-perp-leverage", 100),
        moneyness: opMoneyKind(),
      };
      const d = await apiJson("/api/hedge-plan/start", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      setGateLine(d.gates);
      setPoStrategyStatus("watching", d.plan_id || null);
      alert((d.msg || "已启动盯盘") + (d.plan_id ? "\n计划 #" + d.plan_id : ""));
      void refreshPoStrategyStatus();
      void loadGates();
    } catch (e) {
      alert(e.message || String(e));
    }
  }

  async function startPlan(planType, fromPreviewModal) {
    const isOo = planType === "options_options";
    const startBtn = $("hp-preview-start");
    try {
      let body;
      if (isOo) {
        if (!state.legA || !state.legB) throw new Error("请选用两条期权腿");
        if (!matchesOoMoneyFilter(state.legA) || !matchesOoMoneyFilter(state.legB)) {
          throw new Error("期期两腿须为平值或虚值,不可选实值");
        }
        const rr = Number(($("hp-profit-rr") && $("hp-profit-rr").value) || 0);
        if (!(rr > 0)) throw new Error("请填写盈亏比(须大于0,默认2)");
        body = {
          plan_type: "options_options",
          underlying: state.underlying,
          profit_rr: rr,
          index_px: indexPx() || 0,
          oo_close_mode: state.ooCloseModeEnabled ? state.ooCloseMode : "hold_expiry",
          oo_sheets_mode: state.ooSheetsMode || "same_sheets",
          leg_a: legPayload(state.legA, ooSheets("hp-oo-sheets-a")),
          leg_b: legPayload(state.legB, ooSheets("hp-oo-sheets-b")),
        };
      } else {
        if (!state.selected) throw new Error("请选用期权腿");
        if (isOptionPrimary()) {
          const sized = computeOpSizing(state.selected.ask, state.selected.ct_mult || 0.01);
          if (!sized) throw new Error("请填写权利金并确认卖一有效");
          const optPts = numInput("hp-opt-target-pts", NaN);
          const perpPts = numInput("hp-perp-target-pts", NaN);
          if (!(optPts > 0) || !(perpPts > 0)) throw new Error("请填写期权/永续目标位点数(须大于0)");
          const exp = currentExp("hp-exp-select");
          const entry = indexPx() || Number((state.market && state.market.mark) || 0);
          body = {
            plan_type: "perp_options",
            option_primary: true,
            underlying: state.underlying,
            direction: getDirection(),
            entry: entry,
            contracts: sized.contracts,
            sheets: sized.sheets,
            contract_size: (state.market && state.market.contract_size) || 0.01,
            ct_mult: state.selected.ct_mult || 0.01,
            opt_inst_id: state.selected.inst_id,
            opt_type: state.selected.opt_type,
            strike: state.selected.strike,
            ask: state.selected.ask,
            index_px: entry,
            exchange_symbol: (state.market && state.market.exchange_symbol) || "",
            leverage: numInput("hp-perp-leverage", 100),
            option_leverage: numInput("hp-opt-leverage", 100),
            premium_budget: numInput("hp-premium-budget", 0),
            option_perp_ratio: numInput("hp-opt-perp-ratio", 2),
            option_target_points: optPts,
            perp_target_points: perpPts,
            strike_interval: numInput("hp-strike-interval", 15),
            min_option_hours: numInput("hp-min-hours", 36),
            moneyness: opMoneyKind(),
            hours_to_expiry: exp ? hoursFromExpMs(exp.exp_time) : null,
          };
        } else {
          const m = (state.selected.moneyness || "").toLowerCase();
          if (m === "otm") throw new Error("永期保险腿须为实值或平值,不可选虚值");
          const entry = Number(($("hp-entry") && $("hp-entry").value) || 0);
          const tp = Number(($("hp-tp") && $("hp-tp").value) || 0);
          const sl = Number(($("hp-sl") && $("hp-sl").value) || 0);
          const contracts = Number(($("hp-contracts") && $("hp-contracts").value) || 0);
          const sheets = Number(($("hp-sheets") && $("hp-sheets").value) || 1);
          if (!entry || !tp || !sl || !contracts) throw new Error("请完整填写开仓/止盈/止损/张数");
          body = {
            plan_type: "perp_options",
            underlying: state.underlying,
            direction: getDirection(),
            entry: entry,
            tp: tp,
            sl: sl,
            contracts: contracts,
            sheets: sheets,
            opt_inst_id: state.selected.inst_id,
            opt_type: state.selected.opt_type,
            strike: state.selected.strike,
            ask: state.selected.ask,
            index_px: indexPx() || entry,
            exchange_symbol: (state.market && state.market.exchange_symbol) || "",
            leverage: 10,
            margin: state.market && state.market.full_margin_sizing && state.market.full_margin_sizing.margin_capital,
          };
        }
      }
      if (!fromPreviewModal) {
        if (!window.confirm("确认启动对冲计划并真实下单?\n(将按期权账户/合约账户分别下单)")) return;
      }
      if (startBtn) startBtn.disabled = true;
      const d = await apiJson("/api/hedge-plan/start", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      setGateLine(d.gates);
      closePreviewModal();
      const refreshHint =
        d.refresh && d.refresh.msg ? "\n" + String(d.refresh.msg) : "";
      if (d.partial) {
        alert(
          (d.msg || "半腿失败,已挂待补") +
            (d.plan_id ? "\n计划 #" + d.plan_id : "") +
            refreshHint +
            "\n请到「进行中的计划」补开缺失腿"
        );
        state.tab = "active";
        syncTabUI();
        void loadActivePlans();
      } else {
        alert(
          "计划已启动 #" +
            (d.plan_id || "") +
            (d.dry_run ? " (dry_run)" : "") +
            refreshHint
        );
      }
      void loadGates();
      if (isOo) void loadChain();
    } catch (e) {
      alert(e.message || String(e));
      syncPreviewStartBtn();
    }
  }

  async function refreshAll() {
    if (state.tab === "perp_options" || state.tab === "options_options") {
      await loadGates();
    }
    try {
      await loadMarket();
    } catch (e) {
      const q = $("hp-perp-quote");
      if (q) q.textContent = e.message || String(e);
    }
    try {
      await loadChain();
    } catch (e) {
      const tbody = $("hp-strike-tbody");
      if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="err">' + (e.message || e) + "</td></tr>";
    }
    if (state.tab === "perp_options") void refreshPoStrategyStatus();
  }

  syncTabUI();
  syncUnderlyingUI();
  syncMoneyUI();
  syncOoRecommendUI();
  syncOoExpandUI();
  syncPoDirUI();
  syncOoSizeModeUI();
  syncOoCloseModeUI();
  applyBudgetBuffer(state.budgetBuffer);
  updateOoBudgetLine(null);
  bind();
  syncOptionPrimaryUI();
  void refreshAll().then(function () {
    void refreshPoStrategyStatus();
  });
  setInterval(function () {
    if (state.tab === "perp_options" && isOptionPrimary()) void refreshPoStrategyStatus();
  }, 15000);
})();
