中控期权浮盈只显示U;总浮盈亏按指数把币本位浮盈计入USDT
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,21 +74,50 @@ def options_balances_usdt_equiv(options_snap: dict[str, Any] | None) -> dict[str
|
||||
|
||||
|
||||
def options_float_pnl_usdt(options_snap: dict[str, Any] | None) -> Optional[float]:
|
||||
"""期权浮盈合计(USDT).币本位按指数换算,勿把 ETH/BTC 数量当 U."""
|
||||
snap = options_snap if isinstance(options_snap, dict) else {}
|
||||
if snap.get("enabled") is False or snap.get("ok") is False:
|
||||
return None
|
||||
upl = snap.get("upl_total_usdc")
|
||||
if upl is not None:
|
||||
|
||||
def _safe(v: Any) -> float | None:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
try:
|
||||
return round(float(upl), 4)
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
# 快照偶发缺合计时,按持仓行回退汇总(与卡片展示一致)
|
||||
return None
|
||||
|
||||
def _index_px() -> float | None:
|
||||
px = _safe(snap.get("options_index_px") or snap.get("index_px"))
|
||||
if px is not None and px > 0:
|
||||
return px
|
||||
for p in snap.get("positions") or []:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
px = _safe(p.get("idx_px") or p.get("idxPx"))
|
||||
if px is not None and px > 0:
|
||||
return px
|
||||
return None
|
||||
|
||||
def _row_is_coin(p: dict[str, Any]) -> bool:
|
||||
ccy = str(p.get("premium_ccy") or "").strip().upper()
|
||||
if ccy in ("ETH", "BTC"):
|
||||
return True
|
||||
if str(p.get("margin_mode") or "").strip().lower() == "coin":
|
||||
return True
|
||||
mid = str(p.get("inst_id") or "")
|
||||
return "-USD-" in mid.upper() and "_UM" not in mid.upper()
|
||||
|
||||
mode = str(snap.get("options_margin_mode") or snap.get("margin_mode") or "").strip().lower()
|
||||
snap_coin = mode == "coin"
|
||||
idx = _index_px()
|
||||
|
||||
try:
|
||||
from lib.options.options_positions_lib import display_pnl_from_option_row
|
||||
|
||||
total = 0.0
|
||||
total_u = 0.0
|
||||
found = False
|
||||
missing_fx = False
|
||||
for p in snap.get("positions") or []:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
@@ -96,10 +125,38 @@ def options_float_pnl_usdt(options_snap: dict[str, Any] | None) -> Optional[floa
|
||||
if pnl is None:
|
||||
continue
|
||||
found = True
|
||||
total += float(pnl)
|
||||
return round(total, 4) if found else None
|
||||
if snap_coin or _row_is_coin(p):
|
||||
px = _safe(p.get("idx_px") or p.get("idxPx"))
|
||||
if px is None or px <= 0:
|
||||
px = idx
|
||||
if px is None or px <= 0:
|
||||
missing_fx = True
|
||||
continue
|
||||
total_u += float(pnl) * float(px)
|
||||
else:
|
||||
total_u += float(pnl)
|
||||
if found and not missing_fx:
|
||||
return round(total_u, 4)
|
||||
if found and missing_fx and abs(total_u) > 1e-12:
|
||||
# 部分腿已换算成功时仍返回可得合计
|
||||
return round(total_u, 4)
|
||||
except Exception:
|
||||
return None
|
||||
pass
|
||||
|
||||
upl = snap.get("upl_total_usdc")
|
||||
if upl is not None:
|
||||
try:
|
||||
raw = float(upl)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if snap_coin or any(
|
||||
isinstance(p, dict) and _row_is_coin(p) for p in (snap.get("positions") or [])
|
||||
):
|
||||
if idx is None or idx <= 0:
|
||||
return None
|
||||
return round(raw * float(idx), 4)
|
||||
return round(raw, 4)
|
||||
return None
|
||||
|
||||
|
||||
def options_open_position_count(options_snap: dict[str, Any] | None) -> int:
|
||||
|
||||
@@ -817,6 +817,22 @@
|
||||
return `${sign}${fmt(n, 2)}U`;
|
||||
}
|
||||
|
||||
/** 中控期权浮盈汇总框:只显示换算后的 U(持仓表净盈亏仍用 fmtOptPnlText 双显). */
|
||||
function fmtOptPnlUsdtOnly(v, ccy, spotPx) {
|
||||
if (v === null || v === undefined || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const unit = String(ccy || "USDC").toUpperCase();
|
||||
let uu = n;
|
||||
if (unit === "ETH" || unit === "BTC") {
|
||||
const px = Number(spotPx);
|
||||
if (!Number.isFinite(px) || !(px > 0)) return "—";
|
||||
uu = n * px;
|
||||
}
|
||||
const uAbs = Math.abs(uu).toFixed(2);
|
||||
const uSign = uu < 0 ? "-" : uu > 0 ? "+" : "";
|
||||
return `${uSign}${uAbs}U`;
|
||||
}
|
||||
|
||||
/** 交易所持仓开仓价(三所子代理 entry_price) */
|
||||
function positionEntryPrice(pos) {
|
||||
if (!pos) return null;
|
||||
@@ -3975,7 +3991,7 @@
|
||||
const spotPx = spotPxFromOptMeta(optMeta);
|
||||
const pnlTxt = upnl == null || Number.isNaN(Number(upnl))
|
||||
? "—"
|
||||
: `<span class="${pnlCls(upnl)}">${fmtOptPnlText(upnl, pnlCcy, spotPx)}</span>`;
|
||||
: `<span class="${pnlCls(upnl)}">${fmtOptPnlUsdtOnly(upnl, pnlCcy, spotPx)}</span>`;
|
||||
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">${tradeTxt}</div></div>
|
||||
@@ -4655,10 +4671,10 @@
|
||||
parts.push(`交易 ${fmt(bal.trading, 2)}U`);
|
||||
}
|
||||
if (optUpl != null && Number.isFinite(Number(optUpl))) {
|
||||
parts.push(`浮盈 ${fmtOptPnlText(optUpl, optCcy, spotPxFromOptMeta(opt))}`);
|
||||
parts.push(`浮盈 ${fmtOptPnlUsdtOnly(optUpl, optCcy, spotPxFromOptMeta(opt))}`);
|
||||
}
|
||||
if (optUpl != null && Number.isFinite(Number(optUpl)) && openCount === 0) {
|
||||
// 永续空仓时主数字优先展示期权浮盈,避免一直显示 0U
|
||||
// 永续空仓时主数字优先展示期权浮盈(只显示 U)
|
||||
pnlShow = optUpl;
|
||||
pnlSuffix = "期权";
|
||||
pnlUnit = optCcy;
|
||||
@@ -4673,7 +4689,7 @@
|
||||
const strategyStats = renderCardStrategyStats(row, hm, flaskOk);
|
||||
const tilePnlHtml =
|
||||
pnlUnit === "ETH" || pnlUnit === "BTC"
|
||||
? `${fmtOptPnlText(pnlShow, pnlUnit, pnlSpotPx)} <small>${pnlSuffix ? esc(pnlSuffix) : ""}</small>`
|
||||
? `${fmtOptPnlUsdtOnly(pnlShow, pnlUnit, pnlSpotPx)} <small>${pnlSuffix ? esc(pnlSuffix) : ""}</small>`
|
||||
: `${fmt(pnlShow, 2)} <small>U${pnlSuffix ? " · " + esc(pnlSuffix) : ""}</small>`;
|
||||
return `<div class="card hub-tile ${tileCls}" data-ex-id="${esc(row.id)}">
|
||||
<div class="hub-tile-body card-expand-zone" title="点击进入全屏详情">
|
||||
|
||||
@@ -66,6 +66,33 @@ class TestHubMonitorTotals(unittest.TestCase):
|
||||
self.assertEqual(out["options_float_pnl_u"], 1.5)
|
||||
self.assertEqual(out["float_pnl_u"], 1.5)
|
||||
|
||||
def test_aggregate_monitor_board_totals_coin_options_to_usdt(self):
|
||||
rows = [
|
||||
{
|
||||
"capabilities": ["options"],
|
||||
"options": {
|
||||
"ok": True,
|
||||
"enabled": True,
|
||||
"options_margin_mode": "coin",
|
||||
"options_index_px": 2000.0,
|
||||
"positions": [
|
||||
{
|
||||
"inst_id": "ETH-USD-260822-2250-C",
|
||||
"margin_mode": "coin",
|
||||
"premium_ccy": "ETH",
|
||||
"upl": 0.002,
|
||||
"idx_px": 2000.0,
|
||||
}
|
||||
],
|
||||
"upl_total_usdc": 0.002,
|
||||
},
|
||||
"agent": {"positions": [], "total_unrealized_pnl": 10.0},
|
||||
}
|
||||
]
|
||||
out = aggregate_monitor_board_totals(rows, trading_day="2026-08-20", reset_hour=8)
|
||||
self.assertEqual(out["options_float_pnl_u"], 4.0)
|
||||
self.assertEqual(out["float_pnl_u"], 14.0)
|
||||
|
||||
def test_aggregate_excludes_option_like_agent_positions(self):
|
||||
"""子代理误把期权当永续上报时:不算进持仓数,浮盈只用期权 snap."""
|
||||
rows = [
|
||||
@@ -74,7 +101,7 @@ class TestHubMonitorTotals(unittest.TestCase):
|
||||
"options": {
|
||||
"ok": True,
|
||||
"enabled": True,
|
||||
"positions": [{"inst_id": "ETH-USD-260806-1875-C"}],
|
||||
"positions": [{"inst_id": "ETH-USD_UM-260806-1875-C"}],
|
||||
"upl_total_usdc": -0.4,
|
||||
},
|
||||
"agent": {
|
||||
|
||||
@@ -63,6 +63,28 @@ class HubOptionsFundsLibTests(TestCase):
|
||||
self.assertEqual(out["options_open_position_count"], 1)
|
||||
self.assertEqual(out["options_float_pnl_u"], 0.5)
|
||||
|
||||
def test_options_float_pnl_usdt_coin_converts_by_index(self):
|
||||
from lib.hub.hub_options_funds_lib import options_float_pnl_usdt
|
||||
|
||||
snap = {
|
||||
"ok": True,
|
||||
"enabled": True,
|
||||
"options_margin_mode": "coin",
|
||||
"options_index_px": 2280.0,
|
||||
"upl_total_usdc": 0.0016,
|
||||
"positions": [
|
||||
{
|
||||
"inst_id": "ETH-USD-260822-2250-C",
|
||||
"margin_mode": "coin",
|
||||
"premium_ccy": "ETH",
|
||||
"upl": 0.0016,
|
||||
"idx_px": 2280.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
out = options_float_pnl_usdt(snap)
|
||||
self.assertAlmostEqual(out, round(0.0016 * 2280.0, 4), places=4)
|
||||
|
||||
def test_repair_double_counted_fund_entry(self):
|
||||
raw = {
|
||||
"funding_usdt": 586.82,
|
||||
|
||||
Reference in New Issue
Block a user