Use all-time closed groups for funds bar trade stats.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 17:07:47 +08:00
parent d97f7d20e5
commit 5c334a4d89
2 changed files with 18 additions and 58 deletions
+10 -12
View File
@@ -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,
+8 -46
View File
@@ -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()