fix(shadow): 双轨对账count_match改按票+方向聚合总量——QMT部分成交拆行致27vs5恒False纯计数噪音 [vps]
CI/CD / test (push) Successful in 14s
CI/CD / nas-deploy (push) Successful in 24s
CI/CD / nas-verify (push) Successful in 1s

08-24实录: momentum一笔3800股被QMT拆19笔fill,对账27vs5恒count_match=False,总量却分毫不差。旧口径按原始行数比(注释自称'差异如实呈现待归因',实则部分成交属成交粒度非分歧)。修=按聚合桶live_volume==shadow_volume判;原始行数保留在live_count/shadow_count供归因。真量差仍判False(新增测试钉死)。trader 254绿。
This commit is contained in:
2026-08-24 23:07:12 +08:00
parent e2dc473682
commit 31596f58f9
2 changed files with 22 additions and 4 deletions
+5 -2
View File
@@ -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,
+17 -2
View File
@@ -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)