(function () { "use strict"; const root = document.getElementById("options-root"); if (!root) return; const state = { underlying: root.dataset.defaultUnderly || "ETH", optType: "C", chain: null, selectedInst: null, convertQuoteId: 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(); } async function refreshBalances() { const d = await apiJson("/api/options/balances"); if (!d.ok) return; document.getElementById("opt-funding-usdt").textContent = fmt(d.funding_usdt) + " U"; document.getElementById("opt-funding-usdc").textContent = fmt(d.funding_usdc) + " U"; document.getElementById("opt-trading-usdt").textContent = fmt(d.trading_usdt) + " U"; document.getElementById("opt-trading-usdc").textContent = fmt(d.trading_usdc) + " U"; document.getElementById("opt-trading-usdg").textContent = fmt(d.trading_usdg) + " U"; document.getElementById("opt-trade-budget").textContent = fmt(d.trade_budget) + " USDC"; } function expLabel(ms) { try { return new Date(Number(ms)).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }); } catch (e) { return String(ms); } } function renderExpiries() { const sel = document.getElementById("opt-exp-select"); sel.innerHTML = ''; 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); }); document.getElementById("opt-index-line").textContent = "指数 " + state.underlying + " ≈ " + fmt(state.chain && state.chain.index_px, 2); } function renderStrikes() { const tbody = document.getElementById("opt-strike-tbody"); const expMs = document.getElementById("opt-exp-select").value; tbody.innerHTML = ""; if (!expMs || !state.chain) { tbody.innerHTML = '请选择到期日'; 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 = '无符合的实值合约'; return; } list.forEach(function (c) { const tr = document.createElement("tr"); tr.innerHTML = "" + c.strike + "" + "" + c.inst_id + "" + "" + fmt(c.ask, 4) + "" + "" + fmt(c.bid, 4) + "" + ''; tbody.appendChild(tr); }); tbody.querySelectorAll(".opt-pick-btn").forEach(function (btn) { btn.addEventListener("click", function () { selectContract(btn.getAttribute("data-inst")); }); }); } async function selectContract(instId) { state.selectedInst = instId; const mode = document.querySelector('input[name="opt-size-mode"]:checked').value; const ethInput = document.getElementById("opt-eth-amount"); let url = "/api/options/quote?inst_id=" + encodeURIComponent(instId) + "&mode=" + mode; if (mode === "eth_amount" && ethInput.value) { url += "ð_amount=" + encodeURIComponent(ethInput.value); } const d = await apiJson(url); const panel = document.getElementById("opt-order-panel"); panel.style.display = ""; document.getElementById("opt-order-inst").textContent = instId; document.getElementById("opt-order-ask").textContent = fmt(d.ask, 4); const sz = d.sizing || {}; 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" : "—"; document.getElementById("opt-order-msg").textContent = sz.ok === false ? (sz.msg || "") : ""; } async function loadChain() { const d = await apiJson("/api/options/chain?underlying=" + encodeURIComponent(state.underlying)); if (!d.ok) { alert(d.msg || "加载失败"); return; } state.chain = d; renderExpiries(); renderStrikes(); } async function openPosition() { if (!state.selectedInst) return; const mode = document.querySelector('input[name="opt-size-mode"]:checked').value; 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); } const d = await apiJson("/api/options/open", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); document.getElementById("opt-order-msg").textContent = d.ok ? "下单已提交" : (d.msg || "失败"); if (d.ok) { refreshBalances(); refreshPositions(); } } async function refreshPositions() { const d = await apiJson("/api/options/positions"); const tbody = document.getElementById("opt-positions-tbody"); tbody.innerHTML = ""; const list = (d.ok && d.positions) || []; if (!list.length) { tbody.innerHTML = '暂无持仓'; return; } list.forEach(function (p) { const tr = document.createElement("tr"); tr.innerHTML = "" + (p.inst_id || "") + "" + "" + fmt(p.pos, 0) + "" + "" + fmt(p.eth_amount, 4) + "" + "" + fmt(p.avg_px, 4) + "" + "" + fmt(p.mark_px, 4) + "" + "" + fmt(p.upl, 4) + "" + "" + (p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "" + ''; tbody.appendChild(tr); }); tbody.querySelectorAll(".opt-close-btn").forEach(function (btn) { btn.addEventListener("click", async function () { const inst = btn.getAttribute("data-inst"); if (!confirm("确认限价卖出 @ 买一?")) return; const r = await apiJson("/api/options/close", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ inst_id: inst }), }); alert(r.ok ? "平仓单已提交" : (r.msg || "失败")); refreshPositions(); refreshBalances(); }); }); } 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-balances").addEventListener("click", refreshBalances); document.getElementById("opt-refresh-positions").addEventListener("click", refreshPositions); document.getElementById("opt-open-btn").addEventListener("click", openPosition); document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) { r.addEventListener("change", function () { document.getElementById("opt-eth-amount").style.display = r.value === "eth_amount" && r.checked ? "" : "none"; if (state.selectedInst) selectContract(state.selectedInst); }); }); document.getElementById("opt-convert-quote-btn").addEventListener("click", async function () { const amount = parseFloat(document.getElementById("opt-convert-amount").value); if (!amount || amount <= 0) { alert("请输入 USDT 数量"); return; } const d = await apiJson("/api/options/convert/quote", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amount: amount }), }); const prev = document.getElementById("opt-convert-preview"); if (!d.ok) { prev.textContent = d.msg || "询价失败"; state.convertQuoteId = null; document.getElementById("opt-convert-exec-btn").disabled = true; return; } state.convertQuoteId = d.quote_id; prev.textContent = "预估获得 " + fmt(d.base_sz, 6) + " USDC,汇率 " + fmt(d.cnvt_px, 6); document.getElementById("opt-convert-exec-btn").disabled = false; }); document.getElementById("opt-convert-exec-btn").addEventListener("click", async function () { if (!state.convertQuoteId) return; const amount = parseFloat(document.getElementById("opt-convert-amount").value); const d = await apiJson("/api/options/convert/execute", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ quote_id: state.convertQuoteId, rfq_sz: amount }), }); document.getElementById("opt-convert-preview").textContent = d.ok ? "兑换成功" : (d.msg || "失败"); state.convertQuoteId = null; document.getElementById("opt-convert-exec-btn").disabled = true; refreshBalances(); }); document.getElementById("opt-transfer-btn").addEventListener("click", async function () { const d = await apiJson("/api/options/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ccy: document.getElementById("opt-transfer-ccy").value, from: document.getElementById("opt-transfer-from").value, to: document.getElementById("opt-transfer-to").value, amount: parseFloat(document.getElementById("opt-transfer-amount").value), }), }); document.getElementById("opt-transfer-msg").textContent = d.ok ? "划转成功" : (d.msg || "失败"); refreshBalances(); }); refreshBalances(); loadChain(); refreshPositions(); })();