diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6ad9746..dc9bb18 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -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