Files
crypto_monitor/lib/common/static/options_panel.js
T

674 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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",
chain: panelCache.chain || null,
selectedInst: null,
orderQuote: null,
};
function fmt(v, d) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
return Number(v).toFixed(d == null ? 2 : d);
}
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 parkOrderPanel() {
const panel = orderPanel();
const host = orderPanelHost();
const inline = document.querySelector(".opt-order-inline-row");
if (inline) inline.remove();
if (panel && host && panel.parentElement !== host) {
host.appendChild(panel);
host.hidden = true;
}
if (panel) panel.style.display = "none";
document.querySelectorAll(".opt-strike-row").forEach(function (r) {
r.classList.remove("opt-row-selected");
});
document.querySelectorAll(".opt-pick-btn").forEach(function (b) {
b.classList.remove("active");
b.disabled = false;
if (b.dataset.origText) b.textContent = b.dataset.origText;
});
}
function placeOrderPanelAfter(instId) {
const panel = orderPanel();
if (!panel || !instId) return;
const row = document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-inst="' + CSS.escape(instId) + '"]');
if (!row) return;
document.querySelectorAll(".opt-strike-row").forEach(function (r) {
r.classList.toggle("opt-row-selected", r === row);
});
document.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
const on = btn.getAttribute("data-inst") === instId;
btn.classList.toggle("active", on);
if (!btn.dataset.origText) btn.dataset.origText = btn.textContent;
if (on) btn.textContent = "已选";
});
const oldInline = document.querySelector(".opt-order-inline-row");
if (oldInline) oldInline.remove();
const tr = document.createElement("tr");
tr.className = "opt-order-inline-row";
const td = document.createElement("td");
td.colSpan = 8;
td.appendChild(panel);
tr.appendChild(td);
row.after(tr);
panel.style.display = "";
orderPanelHost().hidden = true;
tr.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
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 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 renderExpiries() {
const sel = document.getElementById("opt-exp-select");
sel.innerHTML = '<option value="">选择到期日</option>';
const exps = (state.chain && state.chain.expiries) || [];
exps.forEach(function (e) {
const o = document.createElement("option");
o.value = String(e.exp_time);
o.textContent = expLabel(e.exp_time) + " (" + e.contracts.length + ")";
sel.appendChild(o);
});
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));
}
document.getElementById("opt-index-line").textContent =
"指数 " + state.underlying + " ≈ " + fmt(idx, 2) + " · 实值=价内 · 虚值=价外";
}
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 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 estimateExpiryProfit(optType, strike, targetIdx, entryPx, ethAmount) {
if (strike == null || targetIdx == null || entryPx == null || ethAmount == null) return null;
const amt = Number(ethAmount);
const entry = Number(entryPx);
const tgt = Number(targetIdx);
const k = Number(strike);
if (!Number.isFinite(amt) || !Number.isFinite(entry) || !Number.isFinite(tgt) || !Number.isFinite(k) || amt <= 0) {
return null;
}
const o = (optType || "").toUpperCase();
let intrinsic = 0;
if (o === "C") intrinsic = Math.max(0, tgt - k);
else if (o === "P") intrinsic = Math.max(0, k - tgt);
else return null;
return Math.round((intrinsic - entry) * amt * 10000) / 10000;
}
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 updateOrderEstimates() {
const levEl = document.getElementById("opt-order-leverage");
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) {
if (levEl) levEl.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 (profitEl && targetEl) {
const targetRaw = targetEl.value;
if (targetRaw === "" || targetRaw == null) {
profitEl.textContent = "—";
profitEl.className = "v";
if (targetLevEl) targetLevEl.textContent = "—";
} else {
const profit = estimateExpiryProfit(q.opt_type, q.strike, Number(targetRaw), q.ask, ethAmount);
if (profit == null || Number.isNaN(profit)) {
profitEl.textContent = "—";
profitEl.className = "v";
} else {
const sign = profit > 0 ? "+" : "";
profitEl.textContent = sign + profit.toFixed(4) + " USDC";
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 renderStrikes() {
const tbody = document.getElementById("opt-strike-tbody");
const expMs = document.getElementById("opt-exp-select").value;
const prevSelected = state.selectedInst;
parkOrderPanel();
tbody.innerHTML = "";
if (!expMs || !state.chain) {
tbody.innerHTML = '<tr><td colspan="8" 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) return;
const list = (exp.contracts || []).filter(function (c) {
return c.opt_type === state.optType;
});
if (!list.length) {
tbody.innerHTML = '<tr><td colspan="8" class="muted">该到期日暂无报价</td></tr>';
state.selectedInst = null;
return;
}
let stillVisible = 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">' +
'<button type="button" class="btn-secondary opt-pick-btn" data-inst="' + c.inst_id + '">选择</button>' +
"</td>";
tbody.appendChild(tr);
if (c.inst_id === prevSelected) stillVisible = true;
});
tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
selectContract(btn.getAttribute("data-inst"), btn);
});
});
if (stillVisible && prevSelected) {
selectContract(prevSelected, null, true);
} else {
state.selectedInst = null;
}
}
function fillOrderPanel(d) {
state.orderQuote = d && d.ok ? d : null;
const sz = d.sizing || {};
document.getElementById("opt-order-inst").textContent = d.inst_id || state.selectedInst || "";
document.getElementById("opt-order-ask").textContent = fmtPxSz(d.ask, d.ask_sz);
const bidEl = document.getElementById("opt-order-bid");
if (bidEl) bidEl.textContent = fmtPxSz(d.bid, d.bid_sz);
document.getElementById("opt-order-sheets").textContent = sz.sheets != null ? sz.sheets : "—";
document.getElementById("opt-order-eth").textContent = sz.eth_amount != null ? sz.eth_amount : "—";
document.getElementById("opt-order-premium").textContent = sz.total_premium != null ? fmt(sz.total_premium, 4) + " 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 msgEl = document.getElementById("opt-order-msg");
if (!d.ok) {
msgEl.textContent = d.msg || "报价失败";
msgEl.classList.add("opt-error");
} else if (sz.ok === false) {
msgEl.textContent = sz.msg || "";
msgEl.classList.add("opt-error");
} else {
msgEl.textContent = "";
msgEl.classList.remove("opt-error");
}
updateEstimatedProfit();
}
async function selectContract(instId, pickBtn, silent) {
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));
fillOrderPanel(d);
return d;
} finally {
if (pickBtn) {
pickBtn.disabled = false;
pickBtn.textContent = "已选";
pickBtn.classList.add("active");
}
if (!silent) placeOrderPanelAfter(instId);
}
}
async function loadChain() {
const d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(state.underlying));
if (!d.ok) {
alert(d.msg || "加载失败");
return;
}
state.chain = d;
panelCache.chain = d;
panelCache.underlying = state.underlying;
panelCache.optType = state.optType;
state.selectedInst = null;
parkOrderPanel();
renderExpiries();
renderStrikes();
}
async function openPosition() {
if (!state.selectedInst) {
alert("请先选择合约");
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 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 ? "下单已提交,可在 OKX 委托中查看" : (d.msg || "失败");
msgEl.classList.toggle("opt-error", !d.ok);
if (d.ok) {
refreshAllPositions();
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
} else {
alert(d.msg || "下单失败");
}
} finally {
btn.disabled = false;
}
}
function renderPositionCard(p) {
const upl = p.upl;
const uplCls = upl > 0 ? "pos-pnl-profit" : upl < 0 ? "pos-pnl-loss" : "";
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) : "";
return (
'<div class="pos-card opt-pos-card" data-inst="' + (p.inst_id || "") + '">' +
'<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 + '">限价平仓</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">' + fmt(p.premium_paid, 4) + " USDC</span></div>" +
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + fmt(p.avg_px, 4) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + fmt(p.mark_px, 4) + "</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 + '">' + fmt(p.upl, 2) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
(p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "</span></div>" +
"</div></div>"
);
}
async function closePosition(inst, btn) {
const q = await apiJson("/api/options/quote?inst_id=" + encodeURIComponent(inst) + "&mode=sheets&sheets=1");
if (!q.ok) {
alert(q.msg || "获取买一价失败");
return;
}
const bid = q.bid;
if (bid == null || bid <= 0) {
alert("暂无买一价,请稍后在 OKX App 平仓或等盘口恢复");
return;
}
if (!confirm("限价卖出 @ 买一 " + fmt(bid, 4) + "(每 1 ETH/BTC)?\n合约:" + inst)) 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 }),
});
if (r.ok) {
alert("平仓单已提交" + (r.bid != null ? " @ " + fmt(r.bid, 4) : ""));
} else {
alert(r.msg || "平仓失败");
}
refreshAllPositions();
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
} finally {
if (btn) btn.disabled = false;
}
}
async function refreshPositions() {
const d = await apiJson("/api/options/positions");
const wrap = document.getElementById("opt-pos-cards");
const empty = document.getElementById("opt-pos-empty");
const list = (d.ok && d.positions) || [];
wrap.innerHTML = "";
if (!list.length) {
empty.style.display = "";
return;
}
empty.style.display = "none";
list.forEach(function (p) {
const div = document.createElement("div");
div.innerHTML = renderPositionCard(p);
wrap.appendChild(div.firstChild);
});
wrap.querySelectorAll(".opt-close-btn").forEach(function (btn) {
btn.addEventListener("click", function () {
closePosition(btn.getAttribute("data-inst"), btn);
});
});
if (window.OptionsExpiryCountdown && OptionsExpiryCountdown.ensureTimer) {
OptionsExpiryCountdown.ensureTimer();
}
}
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");
if (!d.ok) {
[winEl, plrEl, closedEl, profitEl, lossEl].forEach(function (el) {
if (el) el.textContent = "—";
});
return;
}
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.total_profit != null && d.total_profit > 0
? fmt(d.total_profit, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—");
}
if (lossEl) {
lossEl.textContent = d.total_loss != null && d.total_loss > 0
? fmt(d.total_loss, 4) + " USDC" : (d.total_closed ? "0 USDC" : "—");
}
}
async function deleteHistoryRow(id, status) {
const warn = status === "open"
? "该记录仍为持仓中,仅删除本地记录,不影响交易所持仓。确认删除?"
: "确认删除该条期权历史记录?";
if (!confirm(warn)) return;
const r = await apiJson("/api/options/history/" + encodeURIComponent(id), { method: "DELETE" });
if (!r.ok) {
alert(r.msg || "删除失败");
return;
}
refreshAllPositions();
}
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 prem = h.status === "closed" ? h.premium_received : h.premium_paid;
const pnl = h.realized_pnl;
const pnlTxt = pnl != null ? fmt(pnl, 4) : "—";
const pnlCls = pnl > 0 ? "pos-pnl-profit" : pnl < 0 ? "pos-pnl-loss" : "";
tr.innerHTML =
"<td><code>" + (h.inst_id || "") + "</code></td>" +
"<td>" + fmt(h.sheets, 0) + "</td>" +
"<td>" + fmt(prem, 4) + "</td>" +
"<td>" + (h.status === "closed" ? "已平" : "持仓中") + "</td>" +
'<td class="' + pnlCls + '">' + pnlTxt + "</td>" +
"<td>" + (h.closed_at || h.created_at || "—") + "</td>" +
'<td><button type="button" class="btn-secondary btn-sm opt-history-del" data-id="' + h.id + '" data-status="' + (h.status || "") + '">删除</button></td>';
tbody.appendChild(tr);
});
tbody.querySelectorAll(".opt-history-del").forEach(function (btn) {
btn.addEventListener("click", function () {
deleteHistoryRow(btn.getAttribute("data-id"), btn.getAttribute("data-status"));
});
});
}
function refreshAllPositions() {
refreshPositions();
refreshStats();
refreshHistory();
}
function bootOptionsPanel() {
updateSizeInputs();
const hasCache =
panelCache.chain &&
panelCache.underlying === state.underlying &&
panelCache.optType === state.optType;
if (hasCache) {
state.chain = panelCache.chain;
renderExpiries();
renderStrikes();
refreshAllPositions();
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-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");
renderStrikes();
});
});
document.getElementById("opt-exp-select").addEventListener("change", renderStrikes);
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
document.getElementById("opt-refresh-positions").addEventListener("click", refreshAllPositions);
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
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,
};
})();