fix(live): 守恒/废单跨日过滤属性错位根治——订单时间字段误读不存在的datetime(bullet_trade Order实为add_time,全字段扫描实锤)恒None→跨日过滤从不生效,引擎跨交易日存活时历史终态订单全进当日守恒对比(台账只记当日行)→每日假缺口告警+重试刷到23:00;09-01进程将首次跨日存活必首发作;两处同修(守恒+废单清单),测试替身datetime_=改回add_time_=(08-29周末提交曾喂不存在的datetime=把bug锁进断言)+新增昨日终态订单零缺口回归;531绿 [vps]
CI/CD / test (push) Successful in 6s
CI/CD / nas-deploy (push) Successful in 27s
CI/CD / nas-verify (push) Successful in 17s

This commit is contained in:
2026-08-31 18:32:20 +08:00
parent 6710591d44
commit b695874b3c
2 changed files with 28 additions and 5 deletions
+6 -2
View File
@@ -284,11 +284,15 @@ def eod_reconcile(engine: Any, ledger: Any, db: str, account_id: int,
ledger_vol[vkey] = ledger_vol.get(vkey, 0) + int(r.get("amount") or 0)
# 守恒校验:订单成交合计 vs 台账记录合计(前缀 6 位码归一,跨日订单不计)
# ⚠️ 订单时间字段=bullet_trade Order.add_time(08-31 修:原误读不存在的
# ``datetime`` 属性恒 None → 跨日过滤从不生效,引擎跨交易日存活时历史
# 订单全进对比 → 每日假缺口告警;测试替身当时被喂了 datetime= 字段,
# 把 bug 行为锁进了断言——替身必须贴真实字段名)
order_vol: Dict[tuple, int] = {}
for o in own_by_broker.values():
if _status_str(getattr(o, "status", None)) not in _TERMINAL:
continue
o_dt = getattr(o, "datetime", None)
o_dt = getattr(o, "add_time", None)
if o_dt is not None and not str(o_dt).startswith(today):
continue
filled = int(getattr(o, "filled", 0) or 0)
@@ -313,7 +317,7 @@ def eod_reconcile(engine: Any, ledger: Any, db: str, account_id: int,
for o in own_by_broker.values():
if _status_str(getattr(o, "status", None)) not in ("rejected", "failed", "error"):
continue
o_dt = getattr(o, "datetime", 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:
+22 -3
View File
@@ -26,11 +26,13 @@ from sanguo_portfolio.live_reconcile import (
# ------------------ 公共替身 ------------------
def _order(oid="o1", broker_oid="1001", security="000049.XSHE", is_buy=True,
amount=500, filled=0, status="open", datetime_=None):
amount=500, filled=0, status="open", add_time_=None):
"""替身字段名必须贴真实 bullet_trade Order(08-31 教训:曾被喂不存在的
``datetime``=,把守恒跨日过滤的属性错位 bug 锁进了断言)。"""
return SimpleNamespace(
order_id=oid, _broker_order_id=broker_oid, security=security,
is_buy=is_buy, amount=amount, filled=filled, status=status,
datetime=datetime_,
add_time=add_time_,
)
@@ -361,13 +363,30 @@ class TestEodReconcile:
eng = _engine(
[_order(status="rejected", filled=300, amount=500, broker_oid="a"),
_order(status="rejected", filled=0, amount=200, broker_oid="b",
datetime_="2020-01-01 10:00:00")],
add_time_="2020-01-01 10:00:00")],
[])
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 19, "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 守恒误报回归:引擎跨交易日存活(进程不死)时,**昨日**的
终态足额订单不得进当日守恒对比——台账只记当日行,历史订单计入
必然产生假缺口(原 ``datetime`` 属性误读恒 None,过滤从不生效)。"""
led = LiveInstanceLedger(initial_cash=1_000_000)
from datetime import timedelta as _td
yday = (datetime.now() - _td(days=1)).strftime("%Y-%m-%d 09:30:00")
eng = _engine(
[_order(status="filled", filled=500, broker_oid="old",
add_time_=yday),
_order(status="filled", filled=500, broker_oid="new")],
[_qmt_trade(order_id="new", trade_id="t-new")])
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 19, "s")
assert summary["conservation_gaps"] == []
assert summary["rejected_orders"] == []
def test_gap_warning_dedup_on_identical_retry(self, db, caplog):
"""缺口重试轮:内容不变只首轮回 WARNING,重复轮降级 INFO 防刷屏。"""
from sanguo_live.persistence import save_trade