132 lines
6.0 KiB
Python
132 lines
6.0 KiB
Python
# -*- 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)
|
|
|
|
# 对账连续不一致天数归零 —— 账本刚清空, 上一轮攒下的 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}")
|
|
|
|
# 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()
|