fix期权复盘与数据统计双口径盈亏,并修复卖回封顶导致盈利单冻成亏损。

平仓卖光可卖币后再按账户差分结算复盘;数据统计改为历史币盈亏×卖回价;复盘展示初始权利金与盈利率,并附历史修复脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 09:10:13 +08:00
parent d4de6a23ae
commit 9d77a430e5
16 changed files with 2858 additions and 2242 deletions
+67 -20
View File
@@ -51,55 +51,59 @@ class AccountPnlSettleTests(unittest.TestCase):
self.assertAlmostEqual(float(row2["realized_pnl_usdt"]), 947.05, places=2)
conn.close()
def test_stats_only_count_settled_account_trades(self):
def test_stats_use_coin_times_sell_px_not_account(self):
"""口径 B:有币盈亏即计入,U=币×卖回价;不读账户差分."""
from lib.options.options_db import init_options_tables
from lib.options.options_stats_lib import (
compute_options_stats_from_history,
history_pnl_to_usdt,
history_pnl_to_usdt_stats,
load_local_options_stats_history,
)
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
# 待结算:不进统计
# 账户未结算但有币盈亏+卖回价 → 进统计 B
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
status, trading_usdt_before, pnl_settled, created_at, closed_at
close_index_px, status, trading_usdt_before, pnl_settled,
created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.02, 0.01, 'ETH',
'closed', 500.0, 0, '2026-10-06 10:00:00', '2026-10-08 14:00:00'
3000.0, 'closed', 500.0, 0,
'2026-10-06 10:00:00', '2026-10-08 14:00:00'
)
"""
)
# 已结算
# 账户冻结为负、币盈亏为正 → 统计仍用币×价
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
status, trading_usdt_before, trading_usdt_after,
close_index_px, status, trading_usdt_before, trading_usdt_after,
realized_pnl_usdt, pnl_settled, created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.02, 0.01, 'ETH',
'closed', 1000.0, 1952.96,
952.96, 1, '2026-10-01 10:00:00', '2026-10-02 14:00:00'
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
0.01, 0.025, 0.015, 'BTC',
100000.0, 'closed', 1000.0, 948.82,
-51.18, 1, '2026-10-01 10:00:00', '2026-10-02 14:00:00'
)
"""
)
conn.commit()
hist = load_local_options_stats_history(conn, None)
stats = compute_options_stats_from_history(history_pnl_to_usdt(hist, None))
self.assertEqual(int(stats["total_closed"]), 1)
self.assertAlmostEqual(float(stats["net_realized_pnl"]), 952.96, places=2)
stats = compute_options_stats_from_history(history_pnl_to_usdt_stats(hist, None))
self.assertEqual(int(stats["total_closed"]), 2)
# 0.01*3000 + 0.015*100000 = 30 + 1500 = 1530
self.assertAlmostEqual(float(stats["net_realized_pnl"]), 1530.0, places=2)
conn.close()
def test_header_stats_skip_unsettled_account_trades(self):
def test_header_stats_count_coin_pnl_rows(self):
from datetime import datetime, timezone
from lib.options.options_db import init_options_tables
@@ -112,11 +116,11 @@ class AccountPnlSettleTests(unittest.TestCase):
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, realized_pnl, premium_ccy, status,
premium_paid, realized_pnl, premium_ccy, close_index_px, status,
trading_usdt_before, pnl_settled, created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.01, 'ETH', 'closed',
0.01, 0.01, 'ETH', 3000.0, 'closed',
500.0, 0, '2026-10-08 10:00:00', '2026-10-08 12:00:00'
)
"""
@@ -125,12 +129,12 @@ class AccountPnlSettleTests(unittest.TestCase):
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, realized_pnl, premium_ccy, status,
premium_paid, realized_pnl, premium_ccy, close_index_px, status,
trading_usdt_before, trading_usdt_after, realized_pnl_usdt,
pnl_settled, created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.01, 'ETH', 'closed',
0.01, 0.01, 'ETH', 3000.0, 'closed',
1000.0, 1100.0, 100.0,
1, '2026-10-08 08:00:00', '2026-10-08 09:00:00'
)
@@ -142,7 +146,50 @@ class AccountPnlSettleTests(unittest.TestCase):
"end_utc": datetime(2026, 10, 31, 23, 59, 59, tzinfo=timezone.utc),
}
out = header_options_stats_for_window(conn, win, timezone.utc, None)
self.assertEqual(int(out["total"]), 1)
self.assertEqual(int(out["total"]), 2)
conn.close()
def test_settle_blocked_when_sellable_coin(self):
from lib.options.options_account_pnl_lib import try_settle_after_sellback
from lib.options.options_db import init_options_tables
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, status, bridge_id, trading_usdt_before, pnl_settled,
created_at, closed_at
) VALUES (
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
0.01, 'closed', 7, 1000.0, 0,
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
)
"""
)
conn.commit()
class _Ex:
pass
# monkey: 仍有可卖 BTC
import lib.options.options_account_pnl_lib as mod
old_block = mod.sellable_coin_blocks_settle
mod.sellable_coin_blocks_settle = lambda ex, uly: "仍有可卖 BTC"
try:
n = try_settle_after_sellback(
conn, _Ex(), bridge_id=7, sell_complete=True
)
self.assertEqual(n, 0)
row = conn.execute(
"SELECT pnl_settled FROM options_trades WHERE id=1"
).fetchone()
self.assertEqual(int(row["pnl_settled"]), 0)
finally:
mod.sellable_coin_blocks_settle = old_block
conn.close()