diff --git a/app/api/performance.py b/app/api/performance.py index 058094e..071a18b 100644 --- a/app/api/performance.py +++ b/app/api/performance.py @@ -23,6 +23,12 @@ async def get_performance( wallet: str = Query(..., description="Wallet address (lower-cased internally)"), ts: int = Query(..., description="Signed timestamp (ms)"), 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), ): wallet = wallet.lower().strip() @@ -43,13 +49,20 @@ async def get_performance( # 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 # 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) .where(BotTrade.wallet_address == wallet) .where(BotTrade.closed_at.is_not(None)) .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() total_trades = len(trades)