币本位持仓卡权利金/回收/门控改为按ETH/BTC展示,避免误标USDC与两位小数抹零
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 ? "进行中 · 由对冲计划监控,到位后仅平盈利腿" : "监控中 · 到位按买一限价平") +
|
||||
|
||||
@@ -171,6 +171,21 @@ def format_usdc_amount(v: float | None) -> str | None:
|
||||
return f"{float(v):.2f}"
|
||||
|
||||
|
||||
def format_premium_amount(v: float | None, *, ccy: str | None = "USDC") -> str | None:
|
||||
"""权利金/回收金额文案:USDC 2 位;币本位 ETH/BTC 最多 8 位去尾零."""
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
n = float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
unit = (ccy or "USDC").strip().upper() or "USDC"
|
||||
if unit in ("ETH", "BTC"):
|
||||
txt = f"{n:.8f}".rstrip("0").rstrip(".")
|
||||
return txt or "0"
|
||||
return f"{n:.2f}"
|
||||
|
||||
|
||||
def is_option_full_close_history(raw: dict[str, Any]) -> bool:
|
||||
"""仅保留 OKX 历史仓位中的「全部平仓/强平/ADL 全平」记录,排除部分平仓."""
|
||||
close_type = str(raw.get("type") or "").strip()
|
||||
@@ -1797,6 +1812,16 @@ def format_position_row(
|
||||
ct_mult=ct_mult,
|
||||
)
|
||||
exp_time_ms = normalize_option_exp_ms(pos.get("expTime"), inst_id)
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
|
||||
|
||||
row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc"
|
||||
underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
|
||||
premium_ccy = premium_ccy_for_mode(row_mode, underly)
|
||||
except Exception:
|
||||
row_mode = "usdc"
|
||||
underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
|
||||
premium_ccy = "USDC"
|
||||
return {
|
||||
"inst_id": inst_id or pos.get("instId"),
|
||||
"pos": sheets,
|
||||
@@ -1805,11 +1830,14 @@ def format_position_row(
|
||||
"mark_px": mark,
|
||||
"avg_px_fmt": format_option_px(avg, tick_sz) if avg is not None else None,
|
||||
"mark_px_fmt": format_option_px(mark, tick_sz) if mark is not None else None,
|
||||
"premium_paid_fmt": format_usdc_amount(premium_paid),
|
||||
"premium_paid_fmt": format_premium_amount(premium_paid, ccy=premium_ccy),
|
||||
"tick_sz": tick_sz,
|
||||
"ct_mult": ct_mult,
|
||||
"idx_px": idx_px,
|
||||
"premium_paid": premium_paid,
|
||||
"margin_mode": row_mode,
|
||||
"premium_ccy": premium_ccy,
|
||||
"underlying": underly,
|
||||
"upl": upl,
|
||||
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
|
||||
"exp_time": exp_time_ms,
|
||||
|
||||
@@ -60,6 +60,14 @@ def is_close_gate_passed(inst_id: str) -> bool:
|
||||
return bool((_gates.get(inst) or {}).get("passed"))
|
||||
|
||||
|
||||
def _fmt_gate_amt(v: float, *, ccy: str) -> str:
|
||||
unit = (ccy or "USDC").strip().upper() or "USDC"
|
||||
if unit in ("ETH", "BTC"):
|
||||
txt = f"{float(v):.8f}".rstrip("0").rstrip(".")
|
||||
return txt or "0"
|
||||
return f"{float(v):.4f}"
|
||||
|
||||
|
||||
def update_close_gate(
|
||||
inst_id: str,
|
||||
*,
|
||||
@@ -68,6 +76,7 @@ def update_close_gate(
|
||||
now: float | None = None,
|
||||
min_mult: float | None = None,
|
||||
hold_seconds: float | None = None,
|
||||
premium_ccy: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
根据当前买盘可回收金额刷新门控.
|
||||
@@ -89,9 +98,16 @@ def update_close_gate(
|
||||
if hold < 0:
|
||||
hold = 0.0
|
||||
|
||||
with _lock:
|
||||
prev_ccy = (_gates.get(inst) or {}).get("premium_ccy")
|
||||
ccy = (premium_ccy or prev_ccy or "USDC").strip().upper() or "USDC"
|
||||
if ccy not in ("ETH", "BTC", "USDC"):
|
||||
ccy = "USDC"
|
||||
need_decimals = 8 if ccy in ("ETH", "BTC") else 4
|
||||
|
||||
prem = _safe_float(premium_paid)
|
||||
recv = _safe_float(recycle_usdc)
|
||||
need = round(prem * mult, 4) if prem is not None and prem > 0 else None
|
||||
need = round(prem * mult, need_decimals) if prem is not None and prem > 0 else None
|
||||
recycle_ok = bool(
|
||||
prem is not None and prem > 0 and recv is not None and need is not None and recv + 1e-12 >= need
|
||||
)
|
||||
@@ -117,6 +133,7 @@ def update_close_gate(
|
||||
"min_mult": mult,
|
||||
"hold_seconds": hold,
|
||||
"passed": passed,
|
||||
"premium_ccy": ccy,
|
||||
}
|
||||
_gates[inst] = state
|
||||
|
||||
@@ -126,10 +143,13 @@ def update_close_gate(
|
||||
elif recv is None:
|
||||
msg = "暂无有效买盘可回收金额"
|
||||
elif not recycle_ok:
|
||||
msg = f"可回收 {recv:.4f} USDC < 权利金×{mult:g}({need:.4f}),目标平仓门控未过"
|
||||
msg = (
|
||||
f"可回收 {_fmt_gate_amt(recv, ccy=ccy)} {ccy} < 权利金×{mult:g}"
|
||||
f"({_fmt_gate_amt(need, ccy=ccy)}),目标平仓门控未过"
|
||||
)
|
||||
elif not ready:
|
||||
msg = (
|
||||
f"可回收已达×{mult:g}({recv:.4f}/{need:.4f}),"
|
||||
f"可回收已达×{mult:g}({_fmt_gate_amt(recv, ccy=ccy)}/{_fmt_gate_amt(need, ccy=ccy)} {ccy}),"
|
||||
f"需再持续 {remain:.0f}s(已 {held:.0f}/{hold:.0f}s)门控才通过"
|
||||
)
|
||||
else:
|
||||
@@ -144,6 +164,7 @@ def update_close_gate(
|
||||
"recycle_usdc": recv,
|
||||
"premium_paid": prem,
|
||||
"need_recycle_usdc": need,
|
||||
"premium_ccy": ccy,
|
||||
"min_mult": mult,
|
||||
"hold_seconds": hold,
|
||||
"held_seconds": round(held, 1) if recycle_ok else 0.0,
|
||||
@@ -160,25 +181,39 @@ def check_close_gate(
|
||||
*,
|
||||
recycle_usdc: float | None = None,
|
||||
premium_paid: float | None = None,
|
||||
premium_ccy: str | None = None,
|
||||
refresh: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""检查是否允许平仓;默认先用最新回收/权利金刷新."""
|
||||
inst = (inst_id or "").strip()
|
||||
if refresh:
|
||||
if recycle_usdc is None or premium_paid is None:
|
||||
if recycle_usdc is None or premium_paid is None or premium_ccy is None:
|
||||
with _lock:
|
||||
prev = _gates.get(inst) or {}
|
||||
if recycle_usdc is None:
|
||||
recycle_usdc = prev.get("recycle")
|
||||
if premium_paid is None:
|
||||
premium_paid = prev.get("premium")
|
||||
return update_close_gate(inst, recycle_usdc=recycle_usdc, premium_paid=premium_paid)
|
||||
if premium_ccy is None:
|
||||
premium_ccy = prev.get("premium_ccy")
|
||||
return update_close_gate(
|
||||
inst,
|
||||
recycle_usdc=recycle_usdc,
|
||||
premium_paid=premium_paid,
|
||||
premium_ccy=premium_ccy,
|
||||
)
|
||||
with _lock:
|
||||
prev = _gates.get(inst)
|
||||
if not prev:
|
||||
return update_close_gate(inst, recycle_usdc=recycle_usdc, premium_paid=premium_paid)
|
||||
return update_close_gate(
|
||||
inst,
|
||||
recycle_usdc=recycle_usdc,
|
||||
premium_paid=premium_paid,
|
||||
premium_ccy=premium_ccy,
|
||||
)
|
||||
return update_close_gate(
|
||||
inst,
|
||||
recycle_usdc=recycle_usdc if recycle_usdc is not None else prev.get("recycle"),
|
||||
premium_paid=premium_paid if premium_paid is not None else prev.get("premium"),
|
||||
premium_ccy=premium_ccy if premium_ccy is not None else prev.get("premium_ccy"),
|
||||
)
|
||||
|
||||
@@ -134,7 +134,8 @@ def sum_open_premium_paid(conn: sqlite3.Connection, inst_id: str) -> float | Non
|
||||
).fetchone()
|
||||
if not row or int(row["n"] or 0) < 1:
|
||||
return None
|
||||
return round(float(row["total"] or 0), 4)
|
||||
# 币本位权利金常 <1e-4,保留 8 位避免被裁成 0
|
||||
return round(float(row["total"] or 0), 8)
|
||||
|
||||
|
||||
def sum_open_sheets(conn: sqlite3.Connection, inst_id: str) -> int | None:
|
||||
|
||||
@@ -14,14 +14,21 @@ def enrich_position_row_display(
|
||||
meta_cache: dict[str, dict[str, Any] | None] | None = None,
|
||||
premium_override: float | None = None,
|
||||
) -> dict[str, Any]:
|
||||
from lib.exchange.okx_options_lib import format_position_row, format_usdc_amount, tick_sz_and_ct_mult
|
||||
from lib.exchange.okx_options_lib import format_position_row, format_premium_amount, tick_sz_and_ct_mult
|
||||
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
|
||||
|
||||
inst_id = str(raw_pos.get("instId") or "").strip()
|
||||
tick_sz, ct_mult = tick_sz_and_ct_mult(ex, inst_id, meta_cache)
|
||||
row = format_position_row(raw_pos, ct_mult=ct_mult, tick_sz=tick_sz)
|
||||
row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc"
|
||||
underly = str(row.get("underlying") or (inst_id.split("-")[0] if inst_id else "ETH") or "ETH")
|
||||
premium_ccy = premium_ccy_for_mode(row_mode, underly)
|
||||
row["margin_mode"] = row_mode
|
||||
row["premium_ccy"] = premium_ccy
|
||||
row["margin_mode_label"] = "币本位" if row_mode == "coin" else "USDC"
|
||||
if premium_override is not None:
|
||||
row["premium_paid"] = premium_override
|
||||
row["premium_paid_fmt"] = format_usdc_amount(premium_override)
|
||||
row["premium_paid_fmt"] = format_premium_amount(row.get("premium_paid"), ccy=premium_ccy)
|
||||
return row
|
||||
|
||||
|
||||
|
||||
@@ -51,9 +51,12 @@ def attach_close_preview(
|
||||
intrinsic_px=intrinsic,
|
||||
max_levels=1,
|
||||
)
|
||||
premium_ccy = str(row.get("premium_ccy") or "USDC").strip().upper() or "USDC"
|
||||
# 残档时不累计 2×门控;有效买一时刷新计时(仅自动平仓需要)
|
||||
if preview.get("bid_invalid") or preview.get("auto_close_blocked"):
|
||||
gate = update_close_gate(inst_id, recycle_usdc=None, premium_paid=paid)
|
||||
gate = update_close_gate(
|
||||
inst_id, recycle_usdc=None, premium_paid=paid, premium_ccy=premium_ccy
|
||||
)
|
||||
preview["close_gate"] = gate
|
||||
preview["close_gate_blocked"] = True
|
||||
preview["close_gate_msg"] = preview.get("bid_invalid_reason") or gate.get("msg")
|
||||
@@ -64,6 +67,7 @@ def attach_close_preview(
|
||||
inst_id,
|
||||
recycle_usdc=_safe_float(preview.get("total_received")),
|
||||
premium_paid=paid,
|
||||
premium_ccy=premium_ccy,
|
||||
)
|
||||
passed = bool(gate.get("passed") or is_close_gate_passed(inst_id) or gate.get("ready"))
|
||||
preview["close_gate"] = gate
|
||||
|
||||
@@ -533,7 +533,21 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if mode == "close_preview":
|
||||
paid = _open_premium_paid(cfg, inst_id)
|
||||
target = sheet_count if sheet_count is not None else 0
|
||||
return jsonify(_attach_close_preview(cfg, ex, {**q, "pos": target, "premium_paid": paid}, sheets=target, premium_paid=paid))
|
||||
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
|
||||
|
||||
row_mode = margin_mode_from_inst_id(inst_id)
|
||||
prem_ccy = premium_ccy_for_mode(row_mode, (inst_id.split("-")[0] if inst_id else "ETH"))
|
||||
preview_row = {
|
||||
**q,
|
||||
"pos": target,
|
||||
"premium_paid": paid,
|
||||
"margin_mode": row_mode,
|
||||
"premium_ccy": prem_ccy,
|
||||
}
|
||||
out = _attach_close_preview(cfg, ex, preview_row, sheets=target, premium_paid=paid)
|
||||
out["options_margin_mode"] = row_mode
|
||||
out["premium_ccy"] = prem_ccy
|
||||
return jsonify(out)
|
||||
mode, mode_note = _normalize_size_mode(mode)
|
||||
|
||||
# 币本位:报价预览走 USDT 预算→估币→张数,禁止再查 USDC
|
||||
|
||||
Reference in New Issue
Block a user