From 5c334a4d89ed03ddd229acd09c3724b42b9c5e30 Mon Sep 17 00:00:00 2001 From: dekun Date: Sun, 2 Aug 2026 17:07:47 +0800 Subject: [PATCH] Use all-time closed groups for funds bar trade stats. Co-authored-by: Cursor --- backend/app/api/funds.py | 22 ++++++------ backend/tests/test_funds_summary.py | 54 +++++------------------------ 2 files changed, 18 insertions(+), 58 deletions(-) diff --git a/backend/app/api/funds.py b/backend/app/api/funds.py index f1f187b..543eed9 100644 --- a/backend/app/api/funds.py +++ b/backend/app/api/funds.py @@ -66,16 +66,14 @@ async def funds_summary(_user: Annotated[str, Depends(require_user)]) -> dict[st exchange = str(st.get("exchange") or s.exchange or "okx").upper() trading_day = datetime.now(SH).strftime("%Y-%m-%d") - # 顶栏「总交易 / 胜率 / 盈亏比」与交易日同一口径:上海自然日开仓组 G-YYYYMMDD-* - day_prefix = trading_day.replace("-", "") - day_groups = db.fetchall( - "SELECT realized_pnl FROM groups WHERE group_id LIKE ? AND status='closed'", - (f"G-{day_prefix}-%",), + # 顶栏「总交易 / 胜率 / 盈亏比」统一历史累计(全部已平组) + closed = db.fetchall( + "SELECT realized_pnl FROM groups WHERE status='closed'" ) - day_pnls = [float(r["realized_pnl"] or 0) for r in day_groups] - day_n = len(day_pnls) - day_wins = sum(1 for x in day_pnls if x > 0) - day_win_rate = (day_wins / day_n) if day_n else 0.0 + pnls = [float(r["realized_pnl"] or 0) for r in closed] + n = len(pnls) + wins = sum(1 for x in pnls if x > 0) + win_rate = (wins / n) if n else 0.0 pos = st.get("position") or {} realtime = None @@ -139,9 +137,9 @@ async def funds_summary(_user: Annotated[str, Depends(require_user)]) -> dict[st "mode": mode, "exchange": exchange, "trading_day": trading_day, - "total_trades": day_n, - "win_rate": day_win_rate, - "profit_loss_ratio": _pl_ratio(day_pnls), + "total_trades": n, + "win_rate": win_rate, + "profit_loss_ratio": _pl_ratio(pnls), "total_funds": total, "funding_usdt": funding_usdt, "trading_usdt": trading_usdt, diff --git a/backend/tests/test_funds_summary.py b/backend/tests/test_funds_summary.py index 8c88af0..6467c58 100644 --- a/backend/tests/test_funds_summary.py +++ b/backend/tests/test_funds_summary.py @@ -1,4 +1,4 @@ -"""顶栏资金摘要:总交易/胜率/盈亏比与交易日同口径。""" +"""顶栏资金摘要:总交易/胜率/盈亏比按历史全部已平组。""" from __future__ import annotations @@ -42,58 +42,18 @@ def _insert_closed(db: Database, *, group_id: str, pnl: float) -> None: db._conn.commit() -def test_day_stats_not_mixed_with_history(tmp_path, monkeypatch) -> None: +def test_all_time_stats_consistent(tmp_path, monkeypatch) -> None: monkeypatch.setenv("MODE", "SIM") db = Database(tmp_path / "funds_sum.db") set_db(db) try: - # 历史 3 胜 1 负 → 75%;当日仅 1 笔亏损 + # 历史 3 胜 1 负 → 75%;另有当日 1 笔亏损 → 合计 5 笔、胜率 60% _insert_closed(db, group_id="G-20260101-01", pnl=10.0) _insert_closed(db, group_id="G-20260101-02", pnl=20.0) _insert_closed(db, group_id="G-20260101-03", pnl=5.0) _insert_closed(db, group_id="G-20260101-04", pnl=-10.0) _insert_closed(db, group_id="G-20260802-01", pnl=-8.0) - class _FakeDT: - @staticmethod - def now(tz=None): - from datetime import timezone - - if tz is timezone.utc: - return datetime(2026, 8, 2, 4, 0, tzinfo=timezone.utc) - return datetime(2026, 8, 2, 12, 0, tzinfo=tz or ZoneInfo("Asia/Shanghai")) - - monkeypatch.setattr(funds_api, "datetime", _FakeDT) - - eng = SimpleNamespace( - state=lambda: { - "exchange": "okx", - "position": {"status": "flat", "net_pnl": None}, - } - ) - set_engine(eng) # type: ignore[arg-type] - - body = asyncio.run(funds_api.funds_summary(_user="admin")) - assert body["ok"] is True - assert body["trading_day"] == "2026-08-02" - assert body["total_trades"] == 1 - assert body["win_rate"] == 0.0 - assert body["profit_loss_ratio"] is None - finally: - set_engine(None) # type: ignore[arg-type] - set_db(None) - db.close() - - -def test_day_win_rate_matches_day_trades(tmp_path, monkeypatch) -> None: - monkeypatch.setenv("MODE", "SIM") - db = Database(tmp_path / "funds_sum2.db") - set_db(db) - try: - _insert_closed(db, group_id="G-20260802-01", pnl=10.0) - _insert_closed(db, group_id="G-20260802-02", pnl=20.0) - _insert_closed(db, group_id="G-20260802-03", pnl=-5.0) - class _FakeDT: @staticmethod def now(tz=None): @@ -114,10 +74,12 @@ def test_day_win_rate_matches_day_trades(tmp_path, monkeypatch) -> None: ) body = asyncio.run(funds_api.funds_summary(_user="admin")) - assert body["total_trades"] == 3 - assert abs(body["win_rate"] - (2 / 3)) < 1e-9 + assert body["ok"] is True + assert body["trading_day"] == "2026-08-02" + assert body["total_trades"] == 5 + assert abs(body["win_rate"] - 0.6) < 1e-9 assert body["profit_loss_ratio"] is not None finally: - set_engine(None) # type: ignore[arg-type] + set_engine(None) set_db(None) db.close()