Show editable exit on live cards; price perp PnL via close-side book VWAP.
Align monitor profit-exit with the same book VWAP so card and auto-exit stay consistent. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -929,19 +929,46 @@
|
||||
return m + "分";
|
||||
}
|
||||
|
||||
function formatExitLine(ex) {
|
||||
const e = ex || {};
|
||||
const mode = String(e.mode || "").toLowerCase() === "target" ? "target" : "profit";
|
||||
if (mode === "profit") {
|
||||
return (
|
||||
"出场 · 盈利金额 <strong>" +
|
||||
(e.profit_target_usdt != null ? fmt(e.profit_target_usdt, 2) + " U" : "—") +
|
||||
"</strong>"
|
||||
);
|
||||
}
|
||||
return (
|
||||
"出场 · 目标位 期权±<strong>" +
|
||||
(e.option_target_points != null ? fmt(e.option_target_points, 0) : "—") +
|
||||
"</strong> / 永续±<strong>" +
|
||||
(e.perp_target_points != null ? fmt(e.perp_target_points, 0) : "—") +
|
||||
"</strong>"
|
||||
);
|
||||
}
|
||||
|
||||
function renderLivePosCard(card) {
|
||||
const opt = card.option || {};
|
||||
const perp = card.perp || {};
|
||||
const combo = card.combo || {};
|
||||
const exit = card.exit || {};
|
||||
const optType = String(opt.opt_type || "").toUpperCase() === "P" ? "Put" : "Call";
|
||||
const perpSide = String(perp.side || "").toLowerCase() === "short" ? "空" : "多";
|
||||
const comboCls = pnlClsNum(combo.net_usdt);
|
||||
const liqWarn = opt.liq_ok === false;
|
||||
const perpLiqWarn = perp.liq_ok === false;
|
||||
const unflat = !!card.unflat || String(card.status || "") === "closed";
|
||||
const bidTxt =
|
||||
opt.bid != null
|
||||
? fmt(opt.bid, 4) + (opt.bid_sz != null ? "/" + fmt(opt.bid_sz, 0) : "")
|
||||
: "—";
|
||||
const bookLab = String(perp.book_side || "") === "asks" ? "卖一" : "买一";
|
||||
const perpTopTxt =
|
||||
perp.top_px != null
|
||||
? fmt(perp.top_px, 2) + (perp.top_sz != null ? "/" + fmt(perp.top_sz, 0) : "")
|
||||
: "—";
|
||||
const canEditExit = !unflat;
|
||||
return (
|
||||
'<article class="hp-live-card' +
|
||||
(unflat ? " hp-live-card--unflat" : "") +
|
||||
@@ -969,6 +996,24 @@
|
||||
fmtDuration(card.remain_sec) +
|
||||
"</strong></span>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-exit-row">' +
|
||||
"<span>" +
|
||||
formatExitLine(exit) +
|
||||
"</span>" +
|
||||
(canEditExit
|
||||
? ' <button type="button" class="btn-secondary hp-btn-exit" data-id="' +
|
||||
(card.plan_id || "") +
|
||||
'" data-mode="' +
|
||||
String(exit.mode || "profit") +
|
||||
'" data-profit="' +
|
||||
(exit.profit_target_usdt != null ? exit.profit_target_usdt : "") +
|
||||
'" data-opt-pts="' +
|
||||
(exit.option_target_points != null ? exit.option_target_points : "") +
|
||||
'" data-perp-pts="' +
|
||||
(exit.perp_target_points != null ? exit.perp_target_points : "") +
|
||||
'">改出场</button>'
|
||||
: "") +
|
||||
"</div>" +
|
||||
'<div class="hp-live-legs">' +
|
||||
'<div class="hp-live-leg">' +
|
||||
'<div class="hp-live-leg-title">期权 · ' +
|
||||
@@ -1025,9 +1070,22 @@
|
||||
"<span>开仓 <strong>" +
|
||||
fmt(perp.avg_open, 2) +
|
||||
"</strong></span>" +
|
||||
"<span>标记 <strong>" +
|
||||
'<span class="muted" title="仅参考,净利按盘口均价">标记 <strong>' +
|
||||
fmt(perp.mark_px, 2) +
|
||||
"</strong></span>" +
|
||||
'<span class="' +
|
||||
(perpLiqWarn ? "hp-liq-warn" : "") +
|
||||
'" title="' +
|
||||
String(perp.liq_msg || "").replace(/"/g, """) +
|
||||
'">' +
|
||||
bookLab +
|
||||
"/流动性 <strong>" +
|
||||
perpTopTxt +
|
||||
(perpLiqWarn ? " · 不足" : "") +
|
||||
"</strong></span>" +
|
||||
'<span title="平仓方向扫盘口估成交均价">估均价 <strong>' +
|
||||
(perp.vwap != null ? fmt(perp.vwap, 2) : "—") +
|
||||
"</strong></span>" +
|
||||
"<span>保证金 <strong>" +
|
||||
(perp.margin != null ? fmt(perp.margin, 2) + " U" : "—") +
|
||||
"</strong></span>" +
|
||||
@@ -1039,7 +1097,7 @@
|
||||
"</div></div>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-combo">' +
|
||||
'<span class="muted">组合净盈亏(扣开平手续费)</span>' +
|
||||
'<span class="muted">组合净盈亏(扣开平手续费·永续盘口均价)</span>' +
|
||||
'<strong class="' +
|
||||
comboCls +
|
||||
'">' +
|
||||
@@ -1057,6 +1115,89 @@
|
||||
);
|
||||
}
|
||||
|
||||
function setExitModalMode(mode) {
|
||||
const m = mode === "target" ? "target" : "profit";
|
||||
document.querySelectorAll(".hp-exit-mode-btn").forEach(function (b) {
|
||||
const on = b.getAttribute("data-exit") === m;
|
||||
b.classList.toggle("is-selected", on);
|
||||
});
|
||||
const profitBox = $("hp-exit-fields-profit");
|
||||
const targetBox = $("hp-exit-fields-target");
|
||||
if (profitBox) {
|
||||
const show = m === "profit";
|
||||
profitBox.classList.toggle("hidden", !show);
|
||||
if (show) profitBox.removeAttribute("hidden");
|
||||
else profitBox.setAttribute("hidden", "hidden");
|
||||
}
|
||||
if (targetBox) {
|
||||
const show = m === "target";
|
||||
targetBox.classList.toggle("hidden", !show);
|
||||
if (show) targetBox.removeAttribute("hidden");
|
||||
else targetBox.setAttribute("hidden", "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function openExitModal(btn) {
|
||||
const modal = $("hp-exit-modal");
|
||||
if (!modal || !btn) return;
|
||||
const id = Number(btn.getAttribute("data-id") || 0);
|
||||
if (!(id > 0)) return;
|
||||
if ($("hp-exit-plan-id")) $("hp-exit-plan-id").value = String(id);
|
||||
const mode = btn.getAttribute("data-mode") === "target" ? "target" : "profit";
|
||||
setExitModalMode(mode);
|
||||
if ($("hp-exit-profit")) $("hp-exit-profit").value = btn.getAttribute("data-profit") || "";
|
||||
if ($("hp-exit-opt-pts")) $("hp-exit-opt-pts").value = btn.getAttribute("data-opt-pts") || "";
|
||||
if ($("hp-exit-perp-pts")) $("hp-exit-perp-pts").value = btn.getAttribute("data-perp-pts") || "";
|
||||
if ($("hp-exit-title")) $("hp-exit-title").textContent = "改出场 #" + id;
|
||||
if ($("hp-exit-err")) {
|
||||
$("hp-exit-err").textContent = "";
|
||||
$("hp-exit-err").setAttribute("hidden", "");
|
||||
}
|
||||
modal.hidden = false;
|
||||
}
|
||||
|
||||
function closeExitModal() {
|
||||
const modal = $("hp-exit-modal");
|
||||
if (modal) modal.hidden = true;
|
||||
}
|
||||
|
||||
async function saveExitModal() {
|
||||
const id = Number(($("hp-exit-plan-id") && $("hp-exit-plan-id").value) || 0);
|
||||
const err = $("hp-exit-err");
|
||||
if (!(id > 0)) return;
|
||||
const modeBtn = document.querySelector(".hp-exit-mode-btn.is-selected");
|
||||
const mode = modeBtn && modeBtn.getAttribute("data-exit") === "target" ? "target" : "profit";
|
||||
const body =
|
||||
mode === "profit"
|
||||
? {
|
||||
exit_mode: "profit",
|
||||
profit_target_usdt: Number(($("hp-exit-profit") && $("hp-exit-profit").value) || 0),
|
||||
}
|
||||
: {
|
||||
exit_mode: "target",
|
||||
option_target_points: Number(($("hp-exit-opt-pts") && $("hp-exit-opt-pts").value) || 0),
|
||||
perp_target_points: Number(($("hp-exit-perp-pts") && $("hp-exit-perp-pts").value) || 0),
|
||||
};
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/" + id + "/exit", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
closeExitModal();
|
||||
alert(d.msg || "出场已更新");
|
||||
void loadLivePositions();
|
||||
void loadActivePlans();
|
||||
} catch (e) {
|
||||
if (err) {
|
||||
err.textContent = e.message || String(e);
|
||||
err.removeAttribute("hidden");
|
||||
} else {
|
||||
alert(e.message || String(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadLivePositions() {
|
||||
const box = $("hp-live-pos-list");
|
||||
if (!box) return;
|
||||
@@ -1078,6 +1219,11 @@
|
||||
void endActivePlan(Number(btn.getAttribute("data-id")));
|
||||
});
|
||||
});
|
||||
box.querySelectorAll(".hp-btn-exit").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
openExitModal(btn);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
box.innerHTML = '<p class="err">' + (e.message || e) + "</p>";
|
||||
}
|
||||
@@ -2823,6 +2969,22 @@
|
||||
if (ev.target === modal) closeModal();
|
||||
});
|
||||
}
|
||||
document.querySelectorAll(".hp-exit-mode-btn").forEach(function (b) {
|
||||
b.addEventListener("click", function () {
|
||||
setExitModalMode(b.getAttribute("data-exit") === "target" ? "target" : "profit");
|
||||
});
|
||||
});
|
||||
if ($("hp-exit-close")) $("hp-exit-close").addEventListener("click", closeExitModal);
|
||||
if ($("hp-exit-cancel")) $("hp-exit-cancel").addEventListener("click", closeExitModal);
|
||||
if ($("hp-exit-save")) $("hp-exit-save").addEventListener("click", function () {
|
||||
void saveExitModal();
|
||||
});
|
||||
const exitModal = $("hp-exit-modal");
|
||||
if (exitModal) {
|
||||
exitModal.addEventListener("click", function (ev) {
|
||||
if (ev.target === exitModal) closeExitModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
|
||||
@@ -4202,6 +4202,46 @@ html[data-theme="light"] .opt-be-dist-down {
|
||||
color: #e8ecf4;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-exit-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
margin: 0 0 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(110, 140, 255, 0.08);
|
||||
border: 1px solid rgba(110, 140, 255, 0.18);
|
||||
font-size: 0.78rem;
|
||||
color: #c5d0ee;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-exit-row strong {
|
||||
color: #e8ecf4;
|
||||
font-weight: 650;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-live-exit-row .hp-btn-exit {
|
||||
padding: 2px 8px;
|
||||
font-size: 0.72rem;
|
||||
min-height: 26px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-exit-modal .hp-exit-mode-seg {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-exit-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-exit-fields label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-exit-fields input {
|
||||
max-width: 220px;
|
||||
}
|
||||
.hedge-plan-page-wrap .hp-liq-warn,
|
||||
.hedge-plan-page-wrap .hp-liq-warn strong {
|
||||
color: #f5c542 !important;
|
||||
|
||||
Reference in New Issue
Block a user