Add depth-based option close flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-11 08:46:07 +08:00
parent 02472f19cb
commit 6e45604d93
6 changed files with 396 additions and 20 deletions
+57 -8
View File
@@ -185,6 +185,34 @@
return price + "/" + size;
}
function fmtBidDepth(levels) {
const bids = (levels || []).slice(0, 5);
if (!bids.length) return "—";
return bids.map(function (x, idx) {
return "买" + (idx + 1) + " " + fmtPxSz(x.px, x.sz);
}).join(" · ");
}
function fmtClosePreview(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);
@@ -456,12 +484,15 @@
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 closePreviewCls = pnlCls(closePreview.estimated_pnl);
const closeSheets = p.avail_pos != null && Number(p.avail_pos) > 0 ? p.avail_pos : p.pos;
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 + '">限价平仓</button>' +
'<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>" +
@@ -475,6 +506,8 @@
'<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">' + fmtBidDepth(p.bid_depth) + "</span></div>" +
'<div class="pos-cell"><span class="pos-label">按买盘收回</span><span class="pos-value ' + closePreviewCls + '">' + fmtClosePreview(closePreview) + "</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>" +
@@ -562,26 +595,42 @@
}
async function closePosition(inst, btn) {
const q = await apiJson("/api/options/quote?inst_id=" + encodeURIComponent(inst) + "&mode=sheets&sheets=1");
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 bid = q.bid;
if (bid == null || bid <= 0) {
alert("暂无买一价,请稍后在 OKX App 平仓或等盘口恢复");
const preview = q.close_preview || {};
if (!preview.covered_sheets || preview.covered_sheets <= 0) {
alert("暂无可用买盘深度,请稍后在 OKX App 平仓或等盘口恢复");
return;
}
if (!confirm("限价卖出 @ 买一 " + fmt(bid, 4) + "(每 1 ETH/BTC)?\n合约:" + inst)) return;
const msg = [
"按最多5档买盘拆分限价卖出?",
"合约: " + inst,
"预计收回: " + fmtClosePreview(preview),
preview.estimated_pnl != null ? "预估盈亏: " + fmt(preview.estimated_pnl, 4) + " USDC" : "",
"",
fmtPreviewLevels(preview),
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 }),
body: JSON.stringify({ inst_id: inst, mode: "depth_split", sheets: sheets }),
});
if (r.ok) {
alert("平仓单已提交" + (r.bid != null ? " @ " + fmt(r.bid, 4) : ""));
let okMsg = "平仓单已提交 " + (r.submitted_sheets || 0) + "";
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 || "平仓失败");
}