Fix hub full-close double-booking trend plans.

Sync active plans after hub position close, merge final close snapshots per plan, and backfill missing trade records when ending an already-stopped plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 09:06:36 +08:00
parent e71bfe095c
commit cfa28e7f4e
6 changed files with 344 additions and 7 deletions
+100 -7
View File
@@ -8,6 +8,13 @@ from typing import Any, Callable, Optional
STRATEGY_TREND = "trend_pullback"
STRATEGY_ROLL = "roll"
STRATEGY_SNAPSHOTS_MAX_ROWS = 100
# 同一趋势计划只允许一条「结束类」快照(中控全平 + 监控止损 + 实例结束计划)
FINAL_TREND_CLOSE_RANK = {
"手动平仓": 3,
"止盈": 2,
"止损": 1,
}
FINAL_TREND_CLOSE_LABELS = tuple(FINAL_TREND_CLOSE_RANK.keys())
STRATEGY_SNAPSHOTS_SQL = """
CREATE TABLE IF NOT EXISTS strategy_trade_snapshots (
@@ -134,9 +141,30 @@ def _snapshot_key_exists(
return row is not None
def _final_trend_close_rank(result_label: str) -> int:
return int(FINAL_TREND_CLOSE_RANK.get((result_label or "").strip(), 0))
def _purge_weaker_trend_final_snapshots(
conn, plan_id: int, result_label: str
) -> None:
"""写入更高优先级结束快照时,删除同计划较弱的结束记录。"""
rank = _final_trend_close_rank(result_label)
if rank <= 0 or plan_id <= 0:
return
for label, lr in FINAL_TREND_CLOSE_RANK.items():
if lr < rank:
conn.execute(
"""DELETE FROM strategy_trade_snapshots
WHERE strategy_type=? AND source_id=? AND result_label=?""",
(STRATEGY_TREND, int(plan_id), label),
)
def dedupe_strategy_snapshots(conn) -> int:
"""删除同源同结果的重复快照,仅保留每组最大 id"""
"""删除重复快照:同结果去重 + 同计划仅保留最高优先级结束类记录"""
init_strategy_snapshot_table(conn)
removed = 0
cur = conn.execute(
"""DELETE FROM strategy_trade_snapshots
WHERE id IN (
@@ -148,7 +176,46 @@ def dedupe_strategy_snapshots(conn) -> int:
AND s1.id < s2.id
)"""
)
return int(getattr(cur, "rowcount", 0) or 0)
removed += int(getattr(cur, "rowcount", 0) or 0)
rows = conn.execute(
f"""SELECT id, source_id, result_label FROM strategy_trade_snapshots
WHERE strategy_type=? AND result_label IN ({",".join("?" * len(FINAL_TREND_CLOSE_LABELS))})""",
(STRATEGY_TREND, *FINAL_TREND_CLOSE_LABELS),
).fetchall()
by_plan: dict[int, list] = {}
for row in rows:
d = _row_dict(row)
try:
pid = int(d.get("source_id") or 0)
except (TypeError, ValueError):
pid = 0
if pid <= 0:
continue
by_plan.setdefault(pid, []).append(d)
drop_ids: list[int] = []
for snaps in by_plan.values():
if len(snaps) <= 1:
continue
best = max(
snaps,
key=lambda s: (
_final_trend_close_rank(str(s.get("result_label") or "")),
int(s.get("id") or 0),
),
)
keep_id = int(best.get("id") or 0)
for s in snaps:
sid = int(s.get("id") or 0)
if sid and sid != keep_id:
drop_ids.append(sid)
if drop_ids:
placeholders = ",".join("?" * len(drop_ids))
cur2 = conn.execute(
f"DELETE FROM strategy_trade_snapshots WHERE id IN ({placeholders})",
drop_ids,
)
removed += int(getattr(cur2, "rowcount", 0) or 0)
return removed
def save_trend_plan_snapshot(
@@ -167,7 +234,19 @@ def save_trend_plan_snapshot(
if plan_id <= 0:
return
label = (result_label or "").strip()
if _snapshot_key_exists(conn, STRATEGY_TREND, plan_id, label):
close_rank = _final_trend_close_rank(label)
if close_rank > 0:
existing = conn.execute(
f"""SELECT result_label FROM strategy_trade_snapshots
WHERE strategy_type=? AND source_id=? AND result_label IN ({",".join("?" * len(FINAL_TREND_CLOSE_LABELS))})""",
(STRATEGY_TREND, plan_id, *FINAL_TREND_CLOSE_LABELS),
).fetchall()
for ex in existing:
ex_label = str(_row_dict(ex).get("result_label") or "")
if _final_trend_close_rank(ex_label) >= close_rank:
return
_purge_weaker_trend_final_snapshots(conn, plan_id, label)
elif _snapshot_key_exists(conn, STRATEGY_TREND, plan_id, label):
return
m = cfg.get("app_module")
close_ts = (closed_at or "").strip() or (
@@ -413,14 +492,28 @@ def list_strategy_snapshots(conn, *, limit: int = 200) -> list[dict]:
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())
result_label = (enriched.get("result_label") or "").strip()
close_rank = _final_trend_close_rank(result_label)
if st == STRATEGY_TREND and source_id > 0 and close_rank > 0:
plan_key = (st, source_id)
snap_id = int(enriched.get("id") or 0)
prev = seen.get(plan_key)
if prev is not None:
prev_id, prev_rank = prev
if prev_rank > close_rank or (prev_rank == close_rank and prev_id >= snap_id):
continue
out = [x for x in out if int(x.get("id") or 0) != prev_id]
seen[plan_key] = (snap_id, close_rank)
out.append(enriched)
continue
key = (st, source_id, result_label)
snap_id = int(enriched.get("id") or 0)
prev = seen.get(key)
if prev is not None and prev >= snap_id:
if prev is not None and prev[0] >= 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 = [x for x in out if int(x.get("id") or 0) != prev[0]]
seen[key] = (snap_id, 0)
out.append(enriched)
return out