fix(hub): recalc market floating PnL from live chart mark
Use plan margin x leverage x price change with latest K-line close instead of stale board snapshot floating_pnl. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1380,6 +1380,14 @@
|
||||
const planMargin =
|
||||
trendPlan && trendPlan.plan_margin_capital != null
|
||||
? num(trendPlan.plan_margin_capital)
|
||||
: mo.margin_capital != null
|
||||
? num(mo.margin_capital)
|
||||
: null;
|
||||
const leverage =
|
||||
trendPlan && trendPlan.leverage != null
|
||||
? num(trendPlan.leverage)
|
||||
: mo.leverage != null
|
||||
? num(mo.leverage)
|
||||
: null;
|
||||
return {
|
||||
exchange_id: exchangeId || null,
|
||||
@@ -1394,6 +1402,7 @@
|
||||
unrealized_pnl: upnl != null ? Number(upnl) : null,
|
||||
notional_usdt: num(pos.notional_usdt),
|
||||
plan_margin: planMargin,
|
||||
leverage: leverage,
|
||||
orders: orders,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -936,7 +936,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function findTrendPlanMargin(row, sym, side) {
|
||||
function findTrendPlan(row, sym, side) {
|
||||
const hm = row.hub_monitor;
|
||||
if (!hm || !Array.isArray(hm.trends)) return null;
|
||||
for (let i = 0; i < hm.trends.length; i++) {
|
||||
@@ -944,12 +944,66 @@
|
||||
const ts = normalizeMarketSymbol(t.exchange_symbol || t.symbol || "");
|
||||
if (ts !== sym) continue;
|
||||
if ((t.direction || "").toLowerCase() !== side) continue;
|
||||
const m = t.plan_margin_capital;
|
||||
if (m != null && Number.isFinite(Number(m)) && Number(m) > 0) return Number(m);
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyTrendPlanFields(row, sym, side) {
|
||||
if (!posContext) return;
|
||||
const t = findTrendPlan(row, sym, side);
|
||||
if (!t) return;
|
||||
const m = t.plan_margin_capital;
|
||||
if (m != null && Number.isFinite(Number(m)) && Number(m) > 0) {
|
||||
posContext.plan_margin = Number(m);
|
||||
}
|
||||
const lev = t.leverage;
|
||||
if (lev != null && Number.isFinite(Number(lev)) && Number(lev) > 0) {
|
||||
posContext.leverage = Number(lev);
|
||||
}
|
||||
}
|
||||
|
||||
/** 与四实例 calc_pnl 一致:保证金 × 杠杆 × 价格涨跌幅 */
|
||||
function calcPlanFloatingPnl(ctx, markPx) {
|
||||
if (!ctx || markPx == null || !Number.isFinite(Number(markPx))) return null;
|
||||
const entry = Number(ctx.entry);
|
||||
const margin = Number(ctx.plan_margin);
|
||||
const lev = Number(ctx.leverage);
|
||||
if (!Number.isFinite(entry) || entry <= 0) return null;
|
||||
if (!Number.isFinite(margin) || margin <= 0) return null;
|
||||
if (!Number.isFinite(lev) || lev <= 0) return null;
|
||||
const mark = Number(markPx);
|
||||
const side = (ctx.side || "long").toLowerCase();
|
||||
const ratio =
|
||||
side === "short" ? (entry - mark) / entry : (mark - entry) / entry;
|
||||
return Math.round(margin * lev * ratio * 100) / 100;
|
||||
}
|
||||
|
||||
function latestChartMarkPrice() {
|
||||
if (!lastCandles || !lastCandles.length) return null;
|
||||
const bar = lastCandles[lastCandles.length - 1];
|
||||
const c = bar && bar.close != null ? Number(bar.close) : null;
|
||||
return c != null && Number.isFinite(c) && c > 0 ? c : null;
|
||||
}
|
||||
|
||||
function updateLivePosPnl(markOverride) {
|
||||
if (!posContext) return false;
|
||||
const mark =
|
||||
markOverride != null && Number.isFinite(Number(markOverride))
|
||||
? Number(markOverride)
|
||||
: latestChartMarkPrice() ||
|
||||
(posContext.mark_price != null && Number.isFinite(Number(posContext.mark_price))
|
||||
? Number(posContext.mark_price)
|
||||
: null);
|
||||
if (mark == null) return false;
|
||||
const live = calcPlanFloatingPnl(posContext, mark);
|
||||
if (live == null) return false;
|
||||
posContext.unrealized_pnl = live;
|
||||
posContext.mark_price = mark;
|
||||
renderPosPnlDisplay(posContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
function syncPosTpslFromAgentPosition(p) {
|
||||
if (!posContext || !p) return;
|
||||
const et = p.exchange_tpsl;
|
||||
@@ -979,13 +1033,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function paintPosPnl(ctx) {
|
||||
function renderPosPnlDisplay(ctx) {
|
||||
if (!elPosPnl) return;
|
||||
const p = formatPosPnlText(ctx);
|
||||
elPosPnl.textContent = p.text;
|
||||
elPosPnl.className = "market-pos-pnl " + p.cls;
|
||||
}
|
||||
|
||||
function paintPosPnl(ctx) {
|
||||
if (ctx === posContext && updateLivePosPnl()) return;
|
||||
renderPosPnlDisplay(ctx);
|
||||
}
|
||||
|
||||
function stopPosPnlPoll() {
|
||||
if (posPnlTimer) {
|
||||
clearInterval(posPnlTimer);
|
||||
@@ -997,7 +1056,9 @@
|
||||
stopPosPnlPoll();
|
||||
if (!posContext || !posContext.exchange_id) return;
|
||||
refreshPosPnlFromBoard();
|
||||
posPnlTimer = setInterval(refreshPosPnlFromBoard, 5000);
|
||||
posPnlTimer = setInterval(function () {
|
||||
if (!updateLivePosPnl()) refreshPosPnlFromBoard();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async function refreshPosPnlFromBoard() {
|
||||
@@ -1013,20 +1074,15 @@
|
||||
const row = rows[i];
|
||||
const ex = row.exchange || {};
|
||||
if (ex.id !== posContext.exchange_id) continue;
|
||||
const planMargin = findTrendPlanMargin(row, sym, side);
|
||||
if (planMargin != null) posContext.plan_margin = planMargin;
|
||||
applyTrendPlanFields(row, sym, side);
|
||||
const positions = (row.agent && row.agent.positions) || [];
|
||||
for (let j = 0; j < positions.length; j++) {
|
||||
const p = positions[j];
|
||||
if ((p.side || "").toLowerCase() !== side) continue;
|
||||
if (normalizeMarketSymbol(p.symbol || "") !== sym) continue;
|
||||
let upnl =
|
||||
p.unrealized_pnl != null && Number.isFinite(Number(p.unrealized_pnl))
|
||||
? Number(p.unrealized_pnl)
|
||||
: null;
|
||||
if (upnl == null) upnl = findTrendFloatingPnl(row, sym, side);
|
||||
if (upnl == null) return;
|
||||
posContext.unrealized_pnl = upnl;
|
||||
if (p.mark_price != null && Number.isFinite(Number(p.mark_price))) {
|
||||
posContext.mark_price = Number(p.mark_price);
|
||||
}
|
||||
if (p.notional_usdt != null && Number.isFinite(Number(p.notional_usdt))) {
|
||||
posContext.notional_usdt = Number(p.notional_usdt);
|
||||
}
|
||||
@@ -1037,21 +1093,33 @@
|
||||
if (elPosTp && posContext.take_profit != null && !posContext.tp_monitored) {
|
||||
elPosTp.textContent = fmtPrice(posContext.take_profit);
|
||||
}
|
||||
paintPosPnl(posContext);
|
||||
if (!updateLivePosPnl(p.mark_price)) {
|
||||
let upnl =
|
||||
p.unrealized_pnl != null && Number.isFinite(Number(p.unrealized_pnl))
|
||||
? Number(p.unrealized_pnl)
|
||||
: findTrendFloatingPnl(row, sym, side);
|
||||
if (upnl != null) {
|
||||
posContext.unrealized_pnl = upnl;
|
||||
renderPosPnlDisplay(posContext);
|
||||
}
|
||||
}
|
||||
updatePositionLines();
|
||||
try {
|
||||
sessionStorage.setItem(HUB_MARKET_POS_CTX_KEY, JSON.stringify(posContext));
|
||||
} catch (_) {}
|
||||
return;
|
||||
}
|
||||
applyTrendPlanFields(row, sym, side);
|
||||
if (!updateLivePosPnl()) {
|
||||
const trendUpnl = findTrendFloatingPnl(row, sym, side);
|
||||
if (trendUpnl != null) {
|
||||
posContext.unrealized_pnl = trendUpnl;
|
||||
paintPosPnl(posContext);
|
||||
renderPosPnlDisplay(posContext);
|
||||
}
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(HUB_MARKET_POS_CTX_KEY, JSON.stringify(posContext));
|
||||
} catch (_) {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -1956,6 +2024,8 @@
|
||||
localSeriesVersion = sVer;
|
||||
localChartVersion = ver;
|
||||
loadChart(false, { autoTick: true });
|
||||
} else if (posContext) {
|
||||
updateLivePosPnl();
|
||||
} else if (ver !== localChartVersion) {
|
||||
localChartVersion = ver;
|
||||
}
|
||||
@@ -2099,7 +2169,10 @@
|
||||
applyPriceAutoScale();
|
||||
updateVisibleRangeMarkers();
|
||||
syncPosContextForView(exKey, sym);
|
||||
if (posContext) refreshPosPnlFromBoard();
|
||||
if (posContext) {
|
||||
updateLivePosPnl();
|
||||
refreshPosPnlFromBoard();
|
||||
}
|
||||
showLatestOhlcv();
|
||||
try {
|
||||
updateIndicators();
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart.js?v=20260604-market-pnl-live"></script>
|
||||
<script src="/assets/app.js?v=20260604-market-pnl-plan"></script>
|
||||
<script src="/assets/chart.js?v=20260604-market-pnl-calc"></script>
|
||||
<script src="/assets/app.js?v=20260604-market-pnl-calc"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user