fix(shadow): 双轨对账count_match改按票+方向聚合总量——QMT部分成交拆行致27vs5恒False纯计数噪音 [vps]
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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user