修复币本位期权浮盈二次折算:中控汇总与实例统计把已折 U 的数再乘指数。

约 -6.5U 被当成 ETH 再 ×2700 显示成 -1.7万U;汇总框直接按 U 展示,stats 不再对 open_float 二次换算。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-09-30 22:30:24 +08:00
parent 32746b0a34
commit d2d431e4c9
2 changed files with 34 additions and 24 deletions
+5 -13
View File
@@ -1846,32 +1846,24 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
except Exception: except Exception:
index_px = None index_px = None
stats = compute_options_stats_from_history(history, index_px=index_px) stats = compute_options_stats_from_history(history, index_px=index_px)
# sum_options_net_pnl_usdc 已按指数折 U;勿再 × 指数(否则顶栏会变成上万假亏)
open_float = sum_options_net_pnl_usdc(cfg, ex, raw_live) open_float = sum_options_net_pnl_usdc(cfg, ex, raw_live)
# 币本位浮盈为币数量,折算为 U 再与已平合计
if open_float is not None and str(stats.get("pnl_unit") or "") == "U":
px = index_px
if px is None or px <= 0:
for h in history:
try:
px = float(h.get("idx_px") or 0)
except (TypeError, ValueError):
px = 0
if px > 0:
break
if px and px > 0:
open_float = round(float(open_float) * float(px), 4)
net_realized = _safe_float(stats.get("net_realized_pnl")) or 0.0 net_realized = _safe_float(stats.get("net_realized_pnl")) or 0.0
total_pnl = None total_pnl = None
if open_float is not None: if open_float is not None:
total_pnl = round(net_realized + float(open_float), 4) total_pnl = round(net_realized + float(open_float), 4)
elif stats.get("total_closed"): elif stats.get("total_closed"):
total_pnl = round(net_realized, 4) total_pnl = round(net_realized, 4)
from lib.options.options_margin_mode_lib import is_coin_margin_mode
pnl_unit = "U" if is_coin_margin_mode() else str(stats.get("pnl_unit") or "USDC")
return jsonify( return jsonify(
{ {
"ok": True, "ok": True,
**stats, **stats,
"open_float_pnl": open_float, "open_float_pnl": open_float,
"total_pnl": total_pnl, "total_pnl": total_pnl,
"pnl_unit": pnl_unit,
} }
) )
+29 -11
View File
@@ -817,7 +817,19 @@
return `${sign}${fmt(n, 2)}U`; return `${sign}${fmt(n, 2)}U`;
} }
/** 中控期权浮盈汇总框:只显示换算后的 U(持仓表净盈亏仍用 fmtOptPnlText 双显). */ /** 已是 USDT 的期权浮盈合计(upl_total_usdc),禁止再按币×指数. */
function fmtOptFloatUsdt(v) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v);
const uAbs = Math.abs(n).toFixed(2);
const uSign = n < 0 ? "-" : n > 0 ? "+" : "";
return `${uSign}${uAbs}U`;
}
/**
* 币数量 → U(仅逐仓净盈亏双显/兜底合计用).
* 汇总框勿用:后端 upl_total 已折 U,再乘会变成上万假亏.
*/
function fmtOptPnlUsdtOnly(v, ccy, spotPx) { function fmtOptPnlUsdtOnly(v, ccy, spotPx) {
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—"; if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
const n = Number(v); const n = Number(v);
@@ -3939,6 +3951,7 @@
const bal = const bal =
opt.balances && typeof opt.balances === "object" ? opt.balances : {}; opt.balances && typeof opt.balances === "object" ? opt.balances : {};
const pick = (a, b) => (a != null && a !== "" ? a : b); const pick = (a, b) => (a != null && a !== "" ? a : b);
// upl_total_usdc 实际为已折 USDT 的合计(币本位 × 指数后)
let upl = let upl =
opt.upl_total_usdc != null && Number.isFinite(Number(opt.upl_total_usdc)) opt.upl_total_usdc != null && Number.isFinite(Number(opt.upl_total_usdc))
? Number(opt.upl_total_usdc) ? Number(opt.upl_total_usdc)
@@ -3949,10 +3962,16 @@
let found = false; let found = false;
pos.forEach((p) => { pos.forEach((p) => {
const n = optionsRowDisplayPnl(p); const n = optionsRowDisplayPnl(p);
if (n != null) { if (n == null || !Number.isFinite(Number(n))) return;
sum += n; const ccy = String(optPremiumCcyOf(p) || "").toUpperCase();
found = true; let uu = Number(n);
if (ccy === "ETH" || ccy === "BTC") {
const px = spotPxFromOptMeta(opt, p);
if (!Number.isFinite(px) || !(px > 0)) return;
uu = Number(n) * px;
} }
sum += uu;
found = true;
}); });
if (found) upl = sum; if (found) upl = sum;
} }
@@ -3987,11 +4006,10 @@
? `${fmt(fundUsdt, 2)} <small style="font-size:12px;color:var(--muted)">U</small>` ? `${fmt(fundUsdt, 2)} <small style="font-size:12px;color:var(--muted)">U</small>`
: "—"; : "—";
const tradeTxt = formatCoinTradingLabel(usdt, eth, btc); const tradeTxt = formatCoinTradingLabel(usdt, eth, btc);
const pnlCcy = optionsPanelCcy(optMeta); // 汇总 upnl 已是 USDT,勿再按 ETH/BTC×指数(否则 -6U 会变成 -1.7万U)
const spotPx = spotPxFromOptMeta(optMeta);
const pnlTxt = upnl == null || Number.isNaN(Number(upnl)) const pnlTxt = upnl == null || Number.isNaN(Number(upnl))
? "—" ? "—"
: `<span class="${pnlCls(upnl)}">${fmtOptPnlUsdtOnly(upnl, pnlCcy, spotPx)}</span>`; : `<span class="${pnlCls(upnl)}">${fmtOptFloatUsdt(upnl)}</span>`;
return `<div class="${rowCls}"> return `<div class="${rowCls}">
<div class="stat-box"><div class="stat-label">资金账户</div><div class="stat-value">${fundTxt}</div></div> <div class="stat-box"><div class="stat-label">资金账户</div><div class="stat-value">${fundTxt}</div></div>
<div class="stat-box"><div class="stat-label">交易账户</div><div class="stat-value">${tradeTxt}</div></div> <div class="stat-box"><div class="stat-label">交易账户</div><div class="stat-value">${tradeTxt}</div></div>
@@ -4671,14 +4689,14 @@
parts.push(`交易 ${fmt(bal.trading, 2)}U`); parts.push(`交易 ${fmt(bal.trading, 2)}U`);
} }
if (optUpl != null && Number.isFinite(Number(optUpl))) { if (optUpl != null && Number.isFinite(Number(optUpl))) {
parts.push(`浮盈 ${fmtOptPnlUsdtOnly(optUpl, optCcy, spotPxFromOptMeta(opt))}`); parts.push(`浮盈 ${fmtOptFloatUsdt(optUpl)}`);
} }
if (optUpl != null && Number.isFinite(Number(optUpl)) && openCount === 0) { if (optUpl != null && Number.isFinite(Number(optUpl)) && openCount === 0) {
// 永续空仓时主数字优先展示期权浮盈(只显示 U) // 永续空仓时主数字优先展示期权浮盈(已是 U,勿再按币折算)
pnlShow = optUpl; pnlShow = optUpl;
pnlSuffix = "期权"; pnlSuffix = "期权";
pnlUnit = optCcy; pnlUnit = "USDC";
pnlSpotPx = spotPxFromOptMeta(opt); pnlSpotPx = null;
} }
} }
optLine = parts.join(" · "); optLine = parts.join(" · ");