102 lines
3.9 KiB
Python
102 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""成交回报回执:②解析 [trade-receipt] 指纹行 + 回执↔账本闭环比对。
|
|
|
|
背景(2026-09-04 实证):柜台有成交、台账无行、引擎会话成交视图也无行且
|
|
时间字段退化——「推送到达→回调处理→落库」三跳全静默,故障点无法定位。
|
|
观测契约:
|
|
① 引擎侧(策略 session 实现):成交回调任何处理之前打一行指纹,永不抛:
|
|
[trade-receipt] tid={柜台成交号} order={订单号} acc={账号} sym={代码}
|
|
vol={量} px={价} t_raw={回报自带时间,无则NONE} recv={本地时刻}
|
|
② 本模块:解析该行,与 live_trades 当日行闭环——
|
|
有回执无账行=引擎处理丢(②③之间);柜台有成交且无回据=柜台没推(由
|
|
EOD 恒等式/守恒缺口负责,不在此层)。
|
|
③ 账本行带柜台成交号(硬键),判重/补插不再依赖会退化的时间字段。
|
|
|
|
设计原则:宁缺勿错——tid=NONE 的行不做模糊匹配硬凑,如实分列。
|
|
"""
|
|
|
|
import re
|
|
from typing import Any, Dict, List
|
|
|
|
# 回执行锚:tag 全文匹配,字段名与①契约严格一致(勿改拼写)
|
|
_RECEIPT_RE = re.compile(
|
|
r"\[trade-receipt\]\s+tid=(\S+)\s+order=(\S+)\s+acc=(\S+)\s+sym=(\S+)\s+"
|
|
r"vol=(\S+)\s+px=(\S+)\s+t_raw=(\S+)\s+recv=(\S+)"
|
|
)
|
|
|
|
_NONE = {"NONE", "None", "none", ""}
|
|
|
|
|
|
def _or_none(v: str) -> Any:
|
|
return None if v in _NONE else v
|
|
|
|
|
|
def _to_float(v: str) -> Any:
|
|
try:
|
|
return float(v)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def parse_trade_receipts(log_text: str) -> List[Dict[str, Any]]:
|
|
"""从引擎日志文本解析全部回执行;同 tid 重复回执去重并计 dup_count。"""
|
|
seen: Dict[str, Dict[str, Any]] = {}
|
|
order_keys: List[str] = []
|
|
for m in _RECEIPT_RE.finditer(log_text):
|
|
tid = _or_none(m.group(1))
|
|
key = tid if tid is not None else "_orphan_%d" % len(order_keys)
|
|
if key in seen:
|
|
seen[key]["dup_count"] += 1
|
|
continue
|
|
row = {
|
|
"tid": tid,
|
|
"order": _or_none(m.group(2)),
|
|
"acc": _or_none(m.group(3)),
|
|
"sym": _or_none(m.group(4)),
|
|
"vol": _to_float(m.group(5)),
|
|
"px": _to_float(m.group(6)),
|
|
"t_raw": _or_none(m.group(7)),
|
|
"recv": _or_none(m.group(8)),
|
|
"dup_count": 1,
|
|
}
|
|
seen[key] = row
|
|
order_keys.append(key)
|
|
return [seen[k] for k in order_keys]
|
|
|
|
|
|
def compare_receipts_with_ledger(receipts: List[Dict[str, Any]],
|
|
ledger: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""回执(①的产物)与账本行(live_trades 映射,带 tid)闭环比对。
|
|
|
|
ledger 行键约定:tid/symbol/volume/price(由调用方从 DB 行映射)。
|
|
matched 只认 tid 硬键;tid 缺失双方不猜,各自入 receipt_only/ledger_only。
|
|
ledger_only 为信息项(回执上线前的旧数据/repair 行),不判脏;
|
|
receipt_only 才是警报——「到了没记」即引擎处理跳丢失的直接证据。
|
|
"""
|
|
ledger_by_tid: Dict[str, Dict[str, Any]] = {
|
|
r["tid"]: r for r in ledger if r.get("tid") is not None
|
|
}
|
|
receipt_only: List[Dict[str, Any]] = []
|
|
matched = 0
|
|
for r in receipts:
|
|
tid = r.get("tid")
|
|
if tid is not None and tid in ledger_by_tid:
|
|
matched += 1
|
|
else:
|
|
receipt_only.append(r)
|
|
matched_tids = set()
|
|
for r in receipts:
|
|
if r.get("tid") is not None and r.get("tid") in ledger_by_tid:
|
|
matched_tids.add(r["tid"])
|
|
ledger_only = [r for r in ledger
|
|
if r.get("tid") is not None and r["tid"] not in matched_tids]
|
|
ledger_only += [r for r in ledger if r.get("tid") is None]
|
|
return {
|
|
"receipts_n": len(receipts),
|
|
"ledger_n": len(ledger),
|
|
"matched_n": matched,
|
|
"receipt_only": receipt_only,
|
|
"ledger_only": ledger_only,
|
|
"clean": not receipt_only,
|
|
}
|