tradingSystem/scripts/reset_ledger.py

132 lines
6.0 KiB
Python
Raw Normal View History

2026-07-29 10:01:00 +08:00
# -*- coding: utf-8 -*-
"""
清空 PMS 账本并把回放游标对齐到当前 (影子运行期的重来一次)
===========================================================
运行:
docker compose run --rm pms-web python scripts/reset_ledger.py # 演练, 只统计不删
docker compose run --rm pms-web python scripts/reset_ledger.py --yes # 实际执行
docker compose run --rm pms-web python scripts/reset_ledger.py --yes --keep-commands
什么时候用
----------
账本被脏数据污染需要从干净起点重来时典型场景就是 2026-07-29 那次: 首次启动时回放游标
为空, `replay_fills` `trading_order` 表头开始扫, 把旧系统多年的历史成交**全部**当成
外部成交并入 BASE 批次 账本上凭空长出一堆早已清掉的持仓, 摊薄成本与安全垫全错
( bug 已修: 游标为空时只对齐不追认, ledger_service._seed_cursor)
**只动 PMS 自有表, 绝不碰下游的 trading_*** 那三张表归下游系统维护, PMS 只读
清完之后账本是空的, 而真实持仓在 QMT 那边要让账本反映真实持仓, 有两条路:
* 让日终对账把下游持仓补进来 (以下游为准, 会生成 RECON 批次),
* 在页面用建仓命令把已有持仓登记成 BASE 批次
影子运行期一般选前者: 跑一次 `POST /api/ops/reconcile?apply_fix=true`
"""
import argparse
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 账本类表 —— 会被清空
LEDGER_TABLES = ["pms_lot", "pms_position", "pms_instruction", "pms_action_ledger",
"pms_qmt_order", "pms_qmt_inbox"]
# 命令与方案 —— 默认也清 (方案指向的指令都没了, 留着只会是孤儿); --keep-commands 可保留
COMMAND_TABLES = ["pms_plan", "pms_command", "pms_proposal"]
# 绝不碰: 下游三表 / 行业映射 / 参数表 / 日报
NEVER_TOUCH = ["trading_order", "trading_position", "trading_buy_plan",
"pms_industry_map", "pms_runtime_param", "pms_daily_report"]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--yes", action="store_true", help="确认执行 (缺省只演练)")
ap.add_argument("--keep-commands", action="store_true",
help="保留命令/方案/提议, 只清账本与指令")
ap.add_argument("--no-seed", action="store_true",
help="不重置回放游标 (默认会把它对齐到当前最新成交)")
args = ap.parse_args()
from app.db.session import execute, fetch_one
from app.repo import downstream_repo, pms_repo
from app.services.ledger_service import CURSOR_KEY
tables = list(LEDGER_TABLES) + ([] if args.keep_commands else COMMAND_TABLES)
print("将清空以下表:")
total = 0
for t in tables:
try:
n = int((fetch_one(f"SELECT COUNT(*) AS n FROM {t}") or {}).get("n") or 0)
total += n
print(f" {t:<22} {n}")
except Exception as e:
print(f" {t:<22} 读取失败: {type(e).__name__}: {e}")
print(f"\n合计 {total} 行。以下表**不会**被动: {', '.join(NEVER_TOUCH)}")
cur = pms_repo.get_param(CURSOR_KEY)
print(f"\n当前回放游标: {cur or '(空)'}")
if not args.no_seed:
try:
anchor = downstream_repo.latest_filled_order_id()
print(f"将把游标对齐到当前最新成交: {anchor or '(下游暂无成交)'}")
print(" → 此后只跟踪新成交, 不再追认历史")
except Exception as e:
anchor = None
print(f"读下游最新成交失败 (游标将清空): {type(e).__name__}: {e}")
else:
anchor = None
print("--no-seed: 游标保持不变")
if not args.yes:
print("\n[演练模式] 未删除任何数据。确认后加 --yes 重跑:")
print(" docker compose run --rm pms-web python scripts/reset_ledger.py --yes")
return
print()
failed = []
for t in tables:
try:
n = execute(f"DELETE FROM {t}")
print(f" OK {t:<22} 删除 {n}")
except Exception as e:
print(f" FAIL {t:<22} {type(e).__name__}: {e}")
failed.append(t)
if not args.no_seed:
try:
pms_repo.set_param(CURSOR_KEY, anchor or "", updated_by="reset_ledger")
print(f" OK 回放游标 → {anchor or '(空, 下次回放自动对齐)'}")
except Exception as e:
print(f" FAIL 游标重置: {type(e).__name__}: {e}")
failed.append(CURSOR_KEY)
2026-07-29 10:12:12 +08:00
# 对账连续不一致天数归零 —— 账本刚清空, 上一轮攒下的 streak 会让下一次对账直接判 ERROR
try:
from app.services import param_store
param_store.set_param("PMS_RECON_STREAK", 0, "reset_ledger")
print(" OK PMS_RECON_STREAK 归零 (否则下次对账会带着旧的连续天数直接升 ERROR)")
except Exception as e:
print(f" WARN 对账连续天数归零失败: {type(e).__name__}: {e}")
2026-07-29 10:01:00 +08:00
# ws 通道状态一并归零 —— 指令与 inbox 都清了, 留着旧水位会让补发对不上
try:
execute("UPDATE pms_ws_state SET last_seq = 0, acked_seq = 0, server_seq = 0, "
"resync_flag = 0, conn_state = 'INIT', updated_at = NOW() WHERE id = 1")
print(" OK pms_ws_state 水位归零 (inbox 已清空, 水位必须跟着回零)")
except Exception as e:
print(f" WARN pms_ws_state 归零失败 (表可能还没建): {type(e).__name__}: {e}")
print("\n" + "-" * 62)
if failed:
print(f"FAILED: {', '.join(failed)}")
sys.exit(1)
print("账本已清空。下一步:")
print(" 1. 让对账把下游真实持仓补进账本 (以下游为准):")
print(" curl -X POST 'http://127.0.0.1:38100/api/ops/reconcile?apply_fix=true'")
print(" 2. 或在页面用建仓命令把已有持仓登记成 BASE 批次")
print(" 3. 核对: python scripts/check_db.py")
if __name__ == "__main__":
main()