diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 831b079..d565ac5 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -7173,7 +7173,9 @@ def api_account_snapshot(): from lib.exchange.okx_options_lib import fetch_options_unrealized_pnl_usdc options_unrealized_pnl = fetch_options_unrealized_pnl_usdc(exchange_options) - unrealized_pnl = merge_unrealized_pnl_components(unrealized_pnl, options_unrealized_pnl) + # 币本位期权盈亏单位为币,禁止与永续 U 混加(且 merge 只保留 2 位会把 0.0019 抹成 0) + if options_margin_mode != "coin": + unrealized_pnl = merge_unrealized_pnl_components(unrealized_pnl, options_unrealized_pnl) except Exception: options_unrealized_pnl = None _show_perp_funds = show_perp_funds_enabled(exchange_key="okx") or (options_margin_mode == "coin") diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index fe1b112..899e8c0 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1157,32 +1157,38 @@ function paintRealtimePnlFromSnapshot(data){ const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null; - 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 ? "+" : ""; - let optPart = `${optSign}${optAbs} ${underly}`; - if (Number.isFinite(spotPx) && spotPx > 0) { - const uu = Number(opt) * spotPx; - const uAbs = Math.abs(uu).toFixed(2); - const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; - optPart += ` / ${uSign}${uAbs}U`; + if (coinMode) { + if (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 ? "+" : ""; + let optPart = `${optSign}${optAbs} ${underly}`; + if (Number.isFinite(spotPx) && spotPx > 0) { + const uu = Number(opt) * spotPx; + const uAbs = Math.abs(uu).toFixed(2); + const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; + optPart += ` / ${uSign}${uAbs}U`; + } + const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optPart}`; + lastRealtimePnl = Number(opt); + lastRealtimePnlUnit = underly; + lastRealtimePnlSpotPx = spotPx; + 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; } - const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optPart}`; - lastRealtimePnl = Number(opt); - lastRealtimePnlUnit = underly; - lastRealtimePnlSpotPx = spotPx; - 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); - }); + paintRealtimePnl(opt, underly, spotPx); + return; + } + // 期权盈亏拉取失败时保留上次有效值,避免顶栏闪成 0/— + if (lastRealtimePnl != null && (lastRealtimePnlUnit === "ETH" || lastRealtimePnlUnit === "BTC")) { return; } - paintRealtimePnl(opt, underly, spotPx); - return; } const combined = combineRealtimeFloatPnl(perp, opt); if(combined !== null || perp !== null || opt != null){ @@ -1302,8 +1308,17 @@ function applyAccountSnapshot(data){ ); setFundsFieldText("options-trading-usdc", optTrading); } - if(typeof data.unrealized_pnl !== "undefined"){ - updateRealtimePnl(data.unrealized_pnl); + if(typeof data.unrealized_pnl !== "undefined" || typeof data.options_unrealized_pnl !== "undefined"){ + const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; + if (coinMode && data.options_unrealized_pnl != null && !Number.isNaN(Number(data.options_unrealized_pnl))) { + paintRealtimePnl( + data.options_unrealized_pnl, + String(data.options_underly || "ETH").toUpperCase() || "ETH", + data.options_index_px + ); + } else if (typeof data.unrealized_pnl !== "undefined") { + updateRealtimePnl(data.unrealized_pnl, "U"); + } } if(typeof data.total !== "undefined" && data.total !== null){ setFundsFieldText("stat-total", String(data.total)); diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 8467aee..a24a38e 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1638,33 +1638,39 @@ function paintRealtimePnlFromSnapshot(data){ const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null; - 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 ? "+" : ""; - let optPart = `${optSign}${optAbs} ${underly}`; - if (Number.isFinite(spotPx) && spotPx > 0) { - const uu = Number(opt) * spotPx; - const uAbs = Math.abs(uu).toFixed(2); - const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; - optPart += ` / ${uSign}${uAbs}U`; + if (coinMode) { + if (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 ? "+" : ""; + let optPart = `${optSign}${optAbs} ${underly}`; + if (Number.isFinite(spotPx) && spotPx > 0) { + const uu = Number(opt) * spotPx; + const uAbs = Math.abs(uu).toFixed(2); + const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; + optPart += ` / ${uSign}${uAbs}U`; + } + const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optPart}`; + lastRealtimePnl = Number(opt); + lastRealtimePnlUnit = underly; + lastRealtimePnlSpotPx = spotPx; + 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; } - const text = `${perpSign}${Number(perp).toFixed(2)}U / ${optPart}`; - lastRealtimePnl = Number(opt); - lastRealtimePnlUnit = underly; - lastRealtimePnlSpotPx = spotPx; - 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); - }); + paintRealtimePnl(opt, underly, spotPx); + return; + } + // 期权盈亏拉取失败时保留上次有效值,避免顶栏闪成 0/— + if (lastRealtimePnl != null && (lastRealtimePnlUnit === "ETH" || lastRealtimePnlUnit === "BTC")) { return; } - paintRealtimePnl(opt, underly, spotPx); - return; } const combined = combineRealtimeFloatPnl(perp, opt); if(combined !== null || perp !== null || opt != null){ @@ -1784,8 +1790,18 @@ function applyAccountSnapshot(data){ ); setFundsFieldText("options-trading-usdc", optTrading); } - if(typeof data.unrealized_pnl !== "undefined"){ - updateRealtimePnl(data.unrealized_pnl); + if(typeof data.unrealized_pnl !== "undefined" || typeof data.options_unrealized_pnl !== "undefined"){ + const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; + if (coinMode && data.options_unrealized_pnl != null && !Number.isNaN(Number(data.options_unrealized_pnl))) { + // 币本位:顶栏跟持仓卡同口径(期权净盈亏),勿用混加后的 unrealized_pnl(会被抹成 0.00) + paintRealtimePnl( + data.options_unrealized_pnl, + String(data.options_underly || "ETH").toUpperCase() || "ETH", + data.options_index_px + ); + } else if (typeof data.unrealized_pnl !== "undefined") { + updateRealtimePnl(data.unrealized_pnl, "U"); + } } if(typeof data.total !== "undefined" && data.total !== null){ setFundsFieldText("stat-total", String(data.total)); diff --git a/lib/options/options_positions_lib.py b/lib/options/options_positions_lib.py index a3d6a1d..653481b 100644 --- a/lib/options/options_positions_lib.py +++ b/lib/options/options_positions_lib.py @@ -144,7 +144,7 @@ def sum_options_net_pnl_usdc( continue found = True total += float(pnl) - return round(total, 4) if found else (0.0 if not positions else None) + return round(total, 8) if found else (0.0 if not positions else None) def build_display_option_positions(