修复币本位实时盈亏闪烁:禁止ETH盈亏与U混加后被两位小数抹成0

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-20 17:51:46 +08:00
parent f5c553844f
commit 9bb05113f3
4 changed files with 86 additions and 53 deletions
+3 -1
View File
@@ -7173,7 +7173,9 @@ def api_account_snapshot():
from lib.exchange.okx_options_lib import fetch_options_unrealized_pnl_usdc from lib.exchange.okx_options_lib import fetch_options_unrealized_pnl_usdc
options_unrealized_pnl = fetch_options_unrealized_pnl_usdc(exchange_options) 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: except Exception:
options_unrealized_pnl = None options_unrealized_pnl = None
_show_perp_funds = show_perp_funds_enabled(exchange_key="okx") or (options_margin_mode == "coin") _show_perp_funds = show_perp_funds_enabled(exchange_key="okx") or (options_margin_mode == "coin")
+40 -25
View File
@@ -1157,32 +1157,38 @@ function paintRealtimePnlFromSnapshot(data){
const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin";
const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH";
const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null; const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null;
if (coinMode && opt != null && !Number.isNaN(Number(opt))) { if (coinMode) {
if (perp != null && Math.abs(Number(perp)) >= 0.005) { if (opt != null && !Number.isNaN(Number(opt))) {
const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0"; if (perp != null && Math.abs(Number(perp)) >= 0.005) {
const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : ""); const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0";
const perpSign = Number(perp) > 0 ? "+" : ""; const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : "");
let optPart = `${optSign}${optAbs} ${underly}`; const perpSign = Number(perp) > 0 ? "+" : "";
if (Number.isFinite(spotPx) && spotPx > 0) { let optPart = `${optSign}${optAbs} ${underly}`;
const uu = Number(opt) * spotPx; if (Number.isFinite(spotPx) && spotPx > 0) {
const uAbs = Math.abs(uu).toFixed(2); const uu = Number(opt) * spotPx;
const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; const uAbs = Math.abs(uu).toFixed(2);
optPart += ` / ${uSign}${uAbs}U`; 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}`; paintRealtimePnl(opt, underly, spotPx);
lastRealtimePnl = Number(opt); return;
lastRealtimePnlUnit = underly; }
lastRealtimePnlSpotPx = spotPx; // 期权盈亏拉取失败时保留上次有效值,避免顶栏闪成 0/—
document.querySelectorAll('[data-funds-field="realtime-pnl"]').forEach((pnlEl) => { if (lastRealtimePnl != null && (lastRealtimePnlUnit === "ETH" || lastRealtimePnlUnit === "BTC")) {
pnlEl.innerText = text;
const n = Number(opt) + Number(perp);
pnlEl.classList.toggle("pnl-pos", n > 0);
pnlEl.classList.toggle("pnl-neg", n < 0);
});
return; return;
} }
paintRealtimePnl(opt, underly, spotPx);
return;
} }
const combined = combineRealtimeFloatPnl(perp, opt); const combined = combineRealtimeFloatPnl(perp, opt);
if(combined !== null || perp !== null || opt != null){ if(combined !== null || perp !== null || opt != null){
@@ -1302,8 +1308,17 @@ function applyAccountSnapshot(data){
); );
setFundsFieldText("options-trading-usdc", optTrading); setFundsFieldText("options-trading-usdc", optTrading);
} }
if(typeof data.unrealized_pnl !== "undefined"){ if(typeof data.unrealized_pnl !== "undefined" || typeof data.options_unrealized_pnl !== "undefined"){
updateRealtimePnl(data.unrealized_pnl); 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){ if(typeof data.total !== "undefined" && data.total !== null){
setFundsFieldText("stat-total", String(data.total)); setFundsFieldText("stat-total", String(data.total));
+42 -26
View File
@@ -1638,33 +1638,39 @@ function paintRealtimePnlFromSnapshot(data){
const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin"; const coinMode = String(data.options_margin_mode || "").toLowerCase() === "coin";
const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH"; const underly = String(data.options_underly || "ETH").toUpperCase() || "ETH";
const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null; const spotPx = data.options_index_px != null ? Number(data.options_index_px) : null;
if (coinMode && opt != null && !Number.isNaN(Number(opt))) { if (coinMode) {
// 币本位期权盈亏单位为币,勿与永续 U 混加成「xxU」 if (opt != null && !Number.isNaN(Number(opt))) {
if (perp != null && Math.abs(Number(perp)) >= 0.005) { // 币本位期权盈亏单位为币,勿与永续 U 混加成「xxU」
const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0"; if (perp != null && Math.abs(Number(perp)) >= 0.005) {
const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : ""); const optAbs = Math.abs(Number(opt)).toFixed(8).replace(/\.?0+$/, "") || "0";
const perpSign = Number(perp) > 0 ? "+" : ""; const optSign = Number(opt) > 0 ? "+" : (Number(opt) < 0 ? "-" : "");
let optPart = `${optSign}${optAbs} ${underly}`; const perpSign = Number(perp) > 0 ? "+" : "";
if (Number.isFinite(spotPx) && spotPx > 0) { let optPart = `${optSign}${optAbs} ${underly}`;
const uu = Number(opt) * spotPx; if (Number.isFinite(spotPx) && spotPx > 0) {
const uAbs = Math.abs(uu).toFixed(2); const uu = Number(opt) * spotPx;
const uSign = uu < 0 ? "-" : uu > 0 ? "+" : ""; const uAbs = Math.abs(uu).toFixed(2);
optPart += ` / ${uSign}${uAbs}U`; 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}`; paintRealtimePnl(opt, underly, spotPx);
lastRealtimePnl = Number(opt); return;
lastRealtimePnlUnit = underly; }
lastRealtimePnlSpotPx = spotPx; // 期权盈亏拉取失败时保留上次有效值,避免顶栏闪成 0/—
document.querySelectorAll('[data-funds-field="realtime-pnl"]').forEach((pnlEl) => { if (lastRealtimePnl != null && (lastRealtimePnlUnit === "ETH" || lastRealtimePnlUnit === "BTC")) {
pnlEl.innerText = text;
const n = Number(opt) + Number(perp);
pnlEl.classList.toggle("pnl-pos", n > 0);
pnlEl.classList.toggle("pnl-neg", n < 0);
});
return; return;
} }
paintRealtimePnl(opt, underly, spotPx);
return;
} }
const combined = combineRealtimeFloatPnl(perp, opt); const combined = combineRealtimeFloatPnl(perp, opt);
if(combined !== null || perp !== null || opt != null){ if(combined !== null || perp !== null || opt != null){
@@ -1784,8 +1790,18 @@ function applyAccountSnapshot(data){
); );
setFundsFieldText("options-trading-usdc", optTrading); setFundsFieldText("options-trading-usdc", optTrading);
} }
if(typeof data.unrealized_pnl !== "undefined"){ if(typeof data.unrealized_pnl !== "undefined" || typeof data.options_unrealized_pnl !== "undefined"){
updateRealtimePnl(data.unrealized_pnl); 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){ if(typeof data.total !== "undefined" && data.total !== null){
setFundsFieldText("stat-total", String(data.total)); setFundsFieldText("stat-total", String(data.total));
+1 -1
View File
@@ -144,7 +144,7 @@ def sum_options_net_pnl_usdc(
continue continue
found = True found = True
total += float(pnl) 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( def build_display_option_positions(