From 8a62cfbb48730d0d7765dd5c5b68014431209e8c Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 30 Sep 2026 22:01:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=AF=94=E7=89=B9=E5=B8=81?= =?UTF-8?q?=E6=9C=9F=E6=9D=83=E6=B5=AE=E7=9B=88=E6=8A=8A=E5=B8=81=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E5=BD=93=E6=88=90=20U=EF=BC=9A=E7=9C=8B=E6=9D=BF/?= =?UTF-8?q?=E4=B8=AD=E6=8E=A7=E6=94=B9=E4=B8=BA=E6=8C=89=E6=8C=87=E6=95=B0?= =?UTF-8?q?=E6=8A=98=20USDT=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 币本位 BTC/ETH 净盈亏须 × 指数后再标 U;并兜底从合约推断 premium_ccy,避免缺失时漏折算。 Co-authored-by: Cursor --- lib/instance/instance_dashboard_lib.py | 6 ++-- lib/options/options_hub_lib.py | 6 ++-- lib/options/options_positions_lib.py | 15 ++++++++- tests/test_options_btc_pnl_usdt.py | 45 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 tests/test_options_btc_pnl_usdt.py diff --git a/lib/instance/instance_dashboard_lib.py b/lib/instance/instance_dashboard_lib.py index 70df9c2..824065b 100644 --- a/lib/instance/instance_dashboard_lib.py +++ b/lib/instance/instance_dashboard_lib.py @@ -162,12 +162,12 @@ def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]: inst = str(p.get("inst_id") or p.get("instId") or "-").strip() or "-" opt_type = str(p.get("opt_type") or p.get("optType") or "").upper() label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT") - # 看板期权列:优先买一净盈亏,残档回退交易所 upl + # 看板期权列:统一折成 U(币本位 BTC/ETH 权利金盈亏 × 指数) pnl = None try: - from lib.options.options_positions_lib import display_pnl_from_option_row + from lib.options.options_positions_lib import display_pnl_usdt_from_option_row - pnl = display_pnl_from_option_row(p) + pnl = display_pnl_usdt_from_option_row(p) except Exception: pnl = None pos = _safe_float(p.get("pos")) diff --git a/lib/options/options_hub_lib.py b/lib/options/options_hub_lib.py index 4e0cdd8..021778b 100644 --- a/lib/options/options_hub_lib.py +++ b/lib/options/options_hub_lib.py @@ -85,13 +85,13 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]: conn.close() except Exception: target_monitors = [] - from lib.options.options_positions_lib import display_pnl_from_option_row + from lib.options.options_positions_lib import display_pnl_usdt_from_option_row upl_total = 0.0 has_upl = False for p in positions: - # 与持仓卡展示一致:优先买一净盈亏,残档回退交易所 upl - pnl = display_pnl_from_option_row(p) + # 合计一律折 U:币本位 × 指数,避免 BTC/ETH 币数量与 USDC 混加 + pnl = display_pnl_usdt_from_option_row(p) if pnl is None: continue has_upl = True diff --git a/lib/options/options_positions_lib.py b/lib/options/options_positions_lib.py index e97a762..fb9737f 100644 --- a/lib/options/options_positions_lib.py +++ b/lib/options/options_positions_lib.py @@ -125,11 +125,24 @@ def display_pnl_from_option_row(row: dict[str, Any]) -> float | None: def display_pnl_usdt_from_option_row(row: dict[str, Any]) -> float | None: - """顶栏「实时盈亏」用:币本位净盈亏按指数折 USDT;USDC 仓原样.""" + """顶栏/看板「实时盈亏」用:币本位净盈亏按指数折 USDT;USDC 仓原样. + + BTC/ETH 币本位若不折算,会被界面标成 U 而数值仍是币数量(BTC 尤为离谱). + """ pnl = display_pnl_from_option_row(row) if pnl is None: return None ccy = str(row.get("premium_ccy") or "USDC").strip().upper() or "USDC" + if not ccy or ccy == "USDC": + # 兜底:按合约推断币本位,避免 premium_ccy 缺失时把 BTC 盈亏当 U + inst = str(row.get("inst_id") or row.get("instId") or "") + mode = str(row.get("margin_mode") or "").strip().lower() + if mode == "coin" or ( + "-USD-" in inst.upper() and "_UM" not in inst.upper() + ): + underly = str(row.get("underlying") or (inst.split("-")[0] if inst else "") or "").upper() + if underly in ("ETH", "BTC"): + ccy = underly if ccy in ("ETH", "BTC"): idx = _safe_float(row.get("idx_px") or row.get("idxPx") or row.get("index_px")) if idx is None or idx <= 0: diff --git a/tests/test_options_btc_pnl_usdt.py b/tests/test_options_btc_pnl_usdt.py new file mode 100644 index 0000000..491d173 --- /dev/null +++ b/tests/test_options_btc_pnl_usdt.py @@ -0,0 +1,45 @@ +"""币本位期权浮盈折 U(BTC/ETH 不得把币数量直接当 U).""" +from __future__ import annotations + +import unittest + +from lib.options.options_positions_lib import display_pnl_usdt_from_option_row + + +class DisplayPnlUsdtTests(unittest.TestCase): + def test_btc_coin_pnl_times_index(self): + row = { + "inst_id": "BTC-USD-260327-100000-P", + "premium_ccy": "BTC", + "margin_mode": "coin", + "idx_px": 100000.0, + "upl": -0.001, + "close_preview": {"bid_invalid": True}, + } + # 残档回退 upl: -0.001 BTC × 100000 = -100 U + self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), -100.0, places=4) + + def test_btc_infers_ccy_when_premium_ccy_missing(self): + row = { + "inst_id": "BTC-USD-260327-100000-C", + "margin_mode": "coin", + "underlying": "BTC", + "idx_px": 95000.0, + "upl": 0.002, + "close_preview": {"bid_invalid": True}, + } + self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), 190.0, places=4) + + def test_usdc_unchanged(self): + row = { + "inst_id": "BTC-USD_UM-260327-100000-C", + "premium_ccy": "USDC", + "margin_mode": "usdc", + "upl": -12.5, + "close_preview": {"bid_invalid": True}, + } + self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), -12.5, places=4) + + +if __name__ == "__main__": + unittest.main()