Add win/loss metrics to archive stats with symbol filter sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+67
-29
@@ -1249,46 +1249,86 @@ def resolve_period_bounds(
|
||||
return start_ms, end_ms, d, d, f"本日 {d}"
|
||||
|
||||
|
||||
def _pnl_side(pnl: float) -> str:
|
||||
if pnl > 0.0001:
|
||||
return "win"
|
||||
if pnl < -0.0001:
|
||||
return "loss"
|
||||
return "flat"
|
||||
|
||||
|
||||
def _empty_pnl_bucket() -> dict[str, Any]:
|
||||
return {
|
||||
"open_count": 0,
|
||||
"sick_count": 0,
|
||||
"pnl_total": 0.0,
|
||||
"pnl_ex_sick": 0.0,
|
||||
"win_count": 0,
|
||||
"loss_count": 0,
|
||||
"avg_win": None,
|
||||
"avg_loss": None,
|
||||
"max_win": None,
|
||||
"max_loss": None,
|
||||
}
|
||||
|
||||
|
||||
def _finalize_pnl_bucket(bucket: dict[str, Any]) -> None:
|
||||
wins = bucket.pop("_wins", [])
|
||||
losses = bucket.pop("_losses", [])
|
||||
bucket["win_count"] = len(wins)
|
||||
bucket["loss_count"] = len(losses)
|
||||
bucket["avg_win"] = round(sum(wins) / len(wins), 4) if wins else None
|
||||
bucket["avg_loss"] = round(sum(losses) / len(losses), 4) if losses else None
|
||||
bucket["max_win"] = round(max(wins), 4) if wins else None
|
||||
bucket["max_loss"] = round(min(losses), 4) if losses else None
|
||||
bucket["pnl_total"] = round(float(bucket.get("pnl_total") or 0), 4)
|
||||
bucket["pnl_ex_sick"] = round(float(bucket.get("pnl_ex_sick") or 0), 4)
|
||||
|
||||
|
||||
def _accumulate_trade_stat(bucket: dict[str, Any], *, pnl: float, is_sick: bool) -> None:
|
||||
bucket["open_count"] += 1
|
||||
bucket["pnl_total"] += pnl
|
||||
if is_sick:
|
||||
bucket["sick_count"] += 1
|
||||
else:
|
||||
bucket["pnl_ex_sick"] += pnl
|
||||
side = _pnl_side(pnl)
|
||||
if side == "win":
|
||||
bucket.setdefault("_wins", []).append(pnl)
|
||||
elif side == "loss":
|
||||
bucket.setdefault("_losses", []).append(pnl)
|
||||
|
||||
|
||||
def _compute_period_stats(trade_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
total = len(trade_rows)
|
||||
sick = 0
|
||||
pnl_all = 0.0
|
||||
pnl_ex = 0.0
|
||||
total_bucket = _empty_pnl_bucket()
|
||||
by_ex: dict[str, dict[str, Any]] = {}
|
||||
for td_row in trade_rows:
|
||||
ex = str(td_row.get("exchange_key") or "?")
|
||||
pnl = float(td_row.get("pnl_amount") or 0)
|
||||
tag = str(td_row.get("behavior_tag") or "")
|
||||
is_sick = tag == "sick"
|
||||
if is_sick:
|
||||
sick += 1
|
||||
pnl_all += pnl
|
||||
if not is_sick:
|
||||
pnl_ex += pnl
|
||||
_accumulate_trade_stat(total_bucket, pnl=pnl, is_sick=is_sick)
|
||||
if ex not in by_ex:
|
||||
by_ex[ex] = {
|
||||
"open_count": 0,
|
||||
"sick_count": 0,
|
||||
"pnl_total": 0.0,
|
||||
"pnl_ex_sick": 0.0,
|
||||
}
|
||||
bucket = by_ex[ex]
|
||||
bucket["open_count"] += 1
|
||||
bucket["pnl_total"] += pnl
|
||||
if is_sick:
|
||||
bucket["sick_count"] += 1
|
||||
else:
|
||||
bucket["pnl_ex_sick"] += pnl
|
||||
by_ex[ex] = _empty_pnl_bucket()
|
||||
_accumulate_trade_stat(by_ex[ex], pnl=pnl, is_sick=is_sick)
|
||||
_finalize_pnl_bucket(total_bucket)
|
||||
for ex in by_ex:
|
||||
by_ex[ex]["pnl_total"] = round(by_ex[ex]["pnl_total"], 4)
|
||||
by_ex[ex]["pnl_ex_sick"] = round(by_ex[ex]["pnl_ex_sick"], 4)
|
||||
_finalize_pnl_bucket(by_ex[ex])
|
||||
total = int(total_bucket["open_count"] or 0)
|
||||
sick = int(total_bucket["sick_count"] or 0)
|
||||
sick_pct = round(sick / total * 100, 1) if total else 0.0
|
||||
return {
|
||||
"open_count": total,
|
||||
"sick_count": sick,
|
||||
"sick_pct": sick_pct,
|
||||
"pnl_total": round(pnl_all, 4),
|
||||
"pnl_ex_sick": round(pnl_ex, 4),
|
||||
"pnl_total": total_bucket["pnl_total"],
|
||||
"pnl_ex_sick": total_bucket["pnl_ex_sick"],
|
||||
"win_count": total_bucket["win_count"],
|
||||
"loss_count": total_bucket["loss_count"],
|
||||
"avg_win": total_bucket["avg_win"],
|
||||
"avg_loss": total_bucket["avg_loss"],
|
||||
"max_win": total_bucket["max_win"],
|
||||
"max_loss": total_bucket["max_loss"],
|
||||
"by_exchange": by_ex,
|
||||
}
|
||||
|
||||
@@ -1444,7 +1484,6 @@ def list_daily_trades(
|
||||
params,
|
||||
).fetchall()
|
||||
overlays_by_ex: dict[str, dict[int, dict]] = {}
|
||||
all_rows: list[dict[str, Any]] = []
|
||||
trades: list[dict[str, Any]] = []
|
||||
q = (search or "").strip().lower()
|
||||
for r in rows:
|
||||
@@ -1452,7 +1491,6 @@ def list_daily_trades(
|
||||
if ex_k not in overlays_by_ex:
|
||||
overlays_by_ex[ex_k] = load_overlays(ex_k, db_path=db_path)
|
||||
td_row = _trade_row_to_dict(r, overlays_by_ex[ex_k].get(int(r["trade_id"])))
|
||||
all_rows.append(td_row)
|
||||
pnl = float(td_row.get("pnl_amount") or 0)
|
||||
tag = td_row.get("behavior_tag") or ""
|
||||
if filter_profit and pnl <= 0.0001:
|
||||
@@ -1484,7 +1522,7 @@ def list_daily_trades(
|
||||
"date_from": df,
|
||||
"date_to": dt,
|
||||
"trades": trades,
|
||||
"stats": _compute_period_stats(all_rows),
|
||||
"stats": _compute_period_stats(trades),
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user