fix(live): EOD对账重启兜底归因——remark指纹重建本实例订单集+实例唯一label注入;09-01 002646终局实锤:引擎订单注册表=进程内存态(轮换重启即失,runtime持久化只恢复策略/账本),新进程18:49 EOD报「本实例=0」→无守恒缺口→记日,当日15:05后才补全的迟到成交永久失联(get_trades内部本就是query_stock_trades,重试环与直查同源都不缺接口,缺的是归因锚);修=①runner_live经live_config注入strategy_name=live_{id},订单remark(bt:live_17:hash)实例唯一(此前六实例适配文件同名,label全为live_strateg不可分;仅对注入后新单生效)②eod_reconcile补_own_orders_remarked:broker.get_orders(QMT当日订单,带remark)按前缀筛本实例→shim补进own_by_broker(setdefault,engine自己的单优先),状态复用broker._map_order_status,is_buy缺失弃用(宁漏勿错);_PENDING不重建(EOD兜底已覆盖当日全量,省一条风险面);+4重启形态测试+1注入缝钉子,557绿 [vps]
CI/CD / test (push) Successful in 11s
CI/CD / nas-deploy (push) Successful in 5s
CI/CD / nas-verify (push) Successful in 10s

This commit is contained in:
2026-09-01 19:18:22 +08:00
parent 8bcca3146b
commit 7b7667e0ac
4 changed files with 168 additions and 3 deletions
+86
View File
@@ -558,3 +558,89 @@ class TestLedgerSeenIds:
assert snap == {"t1"}
snap.add("t2") # 副本可改,不污染账本
assert led.seen_trade_ids() == {"t1"}
# ------------------ 重启兜底:remark 指纹重建(2026-09-01 002646 事故) ------------------
def _qmt_order_row(order_id="1090", remark="bt:live_19:ab12cd34",
security="600276.XSHG", amount=600, filled=600,
status=56, is_buy=True):
"""贴 QmtBroker.sync_orders 行键(状态为 raw 码,56=全部成交)。"""
return {"order_id": order_id, "security": security, "amount": amount,
"filled": filled, "status": status, "is_buy": is_buy,
"order_remark": remark}
def _restarted_engine(qmt_orders, qmt_trades):
"""重启后的 engine 替身:订单注册表为空(进程态已失),broker 行为贴
QmtBroker(get_orders 带 remark + _map_order_status 归一)。"""
broker = SimpleNamespace(
get_orders=lambda: list(qmt_orders),
get_trades=lambda: list(qmt_trades),
_map_order_status=lambda raw: SimpleNamespace(
value="filled" if raw == 56 else f"raw_{raw}"),
)
return SimpleNamespace(get_orders=lambda: {}, broker=broker)
class TestRemarkRebuildAfterRestart:
"""002646 形态:轮换杀进程 → QMT 视图 18:48-19:15 才补全迟到成交 →
新进程 EOD「本实例=0」记日,孤儿成交永久失联。remark 指纹重建 own 集
后,重启照样能归因/补插/守恒。"""
def test_backfill_via_remark_after_restart(self, db):
"""engine 订单空 + QMT 订单 remark=本实例 → 迟到成交照常补插+守恒平。"""
led = LiveInstanceLedger(initial_cash=1_000_000)
eng = _restarted_engine(
[_qmt_order_row(order_id="1090", amount=600, filled=600)],
[_qmt_trade(order_id="1090", security="600276.XSHG",
amount=600, price=7.0, trade_id="99016")])
summary = eod_reconcile(eng, led, db, 19, "s")
assert summary["ours"] == 1 # remark 重建归因成功
assert summary["backfilled"] == 1 # 002646 那笔 600 股补回来
assert summary["conservation_gaps"] == []
rows = live_reconcile_eod_rows(db, 19)
assert rows and rows[-1]["volume"] == 600
def test_other_instance_remark_not_attributed(self, db):
"""别家实例的 remark 前缀(live_18)不归因给 live_19。"""
led = LiveInstanceLedger(initial_cash=1_000_000)
eng = _restarted_engine(
[_qmt_order_row(order_id="1080", remark="bt:live_18:ffff1111")],
[_qmt_trade(order_id="1080", security="600276.XSHG",
amount=600, price=7.0, trade_id="99017")])
summary = eod_reconcile(eng, led, db, 19, "s")
assert summary["ours"] == 0
assert summary["backfilled"] == 0
assert summary["foreign"] == 1
def test_row_without_side_excluded(self, db, caplog):
"""is_buy 缺失的 remark 行弃用——方向不明宁可漏归因不可错归因。"""
led = LiveInstanceLedger(initial_cash=1_000_000)
row = _qmt_order_row(order_id="1091")
row["is_buy"] = None
eng = _restarted_engine(
[row],
[_qmt_trade(order_id="1091", security="600276.XSHG",
amount=100, price=7.0, trade_id="99018")])
with caplog.at_level("WARNING"):
summary = eod_reconcile(eng, led, db, 19, "s")
assert summary["backfilled"] == 0
assert any("方向" in r.message or "弃用" in r.message
for r in caplog.records)
def test_sell_direction_propagates(self, db):
"""卖出方向的 remark 重建单,补插行 direction=sell(错方向入账=反向污染)。"""
led = LiveInstanceLedger(initial_cash=1_000_000)
eng = _restarted_engine(
[_qmt_order_row(order_id="1092", is_buy=False)],
[_qmt_trade(order_id="1092", security="600276.XSHG",
amount=600, price=7.0, trade_id="99019")])
summary = eod_reconcile(eng, led, db, 19, "s")
assert summary["backfilled"] == 1
rows = live_reconcile_eod_rows(db, 19)
assert rows[-1]["direction"] == "sell"
def live_reconcile_eod_rows(db, account_id):
from sanguo_live.persistence import list_trades
return list_trades(db, account_id)
@@ -56,3 +56,13 @@ def test_backoff_sleep_invoked_per_failure():
with_connect_retry(b, attempts=5, wait_sec=7.0, _sleep=slept.append)
b.connect()
assert slept == [7.0, 7.0] # 每次失败后睡一次
def test_live_config_carries_instance_strategy_name():
"""(2026-09-01 002646)runner_live 经 live_config 注入 strategy_name=
live_{id},使 bullet_trade 订单 remark label(bt:live_17:…)实例唯一——
EOD 对账重启兜底归因(live_reconcile._own_orders_remarked)的前提。
钉住 LiveConfig.load 吃这个键的 seam,上游升级若丢它必红。"""
from bullet_trade.core.live_engine import LiveConfig
cfg = LiveConfig.load({"strategy_name": "live_17"})
assert cfg.strategy_name == "live_17"