fix真盈亏收口为币本位×卖回价减初始权利金U,禁指数冒充平仓价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 12:29:33 +08:00
parent d42e1c7edf
commit 839baea375
10 changed files with 171 additions and 83 deletions
+2 -2
View File
@@ -158,14 +158,14 @@ class AccountPnlSettleTests(unittest.TestCase):
true_pnl_usdt_premium_move,
)
# A = 0.01*80000 - (0.01*90000 - 0.01*80000) = 700
# (0.01+0.01)*80000 - 0.01*90000 = 700
self.assertAlmostEqual(
float(
true_pnl_usdt_premium_move(
coin_pnl=0.01,
premium_paid=0.01,
open_index_px=90000,
sell_back_px=80000,
initial_premium_usdt=900.0,
)
),
700.0,
+16 -6
View File
@@ -26,23 +26,32 @@ class FeeConvertTests(unittest.TestCase):
class PremiumMoveFormulaTests(unittest.TestCase):
def test_user_formula_matches_proceeds_minus_open_cost(self):
def test_user_formula_matches_coin_basis_minus_initial(self):
coin = 0.01
paid = 0.01
open_px = 90000.0
close_px = 80000.0
initial = paid * open_px
a = true_pnl_usdt_premium_move(
coin_pnl=coin,
premium_paid=paid,
open_index_px=open_px,
sell_back_px=close_px,
initial_premium_usdt=initial,
)
# 历史盈亏×卖回 − (权利金×开 − 权利金×平)
expect = coin * close_px - (paid * open_px - paid * close_px)
# (权利金+盈亏)×卖回 − 初始权利金U
expect = (paid + coin) * close_px - initial
self.assertAlmostEqual(float(a), expect, places=4)
# 无费时 ≡ 收回×平 − 已付×开 (recv=paid+coin)
# 无费时 ≡ 收回×平 − 已付×开
recv = paid + coin
self.assertAlmostEqual(float(a), recv * close_px - paid * open_px, places=4)
# 缺初始时用开仓价补算,结果相同
b = true_pnl_usdt_premium_move(
coin_pnl=coin,
premium_paid=paid,
sell_back_px=close_px,
open_index_px=open_px,
)
self.assertAlmostEqual(float(b), float(a), places=4)
class SettleClosedPnlTests(unittest.TestCase):
@@ -100,12 +109,13 @@ class SettleClosedPnlTests(unittest.TestCase):
realized_pnl=-0.0062,
open_index_px=3000,
close_index_px=2500,
initial_premium_usdt=30.0,
open_fee=0.0001,
close_fee=0.0001,
premium_ccy="ETH",
)
self.assertEqual(src, "premium_move")
# -0.0062*2500 - 0.01*(3000-2500) = -15.5 - 5 = -20.5
# (0.01-0.0062)*2500 - 30 = 9.5 - 30 = -20.5
self.assertAlmostEqual(float(pnl), -20.5, places=2)
# 若再扣一遍费会更亏于 -20.5
self.assertGreater(float(pnl), -20.55)
+9 -7
View File
@@ -1,4 +1,4 @@
"""复盘真盈亏=币盈亏×卖回价 − 权利金×(开仓价−卖回价)."""
"""复盘真盈亏=(权利金+币盈亏)×卖回价 − 初始权利金U."""
from __future__ import annotations
import sqlite3
@@ -56,7 +56,7 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
conn.close()
def test_refresh_prefers_bridge_sell_vwap_over_wrong_index(self):
"""库内卖回价若是指数占位,刷新须用桥上卖回 VWAP 纠正."""
"""库内卖回价若是指数占位,刷新须用桥上卖回 VWAP;初始权利金U冻结不改."""
from lib.options.options_db import init_options_tables
from lib.options.options_review_lib import sync_options_from_local_trades
from lib.options.options_spot_bridge_lib import (
@@ -86,13 +86,13 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
open_fee, close_fee, status, realized_pnl_usdt,
open_index_px, close_index_px, pnl_settled, bridge_id,
open_index_px, close_index_px, initial_premium_usdt, pnl_settled, bridge_id,
created_at, closed_at
) VALUES (
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
0.01, 0.02, 0.01, 'BTC',
0, 0, 'closed', 1544.0,
88086.0, 85400.0, 1, ?,
88086.0, 85400.0, 840.10, 1, ?,
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
)
""",
@@ -101,11 +101,13 @@ class ReviewLocalSyncFreezeTests(unittest.TestCase):
conn.commit()
sync_options_from_local_trades(conn, None)
trade = conn.execute(
"SELECT realized_pnl_usdt, open_index_px, close_index_px FROM options_trades WHERE id=1"
"SELECT realized_pnl_usdt, open_index_px, close_index_px, initial_premium_usdt FROM options_trades WHERE id=1"
).fetchone()
self.assertAlmostEqual(float(trade["open_index_px"]), 84010.0, places=2)
# 已冻开仓价/初始权利金不覆盖;卖回价用桥 VWAP
self.assertAlmostEqual(float(trade["open_index_px"]), 88086.0, places=2)
self.assertAlmostEqual(float(trade["initial_premium_usdt"]), 840.10, places=2)
self.assertAlmostEqual(float(trade["close_index_px"]), 81418.0, places=2)
# A = 0.01*81418 - 0.01*(84010-81418) = 814.18 - 25.92 = 788.26
# (0.01+0.01)*81418 - 840.10 = 788.26
self.assertAlmostEqual(float(trade["realized_pnl_usdt"]), 788.26, places=2)
conn.close()
+35 -4
View File
@@ -56,15 +56,15 @@ class SpotVwapBridgeTests(unittest.TestCase):
add_bridge_close_sell_fill(conn, bid, usdt_recovered=1600.0, coin_sold=0.8)
add_bridge_close_sell_fill(conn, bid, usdt_recovered=400.0, coin_sold=0.2)
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
# buy_vwap=2000, sell_vwap=2000; coin_pnl=0.01
# A = 0.01*2000 - (0.01*2000 - 0.01*2000) = 20
# 若误用指数 90000/80000 会得到 700
# buy_vwap=2000, sell_vwap=2000; 初始权利金按买 VWAP=20
# U=(0.01+0.01)*2000 - 20 = 20; 若误用指数 80000 会得到大数
pnl, src = resolve_true_pnl_usdt_from_trade(
premium_paid=0.01,
premium_received=0.02,
realized_pnl=0.01,
open_index_px=90000.0,
open_index_px=2000.0,
close_index_px=80000.0,
initial_premium_usdt=20.0,
premium_ccy="BTC",
conn=conn,
bridge_id=bid,
@@ -120,6 +120,37 @@ class SpotVwapBridgeTests(unittest.TestCase):
self.assertIsNone(pnl2)
conn.close()
def test_closed_bridge_without_sell_vwap_is_missing(self):
"""已关桥但无 close_sell 时不得用指数冒充卖回价."""
from lib.options.options_pnl_settle_lib import resolve_true_pnl_usdt_from_trade
from lib.options.options_spot_bridge_lib import (
BRIDGE_CLOSED,
add_bridge_buy_fill,
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=1000)
add_bridge_buy_fill(conn, bid, usdt_spent=1000.0, coin_bought=0.01)
update_bridge(conn, bid, status=BRIDGE_CLOSED, close=True)
pnl, src = resolve_true_pnl_usdt_from_trade(
premium_paid=0.01,
realized_pnl=0.01,
open_index_px=100000.0,
close_index_px=90000.0,
initial_premium_usdt=1000.0,
premium_ccy="BTC",
conn=conn,
bridge_id=bid,
)
self.assertEqual(src, "none")
self.assertIsNone(pnl)
conn.close()
def test_buy_vwap_ignores_shrunk_coin_bought(self):
"""开仓后 coin_bought 被改成权利金量时,不得用 budget/缩水量当买价."""
from lib.options.options_spot_bridge_lib import (