Dedupe duplicate ticket closes so stats match MT5 today PnL.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -174,6 +174,45 @@ def scrub_synthetic_trade_profits(conn: sqlite3.Connection) -> int:
|
||||
return n
|
||||
|
||||
|
||||
def scrub_duplicate_ticket_profits(conn: sqlite3.Connection) -> int:
|
||||
"""同一账户+单号多条真实平仓时,只保留 id 最大的一条盈亏,其余标为去重。"""
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT id, account_id, ticket, profit, snapshot_json, close_time
|
||||
FROM trades
|
||||
WHERE ticket IS NOT NULL AND ticket != ''
|
||||
AND close_time IS NOT NULL AND close_time != ''
|
||||
ORDER BY account_id, ticket, id DESC
|
||||
"""
|
||||
).fetchall()
|
||||
keep: set[tuple[Any, str]] = set()
|
||||
n = 0
|
||||
now_ts = now()
|
||||
for r in rows:
|
||||
if is_synthetic_close(r["snapshot_json"]):
|
||||
continue
|
||||
key = (r["account_id"], str(r["ticket"]))
|
||||
if key not in keep:
|
||||
keep.add(key)
|
||||
continue
|
||||
# 重复平仓副本:清空盈亏并标记
|
||||
try:
|
||||
snap = json.loads(r["snapshot_json"] or "{}")
|
||||
except json.JSONDecodeError:
|
||||
snap = {}
|
||||
if not isinstance(snap, dict):
|
||||
snap = {}
|
||||
snap["event"] = "dedupe_closed"
|
||||
snap["deduped_at"] = now_ts
|
||||
snap["reason"] = "duplicate_ticket_close"
|
||||
conn.execute(
|
||||
"UPDATE trades SET profit=NULL, snapshot_json=? WHERE id=?",
|
||||
(json.dumps(snap, ensure_ascii=False), r["id"]),
|
||||
)
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def row_to_dict(row: Optional[sqlite3.Row]) -> Optional[dict]:
|
||||
if row is None:
|
||||
return None
|
||||
@@ -537,6 +576,16 @@ def trade_stats(
|
||||
|
||||
rows: list[dict] = []
|
||||
skipped_synthetic = 0
|
||||
skipped_dup_ticket = 0
|
||||
seen_closed_ticket: set[str] = set()
|
||||
# raw 已按时间 DESC;再按 id 大的优先,便于同 ticket 去重留最新
|
||||
raw_rows.sort(
|
||||
key=lambda t: (
|
||||
float(t.get("close_time") or t.get("open_time") or t.get("created_at") or 0),
|
||||
int(t.get("id") or 0),
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
for t in raw_rows:
|
||||
closed_flag = t.get("close_time") not in (None, "")
|
||||
if closed_flag and is_synthetic_close(t.get("snapshot_json")):
|
||||
@@ -560,6 +609,13 @@ def trade_stats(
|
||||
continue
|
||||
if ots < start_ts or ots >= end_ts:
|
||||
continue
|
||||
if closed_flag:
|
||||
tk = str(t.get("ticket") or "").strip()
|
||||
if tk:
|
||||
if tk in seen_closed_ticket:
|
||||
skipped_dup_ticket += 1
|
||||
continue
|
||||
seen_closed_ticket.add(tk)
|
||||
rows.append(t)
|
||||
|
||||
total = len(rows)
|
||||
@@ -656,6 +712,7 @@ def trade_stats(
|
||||
"annotated": annotated,
|
||||
"unannotated": total - annotated,
|
||||
"skipped_synthetic": skipped_synthetic,
|
||||
"skipped_dup_ticket": skipped_dup_ticket,
|
||||
"period": period,
|
||||
"day": day_label,
|
||||
"by_hold": [_finalize_group(by_hold[k]) for k in ("lt_5m", "m5_30", "m30_2h", "ge_2h", "unknown") if by_hold[k]["count"] or k != "unknown"],
|
||||
|
||||
Reference in New Issue
Block a user