From f6156a2cfebf178315c1c552ec11ef674be72f29 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 1 Mar 2026 11:55:00 +0000 Subject: [PATCH] Add strategy-aware paper trade schema and API endpoints --- backend/db.py | 4 +++ backend/main.py | 72 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/backend/db.py b/backend/db.py index 5a0df3a..041155b 100644 --- a/backend/db.py +++ b/backend/db.py @@ -355,5 +355,9 @@ def init_schema(): conn.rollback() # 忽略已存在错误 continue + cur.execute( + "ALTER TABLE paper_trades " + "ADD COLUMN IF NOT EXISTS strategy VARCHAR(32) DEFAULT 'v51_baseline'" + ) conn.commit() ensure_partitions() diff --git a/backend/main.py b/backend/main.py index 7baa144..2cf849f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -442,7 +442,7 @@ async def get_market_indicators(user: dict = Depends(get_current_user)): result = {} for sym in SYMBOLS: indicators = {} - for ind_type in ["long_short_ratio", "top_trader_position", "open_interest_hist", "coinbase_premium"]: + for ind_type in ["long_short_ratio", "top_trader_position", "open_interest_hist", "coinbase_premium", "funding_rate"]: row = await async_fetchrow( "SELECT value, timestamp_ms FROM market_indicators WHERE symbol = $1 AND indicator_type = $2 ORDER BY timestamp_ms DESC LIMIT 1", sym, @@ -568,8 +568,8 @@ async def paper_summary(user: dict = Depends(get_current_user)): async def paper_positions(user: dict = Depends(get_current_user)): """当前活跃持仓(含实时价格和浮动盈亏)""" rows = await async_fetch( - "SELECT id, symbol, direction, score, tier, entry_price, entry_ts, " - "tp1_price, tp2_price, sl_price, tp1_hit, status, atr_at_entry " + "SELECT id, symbol, direction, score, tier, strategy, entry_price, entry_ts, " + "tp1_price, tp2_price, sl_price, tp1_hit, status, atr_at_entry, score_factors " "FROM paper_trades WHERE status IN ('active','tp1_hit') ORDER BY entry_ts DESC" ) # 从币安API获取实时价格 @@ -624,6 +624,7 @@ async def paper_positions(user: dict = Depends(get_current_user)): async def paper_trades( symbol: str = "all", result: str = "all", + strategy: str = "all", limit: int = 100, user: dict = Depends(get_current_user), ): @@ -642,11 +643,16 @@ async def paper_trades( elif result == "loss": conditions.append("pnl_r <= 0") + if strategy != "all": + conditions.append(f"strategy = ${idx}") + params.append(strategy) + idx += 1 + where = " AND ".join(conditions) params.append(limit) rows = await async_fetch( - f"SELECT id, symbol, direction, score, tier, entry_price, exit_price, " - f"entry_ts, exit_ts, pnl_r, status, tp1_hit " + f"SELECT id, symbol, direction, score, tier, strategy, entry_price, exit_price, " + f"entry_ts, exit_ts, pnl_r, status, tp1_hit, score_factors " f"FROM paper_trades WHERE {where} ORDER BY exit_ts DESC LIMIT ${idx}", *params ) @@ -788,6 +794,62 @@ async def paper_stats(user: dict = Depends(get_current_user)): } +@app.get("/api/paper/stats-by-strategy") +async def paper_stats_by_strategy(user: dict = Depends(get_current_user)): + """按策略聚合模拟盘表现""" + rows = await async_fetch( + "SELECT strategy, pnl_r FROM paper_trades WHERE status NOT IN ('active','tp1_hit')" + ) + active_rows = await async_fetch( + "SELECT strategy, COUNT(*) AS active_count FROM paper_trades " + "WHERE status IN ('active','tp1_hit') GROUP BY strategy" + ) + if not rows and not active_rows: + return {"data": []} + + active_map = {r["strategy"] or "v51_baseline": int(r["active_count"]) for r in active_rows} + by_strategy: dict[str, list[float]] = {} + for row in rows: + strategy = row["strategy"] or "v51_baseline" + by_strategy.setdefault(strategy, []).append(float(row["pnl_r"])) + + stats = [] + for strategy, pnls in by_strategy.items(): + total = len(pnls) + wins = [p for p in pnls if p > 0] + losses = [p for p in pnls if p <= 0] + avg_win = sum(wins) / len(wins) if wins else 0 + avg_loss = abs(sum(losses) / len(losses)) if losses else 0 + stats.append( + { + "strategy": strategy, + "total": total, + "win_rate": round((len(wins) / total) * 100, 1) if total else 0, + "total_pnl": round(sum(pnls), 2), + "avg_win": round(avg_win, 2), + "avg_loss": round(avg_loss, 2), + "active_positions": active_map.get(strategy, 0), + } + ) + + for strategy, active_count in active_map.items(): + if strategy not in by_strategy: + stats.append( + { + "strategy": strategy, + "total": 0, + "win_rate": 0, + "total_pnl": 0, + "avg_win": 0, + "avg_loss": 0, + "active_positions": active_count, + } + ) + + stats.sort(key=lambda x: x["strategy"]) + return {"data": stats} + + # ─── 服务器状态监控 ─────────────────────────────────────────────── import shutil, subprocess, psutil