From c781dfef089902d4473c6b63591695fe76a2615d Mon Sep 17 00:00:00 2001 From: aaron <> Date: Fri, 22 May 2026 11:41:42 +0800 Subject: [PATCH] 1 --- app/db/paper_trading.py | 92 +++++++++++++++++++++++++++++++++ app/web/routes_paper_trading.py | 7 +++ static/paper_trading.html | 21 +++++++- tests/test_paper_trading.py | 22 ++++++++ 4 files changed, 140 insertions(+), 2 deletions(-) diff --git a/app/db/paper_trading.py b/app/db/paper_trading.py index 4728d81..acf58ff 100644 --- a/app/db/paper_trading.py +++ b/app/db/paper_trading.py @@ -1256,6 +1256,98 @@ def get_paper_trading_summary(days: int = 30) -> dict: } +def get_paper_trading_performance(days: int = 30) -> dict: + days = max(1, min(_safe_int(days, 30), 365)) + cfg = paper_trading_config() + initial_equity = default_account_equity_usdt(cfg) + today = datetime.now().date() + start_date = today - timedelta(days=days - 1) + conn = get_conn() + try: + closed_rows = conn.execute( + """ + SELECT closed_at, realized_pnl_usdt + FROM paper_trades + WHERE status='closed' AND closed_at IS NOT NULL + ORDER BY closed_at ASC, id ASC + """ + ).fetchall() + open_unrealized = conn.execute( + """ + SELECT COALESCE(SUM(notional_usdt * pnl_pct / 100.0), 0) + FROM paper_trades + WHERE status='open' + """ + ).fetchone()[0] + finally: + conn.close() + + daily_realized = {} + realized_before = 0.0 + for row in closed_rows: + closed_at = _parse_time(row["closed_at"]) + pnl = _safe_float(row["realized_pnl_usdt"]) + if not closed_at: + continue + closed_date = closed_at.date() + if closed_date < start_date: + realized_before += pnl + continue + if closed_date > today: + continue + key = closed_date.isoformat() + daily_realized[key] = daily_realized.get(key, 0.0) + pnl + + points = [] + cumulative_realized = realized_before + prev_equity = initial_equity + realized_before + peak_equity = max(initial_equity, prev_equity) + max_drawdown_pct = 0.0 + max_drawdown_usdt = 0.0 + open_unrealized = _safe_float(open_unrealized) + + for idx in range(days): + day = start_date + timedelta(days=idx) + key = day.isoformat() + realized = round(daily_realized.get(key, 0.0), 8) + cumulative_realized += realized + unrealized = open_unrealized if day == today else 0.0 + equity = round(initial_equity + cumulative_realized + unrealized, 8) + daily_pnl = round(equity - prev_equity, 8) + daily_return_pct = round(daily_pnl / prev_equity * 100, 6) if prev_equity > 0 else 0.0 + peak_equity = max(peak_equity, equity) + drawdown_usdt = round(max(0.0, peak_equity - equity), 8) + drawdown_pct = round(drawdown_usdt / peak_equity * 100, 6) if peak_equity > 0 else 0.0 + max_drawdown_pct = max(max_drawdown_pct, drawdown_pct) + max_drawdown_usdt = max(max_drawdown_usdt, drawdown_usdt) + points.append( + { + "date": key, + "equity_usdt": equity, + "daily_pnl_usdt": daily_pnl, + "daily_return_pct": daily_return_pct, + "realized_pnl_usdt": round(cumulative_realized, 8), + "unrealized_pnl_usdt": round(unrealized, 8), + "return_pct": _account_return_pct(equity - initial_equity, initial_equity, cfg), + "drawdown_usdt": drawdown_usdt, + "drawdown_pct": drawdown_pct, + } + ) + prev_equity = equity + + total_pnl = round((points[-1]["equity_usdt"] - initial_equity) if points else 0.0, 8) + return { + "days": days, + "initial_equity_usdt": initial_equity, + "current_equity_usdt": points[-1]["equity_usdt"] if points else initial_equity, + "total_pnl_usdt": total_pnl, + "total_return_pct": _account_return_pct(total_pnl, initial_equity, cfg), + "max_drawdown_usdt": round(max_drawdown_usdt, 8), + "max_drawdown_pct": round(max_drawdown_pct, 6), + "points": points, + } + + def list_paper_trades(limit: int = 50, offset: int = 0, status: str = "") -> dict: limit = max(1, min(_safe_int(limit, 50), 200)) offset = max(0, _safe_int(offset, 0)) diff --git a/app/web/routes_paper_trading.py b/app/web/routes_paper_trading.py index c4dbdc3..4c7feec 100644 --- a/app/web/routes_paper_trading.py +++ b/app/web/routes_paper_trading.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Cookie from app.db.paper_trading import ( + get_paper_trading_performance, get_paper_trading_summary, list_paper_orders, list_paper_trade_events, @@ -19,6 +20,12 @@ async def api_paper_trading_summary(days: int = 30, altcoin_session: str = Cooki return get_paper_trading_summary(days=days) +@router.get("/api/paper-trading/performance") +async def api_paper_trading_performance(days: int = 30, altcoin_session: str = Cookie(default="")): + require_admin(altcoin_session) + return get_paper_trading_performance(days=days) + + @router.get("/api/paper-trading/trades") async def api_paper_trading_trades( limit: int = 50, diff --git a/static/paper_trading.html b/static/paper_trading.html index 3477fc0..eaebde9 100644 --- a/static/paper_trading.html +++ b/static/paper_trading.html @@ -2,7 +2,7 @@ {% block title %}AlphaX Agent — 策略交易{% endblock %} {% block extra_head_css %} {% endblock %} {% block content %} @@ -20,6 +20,16 @@