diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 4670b48..cc16f95 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -1380,7 +1380,15 @@ const planMargin = trendPlan && trendPlan.plan_margin_capital != null ? num(trendPlan.plan_margin_capital) - : null; + : 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, symbol: (pos.symbol || "").trim(), @@ -1394,6 +1402,7 @@ unrealized_pnl: upnl != null ? Number(upnl) : null, notional_usdt: num(pos.notional_usdt), plan_margin: planMargin, + leverage: leverage, orders: orders, }; } diff --git a/manual_trading_hub/static/chart.js b/manual_trading_hub/static/chart.js index 526ebe0..d09efda 100644 --- a/manual_trading_hub/static/chart.js +++ b/manual_trading_hub/static/chart.js @@ -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; } - const trendUpnl = findTrendFloatingPnl(row, sym, side); - if (trendUpnl != null) { - posContext.unrealized_pnl = trendUpnl; - paintPosPnl(posContext); - try { - sessionStorage.setItem(HUB_MARKET_POS_CTX_KEY, JSON.stringify(posContext)); - } catch (_) {} + applyTrendPlanFields(row, sym, side); + if (!updateLivePosPnl()) { + const trendUpnl = findTrendFloatingPnl(row, sym, side); + if (trendUpnl != null) { + posContext.unrealized_pnl = trendUpnl; + 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(); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 62a550d..15ef894 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -249,7 +249,7 @@
- - + +