(function () {
  "use strict";

  const root = document.getElementById("options-root");
  if (!root) return;
  if (root.getAttribute("data-options-booted") === "1") return;
  root.setAttribute("data-options-booted", "1");

  const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {});

  const state = {
    underlying: root.dataset.defaultUnderly || "ETH",
    optType: "C",
    moneyFilter: "all",
    chainView: "list",
    strikeExpandAll: false,
    chain: panelCache.chain || null,
    selectedInst: null,
    orderQuote: null,
    expandedPosInst: null,
    posTab: "live",
    /** 未点设定前的目标输入草稿,避免持仓轮询重绘清空 */
    targetDraftByInst: {},
  };

  let lastGoodPositions = null;
  let lastGoodPositionsAt = 0;
  let positionsRefreshSeq = 0;
  let chainLoadSeq = 0;
  let selectSeq = 0;
  let refreshAllTimer = null;
  let pendingRefreshTimer = null;
  let pendingTtlSeconds = 600;
  const POSITIONS_STALE_MS = 45000;
  const PENDING_POLL_MS = 8000;
  const orderPanelHome = (function () {
    const host = document.getElementById("opt-order-panel-host");
    return host ? host.parentElement : null;
  })();

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

  function fmtDisplay(v, fallback) {
    if (v !== null && v !== undefined && String(v).trim() !== "") return String(v);
    if (fallback !== undefined) return fmtDisplay(fallback);
    return "—";
  }

  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) {
      // 无 tick 时裁掉浮点毛刺,勿 482.4881990066513
      let s = n.toFixed(4).replace(/\.?0+$/, "");
      return s || "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";
  }

  async function apiJson(url, opts) {
    const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
    return r.json();
  }

  function orderPanel() {
    return document.getElementById("opt-order-panel");
  }

  function orderPanelHost() {
    return document.getElementById("opt-order-panel-host");
  }

  function syncPickButtons(instId) {
    document.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
      const on = !!instId && btn.getAttribute("data-inst") === instId;
      btn.classList.toggle("active", on);
      btn.disabled = false;
      if (!btn.dataset.origText) btn.dataset.origText = "选择";
      btn.textContent = on ? "已选" : btn.dataset.origText;
    });
  }

  function parkOrderPanel() {
    stopPendingOrdersPoll();
    const panel = orderPanel();
    const host = orderPanelHost();
    // 把整块 host(含面板)移回原位,再删行内 tr,避免 tbody 重绘销毁下单 DOM
    if (host && orderPanelHome && host.parentElement !== orderPanelHome) {
      orderPanelHome.appendChild(host);
    } else if (panel && host && panel.parentElement !== host) {
      host.appendChild(panel);
    }
    if (host) host.hidden = true;
    if (panel) panel.style.display = "none";
    const inline = document.querySelector(".opt-order-inline-row");
    if (inline) inline.remove();
    document.querySelectorAll(".opt-strike-row").forEach(function (r) {
      r.classList.remove("opt-row-selected");
    });
    syncPickButtons(null);
  }

  function placeOrderPanelAfter(instId) {
    const panel = orderPanel();
    const host = orderPanelHost();
    if (!panel || !host || !instId) {
      syncPickButtons(instId || null);
      return false;
    }
    const row =
      document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-inst="' + CSS.escape(instId) + '"]') ||
      document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-call-inst="' + CSS.escape(instId) + '"]') ||
      document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-put-inst="' + CSS.escape(instId) + '"]');
    if (!row) {
      syncPickButtons(null);
      return false;
    }
    document.querySelectorAll(".opt-strike-row").forEach(function (r) {
      r.classList.toggle("opt-row-selected", r === row);
    });
    syncPickButtons(instId);
    const oldInline = document.querySelector(".opt-order-inline-row");
    if (oldInline) oldInline.remove();
    if (panel.parentElement !== host) host.appendChild(panel);
    const tr = document.createElement("tr");
    tr.className = "opt-order-inline-row";
    const td = document.createElement("td");
    td.colSpan = strikeTableColspan();
    td.appendChild(host);
    tr.appendChild(td);
    row.after(tr);
    host.hidden = false;
    panel.style.display = "";
    tr.scrollIntoView({ behavior: "smooth", block: "nearest" });
    refreshPendingOrders();
    startPendingOrdersPoll();
    return true;
  }

  function fmtPendingAge(sec) {
    if (sec == null || Number.isNaN(Number(sec))) return "—";
    let s = Math.max(0, Math.round(Number(sec)));
    if (s < 60) return s + "秒";
    const m = Math.floor(s / 60);
    const rs = s % 60;
    if (m < 60) return rs ? m + "分" + rs + "秒" : m + "分";
    const h = Math.floor(m / 60);
    const rm = m % 60;
    return rm ? h + "时" + rm + "分" : h + "时";
  }

  function paintPendingOrders(orders, ttlSec) {
    const host = document.getElementById("opt-pending-list");
    const hint = document.getElementById("opt-pending-ttl-hint");
    if (ttlSec != null && !Number.isNaN(Number(ttlSec))) {
      pendingTtlSeconds = Number(ttlSec);
    }
    if (hint) {
      const ttl = pendingTtlSeconds;
      hint.textContent = ttl > 0
        ? ("平仓限价超 " + fmtPendingAge(ttl) + " 未成交将自动撤销")
        : "平仓超时自动撤单已关闭";
    }
    if (!host) return;
    const rows = Array.isArray(orders) ? orders : [];
    if (!rows.length) {
      host.innerHTML = '<div class="muted opt-pending-empty">暂无未成交委托</div>';
      return;
    }
    host.innerHTML = rows.map(function (o) {
      const side = String(o.side || "").toLowerCase();
      const sideCls = side === "buy" ? "is-buy" : side === "sell" ? "is-sell" : "";
      const remain = (o.sz != null && o.fill_sz != null) ? Math.max(0, Number(o.sz) - Number(o.fill_sz)) : o.sz;
      const pxTxt = o.px != null ? fmtOptionPx(o.px, null) : "—";
      const kind = o.is_close_order ? "平仓" : "开仓";
      let ttlTxt = "";
      if (o.auto_cancel_enabled) {
        if (o.stale) ttlTxt = " · 超时待撤";
        else if (o.expire_in_sec != null) ttlTxt = " · 剩 " + fmtPendingAge(o.expire_in_sec) + " 自动撤";
      }
      const ageTxt = o.age_sec != null ? ("已挂 " + fmtPendingAge(o.age_sec)) : "";
      return (
        '<div class="opt-pending-item" data-ord="' + (o.ord_id || "") + '" data-inst="' + (o.inst_id || "") + '">' +
        '<div class="opt-pending-item-top">' +
        '<span class="opt-pending-side ' + sideCls + '">' + kind + " · " + (o.side_label || side || "—") + "</span>" +
        '<button type="button" class="btn-secondary opt-pending-cancel" data-ord="' + (o.ord_id || "") +
        '" data-inst="' + (o.inst_id || "") + '">撤销</button>' +
        "</div>" +
        '<div class="opt-pending-inst" title="' + (o.inst_id || "") + '">' + (o.inst_id || "—") + "</div>" +
        '<div class="opt-pending-meta">价 ' + pxTxt +
        " · 张数 " + (o.sz != null ? o.sz : "—") +
        (o.fill_sz != null && Number(o.fill_sz) > 0 ? " · 已成 " + o.fill_sz : "") +
        (remain != null && o.fill_sz != null && Number(o.fill_sz) > 0 ? " · 剩余 " + remain : "") +
        (ageTxt ? " · " + ageTxt : "") +
        ttlTxt +
        "</div></div>"
      );
    }).join("");
    host.querySelectorAll(".opt-pending-cancel").forEach(function (btn) {
      btn.addEventListener("click", function () {
        cancelPendingOrder(btn.getAttribute("data-inst"), btn.getAttribute("data-ord"), btn);
      });
    });
  }

  async function refreshPendingOrders() {
    const host = document.getElementById("opt-pending-list");
    if (!host) return;
    try {
      const d = await apiJson("/api/options/orders/pending");
      if (!d.ok) {
        host.innerHTML = '<div class="muted opt-pending-empty">' + (d.msg || "获取委托失败") + "</div>";
        return;
      }
      paintPendingOrders(d.orders || [], d.pending_ttl_seconds);
    } catch (e) {
      host.innerHTML = '<div class="muted opt-pending-empty">获取委托失败</div>';
    }
  }

  function startPendingOrdersPoll() {
    stopPendingOrdersPoll();
    pendingRefreshTimer = setInterval(function () {
      if (!document.getElementById("options-root")) {
        stopPendingOrdersPoll();
        return;
      }
      refreshPendingOrders();
    }, PENDING_POLL_MS);
  }

  function stopPendingOrdersPoll() {
    if (pendingRefreshTimer) {
      clearInterval(pendingRefreshTimer);
      pendingRefreshTimer = null;
    }
  }

  async function cancelPendingOrder(inst, ordId, btn) {
    if (!inst || !ordId) return;
    if (!confirm("撤销该委托?\n合约: " + inst + "\n订单: " + ordId)) return;
    if (btn) btn.disabled = true;
    try {
      const d = await apiJson("/api/options/orders/cancel", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ inst_id: inst, ord_id: ordId }),
      });
      if (!d.ok) {
        alert(d.msg || "撤销失败");
        return;
      }
      await refreshPendingOrders();
      refreshAllPositions();
      if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
    } finally {
      if (btn) btn.disabled = false;
    }
  }

  function currentSizeMode() {
    const el = document.querySelector('input[name="opt-size-mode"]:checked');
    return el ? el.value : "sheets";
  }

  function updateSizeInputs() {
    const mode = currentSizeMode();
    const sheetsEl = document.getElementById("opt-sheets-amount");
    const ethEl = document.getElementById("opt-eth-amount");
    if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none";
    if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none";
  }

  function quoteUrl(instId) {
    const mode = currentSizeMode();
    let url = "/api/options/quote?inst_id=" + encodeURIComponent(instId) + "&mode=" + mode;
    if (mode === "eth_amount") {
      const eth = document.getElementById("opt-eth-amount").value;
      if (eth) url += "&eth_amount=" + encodeURIComponent(eth);
    } else if (mode === "sheets") {
      const sheets = document.getElementById("opt-sheets-amount").value;
      if (sheets) url += "&sheets=" + encodeURIComponent(sheets);
    }
    return url;
  }

  function strikeTableColspan() {
    return state.chainView === "t" ? 9 : 8;
  }

  function syncChainViewUI() {
    const isT = state.chainView === "t";
    document.querySelectorAll(".opt-view-btn").forEach(function (b) {
      b.classList.toggle("active", (b.getAttribute("data-view") || "") === state.chainView);
    });
    const typeGroup = document.getElementById("opt-type-btn-group");
    if (typeGroup) typeGroup.hidden = isT;
    const expandWrap = document.getElementById("opt-strike-expand-wrap");
    if (expandWrap) expandWrap.hidden = !isT;
    const headList = document.getElementById("opt-strike-head-list");
    const headT = document.getElementById("opt-strike-head-t");
    const headTCols = document.getElementById("opt-strike-head-t-cols");
    if (headList) {
      headList.classList.toggle("hidden", isT);
      headList.hidden = isT;
    }
    if (headT) {
      headT.classList.toggle("hidden", !isT);
      headT.hidden = !isT;
    }
    if (headTCols) {
      headTCols.classList.toggle("hidden", !isT);
      headTCols.hidden = !isT;
    }
    const wrap = document.getElementById("opt-strike-table-wrap");
    if (wrap) wrap.classList.toggle("options-strike-table-wrap--t", isT);
    const table = document.getElementById("opt-strike-table");
    if (table) table.classList.toggle("options-strike-table--t", isT);
  }

  function matchesMoneyFilter(moneyness) {
    const m = (moneyness || "").toLowerCase();
    if (state.moneyFilter === "all") return true;
    if (state.moneyFilter === "otm") return m === "otm";
    return m === "itm" || m === "atm";
  }

  function moneyFilterLabel() {
    if (state.moneyFilter === "otm") return "虚值";
    if (state.moneyFilter === "itm") return "实值";
    return "";
  }

  function countContractsForType(contracts) {
    if (state.chainView === "t") {
      return countStraddleStrikes(contracts);
    }
    return (contracts || []).filter(function (c) {
      return c.opt_type === state.optType;
    }).length;
  }

  function countStraddleStrikes(contracts) {
    const strikes = new Set();
    (contracts || []).forEach(function (c) {
      if (c.strike != null) strikes.add(String(c.strike));
    });
    return strikes.size;
  }

  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 findAtmStrike(rows, indexPx) {
    if (!rows.length || indexPx == null || Number.isNaN(Number(indexPx))) return null;
    let best = rows[0].strike;
    let bestDist = Math.abs(Number(rows[0].strike) - Number(indexPx));
    rows.forEach(function (row) {
      const d = Math.abs(Number(row.strike) - Number(indexPx));
      if (d < bestDist || (d === bestDist && Number(row.strike) < Number(best))) {
        bestDist = d;
        best = row.strike;
      }
    });
    return best;
  }

  function matchesStrikeRowFilter(strike, indexPx, atmStrike) {
    if (state.moneyFilter === "all") return true;
    if (atmStrike != null && Number(strike) === Number(atmStrike)) return true;
    if (indexPx == null || Number.isNaN(Number(indexPx))) return true;
    if (state.moneyFilter === "itm") return Number(strike) <= Number(indexPx);
    if (state.moneyFilter === "otm") return Number(strike) >= Number(indexPx);
    return true;
  }

  function filterStraddleRows(rows, indexPx) {
    const atmStrike = findAtmStrike(rows, indexPx);
    return rows.filter(function (row) {
      return matchesStrikeRowFilter(row.strike, indexPx, atmStrike);
    });
  }

  function sliceAtmWindow(rows, indexPx) {
    if (state.strikeExpandAll || !rows.length) return rows;
    const atmStrike = findAtmStrike(rows, indexPx);
    const idx = rows.findIndex(function (r) { return Number(r.strike) === Number(atmStrike); });
    if (idx < 0) return rows.slice(0, Math.min(rows.length, 11));
    const start = Math.max(0, idx - 5);
    const end = Math.min(rows.length, idx + 6);
    return rows.slice(start, end);
  }

  function straddleAskPerUnit(callAsk, putAsk) {
    const c = Number(callAsk);
    const p = Number(putAsk);
    if (!Number.isFinite(c) || !Number.isFinite(p) || c <= 0 || p <= 0) return null;
    return Math.round((c + p) * 10000) / 10000;
  }

  function formatStraddleBand(strike, combinedAsk) {
    const per = combinedAsk;
    if (strike == null || per == null) return "—";
    const k = Number(strike);
    const d = Number(per);
    if (!Number.isFinite(k) || !Number.isFinite(d)) return "—";
    const lo = Math.round((k - d) * 10) / 10;
    const hi = Math.round((k + d) * 10) / 10;
    return lo.toFixed(0) + " ~ " + hi.toFixed(0);
  }

  function formatStraddlePremiumCell(callAsk, putAsk) {
    const per = straddleAskPerUnit(callAsk, putAsk);
    if (per == null) return '<span class="muted">不可双买</span>';
    return fmtUsdc(per) + " USDC";
  }

  function pickBtnHtml(instId) {
    if (!instId) return "—";
    return '<button type="button" class="btn-secondary opt-pick-btn" data-inst="' + instId + '">选择</button>';
  }

  function syncMoneyFilterButtons() {
    document.querySelectorAll(".opt-money-btn").forEach(function (b) {
      b.classList.toggle("active", (b.getAttribute("data-money") || "") === state.moneyFilter);
    });
  }

  function resetMoneyFilterToAll() {
    state.moneyFilter = "all";
    syncMoneyFilterButtons();
  }

  function updateUnderlyingLabel() {
    const el = document.getElementById("opt-order-eth-label");
    if (el) el.textContent = state.underlying + " 数量";
  }

  function filterChainContracts(contracts) {
    return (contracts || []).filter(function (c) {
      return c.opt_type === state.optType && matchesMoneyFilter(c.moneyness);
    });
  }

  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 optTypeLabel(t) {
    return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
  }

  function expLabel(ms) {
    try {
      const dt = new Date(Number(ms));
      const now = Date.now();
      const dte = Math.max(0, Math.ceil((Number(ms) - now) / 86400000));
      const base = dt.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
      return base + " · " + dte + "D";
    } catch (e) {
      return String(ms);
    }
  }

  function renderIndexLine() {
    const idx = state.chain && state.chain.index_px;
    const dte = state.chain && state.chain.chain_max_dte_days;
    if (dte != null) {
      const el = document.getElementById("opt-chain-dte");
      if (el) el.textContent = String(Math.round(dte));
    }
    const line = document.getElementById("opt-index-line");
    if (line) {
      line.textContent =
        "指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 默认显示全部 · 实值含平值 · 虚值=价外";
    }
  }

  function renderExpiryOptions(preserveSelection) {
    const sel = document.getElementById("opt-exp-select");
    if (!sel) return;
    const prev = preserveSelection !== false ? sel.value : "";
    const exps = (state.chain && state.chain.expiries) || [];
    sel.innerHTML = '<option value="">选择到期日</option>';
    exps.forEach(function (e) {
      const o = document.createElement("option");
      o.value = String(e.exp_time);
      o.textContent = expLabel(e.exp_time) + " (" + countContractsForType(e.contracts) + ")";
      sel.appendChild(o);
    });
    if (prev && exps.some(function (e) { return String(e.exp_time) === String(prev); })) {
      sel.value = prev;
    }
  }

  function setExpirySelectStatus(text) {
    const sel = document.getElementById("opt-exp-select");
    if (!sel) return;
    sel.innerHTML = "";
    const o = document.createElement("option");
    o.value = "";
    o.textContent = text || "选择到期日";
    sel.appendChild(o);
  }

  function chainHasExpiries(chain) {
    return !!(chain && Array.isArray(chain.expiries) && chain.expiries.length > 0);
  }

  function renderExpiries() {
    renderExpiryOptions(true);
    renderIndexLine();
  }

  function fmtPxSz(px, sz, estimated) {
    if (px === null || px === undefined || Number.isNaN(Number(px))) return "—";
    let price = Number(px).toFixed(4).replace(/\.?0+$/, "");
    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 fmtCloseLevels(preview, tickSz) {
    if (preview && preview.bid_invalid) {
      return "暂无有效买盘";
    }
    const levels = ((preview && preview.levels) || []).slice(0, 5);
    if (!levels.length) return "—";
    return levels.map(function (x, idx) {
      const levelNo = x.level != null ? x.level : idx + 1;
      const liq = x.available_sheets != null ? x.available_sheets : x.sz;
      const pxTxt = fmtOptionPx(x.px, tickSz);
      if (liq === null || liq === undefined || liq === "" || Number.isNaN(Number(liq))) {
        return "买" + levelNo + " " + pxTxt;
      }
      const s = Number(liq);
      const size = Math.abs(s - Math.round(s)) < 1e-9 ? String(Math.round(s)) : String(s);
      return "买" + levelNo + " " + pxTxt + "/" + size;
    }).join(" · ");
  }

  function closeGateHint(preview) {
    if (!preview) return "";
    if (preview.bid_invalid || preview.manual_close_blocked) {
      return preview.bid_invalid_reason || "当前买一无效,禁止买一平仓";
    }
    const gate = preview.close_gate || {};
    // 2× 只是目标平仓门控,本身不会自动平;手动买一平不拦截
    if (preview.close_gate_blocked || (gate.ready === false && !gate.passed)) {
      return "目标门控: " + (preview.close_gate_msg || gate.msg || "可回收需≥2×权利金并持续2分钟");
    }
    return "";
  }

  function netPnlFromPos(p) {
    const preview = (p && p.close_preview) || {};
    if (preview.estimated_pnl != null && !Number.isNaN(Number(preview.estimated_pnl))) {
      return Number(preview.estimated_pnl);
    }
    const recv = Number(preview.total_received);
    const prem = Number(p && p.premium_paid);
    if (preview.total_received != null && !Number.isNaN(recv) && !Number.isNaN(prem)) {
      return recv - prem;
    }
    return null;
  }

  function netRoiFromPos(p, net) {
    const preview = (p && p.close_preview) || {};
    if (preview.estimated_pnl_ratio_pct != null && !Number.isNaN(Number(preview.estimated_pnl_ratio_pct))) {
      return Number(preview.estimated_pnl_ratio_pct);
    }
    const prem = Number(p && p.premium_paid);
    if (net == null || Number.isNaN(prem) || prem <= 0) return null;
    return (net / prem) * 100;
  }

  function fmtUsdc(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    return Number(v).toFixed(2);
  }

  function fmtClosePreview(preview, premiumPaid) {
    if (!preview || preview.total_received == null) return "—";
    const recvTxt = fmtUsdc(preview.total_received);
    let cls = "";
    const prem = Number(premiumPaid);
    const recv = Number(preview.total_received);
    if (!Number.isNaN(prem) && !Number.isNaN(recv)) {
      if (recv > prem) cls = " pos-pnl-profit";
      else if (recv < prem) cls = " pos-pnl-loss";
    }
    return '<span class="opt-close-value' + cls + '">' + recvTxt + " USDC</span>";
  }

  function fmtClosePreviewText(preview) {
    if (!preview || preview.total_received == null) return "—";
    let text = fmt(preview.total_received, 4) + " USDC";
    if (preview.covered_sheets != null) {
      text += " · 覆盖 " + preview.covered_sheets + "张";
    }
    if (preview.uncovered_sheets > 0) {
      text += " · 缺 " + preview.uncovered_sheets + "张";
    }
    return text;
  }

  function fmtPreviewLevels(preview) {
    const levels = (preview && preview.levels) || [];
    if (!levels.length) return "暂无可用买盘深度";
    return levels.map(function (x) {
      return "买" + x.level + " " + fmt(x.px, 4) + " × " + x.sheets + "张 ≈ " + fmt(x.received, 4) + " USDC";
    }).join("\n");
  }

  function pnlCls(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "";
    const n = Number(v);
    if (n > 0) return "pos-pnl-profit";
    if (n < 0) return "pos-pnl-loss";
    return "";
  }

  function expiryIntrinsicPerUnit(optType, strike, targetIdx) {
    const tgt = Number(targetIdx);
    const k = Number(strike);
    if (!Number.isFinite(tgt) || !Number.isFinite(k)) return null;
    const o = (optType || "").toUpperCase();
    if (o === "C") return Math.max(0, tgt - k);
    if (o === "P") return Math.max(0, k - tgt);
    return null;
  }

  function estimateExpiryValue(optType, strike, targetIdx, ethAmount) {
    const amt = Number(ethAmount);
    const intrinsic = expiryIntrinsicPerUnit(optType, strike, targetIdx);
    if (intrinsic == null || !Number.isFinite(amt) || amt <= 0) return null;
    return Math.round(intrinsic * amt * 100) / 100;
  }

  function estimateExpiryProfit(optType, strike, targetIdx, ethAmount, totalPremium) {
    const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
    const prem = Number(totalPremium);
    if (value == null || !Number.isFinite(prem)) return null;
    return Math.round((value - prem) * 100) / 100;
  }

  function calcContractLeverage(indexPx, ethAmount, totalPremium) {
    if (indexPx == null || ethAmount == null || totalPremium == null) return null;
    const idx = Number(indexPx);
    const amt = Number(ethAmount);
    const prem = Number(totalPremium);
    if (!Number.isFinite(idx) || !Number.isFinite(amt) || !Number.isFinite(prem) || amt <= 0 || prem <= 0) {
      return null;
    }
    return Math.round((idx * amt) / prem * 10) / 10;
  }

  function fmtLeverage(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    return "约 " + Number(v).toFixed(1) + "×";
  }

  function fmtUsdcSigned(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    const n = Number(v);
    const sign = n > 0 ? "+" : "";
    return sign + fmtUsdc(n) + " USDC";
  }

  function updateOrderEstimates() {
    const levEl = document.getElementById("opt-order-leverage");
    const valueEl = document.getElementById("opt-est-value");
    const profitEl = document.getElementById("opt-est-profit");
    const targetLevEl = document.getElementById("opt-est-leverage");
    const targetEl = document.getElementById("opt-target-idx");
    const q = state.orderQuote;
    if (!q || !q.ok || !q.can_open) {
      if (levEl) levEl.textContent = "—";
      if (valueEl) valueEl.textContent = "—";
      if (profitEl) {
        profitEl.textContent = "—";
        profitEl.className = "v";
      }
      if (targetLevEl) targetLevEl.textContent = "—";
      return;
    }
    const sz = q.sizing || {};
    const ethAmount = sz.eth_amount;
    const premium = sz.total_premium;
    const lev = calcContractLeverage(q.index_px, ethAmount, premium);
    if (levEl) levEl.textContent = fmtLeverage(lev);

    if (valueEl && profitEl && targetEl) {
      const targetRaw = targetEl.value;
      if (targetRaw === "" || targetRaw == null) {
        valueEl.textContent = "—";
        profitEl.textContent = "—";
        profitEl.className = "v";
        if (targetLevEl) targetLevEl.textContent = "—";
      } else {
        const value = estimateExpiryValue(q.opt_type, q.strike, Number(targetRaw), ethAmount);
        const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), ethAmount, premium);
        if (value == null || Number.isNaN(value)) {
          valueEl.textContent = "—";
        } else {
          valueEl.textContent = fmtUsdc(value) + " USDC";
        }
        if (profit == null || Number.isNaN(profit)) {
          profitEl.textContent = "—";
          profitEl.className = "v";
        } else {
          profitEl.textContent = fmtUsdcSigned(profit);
          profitEl.className = "v " + pnlCls(profit);
        }
        const targetLev = calcContractLeverage(Number(targetRaw), ethAmount, premium);
        if (targetLevEl) targetLevEl.textContent = fmtLeverage(targetLev);
      }
    }
  }

  function updateEstimatedProfit() {
    updateOrderEstimates();
  }

  function fmtDist(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
    const n = Number(v);
    const sign = n > 0 ? "+" : "";
    return sign + n.toFixed(1);
  }

  function distBeClass(v) {
    if (v === null || v === undefined || Number.isNaN(Number(v))) return "";
    const n = Number(v);
    if (n > 0) return "opt-be-dist-up";
    if (n < 0) return "opt-be-dist-down";
    return "";
  }

  function bindStrikePickButtons(tbody) {
    tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
      btn.addEventListener("click", function () {
        selectContract(btn.getAttribute("data-inst"), btn);
      });
    });
  }

  function finishStrikeRender(tbody, prevSelected, matchedSelected) {
    bindStrikePickButtons(tbody);
    if (matchedSelected && prevSelected) {
      selectContract(prevSelected, null, true);
    } else if (!matchedSelected) {
      state.selectedInst = null;
    }
  }

  function renderStrikes() {
    syncChainViewUI();
    if (state.chainView === "t") renderStrikesT();
    else renderStrikesList();
  }

  function renderStrikesList() {
    const tbody = document.getElementById("opt-strike-tbody");
    const expMs = document.getElementById("opt-exp-select").value;
    const prevSelected = state.selectedInst;
    const cols = strikeTableColspan();
    parkOrderPanel();
    tbody.innerHTML = "";
    if (!expMs || !state.chain) {
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">请选择到期日</td></tr>';
      state.selectedInst = null;
      return;
    }
    const exp = (state.chain.expiries || []).find(function (e) {
      return String(e.exp_time) === String(expMs);
    });
    if (!exp) {
      state.selectedInst = null;
      return;
    }
    const list = filterChainContracts(exp.contracts);
    if (!list.length) {
      const label = moneyFilterLabel();
      const suffix = label ? label : optTypeLabel(state.optType);
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">该到期日暂无' + suffix + "合约</td></tr>";
      state.selectedInst = null;
      return;
    }
    let matchedSelected = false;
    list.forEach(function (c) {
      const tr = document.createElement("tr");
      tr.className = "opt-strike-row";
      tr.setAttribute("data-inst", c.inst_id);
      if (c.moneyness) tr.classList.add("opt-row-" + c.moneyness);
      tr.innerHTML =
        "<td>" + c.strike + "</td>" +
        "<td>" + moneynessBadge(c) + "</td>" +
        "<td><code>" + c.inst_id + "</code></td>" +
        "<td class=\"opt-px-sz\">" + fmtPxSz(c.ask, c.ask_sz, c.ask_estimated) + "</td>" +
        "<td class=\"opt-px-sz\">" + fmtPxSz(c.bid, c.bid_sz) + "</td>" +
        "<td>" + (c.expiry_be_px != null ? fmt(c.expiry_be_px, 0) : "—") + "</td>" +
        '<td class="' + distBeClass(c.dist_expiry_be) + '">' + fmtDist(c.dist_expiry_be) + "</td>" +
        '<td class="opt-row-actions">' +
        pickBtnHtml(c.inst_id) +
        "</td>";
      tbody.appendChild(tr);
      if (c.inst_id === prevSelected) matchedSelected = true;
    });
    finishStrikeRender(tbody, prevSelected, matchedSelected);
  }

  function renderStrikesT() {
    const tbody = document.getElementById("opt-strike-tbody");
    const expMs = document.getElementById("opt-exp-select").value;
    const prevSelected = state.selectedInst;
    const cols = strikeTableColspan();
    const indexPx = state.chain && state.chain.index_px;
    parkOrderPanel();
    tbody.innerHTML = "";
    if (!expMs || !state.chain) {
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">请选择到期日</td></tr>';
      state.selectedInst = null;
      return;
    }
    const exp = (state.chain.expiries || []).find(function (e) {
      return String(e.exp_time) === String(expMs);
    });
    if (!exp) {
      state.selectedInst = null;
      return;
    }
    let rows = filterStraddleRows(buildStraddleRows(exp.contracts), indexPx);
    rows = sliceAtmWindow(rows, indexPx);
    if (!rows.length) {
      const label = moneyFilterLabel();
      const suffix = label ? label + "区" : "匹配";
      tbody.innerHTML = '<tr><td colspan="' + cols + '" class="muted">该到期日暂无' + suffix + "行权价</td></tr>";
      state.selectedInst = null;
      return;
    }
    const atmStrike = findAtmStrike(rows, indexPx);
    let matchedSelected = false;
    rows.forEach(function (row) {
      const call = row.call;
      const put = row.put;
      const combined = straddleAskPerUnit(call && call.ask, put && put.ask);
      const tr = document.createElement("tr");
      tr.className = "opt-strike-row opt-strike-row-t";
      tr.setAttribute("data-strike", String(row.strike));
      if (Number(row.strike) === Number(atmStrike)) tr.classList.add("opt-strike-row-atm");
      if (call && call.inst_id) tr.setAttribute("data-call-inst", call.inst_id);
      if (put && put.inst_id) tr.setAttribute("data-put-inst", put.inst_id);
      tr.innerHTML =
        '<td class="opt-t-call opt-px-sz">' + (call ? fmtPxSz(call.ask, call.ask_sz, call.ask_estimated) : "—") + "</td>" +
        '<td class="opt-t-call">' + (call ? moneynessBadge(call) : "—") + "</td>" +
        '<td class="opt-t-call opt-row-actions">' + pickBtnHtml(call && call.inst_id) + "</td>" +
        '<td class="opt-t-mid opt-t-strike"><strong>' + row.strike + "</strong></td>" +
        '<td class="opt-t-mid opt-t-straddle-prem">' + formatStraddlePremiumCell(call && call.ask, put && put.ask) + "</td>" +
        '<td class="opt-t-mid opt-t-straddle-band">' + formatStraddleBand(row.strike, combined) + "</td>" +
        '<td class="opt-t-put">' + (put ? moneynessBadge(put) : "—") + "</td>" +
        '<td class="opt-t-put opt-px-sz">' + (put ? fmtPxSz(put.ask, put.ask_sz, put.ask_estimated) : "—") + "</td>" +
        '<td class="opt-t-put opt-row-actions">' + pickBtnHtml(put && put.inst_id) + "</td>";
      tbody.appendChild(tr);
      if (prevSelected && ((call && call.inst_id === prevSelected) || (put && put.inst_id === prevSelected))) {
        matchedSelected = true;
      }
    });
    if (!state.strikeExpandAll && rows.length >= 1) {
      const hint = document.createElement("tr");
      hint.className = "opt-strike-hint-row";
      hint.innerHTML = '<td colspan="' + cols + '" class="muted opt-strike-hint">默认显示 ATM ±5 档 · 勾选「展开全部」查看该到期全部行权价</td>';
      tbody.appendChild(hint);
    }
    finishStrikeRender(tbody, prevSelected, matchedSelected);
  }

  function fillOrderPanel(d) {
    state.orderQuote = d && d.ok ? d : null;
    const sz = d.sizing || {};
    const canOpen = !!(d && d.ok && d.can_open);
    document.getElementById("opt-order-inst").textContent = d.inst_id || state.selectedInst || "";
    const askEl = document.getElementById("opt-order-ask");
    if (askEl) {
      askEl.textContent = canOpen ? fmtPxSz(d.ask, d.ask_sz) : "—";
    }
    const bidEl = document.getElementById("opt-order-bid");
    if (bidEl) bidEl.textContent = fmtPxSz(d.bid, d.bid_sz);
    const refEl = document.getElementById("opt-order-ref-ask");
    if (refEl) {
      if (canOpen) {
        refEl.textContent = "—";
      } else if (d.ref_ask != null && !Number.isNaN(Number(d.ref_ask))) {
        refEl.textContent = fmtPxSz(d.ref_ask, null, true) + " (不可开仓)";
      } else if (d.mark != null && !Number.isNaN(Number(d.mark))) {
        refEl.textContent = fmtPxSz(d.mark, null, true) + " (不可开仓)";
      } else {
        refEl.textContent = "—";
      }
    }
    document.getElementById("opt-order-sheets").textContent = canOpen && sz.sheets != null ? sz.sheets : "—";
    document.getElementById("opt-order-eth").textContent = canOpen && sz.eth_amount != null ? sz.eth_amount : "—";
    updateUnderlyingLabel();
    document.getElementById("opt-order-premium").textContent =
      canOpen && sz.total_premium != null ? fmtUsdc(sz.total_premium) + " USDC" : "—";
    const beEl = document.getElementById("opt-order-expiry-be");
    const distEl = document.getElementById("opt-order-dist-be");
    if (beEl) {
      beEl.textContent = d.expiry_be_px != null ? fmt(d.expiry_be_px, 0) : "—";
    }
    if (distEl) {
      distEl.textContent = fmtDist(d.dist_expiry_be);
      distEl.className = "v " + distBeClass(d.dist_expiry_be);
    }
    const openBtn = document.getElementById("opt-open-btn");
    if (openBtn) {
      openBtn.disabled = !canOpen || sz.ok === false;
      openBtn.textContent = canOpen ? "限价买入 @ 卖一" : "暂无卖一深度,无法开仓";
    }
    const msgEl = document.getElementById("opt-order-msg");
    if (!d.ok) {
      msgEl.textContent = d.msg || "报价失败";
      msgEl.classList.add("opt-error");
    } else if (!canOpen) {
      const ref = d.ref_ask != null ? d.ref_ask : d.mark;
      let tip = d.msg || d.open_block_msg || "当前无卖一深度,无法按卖一限价买入";
      if (ref != null && !Number.isNaN(Number(ref))) {
        tip += "。参考标记价 ~" + Number(ref).toFixed(4).replace(/\.?0+$/, "") + "(仅供参考,不可用于开仓)";
      } else {
        tip += "。无可用参考标记价";
      }
      msgEl.textContent = tip;
      msgEl.classList.add("opt-error");
    } else if (sz.ok === false) {
      msgEl.textContent = sz.msg || "";
      msgEl.classList.add("opt-error");
    } else if (sz.ask_depth_capped) {
      msgEl.textContent = sz.msg || "已按卖一深度限制张数";
      msgEl.classList.remove("opt-error");
    } else {
      msgEl.textContent = "";
      msgEl.classList.remove("opt-error");
    }
    updateEstimatedProfit();
  }

  async function selectContract(instId, pickBtn, silent) {
    const seq = ++selectSeq;
    state.selectedInst = instId;
    placeOrderPanelAfter(instId);
    if (pickBtn) {
      pickBtn.disabled = true;
      if (!pickBtn.dataset.origText) pickBtn.dataset.origText = "选择";
      pickBtn.textContent = "加载…";
    }
    try {
      const d = await apiJson(quoteUrl(instId));
      if (seq !== selectSeq) return d;
      fillOrderPanel(d);
      return d;
    } finally {
      if (seq === selectSeq) {
        syncPickButtons(instId);
        if (!silent) placeOrderPanelAfter(instId);
      }
    }
  }

  async function loadChain(opts) {
    const soft = !!(opts && opts.soft);
    const uly = state.underlying;
    const seq = ++chainLoadSeq;
    const btn = document.getElementById("opt-load-chain");
    if (btn && !soft) btn.disabled = true;
    if (!soft) {
      setExpirySelectStatus("加载到期日中…");
      const tbody = document.getElementById("opt-strike-tbody");
      if (tbody) {
        tbody.innerHTML =
          '<tr><td colspan="' + strikeTableColspan() + '" class="muted">加载期权链…</td></tr>';
      }
    }
    try {
      let d = null;
      let lastMsg = "";
      for (let attempt = 0; attempt < 2; attempt++) {
        if (seq !== chainLoadSeq) return;
        d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(uly));
        if (seq !== chainLoadSeq) return;
        if (d && d.ok && chainHasExpiries(d)) break;
        lastMsg = (d && (d.msg || d.chain_error)) || "暂无到期日";
        d = null;
        if (attempt === 0) {
          if (!soft) setExpirySelectStatus("重试加载到期日…");
          await new Promise(function (resolve) { setTimeout(resolve, 400); });
        }
      }
      if (seq !== chainLoadSeq) return;
      if (!d || !d.ok || !chainHasExpiries(d)) {
        if (chainHasExpiries(state.chain) && state.chain.underlying === uly) {
          if (!soft) {
            renderExpiries();
            renderStrikes();
          }
          return;
        }
        if (soft) return;
        setExpirySelectStatus("选择到期日");
        const tbody = document.getElementById("opt-strike-tbody");
        if (tbody) {
          tbody.innerHTML =
            '<tr><td colspan="' + strikeTableColspan() + '" class="muted">' +
            (lastMsg || "暂无到期日,请点「刷新链」") +
            "</td></tr>";
        }
        alert(lastMsg || "加载到期日失败,请点「刷新链」重试");
        return;
      }
      const keepExp = soft ? (document.getElementById("opt-exp-select") || {}).value : "";
      state.chain = d;
      panelCache.chain = d;
      panelCache.underlying = uly;
      panelCache.optType = state.optType;
      if (!soft) {
        state.selectedInst = null;
        resetMoneyFilterToAll();
        state.strikeExpandAll = false;
        const expandCb = document.getElementById("opt-strike-expand-all");
        if (expandCb) expandCb.checked = false;
        parkOrderPanel();
      }
      updateUnderlyingLabel();
      renderExpiries();
      if (soft && keepExp) {
        const sel = document.getElementById("opt-exp-select");
        if (sel && Array.from(sel.options).some(function (o) { return o.value === keepExp; })) {
          sel.value = keepExp;
        }
      }
      // soft 时保留 selectedInst；renderStrikes 会先 park 再按 prevSelected 静默重挂下单面板
      renderStrikes();
    } catch (e) {
      if (seq !== chainLoadSeq || soft) return;
      setExpirySelectStatus("选择到期日");
      const tbody = document.getElementById("opt-strike-tbody");
      if (tbody) {
        tbody.innerHTML =
          '<tr><td colspan="' + strikeTableColspan() + '" class="muted">加载失败: ' +
          String((e && e.message) || e) +
          "</td></tr>";
      }
    } finally {
      if (seq === chainLoadSeq && btn) btn.disabled = false;
    }
  }

  async function openPosition() {
    if (!state.selectedInst) {
      alert("请先选择合约");
      return;
    }
    const q = state.orderQuote;
    if (!q || !q.ok || !q.can_open) {
      alert((q && (q.msg || q.open_block_msg)) || "暂无卖一深度,无法按卖一开仓");
      return;
    }
    if (q.sizing && q.sizing.ok === false) {
      alert(q.sizing.msg || "张数无效");
      return;
    }
    const btn = document.getElementById("opt-open-btn");
    btn.disabled = true;
    try {
      const mode = currentSizeMode();
      const body = {
        inst_id: state.selectedInst,
        mode: mode,
        signal_note: document.getElementById("opt-signal-note").value || "",
      };
      if (mode === "eth_amount") {
        body.eth_amount = parseFloat(document.getElementById("opt-eth-amount").value);
      } else if (mode === "sheets") {
        body.sheets = parseInt(document.getElementById("opt-sheets-amount").value, 10);
      }
      const tgtRaw = (document.getElementById("opt-target-idx").value || "").trim();
      if (tgtRaw !== "") {
        const tgt = parseFloat(tgtRaw);
        if (!Number.isFinite(tgt) || tgt <= 0) {
          alert("目标位无效");
          return;
        }
        body.target_index = tgt;
      }
      const d = await apiJson("/api/options/open", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const msgEl = document.getElementById("opt-order-msg");
      msgEl.textContent = d.ok ? "下单已提交,右侧可查看/撤销未成交委托" : (d.msg || "失败");
      msgEl.classList.toggle("opt-error", !d.ok);
      if (d.ok) {
        refreshPendingOrders();
        startPendingOrdersPoll();
        refreshAllPositions();
        if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
      } else {
        alert(d.msg || "下单失败");
      }
    } finally {
      const latest = state.orderQuote;
      btn.disabled = !(latest && latest.ok && latest.can_open && !(latest.sizing && latest.sizing.ok === false));
      btn.textContent = (latest && latest.can_open) ? "限价买入 @ 卖一" : "暂无卖一深度,无法开仓";
    }
  }

  function renderPositionCardInner(p) {
    const net = netPnlFromPos(p);
    const roi = netRoiFromPos(p, net);
    const uplCls = pnlCls(net);
    const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
    const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
    const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
    const closePreview = p.close_preview || {};
    const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos;
    const tickSz = p.tick_sz;
    const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
    // 优先用数值+tick 现算,避免接口侧 mark_px_fmt 带着浮点毛刺直出
    const avgTxt = p.avg_px != null ? fmtOptionPx(p.avg_px, tickSz) : fmtDisplay(p.avg_px_fmt);
    const markTxt = p.mark_px != null ? fmtOptionPx(p.mark_px, tickSz) : fmtDisplay(p.mark_px_fmt);
    return (
      '<div class="pos-card-head">' +
      '<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
      '<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span></div>" +
      '<div class="pos-head-actions">' +
      '<button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '" data-sheets="' + closeSheets + '">买一平仓</button>' +
      "</div></div>" +
      '<div class="pos-meta">' +
      '<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
      '<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
      (expAttr
        ? '<span class="pos-meta-item">到期倒计时: <span class="opt-expiry-cd" data-opt-exp-ms="' + expAttr + '">—</span></span>'
        : "") +
      "</div>" +
      '<div class="pos-grid">' +
      '<div class="pos-cell"><span class="pos-label">权利金</span><span class="pos-value">' + premTxt + " USDC</span></div>" +
      '<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + avgTxt + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + markTxt + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">指数价</span><span class="pos-value">' + fmt(p.idx_px, 0) + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">到期平衡</span><span class="pos-value">' + fmt(p.expiry_be_px, 0) + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">平掉回本</span><span class="pos-value">' + fmt(p.close_be_px, 0) + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">净盈亏</span><span class="pos-value ' + uplCls + '">' +
      (closePreview.bid_invalid || net == null ? "—" : fmt(net, 2)) + "</span></div>" +
      '<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
      (closePreview.bid_invalid || roi == null ? "—" : fmt(roi, 2) + "%") + "</span></div>" +
      '<div class="pos-cell opt-pos-cell--depth"><span class="pos-label">买盘深度</span><span class="pos-value opt-bid-plain">' + fmtCloseLevels(closePreview, tickSz) + "</span></div>" +
      '<div class="pos-cell opt-pos-cell--close"><span class="pos-label">按买盘回收</span><span class="pos-value">' +
      (closePreview.bid_invalid
        ? '<span class="muted">暂无有效买盘</span>'
        : fmtClosePreview(closePreview, p.premium_paid)) + "</span></div>" +
      "</div>" +
      (function () {
        const hint = closeGateHint(closePreview);
        return hint ? '<div class="muted opt-bid-invalid-hint">' + hint + "</div>" : "";
      })() +
      renderTargetDelegateRow(p)
    );
  }

  function posEthAmount(p) {
    if (p.eth_amount != null && Number(p.eth_amount) > 0) return Number(p.eth_amount);
    const sheets = Number(p.avail_pos != null ? p.avail_pos : p.pos);
    const ct = Number(p.ct_mult != null ? p.ct_mult : 0.01);
    if (Number.isFinite(sheets) && sheets > 0 && Number.isFinite(ct) && ct > 0) return sheets * ct;
    return null;
  }

  function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid) {
    const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
    const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid);
    if (value == null && profit == null) return "";
    let html = '<span class="opt-target-est">';
    html += '<span class="opt-target-est-item"><span class="k">价值</span><span class="v">' +
      (value == null ? "—" : fmtUsdc(value) + " USDC") + "</span></span>";
    html += '<span class="opt-target-est-item"><span class="k">预估盈利</span><span class="v ' + pnlCls(profit) + '">' +
      (profit == null ? "—" : fmtUsdcSigned(profit)) + "</span></span>";
    html += "</span>";
    return html;
  }

  function renderTargetDelegateRow(p) {
    const inst = p.inst_id || "";
    const hedgeTarget = p.hedge_plan_target || null;
    if (hedgeTarget && Number(hedgeTarget.target_index) > 0) {
      const side = (p.opt_type || hedgeTarget.opt_type || "").toUpperCase() === "P" ? "Put ≤" : "Call ≥";
      return (
        '<div class="opt-target-row opt-target-row--managed">' +
        '<span class="opt-target-row-label">对冲计划</span>' +
        '<span class="opt-target-armed">计划 #' +
        hedgeTarget.plan_id +
        " · " +
        side +
        " " +
        fmt(hedgeTarget.target_index, 1) +
        "</span>" +
        '<span class="muted opt-target-row-hint">进行中 · 由对冲计划监控，到位后仅平盈利腿</span>' +
        "</div>"
      );
    }
    const tgt = p.target_index != null && p.target_index !== "" ? Number(p.target_index) : null;
    const armed = tgt != null && Number.isFinite(tgt) && tgt > 0;
    const ethAmt = posEthAmount(p);
    const prem = p.premium_paid;
    const estHtml = armed
      ? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem)
      : '<span class="opt-target-est opt-target-est--idle"></span>';
    return (
      '<div class="opt-target-row" data-inst="' + inst + '"' +
      ' data-opt-type="' + (p.opt_type || "") + '"' +
      ' data-strike="' + (p.strike != null ? p.strike : "") + '"' +
      ' data-eth="' + (ethAmt != null ? ethAmt : "") + '"' +
      ' data-prem="' + (prem != null ? prem : "") + '"' +
      ' data-armed-target="' + (armed ? tgt : "") + '">' +
      '<span class="opt-target-row-label">委托</span>' +
      '<input type="number" class="opt-pos-target-input" data-inst="' + inst + '" step="0.1" min="0" placeholder="监控目标指数" value="' +
      (state.targetDraftByInst[inst] != null ? String(state.targetDraftByInst[inst]) : "") + '">' +
      '<button type="button" class="btn-secondary opt-target-set-btn" data-inst="' + inst + '">设定</button>' +
      '<button type="button" class="btn-secondary opt-target-cancel-btn" data-inst="' + inst + '"' + (armed ? "" : " disabled") + ">取消</button>" +
      (armed
        ? '<span class="opt-target-armed">目标 ' + fmt(tgt, 1) + "</span>"
        : "") +
      estHtml +
      '<span class="muted opt-target-row-hint">' +
      (armed ? "监控中 · 到位按买一限价平" : "输入后设定 · 到位按买一限价平 · 到期即止损") +
      "</span>" +
      "</div>"
    );
  }

  function updatePosTargetEstimate(row) {
    if (!row) return;
    const est = row.querySelector(".opt-target-est");
    if (!est) return;
    const inp = row.querySelector(".opt-pos-target-input");
    const typed = inp ? String(inp.value || "").trim() : "";
    const armed = row.getAttribute("data-armed-target") || "";
    const targetRaw = typed !== "" ? typed : armed;
    if (targetRaw === "") {
      est.className = "opt-target-est opt-target-est--idle";
      est.innerHTML = "";
      return;
    }
    const html = formatTargetEstimateHtml(
      row.getAttribute("data-opt-type"),
      row.getAttribute("data-strike"),
      targetRaw,
      row.getAttribute("data-eth"),
      row.getAttribute("data-prem")
    );
    if (!html) {
      est.className = "opt-target-est opt-target-est--idle";
      est.innerHTML = "";
      return;
    }
    const tmp = document.createElement("div");
    tmp.innerHTML = html;
    const node = tmp.firstChild;
    est.className = "opt-target-est";
    est.innerHTML = node ? node.innerHTML : "";
  }

  function renderPositionCard(p) {
    return (
      '<div class="pos-card opt-pos-card" data-inst="' + (p.inst_id || "") + '">' +
      renderPositionCardInner(p) +
      "</div>"
    );
  }

  function renderPositionAccordionItem(p, expanded) {
    const net = netPnlFromPos(p);
    const roi = netRoiFromPos(p, net);
    const uplCls = pnlCls(net);
    const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
    const expMs = p.exp_time_ms != null ? p.exp_time_ms : p.exp_time;
    const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
    const inst = p.inst_id || "";
    return (
      '<div class="opt-pos-accordion-item' + (expanded ? " is-expanded" : "") + '" data-inst="' + inst + '">' +
      '<button type="button" class="opt-pos-bar" aria-expanded="' + (expanded ? "true" : "false") + '">' +
      '<span class="opt-pos-bar-main">' +
      '<span class="opt-pos-bar-chevron" aria-hidden="true">▶</span>' +
      '<span class="opt-pos-bar-id-group">' +
      '<strong class="opt-pos-bar-title" title="' + inst + '">' + inst + "</strong>" +
      '<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
      "</span>" +
      '<span class="opt-pos-bar-meta">行权 ' + fmt(p.strike, 0) + " · " + fmt(p.pos, 0) + "张</span>" +
      "</span>" +
      '<span class="opt-pos-bar-side">' +
      (expAttr
        ? '<span class="opt-pos-bar-cd">到期 <span class="opt-expiry-cd" data-opt-exp-ms="' + expAttr + '">—</span></span>'
        : "") +
      '<span class="opt-pos-bar-pnl ' + uplCls + '">' + (net == null ? "—" : fmt(net, 2) + " USDC") + "</span>" +
      '<span class="opt-pos-bar-roi ' + uplCls + '">' +
      (roi == null ? "—" : fmt(roi, 2) + "%") + "</span>" +
      "</span>" +
      "</button>" +
      '<div class="opt-pos-accordion-body"' + (expanded ? "" : ' hidden') + '>' +
      '<div class="pos-card opt-pos-card opt-pos-card--inline" data-inst="' + inst + '">' +
      renderPositionCardInner(p) +
      "</div></div></div>"
    );
  }

  function applyAccordionState() {
    const wrap = document.getElementById("opt-pos-cards");
    if (!wrap) return;
    wrap.querySelectorAll(".opt-pos-accordion-item").forEach(function (el) {
      const open = el.getAttribute("data-inst") === state.expandedPosInst;
      el.classList.toggle("is-expanded", open);
      const btn = el.querySelector(".opt-pos-bar");
      const body = el.querySelector(".opt-pos-accordion-body");
      if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false");
      if (body) body.hidden = !open;
    });
  }

  function bindPositionActions(container) {
    if (!container) return;
    container.querySelectorAll(".opt-close-btn").forEach(function (btn) {
      btn.addEventListener("click", function (e) {
        e.stopPropagation();
        closePosition(btn.getAttribute("data-inst"), btn);
      });
    });
    container.querySelectorAll(".opt-target-set-btn").forEach(function (btn) {
      btn.addEventListener("click", function (e) {
        e.stopPropagation();
        setPositionTarget(btn.getAttribute("data-inst"), btn);
      });
    });
    container.querySelectorAll(".opt-target-cancel-btn").forEach(function (btn) {
      btn.addEventListener("click", function (e) {
        e.stopPropagation();
        cancelPositionTarget(btn.getAttribute("data-inst"), btn);
      });
    });
    container.querySelectorAll(".opt-pos-target-input").forEach(function (inp) {
      inp.addEventListener("click", function (e) { e.stopPropagation(); });
      inp.addEventListener("input", function () {
        const instId = inp.getAttribute("data-inst") || "";
        const draft = String(inp.value || "");
        if (instId) {
          if (draft.trim() === "") delete state.targetDraftByInst[instId];
          else state.targetDraftByInst[instId] = draft;
        }
        updatePosTargetEstimate(inp.closest(".opt-target-row"));
      });
      inp.addEventListener("keydown", function (e) {
        if (e.key === "Enter") {
          e.preventDefault();
          e.stopPropagation();
          setPositionTarget(inp.getAttribute("data-inst"), null);
        }
      });
      // 重绘后恢复预估展示(草稿或已设定目标)
      updatePosTargetEstimate(inp.closest(".opt-target-row"));
    });
    container.querySelectorAll(".opt-pos-bar").forEach(function (bar) {
      bar.addEventListener("click", function () {
        const item = bar.closest(".opt-pos-accordion-item");
        if (!item) return;
        const inst = item.getAttribute("data-inst");
        state.expandedPosInst = state.expandedPosInst === inst ? null : inst;
        applyAccordionState();
        if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) {
          OptionsExpiryCountdown.ensureTimer();
        }
      });
    });
  }

  async function setPositionTarget(inst, btn) {
    if (!inst) return;
    const card = document.querySelector('.opt-pos-card[data-inst="' + inst + '"]') ||
      document.querySelector('.opt-pos-accordion-item[data-inst="' + inst + '"]');
    const row = card ? card.querySelector(".opt-target-row") : null;
    const inp = card ? card.querySelector(".opt-pos-target-input") : null;
    const raw = inp ? String(inp.value || "").trim() : "";
    const tgt = parseFloat(raw);
    if (!Number.isFinite(tgt) || tgt <= 0) {
      alert("请输入有效目标指数价");
      return;
    }
    if (btn) btn.disabled = true;
    try {
      const d = await apiJson("/api/options/target", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ inst_id: inst, target_index: tgt }),
      });
      if (!d.ok) {
        alert(d.msg || "设定失败");
        return;
      }
      delete state.targetDraftByInst[inst];
      if (inp) inp.value = "";
      if (row) {
        row.setAttribute("data-armed-target", String(tgt));
        updatePosTargetEstimate(row);
      }
      await refreshAllPositions();
    } finally {
      if (btn) btn.disabled = false;
    }
  }

  async function cancelPositionTarget(inst, btn) {
    if (!inst) return;
    if (btn) btn.disabled = true;
    try {
      const d = await apiJson("/api/options/target/cancel", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ inst_id: inst }),
      });
      if (!d.ok) {
        alert(d.msg || "取消失败");
        return;
      }
      delete state.targetDraftByInst[inst];
      await refreshAllPositions();
    } finally {
      if (btn) btn.disabled = false;
    }
  }

  function paintTargetMonitors(list) {
    const box = document.getElementById("opt-target-monitors");
    const host = document.getElementById("opt-target-monitors-list");
    if (!box || !host) return;
    const rows = Array.isArray(list) ? list.filter(function (t) { return t && t.inst_id; }) : [];
    if (!rows.length) {
      box.hidden = true;
      host.innerHTML = "";
      return;
    }
    box.hidden = false;
    host.innerHTML = rows.map(function (t) {
      const side = (t.opt_type || "").toUpperCase() === "P" ? "Put ≤" : "Call ≥";
      const managed = t.managed_by === "hedge_plan";
      return (
        '<div class="opt-target-mon-item' + (managed ? " opt-target-mon-item--managed" : "") + '">' +
        '<code class="opt-target-mon-inst" title="' + (t.inst_id || "") + '">' + (t.inst_id || "") + "</code>" +
        '<span class="opt-target-mon-rule">' + side + " " + fmt(t.target_index, 1) + "</span>" +
        (managed
          ? '<span class="opt-target-mon-managed">对冲计划 #' + (t.plan_id || "") + " · 进行中</span>"
          : '<button type="button" class="btn-secondary opt-target-mon-cancel" data-inst="' + (t.inst_id || "") + '">取消</button>') +
        "</div>"
      );
    }).join("");
    host.querySelectorAll(".opt-target-mon-cancel").forEach(function (btn) {
      btn.addEventListener("click", function () {
        cancelPositionTarget(btn.getAttribute("data-inst"), btn);
      });
    });
  }

  async function closePosition(inst, btn) {
    const sheets = btn && btn.getAttribute("data-sheets") ? parseInt(btn.getAttribute("data-sheets"), 10) : null;
    let url = "/api/options/quote?inst_id=" + encodeURIComponent(inst) + "&mode=close_preview";
    if (sheets && sheets > 0) url += "&sheets=" + encodeURIComponent(sheets);
    const q = await apiJson(url);
    if (!q.ok) {
      alert(q.msg || "获取买一价失败");
      return;
    }
    const preview = q.close_preview || {};
    if (preview.bid_invalid || preview.manual_close_blocked) {
      alert(preview.bid_invalid_reason || "当前买一为无效残档,禁止买一平仓。");
      return;
    }
    if (!preview.covered_sheets || preview.covered_sheets <= 0) {
      alert("暂无有效买一深度,请稍后重试或到 OKX App 挂限价");
      return;
    }
    const lv = (preview.levels && preview.levels[0]) || {};
    const msg = [
      "按买一限价卖出本轮可平张数?",
      "合约: " + inst,
      "锁定买一: " + (lv.px != null ? lv.px : "—") + " × " + (lv.sheets != null ? lv.sheets : preview.covered_sheets) + " 张",
      "预计收回: " + fmtClosePreviewText(preview),
      preview.estimated_pnl != null ? "预估盈亏: " + fmt(preview.estimated_pnl, 4) + " USDC" : "",
      preview.uncovered_sheets > 0 ? "\n注意: 买一深度不足,预计仍剩 " + preview.uncovered_sheets + " 张,需下次再平。" : ""
    ].filter(function (x) { return x !== ""; }).join("\n");
    if (!confirm(msg)) return;
    if (btn) btn.disabled = true;
    try {
      const r = await apiJson("/api/options/close", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ inst_id: inst, mode: "bid1", sheets: sheets }),
      });
      if (r.ok) {
        let okMsg = "买一平仓已提交 " + (r.submitted_sheets || 0) + " 张";
        if (r.locked_bid_px != null) okMsg += "\n锁定买一: " + r.locked_bid_px;
        if (r.premium_received != null) okMsg += "\n预估收回: " + fmt(r.premium_received, 4) + " USDC";
        if (r.remaining_sheets > 0) okMsg += "\n剩余: " + r.remaining_sheets + " 张(下次再平)";
        if (r.stopped_reason) okMsg += "\n状态: " + r.stopped_reason;
        alert(okMsg);
      } else {
        alert(r.msg || "平仓失败");
      }
      refreshAllPositions();
      if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
    } finally {
      if (btn) btn.disabled = false;
    }
  }

  function setOptionsPosTab(tabId) {
    const tab = tabId || "live";
    state.posTab = tab;
    document.querySelectorAll(".opt-pos-tab").forEach(function (btn) {
      const on = btn.getAttribute("data-opt-pos-tab") === tab;
      btn.classList.toggle("active", on);
      btn.setAttribute("aria-selected", on ? "true" : "false");
    });
    document.querySelectorAll("[data-opt-pos-pane]").forEach(function (pane) {
      const on = pane.getAttribute("data-opt-pos-pane") === tab;
      pane.classList.toggle("is-active", on);
      pane.hidden = !on;
    });
    if (tab === "live" && window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) {
      OptionsExpiryCountdown.ensureTimer();
    }
  }

  function bindOptionsPosTabs() {
    document.querySelectorAll(".opt-pos-tab").forEach(function (btn) {
      btn.addEventListener("click", function () {
        setOptionsPosTab(btn.getAttribute("data-opt-pos-tab"));
      });
    });
    setOptionsPosTab(state.posTab);
  }

  function resolvePositionsList(d) {
    const now = Date.now();
    const list = (d && d.ok && d.positions) ? d.positions : [];
    if (d && d.ok) {
      if (list.length) {
        lastGoodPositions = list;
        lastGoodPositionsAt = now;
        return list;
      }
      lastGoodPositions = null;
      lastGoodPositionsAt = 0;
      return list;
    }
    if (lastGoodPositions && lastGoodPositions.length && now - lastGoodPositionsAt < POSITIONS_STALE_MS) {
      return lastGoodPositions;
    }
    return [];
  }

  function paintPositions(list) {
    const wrap = document.getElementById("opt-pos-cards");
    const empty = document.getElementById("opt-pos-empty");
    const livePane = document.getElementById("opt-pos-live");
    if (!wrap) return;
    const active = document.activeElement;
    // 正在输入目标指数:先落到草稿,本轮不重绘整卡,避免数字往回退
    if (active && active.classList && active.classList.contains("opt-pos-target-input")) {
      const focusInst = active.getAttribute("data-inst") || "";
      if (focusInst) {
        state.targetDraftByInst[focusInst] = String(active.value || "");
      }
      return;
    }
    // 未聚焦时也同步可见输入,防止漏掉 input 事件
    wrap.querySelectorAll(".opt-pos-target-input").forEach(function (inp) {
      const id = inp.getAttribute("data-inst") || "";
      if (!id) return;
      const v = String(inp.value || "");
      if (v.trim() === "") delete state.targetDraftByInst[id];
      else state.targetDraftByInst[id] = v;
    });
    wrap.innerHTML = "";
    if (!list.length) {
      if (empty) empty.style.display = "";
      state.expandedPosInst = null;
      if (livePane) livePane.classList.remove("options-pos-live-pane--accordion");
      return;
    }
    if (empty) empty.style.display = "none";
    const multi = list.length >= 2;
    wrap.classList.toggle("opt-pos-cards--accordion", multi);
    if (livePane) livePane.classList.toggle("options-pos-live-pane--accordion", multi);
    if (multi) {
      const ids = list.map(function (p) { return p.inst_id; });
      if (state.expandedPosInst && ids.indexOf(state.expandedPosInst) < 0) {
        state.expandedPosInst = null;
      }
      list.forEach(function (p) {
        const div = document.createElement("div");
        div.innerHTML = renderPositionAccordionItem(p, p.inst_id === state.expandedPosInst);
        wrap.appendChild(div.firstChild);
      });
    } else {
      state.expandedPosInst = null;
      list.forEach(function (p) {
        const div = document.createElement("div");
        div.innerHTML = renderPositionCard(p);
        wrap.appendChild(div.firstChild);
      });
    }
    bindPositionActions(wrap);
    if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) {
      OptionsExpiryCountdown.ensureTimer();
    }
  }

  async function refreshPositions() {
    const seq = ++positionsRefreshSeq;
    const d = await apiJson("/api/options/positions");
    if (seq !== positionsRefreshSeq) return;
    const list = resolvePositionsList(d);
    paintPositions(list);
    const fromPos = list.reduce(function (targets, p) {
      if (!p) return targets;
      if (p.target_index != null) {
        targets.push({
          id: p.target_monitor_id,
          inst_id: p.inst_id,
          opt_type: p.opt_type,
          target_index: p.target_index,
        });
      }
      const hedgeTarget = p.hedge_plan_target;
      if (hedgeTarget && hedgeTarget.target_index != null) {
        targets.push({
          inst_id: p.inst_id,
          opt_type: p.opt_type || hedgeTarget.opt_type,
          target_index: hedgeTarget.target_index,
          plan_id: hedgeTarget.plan_id,
          managed_by: hedgeTarget.managed_by,
        });
      }
      return targets;
    }, []);
    if (fromPos.length) {
      paintTargetMonitors(fromPos);
    } else {
      const t = await apiJson("/api/options/targets");
      if (seq !== positionsRefreshSeq) return;
      paintTargetMonitors((t && t.ok && t.targets) ? t.targets : []);
    }
  }

  function paintPnlStat(el, value) {
    if (!el) return;
    if (value == null || value === "" || Number.isNaN(Number(value))) {
      el.textContent = "—";
      el.classList.remove("pos-pnl-profit", "pos-pnl-loss");
      return;
    }
    const n = Number(value);
    el.textContent = (n > 0 ? "+" : "") + fmt(n, 2) + " USDC";
    el.classList.toggle("pos-pnl-profit", n > 0);
    el.classList.toggle("pos-pnl-loss", n < 0);
  }

  async function refreshStats() {
    const d = await apiJson("/api/options/stats");
    const winEl = document.getElementById("opt-stats-winrate");
    const plrEl = document.getElementById("opt-stats-plr");
    const closedEl = document.getElementById("opt-stats-closed");
    const profitEl = document.getElementById("opt-stats-profit");
    const lossEl = document.getElementById("opt-stats-loss");
    const avgHoldEl = document.getElementById("opt-stats-avg-hold");
    const winHoldEl = document.getElementById("opt-stats-win-hold");
    const lossHoldEl = document.getElementById("opt-stats-loss-hold");
    const openHoldEl = document.getElementById("opt-stats-open-hold");
    const totalPnlEl = document.getElementById("opt-stats-total-pnl");
    const netRealizedEl = document.getElementById("opt-stats-net-realized");
    const openFloatEl = document.getElementById("opt-stats-open-float");
    const statEls = [winEl, plrEl, closedEl, profitEl, lossEl, avgHoldEl, winHoldEl, lossHoldEl, openHoldEl];
    if (!d.ok) {
      statEls.forEach(function (el) {
        if (el) el.textContent = "—";
      });
      paintPnlStat(totalPnlEl, null);
      paintPnlStat(netRealizedEl, null);
      paintPnlStat(openFloatEl, null);
      paintStatsCharts(null);
      return;
    }
    paintPnlStat(totalPnlEl, d.total_pnl);
    paintPnlStat(netRealizedEl, d.net_realized_pnl);
    paintPnlStat(openFloatEl, d.open_float_pnl);
    if (winEl) winEl.textContent = d.total_closed ? d.win_rate + "%" : "0%";
    if (plrEl) {
      plrEl.textContent = d.profit_loss_ratio != null ? String(d.profit_loss_ratio) : "—";
    }
    if (closedEl) closedEl.textContent = String(d.total_closed || 0);
    if (profitEl) {
      profitEl.textContent = d.avg_win != null && d.avg_win > 0
        ? fmt(d.avg_win, 2) + " USDC" : (d.win_count ? "0 USDC" : "—");
    }
    if (lossEl) {
      lossEl.textContent = d.avg_loss != null && d.avg_loss > 0
        ? fmt(d.avg_loss, 2) + " USDC" : (d.loss_count ? "0 USDC" : "—");
    }
    if (avgHoldEl) avgHoldEl.textContent = fmtDuration(d.avg_hold_sec);
    if (winHoldEl) winHoldEl.textContent = fmtDuration(d.avg_win_hold_sec);
    if (lossHoldEl) lossHoldEl.textContent = fmtDuration(d.avg_loss_hold_sec);
    if (openHoldEl) {
      const cnt = Number(d.open_count) || 0;
      if (!cnt) {
        openHoldEl.textContent = "0 笔";
      } else {
        openHoldEl.textContent = cnt + " 笔 · " + fmtDuration(d.avg_open_hold_sec);
      }
    }
    paintStatsCharts(d);
  }

  function fmtDuration(sec) {
    if (sec == null || sec === "" || Number.isNaN(Number(sec))) return "—";
    let s = Math.max(0, Math.round(Number(sec)));
    if (s < 60) return s + "秒";
    const m = Math.floor(s / 60);
    if (m < 60) {
      const rs = s % 60;
      return rs ? m + "分" + rs + "秒" : m + "分";
    }
    const h = Math.floor(m / 60);
    const rm = m % 60;
    if (h < 24) return rm ? h + "时" + rm + "分" : h + "时";
    const d = Math.floor(h / 24);
    const rh = h % 24;
    return rh ? d + "天" + rh + "时" : d + "天";
  }

  function setBarFill(el, pct) {
    if (!el) return;
    const n = Math.max(0, Math.min(100, Number(pct) || 0));
    el.style.width = n + "%";
  }

  function paintStatsCharts(d) {
    const ring = document.getElementById("opt-stats-ring");
    const ringLabel = document.getElementById("opt-stats-ring-label");
    const profitBar = document.getElementById("opt-stats-bar-profit");
    const lossBar = document.getElementById("opt-stats-bar-loss");
    const profitBarLabel = document.getElementById("opt-stats-bar-profit-label");
    const lossBarLabel = document.getElementById("opt-stats-bar-loss-label");
    const winHoldBar = document.getElementById("opt-stats-bar-win-hold");
    const lossHoldBar = document.getElementById("opt-stats-bar-loss-hold");
    const winHoldBarLabel = document.getElementById("opt-stats-win-hold-label");
    const lossHoldBarLabel = document.getElementById("opt-stats-loss-hold-label");
    if (!d || !d.ok) {
      if (ring) ring.style.setProperty("--win-pct", "0");
      if (ringLabel) ringLabel.textContent = "—";
      [profitBar, lossBar, winHoldBar, lossHoldBar].forEach(function (el) { setBarFill(el, 0); });
      [profitBarLabel, lossBarLabel, winHoldBarLabel, lossHoldBarLabel].forEach(function (el) {
        if (el) el.textContent = "—";
      });
      return;
    }
    const winRate = d.total_closed ? Number(d.win_rate) || 0 : 0;
    if (ring) ring.style.setProperty("--win-pct", String(winRate));
    if (ringLabel) ringLabel.textContent = d.total_closed ? winRate.toFixed(0) + "%" : "0%";

    const profit = Math.max(0, Number(d.avg_win) || 0);
    const loss = Math.max(0, Number(d.avg_loss) || 0);
    const pnlTotal = profit + loss;
    if (pnlTotal > 0) {
      setBarFill(profitBar, (profit / pnlTotal) * 100);
      setBarFill(lossBar, (loss / pnlTotal) * 100);
      if (profitBarLabel) profitBarLabel.textContent = fmt(profit, 2) + " USDC";
      if (lossBarLabel) lossBarLabel.textContent = fmt(loss, 2) + " USDC";
    } else {
      setBarFill(profitBar, 0);
      setBarFill(lossBar, 0);
      if (profitBarLabel) profitBarLabel.textContent = d.win_count ? "0 USDC" : "—";
      if (lossBarLabel) lossBarLabel.textContent = d.loss_count ? "0 USDC" : "—";
    }

    const winHold = Number(d.avg_win_hold_sec) || 0;
    const lossHold = Number(d.avg_loss_hold_sec) || 0;
    const holdMax = Math.max(winHold, lossHold);
    if (holdMax > 0) {
      setBarFill(winHoldBar, (winHold / holdMax) * 100);
      setBarFill(lossHoldBar, (lossHold / holdMax) * 100);
      if (winHoldBarLabel) winHoldBarLabel.textContent = fmtDuration(d.avg_win_hold_sec);
      if (lossHoldBarLabel) lossHoldBarLabel.textContent = fmtDuration(d.avg_loss_hold_sec);
    } else {
      setBarFill(winHoldBar, 0);
      setBarFill(lossHoldBar, 0);
      if (winHoldBarLabel) winHoldBarLabel.textContent = "—";
      if (lossHoldBarLabel) lossHoldBarLabel.textContent = "—";
    }
  }

  async function deleteHistoryRow(key, status, instId, closedAt) {
    const warn = status === "open"
      ? "该记录仍为持仓中,仅从列表隐藏,不影响交易所持仓.确认删除?"
      : "确认从列表隐藏该条历史记录?(期权复盘页也会同步隐藏)";
    if (!confirm(warn)) return;
    const body = {};
    if (instId) body.inst_id = instId;
    if (closedAt) body.closed_at = closedAt;
    const r = await apiJson("/api/options/history/" + encodeURIComponent(key), {
      method: "DELETE",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!r.ok) {
      alert(r.msg || "删除失败");
      return;
    }
    refreshAllPositions();
  }

  function optHistoryStatus(h) {
    if (h.status_label) return h.status_label;
    if (h.status === "open") return "持仓中";
    if (h.status !== "closed") return "持仓中";
    return "已平";
  }

  function optHistoryStatusHtml(h) {
    const s = optHistoryStatus(h);
    let cls = "opt-hist-status";
    if (s === "已平") cls += " opt-hist-status--closed";
    else if (s === "到期" || s === "强平") cls += " opt-hist-status--expired";
    else cls += " opt-hist-status--open";
    return '<span class="' + cls + '">' + s + "</span>";
  }

  async function refreshHistory() {
    const d = await apiJson("/api/options/history");
    const tbody = document.getElementById("opt-history-tbody");
    tbody.innerHTML = "";
    const list = (d.ok && d.history) || [];
    if (!list.length) {
      tbody.innerHTML = '<tr><td colspan="7" class="muted">暂无历史记录</td></tr>';
      return;
    }
    list.forEach(function (h) {
      const tr = document.createElement("tr");
      const premTxt = fmtDisplay(h.premium_paid_fmt, h.premium_paid != null ? fmtUsdc(h.premium_paid) : null);
      const isOpen = h.status === "open";
      const pnl = isOpen ? null : h.realized_pnl;
      const pnlTxt = pnl != null ? fmt(pnl, 2) : "—";
      const pnlCls = pnl > 0 ? "pos-pnl-profit" : pnl < 0 ? "pos-pnl-loss" : "";
      const timeTxt = (h.closed_at || h.created_at || "—").replace("T", " ").slice(0, 19);
      const histKey = h.history_key || "";
      tr.innerHTML =
        '<td class="opt-hist-inst"><code>' + (h.inst_id || "") + "</code></td>" +
        "<td>" + fmt(h.sheets, 0) + "</td>" +
        "<td>" + premTxt + "</td>" +
        "<td>" + optHistoryStatusHtml(h) + "</td>" +
        '<td class="' + pnlCls + '">' + pnlTxt + "</td>" +
        '<td class="opt-hist-time">' + timeTxt + "</td>" +
        '<td><button type="button" class="btn-secondary btn-sm opt-history-del" data-key="' +
        histKey +
        '" data-status="' +
        (h.status || "") +
        '" data-inst="' +
        (h.inst_id || "") +
        '" data-closed="' +
        (h.closed_at || h.created_at || "") +
        '">删除</button></td>';
      tbody.appendChild(tr);
    });
    tbody.querySelectorAll(".opt-history-del").forEach(function (btn) {
      btn.addEventListener("click", function () {
        deleteHistoryRow(
          btn.getAttribute("data-key"),
          btn.getAttribute("data-status"),
          btn.getAttribute("data-inst"),
          btn.getAttribute("data-closed")
        );
      });
    });
  }

  function refreshAllPositions() {
    if (refreshAllTimer) clearTimeout(refreshAllTimer);
    refreshAllTimer = setTimeout(function () {
      refreshAllTimer = null;
      refreshPositions();
      refreshStats();
      refreshHistory();
    }, 120);
  }

  function onExpiryChange() {
    resetMoneyFilterToAll();
    state.strikeExpandAll = false;
    const expandCb = document.getElementById("opt-strike-expand-all");
    if (expandCb) expandCb.checked = false;
    renderStrikes();
  }

  function bootOptionsPanel() {
    updateSizeInputs();
    syncMoneyFilterButtons();
    syncChainViewUI();
    updateUnderlyingLabel();
    refreshPendingOrders();
    startPendingOrdersPoll();
    const hasCache =
      chainHasExpiries(panelCache.chain) &&
      panelCache.underlying === state.underlying &&
      panelCache.optType === state.optType;
    if (hasCache) {
      state.chain = panelCache.chain;
      renderExpiries();
      renderStrikes();
      refreshAllPositions();
      // 后台静默刷新,避免缓存过期后到期日变空
      loadChain({ soft: true });
      return;
    }
    requestAnimationFrame(function () {
      loadChain();
      refreshAllPositions();
    });
  }

  document.querySelectorAll(".opt-uly-btn").forEach(function (btn) {
    btn.addEventListener("click", function () {
      document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
      btn.classList.add("active");
      state.underlying = btn.getAttribute("data-uly");
      loadChain();
    });
  });

  document.querySelectorAll(".opt-view-btn").forEach(function (btn) {
    btn.addEventListener("click", function () {
      const view = btn.getAttribute("data-view") || "list";
      if (view === state.chainView) return;
      state.chainView = view;
      if (view === "t") {
        state.strikeExpandAll = false;
        const expandCb = document.getElementById("opt-strike-expand-all");
        if (expandCb) expandCb.checked = false;
      }
      syncChainViewUI();
      renderStrikes();
    });
  });

  const expandAllCb = document.getElementById("opt-strike-expand-all");
  if (expandAllCb) {
    expandAllCb.addEventListener("change", function () {
      state.strikeExpandAll = !!expandAllCb.checked;
      renderStrikes();
    });
  }

  document.querySelectorAll(".opt-type-btn").forEach(function (btn) {
    btn.addEventListener("click", function () {
      document.querySelectorAll(".opt-type-btn").forEach(function (b) { b.classList.remove("active"); });
      btn.classList.add("active");
      state.optType = btn.getAttribute("data-type");
      resetMoneyFilterToAll();
      renderExpiryOptions(true);
      renderStrikes();
    });
  });

  document.querySelectorAll(".opt-money-btn").forEach(function (btn) {
    btn.addEventListener("click", function () {
      state.moneyFilter = btn.getAttribute("data-money") || "all";
      syncMoneyFilterButtons();
      renderStrikes();
    });
  });

  document.getElementById("opt-exp-select").addEventListener("change", onExpiryChange);
  document.getElementById("opt-load-chain").addEventListener("click", loadChain);
  document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
  document.getElementById("opt-open-btn").addEventListener("click", openPosition);
  const pendingRefreshBtn = document.getElementById("opt-pending-refresh");
  if (pendingRefreshBtn) {
    pendingRefreshBtn.addEventListener("click", function () {
      refreshPendingOrders();
    });
  }
  bindOptionsPosTabs();

  document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) {
    r.addEventListener("change", function () {
      updateSizeInputs();
      if (state.selectedInst) selectContract(state.selectedInst, null, true);
    });
  });

  ["opt-sheets-amount", "opt-eth-amount", "opt-target-idx"].forEach(function (id) {
    const el = document.getElementById(id);
    if (!el) return;
    el.addEventListener("change", function () {
      if (id === "opt-target-idx") {
        updateEstimatedProfit();
        return;
      }
      if (state.selectedInst) selectContract(state.selectedInst, null, true);
    });
    if (id === "opt-target-idx") {
      el.addEventListener("input", updateEstimatedProfit);
    }
  });

  bootOptionsPanel();

  window.OptionsPanelLive = {
    refreshSoft: function () {
      refreshAllPositions();
    },
    refreshChain: loadChain,
  };
})();
