修复币本位期权浮盈二次折算:中控汇总与实例统计把已折 U 的数再乘指数。
约 -6.5U 被当成 ETH 再 ×2700 显示成 -1.7万U;汇总框直接按 U 展示,stats 不再对 open_float 二次换算。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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(" · ");
|
||||||
|
|||||||
Reference in New Issue
Block a user