fix(live): 废单告警补第二肢治柜台级拒单盲区——08-31 T+1探针实锤:柜台拒单不进QMT当日订单API(query_stock_orders 15笔全SUCCEEDED不含被拒卖单),订单状态永停非终态,原告警等柜台报rejected结构性等不到;新肢=当日自提交订单EOD仍非终态且零成交→疑似废单(提交侧自证,不依赖QMT报状态;正常撤单已到终态不计,部分成交残量仍归守恒管);两肢合并入rejected_orders带why字段区分;+2测试(疑似单命中/撤单与跨日不误报);全量1368绿 [vps]
CI/CD / test (push) Successful in 4s
CI/CD / nas-deploy (push) Successful in 19s
CI/CD / nas-verify (push) Successful in 18s

This commit is contained in:
2026-09-01 12:17:08 +08:00
parent b695874b3c
commit 1d9265b922
2 changed files with 52 additions and 9 deletions
+20 -8
View File
@@ -310,22 +310,34 @@ def eod_reconcile(engine: Any, ledger: Any, db: str, account_id: int,
"order_volume": ov, "ledger_volume": lv, "gap": ov - lv,
})
# 当日废单告警(filled=0 的 rejected/failed):08-28 600818 实锤——盘中
# 废单只有 ERROR 日志躺在文件里,复盘才发现当日目标缺仓 ~8 万;EOD 汇总
# 让它当天可见。部分成交残量不进此清单(守恒/补插路径已覆盖)。
# 当日废单告警:08-28 600818 实锤——盘中废单只有 ERROR 日志躺在文件里,
# 复盘才发现当日目标缺仓 ~8 万;EOD 汇总让它当天可见。部分成交残量不进
# 此清单(守恒/补插路径已覆盖)。两肢:
# ① 柜台报 rejected/failed/error(交易所级拒单,QMT 订单视图可见);
# ② EOD 仍非终态且零成交的当日自提交订单 = 疑似废单——柜台级拒单
# 不进 QMT 当日订单 API(09-01 前实测 08-31 T+1 探针:query_stock_orders
# 15 笔全 SUCCEEDED,被拒的卖单根本不在列表),状态永停非终态,等
# 柜台报状态等不到,只能提交侧自证:下过的单收盘零成交又没到终态,
# 就是废单/挂死(当日目标缺仓同形态;正常撤单已到终态不计)。
rejected_orders = []
for o in own_by_broker.values():
if _status_str(getattr(o, "status", None)) not in ("rejected", "failed", "error"):
continue
st = _status_str(getattr(o, "status", None))
o_dt = getattr(o, "add_time", None)
if o_dt is not None and not str(o_dt).startswith(today):
continue
if int(getattr(o, "filled", 0) or 0) > 0:
continue
if st in ("rejected", "failed", "error"):
why = "废单(柜台报rejected)"
elif st not in _TERMINAL:
why = "疑似废单(EOD非终态零成交)"
else:
continue
rejected_orders.append({
"symbol": str(getattr(o, "security", "") or "")[:6],
"side": "buy" if bool(getattr(o, "is_buy", True)) else "sell",
"amount": int(getattr(o, "amount", 0) or 0)})
"amount": int(getattr(o, "amount", 0) or 0),
"why": why})
summary = {"qmt_trades": len(trades_all), "ours": ours,
"backfilled": backfilled,
@@ -363,8 +375,8 @@ def eod_reconcile(engine: Any, ledger: Any, db: str, account_id: int,
rsig = "%s|%s" % (today, rejected_orders)
if rsig != _LAST_REJ_LOG_SIG:
logger.warning(
"[live-reconcile] 当日废单 %d 笔 (account=%s): %s——当日目标缺仓,"
"查柜台废单原因(600818 形态)",
"[live-reconcile] 当日废单/疑似废单 %d 笔 (account=%s): %s——当日"
"目标缺仓,查柜台废单原因(600818/柜台拒单不进API 两形态)",
len(rejected_orders), account_id, rejected_orders)
_LAST_REJ_LOG_SIG = rsig
return summary
+32 -1
View File
@@ -354,7 +354,8 @@ class TestEodReconcile:
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 19, "momentum")
assert summary["rejected_orders"] == [
{"symbol": "600818", "side": "buy", "amount": 10900}]
{"symbol": "600818", "side": "buy", "amount": 10900,
"why": "废单(柜台报rejected)"}]
assert any("废单" in r.message for r in caplog.records)
def test_rejected_partial_or_crossday_not_in_alarm(self, db, caplog):
@@ -370,6 +371,36 @@ class TestEodReconcile:
assert summary["rejected_orders"] == []
assert not any("废单" in r.message for r in caplog.records)
def test_stale_order_suspected_alarm(self, db, caplog):
"""09-01 盲区补第二肢:当日订单 EOD 非终态+零成交 = 疑似废单。
08-31 T+1 探针实锤:柜台级拒单不进 QMT 当日订单 API,订单状态
永停非终态——等柜台报 rejected 等不到,只能提交侧自证。
"""
led = LiveInstanceLedger()
eng = _engine(
[_order(status="submitted", filled=0, amount=100, is_buy=False,
security="300059.XSHE", broker_oid="p1")], [])
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 18, "channel_test")
assert summary["rejected_orders"] == [
{"symbol": "300059", "side": "sell", "amount": 100,
"why": "疑似废单(EOD非终态零成交)"}]
assert any("废单" in r.message for r in caplog.records)
def test_cancelled_or_crossday_stale_not_suspected(self, db, caplog):
"""正常撤单(零成交但已到终态)与昨日挂死单都不进疑似清单。"""
led = LiveInstanceLedger()
eng = _engine(
[_order(status="cancelled", filled=0, broker_oid="c1"),
_order(status="submitted", filled=0, broker_oid="c2",
add_time_="2020-01-01 10:00:00")],
[])
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 18, "s")
assert summary["rejected_orders"] == []
assert not any("废单" in r.message for r in caplog.records)
def test_yesterday_order_no_conservation_gap(self, db, caplog):
"""08-31 守恒误报回归:引擎跨交易日存活(进程不死)时,**昨日**的
终态足额订单不得进当日守恒对比——台账只记当日行,历史订单计入