diff --git a/sanguo_trader/shadow/reconcile_report.py b/sanguo_trader/shadow/reconcile_report.py index 1fd228b..8bea2c4 100644 --- a/sanguo_trader/shadow/reconcile_report.py +++ b/sanguo_trader/shadow/reconcile_report.py @@ -104,13 +104,16 @@ def _reconcile_trades(conn: sqlite3.Connection, live_id: int, shadow_id: int, "price_diff_bps": round(bps, 2) if bps is not None else None, }) avg_bps = sum(diffs) / len(diffs) if diffs else 0.0 - # 笔数=原始成交行数(非聚合桶):实盘部分成交会多行,差异如实呈现待归因 + # 笔数口径(2026-08-24 修订):count_match 按 票+方向 聚合桶总量比较——实盘 + # QMT 部分成交会把一笔 3800 股拆 19 行(08-24 momentum 27 vs 5 恒 False, + # 纯计数噪音);原始行数仍如实呈现(live_count/shadow_count)供归因。 + volume_match = all(r["live_volume"] == r["shadow_volume"] for r in rows) live_count = len(live_rows) shadow_count = len(shadow_rows) return { "live_count": live_count, "shadow_count": shadow_count, - "count_match": live_count == shadow_count, + "count_match": volume_match, "rows": rows, "avg_price_diff_bps": round(avg_bps, 2), "pass_price": avg_bps <= PRICE_DIFF_BPS_MAX, diff --git a/tests/trader/test_shadow_reconcile_report.py b/tests/trader/test_shadow_reconcile_report.py index cb62b61..43b31c3 100644 --- a/tests/trader/test_shadow_reconcile_report.py +++ b/tests/trader/test_shadow_reconcile_report.py @@ -148,7 +148,11 @@ class TestBuildReconcileReport: assert r["nav"]["mtd_deviation_pct"] == pytest.approx(0, abs=1e-9) assert r["passed"] is True - def test_count_mismatch_fails(self, db): + def test_partial_fill_split_same_total_volume_matches(self, db): + """QMT 部分成交拆多行 vs 影子一行,总量一致 → count_match=True。 + + 2026-08-24 修订:旧口径按原始行数比(27 vs 5 恒 False,000887 一笔 + 3800 股被拆 19 行)纯计数噪音;改按 票+方向 聚合总量比。""" _add_live_account(db) _add_shadow_account(db) _add_live_trade(db, 5, "510300.SH", "buy", 4.00, 1000, f"{D} 09:35:00") @@ -156,7 +160,18 @@ class TestBuildReconcileReport: _add_paper_trade(db, 39, "510300.XSHG", "long", 4.00, 1500, f"{D} 09:35:00", D) r = build_reconcile_report(db, 5, 39, D) - assert r["trades"]["count_match"] is False # 2 vs 1 + assert r["trades"]["count_match"] is True # 聚合总量 1500 == 1500 + assert r["trades"]["live_count"] == 2 and r["trades"]["shadow_count"] == 1 # 原始行数仍如实呈现 + + def test_aggregated_volume_mismatch_fails(self, db): + """聚合总量不一致(真缺一笔/量差)→ count_match=False。""" + _add_live_account(db) + _add_shadow_account(db) + _add_live_trade(db, 5, "510300.SH", "buy", 4.00, 1500, f"{D} 09:35:00") + _add_paper_trade(db, 39, "510300.XSHG", "long", 4.00, 1200, + f"{D} 09:35:00", D) + r = build_reconcile_report(db, 5, 39, D) + assert r["trades"]["count_match"] is False # 1500 != 1200 def test_price_diff_over_10bps_fails(self, db): _add_live_account(db)