fix(performance): exclude paper trades by default (real-money tile must be real)
/performance summed paper + live P&L into one number. DashboardClient's 30d Performance tile consumes this directly and presents it as real performance — so a user who tried paper mode then went live saw simulated gains inflating the headline number on the homepage. Now: exclude hl_order_id=='paper' by default. Added optional ?include_paper query param for callers that explicitly want simulated stats. Analytics page already filters client-side by is_paper (separate fix), so both surfaces now agree: real-money numbers never include simulated fills. 72 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-2
@@ -23,6 +23,12 @@ async def get_performance(
|
|||||||
wallet: str = Query(..., description="Wallet address (lower-cased internally)"),
|
wallet: str = Query(..., description="Wallet address (lower-cased internally)"),
|
||||||
ts: int = Query(..., description="Signed timestamp (ms)"),
|
ts: int = Query(..., description="Signed timestamp (ms)"),
|
||||||
sig: str = Query(..., description="EIP-191 signature"),
|
sig: str = Query(..., description="EIP-191 signature"),
|
||||||
|
include_paper: bool = Query(
|
||||||
|
False,
|
||||||
|
description="Include paper (simulated) trades. Default false — this "
|
||||||
|
"endpoint reports REAL-money performance, so paper fills "
|
||||||
|
"(hl_order_id='paper') are excluded unless explicitly asked.",
|
||||||
|
),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
wallet = wallet.lower().strip()
|
wallet = wallet.lower().strip()
|
||||||
@@ -43,13 +49,20 @@ async def get_performance(
|
|||||||
# realization order. A trade opened before the window but closed inside it
|
# realization order. A trade opened before the window but closed inside it
|
||||||
# correctly counts; one opened inside but still open does not (closed_at IS
|
# correctly counts; one opened inside but still open does not (closed_at IS
|
||||||
# NOT NULL already excludes it).
|
# NOT NULL already excludes it).
|
||||||
result = await db.execute(
|
#
|
||||||
|
# MONEY-SAFETY: by default exclude paper trades (hl_order_id == "paper").
|
||||||
|
# Mixing simulated and real P&L into one "performance" number is misleading
|
||||||
|
# — the dashboard tile that consumes this shows it as real performance.
|
||||||
|
stmt = (
|
||||||
select(BotTrade)
|
select(BotTrade)
|
||||||
.where(BotTrade.wallet_address == wallet)
|
.where(BotTrade.wallet_address == wallet)
|
||||||
.where(BotTrade.closed_at.is_not(None))
|
.where(BotTrade.closed_at.is_not(None))
|
||||||
.where(BotTrade.closed_at >= since)
|
.where(BotTrade.closed_at >= since)
|
||||||
.order_by(BotTrade.closed_at.asc())
|
|
||||||
)
|
)
|
||||||
|
if not include_paper:
|
||||||
|
stmt = stmt.where(BotTrade.hl_order_id != "paper")
|
||||||
|
stmt = stmt.order_by(BotTrade.closed_at.asc())
|
||||||
|
result = await db.execute(stmt)
|
||||||
trades = result.scalars().all()
|
trades = result.scalars().all()
|
||||||
|
|
||||||
total_trades = len(trades)
|
total_trades = len(trades)
|
||||||
|
|||||||
Reference in New Issue
Block a user