From 1aa279f1919cd27d5b075fc86246692ddf363179 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 13 Oct 2026 10:50:50 +0800 Subject: [PATCH] =?UTF-8?q?fix=E5=8D=96=E5=9B=9E=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=A6=81=E6=AD=A2=E7=94=A8=E6=8C=87=E6=95=B0=E6=8F=90?= =?UTF-8?q?=E5=89=8D=E7=BB=93=E7=AE=97=EF=BC=8C=E5=B9=B6=E7=BA=A0=E6=AD=A3?= =?UTF-8?q?=E4=B9=B0=E5=8D=96VWAP=E5=9B=9E=E9=80=80=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- lib/options/options_pnl_settle_lib.py | 8 +++--- lib/options/options_review_lib.py | 12 +++++++++ lib/options/options_spot_bridge_lib.py | 27 +++++++++----------- tests/test_options_spot_vwap.py | 34 ++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lib/options/options_pnl_settle_lib.py b/lib/options/options_pnl_settle_lib.py index 1e860b1..528ef47 100644 --- a/lib/options/options_pnl_settle_lib.py +++ b/lib/options/options_pnl_settle_lib.py @@ -226,7 +226,6 @@ def resolve_spot_vwap_prices( try: from lib.options.options_spot_bridge_lib import ( BRIDGE_CLOSED, - BRIDGE_PENDING_SELL, bridge_buy_vwap, bridge_sell_vwap, ensure_bridge_table, @@ -243,14 +242,13 @@ def resolve_spot_vwap_prices( st = str( row["status"] if row and hasattr(row, "keys") else (row[0] if row else "") or "" ) - if st == BRIDGE_PENDING_SELL: + # 桥未关闭(=卖回流程未结束):禁止用平仓指数冒充卖回 VWAP 提前结算 + if st and st != BRIDGE_CLOSED: return open_px, None, "pending_sell" sv = bridge_sell_vwap(conn, bid) if sv is not None and sv > 0: close_px = sv - elif st == BRIDGE_CLOSED and (close_px is None or close_px <= 0): - # 已关桥但无 VWAP/冻结卖回价 - return open_px, None, "missing" + # 已关桥但无卖回 VWAP:允许回退 close_index_px;两者都无则 missing except Exception: pass if open_px is None or open_px <= 0 or close_px is None or close_px <= 0: diff --git a/lib/options/options_review_lib.py b/lib/options/options_review_lib.py index 8c99f77..bcdc41f 100644 --- a/lib/options/options_review_lib.py +++ b/lib/options/options_review_lib.py @@ -599,6 +599,18 @@ def sync_options_from_local_trades( if pnl_src == "pending_sell": status_label = "待卖回" pnl_u = None + # 清掉曾用指数提前冻上的假真盈亏 + try: + conn.execute( + """ + UPDATE options_trades + SET realized_pnl_usdt = NULL, pnl_settled = 0 + WHERE id = ? AND COALESCE(pnl_settled, 0) = 1 + """, + (trade_id,), + ) + except Exception: + pass elif pnl_u is not None and pnl_src not in ("none", "pending_sell"): if frozen_usdt is None or abs(float(frozen_usdt) - float(pnl_u)) > 0.05: try: diff --git a/lib/options/options_spot_bridge_lib.py b/lib/options/options_spot_bridge_lib.py index 802dcf1..65dc7ec 100644 --- a/lib/options/options_spot_bridge_lib.py +++ b/lib/options/options_spot_bridge_lib.py @@ -184,12 +184,16 @@ def add_bridge_close_sell_fill( def bridge_buy_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | None: - """开仓买币 VWAP(USDT/币);多笔加权.忽略后续把 coin_bought 改成权利金量.""" + """开仓买币 VWAP(USDT/币);多笔加权. + + 只用 buy_usdt_spent/buy_coin_accum.禁止 budget/coin_bought: + 开仓成功后 coin_bought 会被改成权利金量,用 budget/缩水量会把开仓价抬飞. + """ if bridge_id is None: return None ensure_bridge_table(conn) row = conn.execute( - "SELECT buy_usdt_spent, buy_coin_accum, budget_usdt, coin_bought FROM options_spot_bridge WHERE id=?", + "SELECT buy_usdt_spent, buy_coin_accum FROM options_spot_bridge WHERE id=?", (int(bridge_id),), ).fetchone() if not row: @@ -198,21 +202,19 @@ def bridge_buy_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | coins = _safe_float(row["buy_coin_accum"] if hasattr(row, "keys") else row[1]) if spent is not None and coins is not None and spent > 0 and coins > 0: return round(float(spent) / float(coins), 8) - # 旧桥无累加字段:用预算/买入量近似单笔 - budget = _safe_float(row["budget_usdt"] if hasattr(row, "keys") else row[2]) - bought = _safe_float(row["coin_bought"] if hasattr(row, "keys") else row[3]) - if budget is not None and bought is not None and budget > 0 and bought > 0: - return round(float(budget) / float(bought), 8) return None def bridge_sell_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | None: - """平仓后卖回 VWAP(USDT/币);仅 close_sell_* 多笔加权,不含开仓多余卖回.""" + """平仓后卖回 VWAP(USDT/币);仅 close_sell_* 多笔加权. + + 禁止回退 usdt_recovered/coin_sold:后者常混有开仓多余卖回,会污染卖回均价. + """ if bridge_id is None: return None ensure_bridge_table(conn) row = conn.execute( - "SELECT close_sell_usdt, close_sell_coin, usdt_recovered, coin_sold, status FROM options_spot_bridge WHERE id=?", + "SELECT close_sell_usdt, close_sell_coin FROM options_spot_bridge WHERE id=?", (int(bridge_id),), ).fetchone() if not row: @@ -221,13 +223,6 @@ def bridge_sell_vwap(conn: sqlite3.Connection, bridge_id: int | None) -> float | c = _safe_float(row["close_sell_coin"] if hasattr(row, "keys") else row[1]) if u is not None and c is not None and u > 0 and c > 0: return round(float(u) / float(c), 8) - # 旧桥无分拆字段且已关桥:用总收回/总卖出近似(可能含开仓多余卖回) - st = str(row["status"] if hasattr(row, "keys") else row[4] or "") - if st == BRIDGE_CLOSED: - ru = _safe_float(row["usdt_recovered"] if hasattr(row, "keys") else row[2]) - sc = _safe_float(row["coin_sold"] if hasattr(row, "keys") else row[3]) - if ru is not None and sc is not None and ru > 0 and sc > 0: - return round(float(ru) / float(sc), 8) return None diff --git a/tests/test_options_spot_vwap.py b/tests/test_options_spot_vwap.py index 7c78c83..df4129c 100644 --- a/tests/test_options_spot_vwap.py +++ b/tests/test_options_spot_vwap.py @@ -76,6 +76,7 @@ class SpotVwapBridgeTests(unittest.TestCase): def test_pending_sell_blocks_true_pnl(self): from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade from lib.options.options_spot_bridge_lib import ( + BRIDGE_HOLDING, BRIDGE_PENDING_SELL, add_bridge_buy_fill, add_bridge_close_sell_fill, @@ -103,6 +104,39 @@ class SpotVwapBridgeTests(unittest.TestCase): ) self.assertEqual(src, "pending_sell") self.assertIsNone(pnl) + # holding 未关桥同样不得用指数提前结算 + update_bridge(conn, bid, status=BRIDGE_HOLDING) + pnl2, src2 = resolve_true_pnl_usdt_from_trade( + premium_paid=0.01, + premium_received=0.02, + realized_pnl=0.01, + open_index_px=3000.0, + close_index_px=3000.0, + premium_ccy="ETH", + conn=conn, + bridge_id=bid, + ) + self.assertEqual(src2, "pending_sell") + self.assertIsNone(pnl2) + conn.close() + + def test_buy_vwap_ignores_shrunk_coin_bought(self): + """开仓后 coin_bought 被改成权利金量时,不得用 budget/缩水量当买价.""" + from lib.options.options_spot_bridge_lib import ( + add_bridge_buy_fill, + bridge_buy_vwap, + ensure_bridge_table, + insert_bridge, + update_bridge, + ) + + conn = sqlite3.connect(":memory:") + conn.row_factory = sqlite3.Row + ensure_bridge_table(conn) + bid = insert_bridge(conn, underlying="BTC", status="holding", budget_usdt=2200) + add_bridge_buy_fill(conn, bid, usdt_spent=2200.0, coin_bought=1.1) + update_bridge(conn, bid, coin_bought=0.01) # 缩成权利金 + self.assertAlmostEqual(float(bridge_buy_vwap(conn, bid)), 2000.0, places=4) conn.close()