113 lines
5.2 KiB
Python
113 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""trade_receipt:成交回报回执解析+回执↔账本闭环比对。
|
|
|
|
背景(2026-09-04 实证):柜台有成交、台账无行、引擎会话视图也无行——
|
|
故障点在「推送到达→回调处理→落库」三跳上且全静默。①(引擎侧)每笔回调
|
|
到达即打 [trade-receipt] 指纹行;本模块②解析该行并与 live_trades 闭环:
|
|
有回执无账行=引擎处理丢,柜台有成交无回执=柜台没推——一分为二定位。
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from sanguo_trader.shadow.trade_receipt import (
|
|
compare_receipts_with_ledger,
|
|
parse_trade_receipts,
|
|
)
|
|
|
|
|
|
class TestParseTradeReceipts:
|
|
def test_parse_full_line(self):
|
|
text = ("2026-09-04 09:34:23,101 INFO __main__: [trade-receipt] "
|
|
"tid=b9765f order=1098910971 acc=20 sym=002633.XSHE "
|
|
"vol=100 px=14.84 t_raw=09:34:23 recv=09:34:23.101")
|
|
rs = parse_trade_receipts(text)
|
|
assert len(rs) == 1
|
|
r = rs[0]
|
|
assert r["tid"] == "b9765f"
|
|
assert r["order"] == "1098910971"
|
|
assert r["acc"] == "20"
|
|
assert r["sym"] == "002633.XSHE"
|
|
assert r["vol"] == 100
|
|
assert r["px"] == 14.84
|
|
assert r["t_raw"] == "09:34:23"
|
|
|
|
def test_parse_none_t_raw_still_counted(self):
|
|
"""时间字段退化(2026-09-04 实证形态)也必须留痕可解析。"""
|
|
text = ("2026-09-04 13:37:38,975 INFO __main__: [trade-receipt] "
|
|
"tid=5d9496 order=1098910973 acc=20 sym=002633.XSHE "
|
|
"vol=400 px=14.84 t_raw=NONE recv=13:37:38.975")
|
|
rs = parse_trade_receipts(text)
|
|
assert len(rs) == 1
|
|
assert rs[0]["t_raw"] is None
|
|
assert rs[0]["vol"] == 400
|
|
|
|
def test_parse_ignores_noise_and_duplicates(self):
|
|
"""噪线不误吞;同 tid 重复回执去重计数(重复本身值得知道)。"""
|
|
text = "\n".join([
|
|
"2026-09-04 09:30:00 INFO jq_strategy: 开盘调度 start",
|
|
("2026-09-04 09:34:23 INFO __main__: [trade-receipt] tid=aaa "
|
|
"order=1 acc=20 sym=600036.XSHG vol=100 px=41.4 t_raw=09:34:23 recv=09:34:23.5"),
|
|
"2026-09-04 09:34:24 INFO __main__: [instance-ledger] 恢复 1 笔",
|
|
("2026-09-04 09:34:25 INFO __main__: [trade-receipt] tid=aaa "
|
|
"order=1 acc=20 sym=600036.XSHG vol=100 px=41.4 t_raw=09:34:23 recv=09:34:25.0"),
|
|
])
|
|
rs = parse_trade_receipts(text)
|
|
assert len(rs) == 1
|
|
assert rs[0]["dup_count"] == 2
|
|
|
|
def test_parse_tid_missing_kept_with_none(self):
|
|
"""tid 缺失(异常形态)不丢弃行——留痕优先。"""
|
|
text = ("2026-09-04 09:34:23 INFO __main__: [trade-receipt] "
|
|
"tid=NONE order=2 acc=20 sym=510500.XSHG vol=100 px=7.85 t_raw=NONE recv=x")
|
|
rs = parse_trade_receipts(text)
|
|
assert len(rs) == 1
|
|
assert rs[0]["tid"] is None
|
|
|
|
|
|
class TestCompareReceiptsWithLedger:
|
|
def test_all_matched_clean(self):
|
|
receipts = [
|
|
{"tid": "a", "order": "1", "sym": "002633.XSHE", "vol": 100, "px": 14.84},
|
|
{"tid": "b", "order": "1", "sym": "002633.XSHE", "vol": 400, "px": 14.84},
|
|
]
|
|
ledger = [
|
|
{"tid": "a", "symbol": "002633.XSHE", "volume": 100, "price": 14.84},
|
|
{"tid": "b", "symbol": "002633.XSHE", "volume": 400, "price": 14.84},
|
|
]
|
|
rep = compare_receipts_with_ledger(receipts, ledger)
|
|
assert rep["receipts_n"] == 2
|
|
assert rep["ledger_n"] == 2
|
|
assert rep["matched_n"] == 2
|
|
assert rep["receipt_only"] == []
|
|
assert rep["ledger_only"] == []
|
|
assert rep["clean"] is True
|
|
|
|
def test_receipt_only_is_the_smoking_gun(self):
|
|
"""有回执无账行=引擎处理丢(②③之间)——2026-09-04 的未定位跳。"""
|
|
receipts = [
|
|
{"tid": "a", "order": "1", "sym": "002633.XSHE", "vol": 100, "px": 14.84},
|
|
{"tid": "b", "order": "1", "sym": "002633.XSHE", "vol": 100, "px": 14.84},
|
|
]
|
|
ledger = [{"tid": "a", "symbol": "002633.XSHE", "volume": 100, "price": 14.84}]
|
|
rep = compare_receipts_with_ledger(receipts, ledger)
|
|
assert rep["clean"] is False
|
|
assert [r["tid"] for r in rep["receipt_only"]] == ["b"]
|
|
|
|
def test_ledger_only_informational(self):
|
|
"""有账行无回执=回执日志后上线前的旧行/估算行,信息项不算脏。"""
|
|
receipts = []
|
|
ledger = [{"tid": "repair:x", "symbol": "002633.XSHE", "volume": 100, "price": 14.84}]
|
|
rep = compare_receipts_with_ledger(receipts, ledger)
|
|
assert rep["ledger_only"] == ledger
|
|
assert rep["clean"] is True # ledger_only 不判脏:只盯「到了没记」
|
|
|
|
def test_tid_none_rows_never_match_by_guess(self):
|
|
"""tid=NONE 的行不做模糊匹配硬凑——宁缺勿错。"""
|
|
receipts = [{"tid": None, "order": "1", "sym": "002633.XSHE", "vol": 100, "px": 14.84}]
|
|
ledger = [{"tid": None, "symbol": "002633.XSHE", "volume": 100, "price": 14.84}]
|
|
rep = compare_receipts_with_ledger(receipts, ledger)
|
|
# tid 缺失双方都不猜:receipt_only 与 ledger_only 各自如实列出
|
|
assert rep["matched_n"] == 0
|
|
assert len(rep["receipt_only"]) == 1
|
|
assert len(rep["ledger_only"]) == 1
|