币本位持仓卡权利金/回收/门控改为按ETH/BTC展示,避免误标USDC与两位小数抹零

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-20 13:36:34 +08:00
parent 6c9825284f
commit 6c49c7d51c
8 changed files with 240 additions and 45 deletions
+94 -23
View File
@@ -868,9 +868,46 @@
return Number(v).toFixed(2);
}
function fmtClosePreview(preview, premiumPaid) {
function posPremiumCcy(p) {
const ccy = String((p && p.premium_ccy) || "").trim().toUpperCase();
if (ccy) return ccy;
const mode = String((p && p.margin_mode) || "").toLowerCase();
const inst = String((p && p.inst_id) || "");
if (mode === "coin" || (inst.indexOf("-USD-") >= 0 && inst.indexOf("_UM") < 0)) {
return (inst.split("-")[0] || "ETH").toUpperCase() || "ETH";
}
if (isCoinMarginMode && isCoinMarginMode()) {
return ((inst.split("-")[0]) || "ETH").toUpperCase() || "ETH";
}
return "USDC";
}
function isCoinPos(p) {
return posPremiumCcy(p) !== "USDC";
}
function fmtPremiumAmt(v, ccy) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v);
const unit = String(ccy || "USDC").toUpperCase();
if (unit === "ETH" || unit === "BTC") {
let s = n.toFixed(8).replace(/\.?0+$/, "");
return s || "0";
}
return fmtUsdc(n);
}
function fmtPremiumAmtSigned(v, ccy) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v);
const sign = n > 0 ? "+" : "";
return sign + fmtPremiumAmt(n, ccy) + " " + (String(ccy || "USDC").toUpperCase());
}
function fmtClosePreview(preview, premiumPaid, p) {
if (!preview || preview.total_received == null) return "—";
const recvTxt = fmtUsdc(preview.total_received);
const ccy = posPremiumCcy(p);
const recvTxt = fmtPremiumAmt(preview.total_received, ccy);
let cls = "";
const prem = Number(premiumPaid);
const recv = Number(preview.total_received);
@@ -878,12 +915,13 @@
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>";
return '<span class="opt-close-value' + cls + '">' + recvTxt + " " + ccy + "</span>";
}
function fmtClosePreviewText(preview) {
function fmtClosePreviewText(preview, p) {
if (!preview || preview.total_received == null) return "—";
let text = fmt(preview.total_received, 4) + " USDC";
const ccy = posPremiumCcy(p);
let text = fmtPremiumAmt(preview.total_received, ccy) + " " + ccy;
if (preview.covered_sheets != null) {
text += " · 覆盖 " + preview.covered_sheets + "张";
}
@@ -893,11 +931,13 @@
return text;
}
function fmtPreviewLevels(preview) {
function fmtPreviewLevels(preview, p) {
const levels = (preview && preview.levels) || [];
if (!levels.length) return "暂无可用买盘深度";
const ccy = posPremiumCcy(p);
return levels.map(function (x) {
return "买" + x.level + " " + fmt(x.px, 4) + " × " + x.sheets + "张 ≈ " + fmt(x.received, 4) + " USDC";
return "买" + x.level + " " + fmt(x.px, 4) + " × " + x.sheets + "张 ≈ " +
fmtPremiumAmt(x.received, ccy) + " " + ccy;
}).join("\n");
}
@@ -1555,10 +1595,18 @@
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);
const premCcy = posPremiumCcy(p);
const coinPos = isCoinPos(p);
const premTxt = fmtDisplay(
p.premium_paid_fmt,
p.premium_paid != null ? fmtPremiumAmt(p.premium_paid, premCcy) : 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);
const netTxt = closePreview.bid_invalid || net == null
? "—"
: (fmtPremiumAmt(net, premCcy) + (coinPos ? (" " + premCcy) : ""));
return (
'<div class="pos-card-head">' +
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
@@ -1577,21 +1625,21 @@
: "") +
"</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">' + premTxt + " " + premCcy + "</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>" +
netTxt + "</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>" +
: fmtClosePreview(closePreview, p.premium_paid, p)) + "</span></div>" +
"</div>" +
(function () {
const hint = closeGateHint(closePreview);
@@ -1628,6 +1676,7 @@
);
const statePe = String(p.profit_exit_state || (enabled ? "active" : "idle"));
const req = p.profit_exit_required_recycle;
const premCcy = posPremiumCcy(p);
let statusTxt = enabled ? ("监控中 · " + multLabel) : "未开启";
if (enabled && statePe === "closing") statusTxt = "平仓挂单中 · " + multLabel;
return (
@@ -1644,7 +1693,7 @@
'<span class="opt-target-armed">' + statusTxt + "</span>" +
'<span class="muted opt-target-row-hint">' +
(enabled
? ("1倍=盈利=权利金" + (req != null ? (" · 需回收≥" + fmtUsdc(req)) : ""))
? ("1倍=盈利=权利金" + (req != null ? (" · 需回收≥" + fmtPremiumAmt(req, premCcy) + " " + premCcy) : ""))
: "开启后自选倍数;达标按买一限价平;可随时关闭") +
"</span>" +
"</div>"
@@ -1659,16 +1708,23 @@
return null;
}
function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid) {
function formatTargetEstimateHtml(optType, strike, targetIdx, ethAmount, premiumPaid, indexPx, p) {
const value = estimateExpiryValue(optType, strike, targetIdx, ethAmount);
const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid);
const rr = estimateProfitRr(profit, premiumPaid);
const profit = estimateExpiryProfit(optType, strike, targetIdx, ethAmount, premiumPaid, indexPx);
const rr = estimateProfitRr(profit, premiumPaid, indexPx);
if (value == null && profit == null && rr == null) return "";
const coinPos = isCoinPos(p);
const valueUnit = coinPos ? " U(估)" : " USDC";
const profitTxt = profit == null
? "—"
: (coinPos
? ((Number(profit) > 0 ? "+" : "") + fmtUsdc(profit) + " U(估)")
: fmtUsdcSigned(profit));
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>";
(value == null ? "—" : fmtUsdc(value) + valueUnit) + "</span></span>";
html += '<span class="opt-target-est-item"><span class="k">预估盈利</span><span class="v ' + pnlCls(profit) + '">' +
(profit == null ? "—" : fmtUsdcSigned(profit)) + "</span></span>";
profitTxt + "</span></span>";
html += '<span class="opt-target-est-item"><span class="k">盈亏比</span><span class="v ' + pnlCls(rr) + '">' +
(rr == null ? "—" : fmtProfitRr(rr)) + "</span></span>";
html += "</span>";
@@ -1715,7 +1771,7 @@
const ethAmt = posEthAmount(p);
const prem = p.premium_paid;
const estHtml = armed
? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem)
? formatTargetEstimateHtml(p.opt_type, p.strike, tgt, ethAmt, prem, p.idx_px, p)
: '<span class="opt-target-est opt-target-est--idle"></span>';
return (
'<div class="opt-target-row" data-inst="' + inst + '"' +
@@ -1723,6 +1779,8 @@
' data-strike="' + (p.strike != null ? p.strike : "") + '"' +
' data-eth="' + (ethAmt != null ? ethAmt : "") + '"' +
' data-prem="' + (prem != null ? prem : "") + '"' +
' data-idx="' + (p.idx_px != null ? p.idx_px : "") + '"' +
' data-prem-ccy="' + posPremiumCcy(p) + '"' +
' 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="' +
@@ -1758,7 +1816,9 @@
row.getAttribute("data-strike"),
targetRaw,
row.getAttribute("data-eth"),
row.getAttribute("data-prem")
row.getAttribute("data-prem"),
row.getAttribute("data-idx"),
{ premium_ccy: row.getAttribute("data-prem-ccy"), margin_mode: row.getAttribute("data-prem-ccy") === "USDC" ? "usdc" : "coin", inst_id: row.getAttribute("data-inst") }
);
if (!html) {
est.className = "opt-target-est opt-target-est--idle";
@@ -1804,7 +1864,8 @@
(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-pnl ' + uplCls + '">' +
(net == null ? "—" : (fmtPremiumAmt(net, posPremiumCcy(p)) + " " + posPremiumCcy(p))) + "</span>" +
'<span class="opt-pos-bar-roi ' + uplCls + '">' +
(roi == null ? "—" : fmt(roi, 2) + "%") + "</span>" +
"</span>" +
@@ -2060,12 +2121,20 @@
return;
}
const lv = (preview.levels && preview.levels[0]) || {};
const posLike = {
inst_id: inst,
premium_ccy: q.premium_ccy || (preview.close_gate && preview.close_gate.premium_ccy) || null,
margin_mode: q.options_margin_mode || q.margin_mode || null,
};
const premCcy = posPremiumCcy(posLike);
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" : "",
"预计收回: " + fmtClosePreviewText(preview, posLike),
preview.estimated_pnl != null
? ("预估盈亏: " + fmtPremiumAmtSigned(preview.estimated_pnl, premCcy))
: "",
preview.uncovered_sheets > 0 ? "\n注意: 买一深度不足,预计仍剩 " + preview.uncovered_sheets + " 张,需下次再平。" : ""
].filter(function (x) { return x !== ""; }).join("\n");
if (!confirm(msg)) return;
@@ -2079,7 +2148,9 @@
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.premium_received != null) {
okMsg += "\n预估收回: " + fmtPremiumAmt(r.premium_received, premCcy) + " " + premCcy;
}
if (r.remaining_sheets > 0) okMsg += "\n剩余: " + r.remaining_sheets + " 张(下次再平)";
if (r.stopped_reason) okMsg += "\n状态: " + r.stopped_reason;
alert(okMsg);
+45 -10
View File
@@ -34,6 +34,28 @@
return Number(v).toFixed(2);
}
function posPremiumCcy(p) {
const ccy = String((p && p.premium_ccy) || "").trim().toUpperCase();
if (ccy) return ccy;
const mode = String((p && p.margin_mode) || "").toLowerCase();
const inst = String((p && p.inst_id) || "");
if (mode === "coin" || (inst.indexOf("-USD-") >= 0 && inst.indexOf("_UM") < 0)) {
return (inst.split("-")[0] || "ETH").toUpperCase() || "ETH";
}
return "USDC";
}
function fmtPremiumAmt(v, ccy) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v);
const unit = String(ccy || "USDC").toUpperCase();
if (unit === "ETH" || unit === "BTC") {
let s = n.toFixed(8).replace(/\.?0+$/, "");
return s || "0";
}
return fmtUsdc(n);
}
function optTypeLabel(t) {
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
}
@@ -136,9 +158,10 @@
return (net / prem) * 100;
}
function fmtClosePreview(preview, premiumPaid, hub) {
function fmtClosePreview(preview, premiumPaid, hub, p) {
if (!preview || preview.total_received == null) return "—";
const recvTxt = fmtUsdc(preview.total_received);
const ccy = posPremiumCcy(p);
const recvTxt = fmtPremiumAmt(preview.total_received, ccy);
let cls = "";
const prem = Number(premiumPaid);
const recv = Number(preview.total_received);
@@ -146,7 +169,7 @@
if (recv > prem) cls = " " + pnlCls(1, hub);
else if (recv < prem) cls = " " + pnlCls(-1, hub);
}
return '<span class="opt-close-value' + cls + '">' + recvTxt + " USDC</span>";
return '<span class="opt-close-value' + cls + '">' + recvTxt + " " + ccy + "</span>";
}
function expiryCdHtml(expMs) {
@@ -168,7 +191,11 @@
const expAttr = expMs != null && expMs !== "" ? String(expMs) : "";
const closePreview = p.close_preview || {};
const tickSz = p.tick_sz;
const premTxt = fmtDisplay(p.premium_paid_fmt, p.premium_paid != null ? fmtUsdc(p.premium_paid) : null);
const premCcy = posPremiumCcy(p);
const premTxt = fmtDisplay(
p.premium_paid_fmt,
p.premium_paid != null ? fmtPremiumAmt(p.premium_paid, premCcy) : null
);
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);
let headActions = "";
@@ -182,7 +209,7 @@
const pnlCells = hidePnl
? ""
: '<div class="pos-cell"><span class="pos-label">净盈亏</span><span class="pos-value ' + uplCls + '">' +
(net == null ? "—" : fmt(net, 2)) + "</span></div>" +
(net == null ? "—" : (fmtPremiumAmt(net, premCcy) + (premCcy !== "USDC" ? (" " + premCcy) : ""))) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
(roi == null ? "—" : fmt(roi, 2) + "%") + "</span></div>";
return (
@@ -202,7 +229,7 @@
: "") +
"</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">' + premTxt + " " + premCcy + "</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>" +
@@ -213,7 +240,7 @@
'<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, hidePnl ? null : p.premium_paid, hub)) + "</span></div>" +
: fmtClosePreview(closePreview, hidePnl ? null : p.premium_paid, hub, p)) + "</span></div>" +
"</div>" +
(function () {
const hint = closeGateHint(closePreview);
@@ -226,6 +253,7 @@
const strike = Number(p.strike);
const tgt = Number(p.target_index);
const prem = Number(p.premium_paid);
const idx = Number(p.idx_px);
let profit = null;
let value = null;
if (Number.isFinite(tgt) && Number.isFinite(strike) && eth > 0) {
@@ -233,10 +261,17 @@
const intrinsic = o === "C" ? Math.max(0, tgt - strike) : o === "P" ? Math.max(0, strike - tgt) : null;
if (intrinsic != null) {
value = Math.round(intrinsic * eth * 100) / 100;
if (!hidePnl && Number.isFinite(prem)) profit = Math.round((value - prem) * 100) / 100;
if (!hidePnl && Number.isFinite(prem)) {
let premUsd = prem;
if (premCcy !== "USDC" && Number.isFinite(idx) && idx > 0) premUsd = prem * idx;
profit = Math.round((value - premUsd) * 100) / 100;
}
}
}
const profitTxt = profit == null ? "—" : ((profit > 0 ? "+" : "") + fmtUsdc(profit) + " USDC");
const valueUnit = premCcy !== "USDC" ? " U(估)" : " USDC";
const profitTxt = profit == null
? "—"
: ((profit > 0 ? "+" : "") + fmtUsdc(profit) + (premCcy !== "USDC" ? " U(估)" : " USDC"));
const profitCls = profit > 0 ? " pnl-pos" : profit < 0 ? " pnl-neg" : "";
const hedgeTarget = p.hedge_plan_target || null;
const managed = hedgeTarget && hedgeTarget.managed_by === "hedge_plan";
@@ -247,7 +282,7 @@
'<div class="opt-target-row opt-target-row--ro' + (managed ? " opt-target-row--managed" : "") + '">' +
'<span class="opt-target-row-label">' + (managed ? "对冲计划 #" + hedgeTarget.plan_id : "委托") + "</span>" +
'<span class="pos-value">目标 ' + fmt(p.target_index, 1) + "</span>" +
'<span class="pos-value">价值 ' + (value == null ? "—" : fmtUsdc(value) + " USDC") + "</span>" +
'<span class="pos-value">价值 ' + (value == null ? "—" : fmtUsdc(value) + valueUnit) + "</span>" +
profitSpan +
'<span class="muted opt-target-row-hint">' +
(managed ? "进行中 · 由对冲计划监控,到位后仅平盈利腿" : "监控中 · 到位按买一限价平") +