Prevent duplicate strategy trade snapshots on plan close.

Finalize plans before writing snapshots, dedupe on startup and page load, and add a cleanup script for existing repeated rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 09:00:51 +08:00
parent ea92160d54
commit e71bfe095c
6 changed files with 355 additions and 11 deletions
+54 -1
View File
@@ -119,6 +119,38 @@ def attach_trend_dca_levels(plan: dict) -> dict:
return d
def _snapshot_key_exists(
conn, strategy_type: str, source_id: int, result_label: str
) -> bool:
if source_id <= 0:
return False
label = (result_label or "").strip()
row = conn.execute(
"""SELECT 1 FROM strategy_trade_snapshots
WHERE strategy_type=? AND source_id=? AND result_label=?
LIMIT 1""",
(strategy_type, int(source_id), label),
).fetchone()
return row is not None
def dedupe_strategy_snapshots(conn) -> int:
"""删除同源同结果的重复快照,仅保留每组最大 id。"""
init_strategy_snapshot_table(conn)
cur = conn.execute(
"""DELETE FROM strategy_trade_snapshots
WHERE id IN (
SELECT s1.id FROM strategy_trade_snapshots s1
INNER JOIN strategy_trade_snapshots s2
ON s1.strategy_type = s2.strategy_type
AND s1.source_id = s2.source_id
AND s1.result_label = s2.result_label
AND s1.id < s2.id
)"""
)
return int(getattr(cur, "rowcount", 0) or 0)
def save_trend_plan_snapshot(
cfg: dict,
conn,
@@ -134,6 +166,9 @@ def save_trend_plan_snapshot(
plan_id = int(row.get("id") or 0)
if plan_id <= 0:
return
label = (result_label or "").strip()
if _snapshot_key_exists(conn, STRATEGY_TREND, plan_id, label):
return
m = cfg.get("app_module")
close_ts = (closed_at or "").strip() or (
m.app_now_str()
@@ -181,6 +216,9 @@ def save_roll_group_snapshot(
gid = int(g.get("id") or 0)
if gid <= 0:
return
label = (result_label or "结束").strip()
if _snapshot_key_exists(conn, STRATEGY_ROLL, gid, label):
return
legs = []
for leg in conn.execute(
"SELECT * FROM roll_legs WHERE roll_group_id=? ORDER BY leg_index ASC, id ASC",
@@ -231,6 +269,7 @@ def save_roll_group_snapshot(
def prune_strategy_snapshots(conn, *, keep: int = STRATEGY_SNAPSHOTS_MAX_ROWS) -> None:
"""仅保留最近 keep 条策略快照(按 closed_at / id 倒序)。"""
dedupe_strategy_snapshots(conn)
k = max(1, min(int(keep), 500))
conn.execute(
"""DELETE FROM strategy_trade_snapshots
@@ -360,6 +399,7 @@ def list_strategy_snapshots(conn, *, limit: int = 200) -> list[dict]:
(max(1, min(int(limit), 500)),),
).fetchall()
out = []
seen: dict[tuple[str, int, str], int] = {}
for r in rows:
d = _row_dict(r)
try:
@@ -368,7 +408,20 @@ def list_strategy_snapshots(conn, *, limit: int = 200) -> list[dict]:
d["snapshot"] = {}
st = (d.get("strategy_type") or "").strip()
d["strategy_label"] = "趋势回调" if st == STRATEGY_TREND else "顺势加仓"
out.append(enrich_strategy_snapshot_row(d))
enriched = enrich_strategy_snapshot_row(d)
try:
source_id = int(enriched.get("source_id") or 0)
except (TypeError, ValueError):
source_id = 0
key = (st, source_id, (enriched.get("result_label") or "").strip())
snap_id = int(enriched.get("id") or 0)
prev = seen.get(key)
if prev is not None and prev >= snap_id:
continue
if prev is not None:
out = [x for x in out if int(x.get("id") or 0) != prev]
seen[key] = snap_id
out.append(enriched)
return out