docs: Add Common Pitfall #41 - Stats API P&L recalculation bug
Documented the stats API bug where recalculating P&L from entry/exit prices doesn't work for TP1+runner partial closes. Issue: - Stats showed -$26.10 when actual was +$46.97 - Recalculation used average exit price × full size - Doesn't account for different TP1 vs runner exit prices Fix: - Use database realizedPnL field directly - Database values corrected to match Drift UI TP1+runner sums - Each trade shows as 2 lines in Drift (TP1 + runner) Commits referenced: -cd6f590: Corrected database P&L values -d8b0307: Fixed stats API calculation Lesson: Partial closes require storing combined P&L, can't recalculate from average exit price.
This commit is contained in:
53
.github/copilot-instructions.md
vendored
53
.github/copilot-instructions.md
vendored
@@ -2049,7 +2049,58 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
|
||||
- **Verification:** Container restart + new code = no more ghost accumulation possible
|
||||
- **Lesson:** Critical validation logic must NEVER skip during error conditions - use fallback methods that don't require the failing resource
|
||||
|
||||
41. **Missing Telegram notifications for position closures (Fixed Nov 16, 2025):**
|
||||
41. **Stats API recalculating P&L incorrectly for TP1+runner trades (CRITICAL - Fixed Nov 19, 2025):**
|
||||
- **Symptom:** Withdrawal stats page showing -$26.10 P&L when Drift UI shows +$46.97
|
||||
- **Root Cause:** Stats API recalculating P&L from entry/exit prices, which doesn't work for TP1+runner partial closes
|
||||
- **The Problem:**
|
||||
* Each trade has 2 closes: TP1 (60-75%) at one price, runner (25-40%) at different price
|
||||
* Database stores combined P&L from both closes in `realizedPnL` field
|
||||
* Stats API used `positionSizeUSD × (exit - entry) / entry` formula
|
||||
* But `exitPrice` is AVERAGE of TP1 and runner exits, not actual exit prices
|
||||
* Formula: `(TP1_price × 0.6 + runner_price × 0.4) / 1.0` = average exit
|
||||
* Result: Incorrect P&L calculation (-$26.10 vs actual +$46.97)
|
||||
- **Real Example:**
|
||||
* Trade: Entry $138.36 → TP1 $137.66 (60%) + Runner $136.94 (40%)
|
||||
* Database: Combined P&L $54.19 (from Drift: $22.78 TP1 + $31.41 runner)
|
||||
* Stats recalc: $8,326 × (136.96 - 138.36) / 138.36 = $83.94 (wrong!)
|
||||
* Correct: Use database `realizedPnL` $54.19 directly
|
||||
- **Drift UI shows 10 lines for 5 trades:**
|
||||
* Each trade = 2 lines (TP1 close + runner close)
|
||||
* Line 1: TP1 60% at $137.66 = $22.78
|
||||
* Line 2: Runner 40% at $136.94 = $31.41
|
||||
* Total: $54.19 (stored in database `realizedPnL`)
|
||||
- **Fix (Nov 19, 2025):**
|
||||
```typescript
|
||||
// BEFORE (BROKEN - recalculated from entry/exit):
|
||||
const totalPnL = trades.reduce((sum, trade) => {
|
||||
const correctPnL = trade.positionSizeUSD * (
|
||||
trade.direction === 'long'
|
||||
? (trade.exitPrice - trade.entryPrice) / trade.entryPrice
|
||||
: (trade.entryPrice - trade.exitPrice) / trade.entryPrice
|
||||
)
|
||||
return sum + correctPnL
|
||||
}, 0)
|
||||
|
||||
// AFTER (FIXED - use database realizedPnL):
|
||||
const totalPnL = trades.reduce((sum, trade) => {
|
||||
return sum + Number(trade.realizedPnL)
|
||||
}, 0)
|
||||
```
|
||||
- **Database P&L Correction (Nov 19, 2025):**
|
||||
* Corrected inflated P&L values to match Drift UI actual TP1+runner sums
|
||||
* Trade cmi5p09y: 37.67 → 38.90 (TP1 $9.72 + runner $29.18)
|
||||
* Trade cmi5ie3c: 59.35 → 40.09 (TP1 $21.67 + runner $18.42)
|
||||
* Trade cmi5a6jm: 19.79 → 13.72 (TP1 $1.33 + runner $4.08 + $8.31)
|
||||
* v8 total: $46.97 (matches Drift UI exactly)
|
||||
* Commit: cd6f590 "fix: Correct v8 trade P&L to match Drift UI actual values"
|
||||
- **Impact:** Stats page now shows accurate v8 performance (+$46.97)
|
||||
- **Files Changed:**
|
||||
* `app/api/withdrawals/stats/route.ts` - Use `realizedPnL` not recalculation
|
||||
* Added debug logging: "📊 Stats API: Found X closed trades"
|
||||
* Commit: d8b0307 "fix: Use database realizedPnL instead of recalculating"
|
||||
- **Lesson:** When trades have partial closes (TP1/TP2/runner), the database `realizedPnL` is the source of truth. Entry/exit price calculations only work for full position closes. Average exit price × full size ≠ sum of partial close P&Ls.
|
||||
|
||||
42. **Missing Telegram notifications for position closures (Fixed Nov 16, 2025):**
|
||||
- **Symptom:** Position Manager closes trades (TP/SL/manual) but user gets no immediate notification
|
||||
- **Root Cause:** TODO comment in Position Manager for Telegram notifications, never implemented
|
||||
- **Impact:** User unaware of P&L outcomes until checking dashboard or Drift UI manually
|
||||
|
||||
Reference in New Issue
Block a user