diff --git a/docs/规格-期权交易账户盈亏结算.md b/docs/规格-期权交易账户盈亏结算.md index 50a1803..3262252 100644 --- a/docs/规格-期权交易账户盈亏结算.md +++ b/docs/规格-期权交易账户盈亏结算.md @@ -149,7 +149,8 @@ - `net_realized_pnl` / 已平相关:仅 `pnl_settled=1` 的冻结值之和。 - `total_pnl`(持仓页合计)= `net_realized_pnl` + `open_float_pnl`。 -- 待结算已平单:不计入已实现,也不重复计入浮盈(浮盈仅当前期权持仓)。 +- 待结算已平单:不计入已实现,也不重复计入浮盈(浮盈仅当前期权持仓)。 +- **顶栏三项**(总交易 / 胜率 / 盈亏比):与上同一过滤口径(`header_options_stats_for_window`)。 ### 5.5 期权复盘 diff --git a/lib/options/options_stats_lib.py b/lib/options/options_stats_lib.py index a6685ec..00f8eee 100644 --- a/lib/options/options_stats_lib.py +++ b/lib/options/options_stats_lib.py @@ -821,9 +821,10 @@ def header_options_stats_for_window( app_tz, ex: Any = None, ) -> dict[str, Any]: - """顶栏总交易/胜率/盈亏比:纯期权(options_trades),全站导航共用,按列表窗过滤. + """顶栏总交易/胜率/盈亏比:与期权统计同一结算口径. - 币本位 realized_pnl 为币数量,须折 U 后再算盈亏比,否则 BTC/ETH 混算会严重失真. + 新单(有 trading_usdt_before):仅 pnl_settled=1 的账户差分计入; + 旧单(无 before):仍按原 realized 兼容.币数量须折 U 后再算盈亏比. """ from lib.common.history_window_lib import utc_window_to_bj_sql_strings @@ -841,7 +842,11 @@ def header_options_stats_for_window( created_at, closed_at, inst_id, underlying, premium_ccy FROM options_trades WHERE status = 'closed' - AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL) + AND ( + (COALESCE(pnl_settled, 0) = 1 AND realized_pnl_usdt IS NOT NULL) + OR (trading_usdt_before IS NULL + AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL)) + ) AND COALESCE(closed_at, created_at) >= ? AND COALESCE(closed_at, created_at) <= ? """, diff --git a/tests/test_options_account_pnl.py b/tests/test_options_account_pnl.py index 8b8ec06..25829e6 100644 --- a/tests/test_options_account_pnl.py +++ b/tests/test_options_account_pnl.py @@ -99,6 +99,52 @@ class AccountPnlSettleTests(unittest.TestCase): self.assertAlmostEqual(float(stats["net_realized_pnl"]), 952.96, places=2) conn.close() + def test_header_stats_skip_unsettled_account_trades(self): + from datetime import datetime, timezone + + from lib.options.options_db import init_options_tables + from lib.options.options_stats_lib import header_options_stats_for_window + + 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, realized_pnl, premium_ccy, 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', + 500.0, 0, '2026-10-08 10:00:00', '2026-10-08 12:00:00' + ) + """ + ) + conn.execute( + """ + INSERT INTO options_trades( + inst_id, underlying, opt_type, sheets, eth_amount, + premium_paid, realized_pnl, premium_ccy, 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', + 1000.0, 1100.0, 100.0, + 1, '2026-10-08 08:00:00', '2026-10-08 09:00:00' + ) + """ + ) + conn.commit() + win = { + "start_utc": datetime(2026, 10, 1, tzinfo=timezone.utc), + "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) + conn.close() + if __name__ == "__main__": unittest.main()