diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 358d1a4..16bc094 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1312,7 +1312,41 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK - **Impact:** Protects user from unlimited risk during unavailable hours. Phantom trades are rare edge cases (oracle issues, exchange rejections). - **Database tracking:** `status='phantom'`, `exitReason='manual'`, enables analysis of phantom frequency and patterns -33. **Flip-flop price context using wrong data (CRITICAL - Fixed Nov 14, 2025):** +33. **Wrong entry price after orphaned position restoration (CRITICAL - Fixed Nov 15, 2025):** + - **Symptom:** Position Manager tracking SHORT at $141.51 entry, but Drift UI shows $141.31 actual entry + - **Root Cause:** Startup validation restored orphaned position but used OLD database entry price instead of querying Drift for real value + - **Bug sequence:** + 1. Position opened at $141.317 (per Drift order history) + 2. TP1 closed 70% at $140.942 + 3. Database incorrectly saved entry as $141.508 (maybe averaged or from previous position) + 4. Container restart → startup validation found position on Drift + 5. Reopened trade in DB but used stale `trade.entryPrice` from database + 6. Position Manager tracked with wrong entry ($141.51 vs actual $141.31) + 7. Stop loss calculated from wrong base: $141.08 instead of $140.89 + - **Impact:** 0.14% difference ($0.20/SOL) in SL placement - could mean difference between small profit and small loss + - **Fix:** Query Drift SDK for actual entry price during orphaned position restoration + ```typescript + // In lib/startup/init-position-manager.ts (line 121-144): + // When reopening closed trade found on Drift: + const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) + const positionSizeUSD = position.size * currentPrice + + await prisma.trade.update({ + where: { id: trade.id }, + data: { + status: 'open', + exitReason: null, + entryPrice: position.entryPrice, // CRITICAL: Use Drift's actual entry price + positionSizeUSD: positionSizeUSD, // Update to current size (runner after TP1) + } + }) + ``` + - **Drift SDK returns real entry:** `position.entryPrice` from `getPosition()` calculates from on-chain data (quoteAssetAmount / baseAssetAmount) + - **Future-proofed:** All orphaned position restorations now use authoritative Drift entry price, not stale DB value + - **Manual fix required once:** Had to manually UPDATE database for existing position, then restart container + - **Lesson:** Always prefer on-chain data over cached database values for critical trading parameters + +34. **Flip-flop price context using wrong data (CRITICAL - Fixed Nov 14, 2025):** - **Symptom:** Flip-flop detection showing "100% price move" when actual movement was 0.2%, allowing trades that should be blocked - **Root Cause:** `currentPrice` parameter not available in check-risk endpoint (trade hasn't opened yet), so calculation used undefined/zero - **Real incident:** Nov 14, 06:05 CET - SHORT allowed with 0.2% flip-flop, lost -$1.56 in 5 minutes