diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index b9f70b0..c8de900 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1075,7 +1075,7 @@ function refreshOrderDefaults(){ }).catch(()=>{}); } -function paintRealtimePnl(v){ +function paintRealtimePnl(v, unit){ const nodes = document.querySelectorAll('[data-funds-field="realtime-pnl"]'); if(!nodes.length) return; if(v === null || v === undefined || Number.isNaN(Number(v))){ @@ -1086,8 +1086,16 @@ function paintRealtimePnl(v){ return; } const n = Number(v); + const u = String(unit || lastRealtimePnlUnit || "U").toUpperCase() || "U"; + lastRealtimePnlUnit = u; const sign = n > 0 ? "+" : ""; - const text = `${sign}${n.toFixed(2)}U`; + let text; + if (u === "ETH" || u === "BTC") { + const abs = Math.abs(n).toFixed(8).replace(/\.?0+$/, "") || "0"; + text = `${n < 0 ? "-" : sign}${abs}${u}`; + } else { + text = `${sign}${n.toFixed(2)}U`; + } nodes.forEach((pnlEl) => { pnlEl.innerText = text; pnlEl.classList.toggle("pnl-pos", n > 0); @@ -1095,14 +1103,16 @@ function paintRealtimePnl(v){ }); } let lastRealtimePnl = null; -function updateRealtimePnl(v){ +let lastRealtimePnlUnit = "U"; +function updateRealtimePnl(v, unit){ if(v != null && !Number.isNaN(Number(v))){ lastRealtimePnl = Number(v); - paintRealtimePnl(v); + if (unit) lastRealtimePnlUnit = String(unit).toUpperCase(); + paintRealtimePnl(v, lastRealtimePnlUnit); return; } if(lastRealtimePnl != null) return; - paintRealtimePnl(v); + paintRealtimePnl(v, lastRealtimePnlUnit); } function sumOrdersFloatPnl(orders){ if(!orders || !orders.length) return null; @@ -1130,9 +1140,31 @@ function paintRealtimePnlFromSnapshot(data){ const perp = data.order_prices && data.order_prices.length ? sumOrdersFloatPnl(data.order_prices) : null; - const combined = combineRealtimeFloatPnl(perp, data.options_unrealized_pnl); - if(combined !== null || perp !== null || data.options_unrealized_pnl != null){ - paintRealtimePnl(combined); + const opt = data.options_unrealized_pnl; + const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; + const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; + if (coinMode && opt != null && !Number.isNaN(Number(opt))) { + if (perp != null && Math.abs(Number(perp)) >= 0.005) { + const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0"; + const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : ""); + const perpSign = Number(perp) > 0 ? "+" : ""; + const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optSign}${optAbs}${underly}`; + lastRealtimePnl = Number(opt); + lastRealtimePnlUnit = underly; + document.querySelectorAll('[data-funds-field="realtime-pnl"]').forEach((pnlEl) => { + pnlEl.innerText = text; + const n = Number(opt) + Number(perp); + pnlEl.classList.toggle("pnl-pos", n > 0); + pnlEl.classList.toggle("pnl-neg", n < 0); + }); + return; + } + paintRealtimePnl(opt, underly); + return; + } + const combined = combineRealtimeFloatPnl(perp, opt); + if(combined !== null || perp !== null || opt != null){ + paintRealtimePnl(combined, "U"); } } diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 0730f3b..f572fb3 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1556,7 +1556,7 @@ function refreshOrderDefaults(){ }).catch(()=>{}); } -function paintRealtimePnl(v){ +function paintRealtimePnl(v, unit){ const nodes = document.querySelectorAll('[data-funds-field="realtime-pnl"]'); if(!nodes.length) return; if(v === null || v === undefined || Number.isNaN(Number(v))){ @@ -1567,8 +1567,16 @@ function paintRealtimePnl(v){ return; } const n = Number(v); + const u = String(unit || lastRealtimePnlUnit || "U").toUpperCase() || "U"; + lastRealtimePnlUnit = u; const sign = n > 0 ? "+" : ""; - const text = `${sign}${n.toFixed(2)}U`; + let text; + if (u === "ETH" || u === "BTC") { + const abs = Math.abs(n).toFixed(8).replace(/\.?0+$/, "") || "0"; + text = `${n < 0 ? "-" : sign}${abs}${u}`; + } else { + text = `${sign}${n.toFixed(2)}U`; + } nodes.forEach((pnlEl) => { pnlEl.innerText = text; pnlEl.classList.toggle("pnl-pos", n > 0); @@ -1576,14 +1584,16 @@ function paintRealtimePnl(v){ }); } let lastRealtimePnl = null; -function updateRealtimePnl(v){ +let lastRealtimePnlUnit = "U"; +function updateRealtimePnl(v, unit){ if(v != null && !Number.isNaN(Number(v))){ lastRealtimePnl = Number(v); - paintRealtimePnl(v); + if (unit) lastRealtimePnlUnit = String(unit).toUpperCase(); + paintRealtimePnl(v, lastRealtimePnlUnit); return; } if(lastRealtimePnl != null) return; - paintRealtimePnl(v); + paintRealtimePnl(v, lastRealtimePnlUnit); } function sumOrdersFloatPnl(orders){ if(!orders || !orders.length) return null; @@ -1611,9 +1621,32 @@ function paintRealtimePnlFromSnapshot(data){ const perp = data.order_prices && data.order_prices.length ? sumOrdersFloatPnl(data.order_prices) : null; - const combined = combineRealtimeFloatPnl(perp, data.options_unrealized_pnl); - if(combined !== null || perp !== null || data.options_unrealized_pnl != null){ - paintRealtimePnl(combined); + const opt = data.options_unrealized_pnl; + const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; + const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; + if (coinMode && opt != null && !Number.isNaN(Number(opt))) { + // 币本位期权盈亏单位为币,勿与永续 U 混加成「xxU」 + if (perp != null && Math.abs(Number(perp)) >= 0.005) { + const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0"; + const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : ""); + const perpSign = Number(perp) > 0 ? "+" : ""; + const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optSign}${optAbs}${underly}`; + lastRealtimePnl = Number(opt); + lastRealtimePnlUnit = underly; + document.querySelectorAll('[data-funds-field="realtime-pnl"]').forEach((pnlEl) => { + pnlEl.innerText = text; + const n = Number(opt) + Number(perp); + pnlEl.classList.toggle("pnl-pos", n > 0); + pnlEl.classList.toggle("pnl-neg", n < 0); + }); + return; + } + paintRealtimePnl(opt, underly); + return; + } + const combined = combineRealtimeFloatPnl(perp, opt); + if(combined !== null || perp !== null || opt != null){ + paintRealtimePnl(combined, "U"); } } diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 491f8f1..3ba78cb 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -746,6 +746,53 @@ return Number(n).toLocaleString(undefined, { maximumFractionDigits: d }); } + /** 币本位权利金/盈亏:ETH/BTC 保留足量小数;USDC 两位. */ + function optPremiumCcyOf(p, fallbackUnderly) { + const ccy = String((p && p.premium_ccy) || "").trim().toUpperCase(); + if (ccy) return ccy; + const mode = String((p && p.margin_mode) || "").toLowerCase(); + const inst = String((p && p.inst_id) || ""); + if (mode === "coin" || (inst.indexOf("-USD-") >= 0 && inst.indexOf("_UM") < 0)) { + return (inst.split("-")[0] || fallbackUnderly || "ETH").toUpperCase() || "ETH"; + } + return "USDC"; + } + + function optionsPanelCcy(optMeta) { + if (!optMeta || typeof optMeta !== "object") return "USDC"; + const mode = String(optMeta.options_margin_mode || optMeta.margin_mode || "").toLowerCase(); + const underly = String(optMeta.options_underly || "ETH").toUpperCase() || "ETH"; + if (mode === "coin") return underly; + const pos = Array.isArray(optMeta.positions) ? optMeta.positions : []; + for (let i = 0; i < pos.length; i++) { + const c = optPremiumCcyOf(pos[i], underly); + if (c && c !== "USDC") return c; + } + return "USDC"; + } + + function fmtOptPnlAmt(v, ccy) { + if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; + const n = Number(v); + const unit = String(ccy || "USDC").toUpperCase(); + if (unit === "ETH" || unit === "BTC") { + const s = n.toFixed(8).replace(/\.?0+$/, ""); + return s || "0"; + } + return fmt(n, 2); + } + + function fmtOptPnlText(v, ccy) { + if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; + const n = Number(v); + const unit = String(ccy || "USDC").toUpperCase(); + const sign = n > 0 ? "+" : ""; + if (unit === "ETH" || unit === "BTC") { + return `${sign}${fmtOptPnlAmt(n, unit)} ${unit}`; + } + return `${sign}${fmt(n, 2)}U`; + } + /** 交易所持仓开仓价(三所子代理 entry_price) */ function positionEntryPrice(pos) { if (!pos) return null; @@ -3900,10 +3947,14 @@ ? `${fmt(fundUsdt, 2)} U` : "—"; const tradeTxt = formatCoinTradingLabel(usdt, eth, btc); + const pnlCcy = optionsPanelCcy(optMeta); + const pnlTxt = upnl == null || Number.isNaN(Number(upnl)) + ? "—" + : `${fmtOptPnlText(upnl, pnlCcy)}`; return `