Align options realtime PnL with bid-net and show totals in stats.

Header float PnL now uses bid recycle minus premium like position cards. Stats adds realized/open/total net PnL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-15 22:00:45 +08:00
parent 60f3437c73
commit ee7be3e7f3
14 changed files with 198 additions and 19 deletions
+18
View File
@@ -3929,6 +3929,24 @@ html[data-theme="light"] .options-estimate-row {
.options-pos-stats-card {
flex-shrink: 0;
}
.options-stats-pnl-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-bottom: 14px;
}
.options-stats-pnl-summary .options-stat-item {
padding: 10px 12px;
border-radius: 8px;
background: rgba(127, 127, 127, 0.12);
}
.options-stats-pnl-summary .opt-stats-net-item .v {
font-size: 1.15em;
font-weight: 650;
}
html[data-theme="light"] .options-stats-pnl-summary .options-stat-item {
background: rgba(0, 0, 0, 0.04);
}
.options-stats-panel {
display: flex;
flex-direction: column;
+22
View File
@@ -1476,6 +1476,19 @@
}
}
function paintPnlStat(el, value) {
if (!el) return;
if (value == null || value === "" || Number.isNaN(Number(value))) {
el.textContent = "—";
el.classList.remove("pos-pnl-profit", "pos-pnl-loss");
return;
}
const n = Number(value);
el.textContent = (n > 0 ? "+" : "") + fmt(n, 2) + " USDC";
el.classList.toggle("pos-pnl-profit", n > 0);
el.classList.toggle("pos-pnl-loss", n < 0);
}
async function refreshStats() {
const d = await apiJson("/api/options/stats");
const winEl = document.getElementById("opt-stats-winrate");
@@ -1487,14 +1500,23 @@
const winHoldEl = document.getElementById("opt-stats-win-hold");
const lossHoldEl = document.getElementById("opt-stats-loss-hold");
const openHoldEl = document.getElementById("opt-stats-open-hold");
const totalPnlEl = document.getElementById("opt-stats-total-pnl");
const netRealizedEl = document.getElementById("opt-stats-net-realized");
const openFloatEl = document.getElementById("opt-stats-open-float");
const statEls = [winEl, plrEl, closedEl, profitEl, lossEl, avgHoldEl, winHoldEl, lossHoldEl, openHoldEl];
if (!d.ok) {
statEls.forEach(function (el) {
if (el) el.textContent = "—";
});
paintPnlStat(totalPnlEl, null);
paintPnlStat(netRealizedEl, null);
paintPnlStat(openFloatEl, null);
paintStatsCharts(null);
return;
}
paintPnlStat(totalPnlEl, d.total_pnl);
paintPnlStat(netRealizedEl, d.net_realized_pnl);
paintPnlStat(openFloatEl, d.open_float_pnl);
if (winEl) winEl.textContent = d.total_closed ? d.win_rate + "%" : "0%";
if (plrEl) {
plrEl.textContent = d.profit_loss_ratio != null ? String(d.profit_loss_ratio) : "—";