Split live positions from hedge plans; show option+perp net PnL after fees.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -862,7 +862,8 @@
|
||||
}
|
||||
|
||||
function setHpPosTab(tab) {
|
||||
const next = tab === "pending" || tab === "history" || tab === "stats" ? tab : "live";
|
||||
const allowed = { live: 1, plans: 1, pending: 1, history: 1, stats: 1 };
|
||||
const next = allowed[tab] ? tab : "live";
|
||||
state.posTab = next;
|
||||
document.querySelectorAll(".hp-pos-tab").forEach(function (btn) {
|
||||
const on = btn.getAttribute("data-hp-pos-tab") === next;
|
||||
@@ -875,17 +876,146 @@
|
||||
if (on) pane.removeAttribute("hidden");
|
||||
else pane.setAttribute("hidden", "");
|
||||
});
|
||||
if (next === "live" || next === "pending") void loadActivePlans();
|
||||
if (next === "live") void loadLivePositions();
|
||||
else if (next === "plans" || next === "pending") void loadActivePlans();
|
||||
else if (next === "history") void loadHistory();
|
||||
else if (next === "stats") void loadStats();
|
||||
}
|
||||
|
||||
function refreshHpPosPanel() {
|
||||
void loadLivePositions();
|
||||
void loadActivePlans();
|
||||
void loadHistory();
|
||||
if (state.posTab === "stats") void loadStats();
|
||||
}
|
||||
|
||||
function pnlClsNum(v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "";
|
||||
const n = Number(v);
|
||||
if (n > 0) return "hp-pnl-pos";
|
||||
if (n < 0) return "hp-pnl-neg";
|
||||
return "";
|
||||
}
|
||||
|
||||
function fmtSigned(v, d) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const s = fmt(n, d == null ? 2 : d);
|
||||
return n > 0 ? "+" + s : s;
|
||||
}
|
||||
|
||||
function renderLivePosCard(card) {
|
||||
const opt = card.option || {};
|
||||
const perp = card.perp || {};
|
||||
const combo = card.combo || {};
|
||||
const optType = String(opt.opt_type || "").toUpperCase() === "P" ? "Put" : "Call";
|
||||
const perpSide = String(perp.side || "").toLowerCase() === "short" ? "空" : "多";
|
||||
const comboCls = pnlClsNum(combo.net_usdt);
|
||||
return (
|
||||
'<article class="hp-live-card" data-plan-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">' +
|
||||
'<div class="hp-live-card-head">' +
|
||||
"<strong>#" +
|
||||
(card.plan_id || "—") +
|
||||
" · " +
|
||||
(card.underlying || "") +
|
||||
'</strong><span class="muted">' +
|
||||
(card.opened_at || "") +
|
||||
"</span></div>" +
|
||||
'<div class="hp-live-legs">' +
|
||||
'<div class="hp-live-leg">' +
|
||||
'<div class="hp-live-leg-title">期权 · ' +
|
||||
optType +
|
||||
"</div>" +
|
||||
"<code>" +
|
||||
(opt.inst_id || "—") +
|
||||
"</code>" +
|
||||
'<div class="hp-live-leg-grid">' +
|
||||
"<span>张数 <strong>" +
|
||||
fmt(opt.sheets, 0) +
|
||||
"</strong></span>" +
|
||||
"<span>开仓 <strong>" +
|
||||
fmt(opt.avg_open, 4) +
|
||||
"</strong></span>" +
|
||||
"<span>标记 <strong>" +
|
||||
fmt(opt.mark_px, 4) +
|
||||
"</strong></span>" +
|
||||
'<span>净利 <strong class="' +
|
||||
pnlClsNum(opt.net_usdt) +
|
||||
'">' +
|
||||
fmtSigned(opt.net_usdt, 2) +
|
||||
" U</strong></span>" +
|
||||
"</div></div>" +
|
||||
'<div class="hp-live-leg">' +
|
||||
'<div class="hp-live-leg-title">永续 · ' +
|
||||
perpSide +
|
||||
"</div>" +
|
||||
"<code>" +
|
||||
(perp.symbol || "—") +
|
||||
"</code>" +
|
||||
'<div class="hp-live-leg-grid">' +
|
||||
"<span>张数 <strong>" +
|
||||
fmt(perp.contracts, 4) +
|
||||
"</strong></span>" +
|
||||
"<span>开仓 <strong>" +
|
||||
fmt(perp.avg_open, 2) +
|
||||
"</strong></span>" +
|
||||
"<span>标记 <strong>" +
|
||||
fmt(perp.mark_px, 2) +
|
||||
"</strong></span>" +
|
||||
'<span>净利 <strong class="' +
|
||||
pnlClsNum(perp.net_usdt) +
|
||||
'">' +
|
||||
fmtSigned(perp.net_usdt, 2) +
|
||||
" U</strong></span>" +
|
||||
"</div></div>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-combo">' +
|
||||
'<span class="muted">组合净盈亏(扣开平手续费)</span>' +
|
||||
'<strong class="' +
|
||||
comboCls +
|
||||
'">' +
|
||||
fmtSigned(combo.net_usdt, 2) +
|
||||
" U</strong>" +
|
||||
"</div>" +
|
||||
'<div class="hp-live-actions">' +
|
||||
'<button type="button" class="btn-secondary hp-btn-detail" data-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">成交细节</button> ' +
|
||||
'<button type="button" class="btn-secondary hp-btn-end" data-id="' +
|
||||
(card.plan_id || "") +
|
||||
'">结束计划</button>' +
|
||||
"</div></article>"
|
||||
);
|
||||
}
|
||||
|
||||
async function loadLivePositions() {
|
||||
const box = $("hp-live-pos-list");
|
||||
if (!box) return;
|
||||
try {
|
||||
const d = await apiJson("/api/hedge-plan/live-positions");
|
||||
const cards = d.cards || [];
|
||||
if (!cards.length) {
|
||||
box.innerHTML = '<p class="muted">暂无期权/永续对冲持仓</p>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = cards.map(renderLivePosCard).join("");
|
||||
box.querySelectorAll(".hp-btn-detail").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
void showPlanDetail(Number(btn.getAttribute("data-id")));
|
||||
});
|
||||
});
|
||||
box.querySelectorAll(".hp-btn-end").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
void endActivePlan(Number(btn.getAttribute("data-id")));
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
box.innerHTML = '<p class="err">' + (e.message || e) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
/** 手动选约:不自动代选;仅在已选失效(换 Call/Put 或筛掉)时清空. */
|
||||
function autoMatchPoOption(force) {
|
||||
if (!isOptionPrimary()) return;
|
||||
@@ -3379,8 +3509,8 @@
|
||||
if (state.tab === "perp_options" && isOptionPrimary()) void refreshPoStrategyStatus();
|
||||
}, 15000);
|
||||
setInterval(function () {
|
||||
if (state.tab === "perp_options" && (state.posTab === "live" || state.posTab === "pending")) {
|
||||
void loadActivePlans();
|
||||
}
|
||||
}, 20000);
|
||||
if (state.tab !== "perp_options") return;
|
||||
if (state.posTab === "live") void loadLivePositions();
|
||||
else if (state.posTab === "plans" || state.posTab === "pending") void loadActivePlans();
|
||||
}, 8000);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user