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:
+100
-7
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user