critical: Fix breakeven SL using wrong entry price after TP1
- CRITICAL BUG: Drift SDK's position.entryPrice RECALCULATES after partial closes - After TP1, Drift returns COST BASIS of remaining position, NOT original entry - Example: SHORT @ 38.52 → TP1 @ 70% → Drift shows entry 40.01 (runner's basis) - Result: Breakeven SL set .50 ABOVE actual entry = guaranteed loss if triggered Fix: - Always use database trade.entryPrice for breakeven calculations - Drift's position.entryPrice = current state (runner cost basis) - Database entryPrice = original entry (authoritative for breakeven) - Added logging to show both values for verification Impact: - Every TP1 → breakeven transition was using WRONG price - Locking in losses instead of true breakeven protection - Financial loss bug affecting every trade with TP1 Files: - lib/trading/position-manager.ts: Line 513 - use trade.entryPrice not position.entryPrice - .github/copilot-instructions.md: Added Common Pitfall #43, deprecated old #44 Incident: Nov 16, 02:47 CET - SHORT entry 38.52, breakeven SL set at 40.01 Position closed by ghost detection before SL could trigger (lucky)
This commit is contained in:
73
.github/copilot-instructions.md
vendored
73
.github/copilot-instructions.md
vendored
@@ -1833,7 +1833,39 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
|
||||
- **Git commit:** bdf1be1 "fix: Add DNS retry logic to Telegram bot"
|
||||
- **Lesson:** Python urllib3 has same transient DNS issues as Node.js - apply same retry pattern
|
||||
|
||||
43. **Drift account leverage must be set in UI, not via API (CRITICAL - Nov 16, 2025):**
|
||||
43. **Drift SDK position.entryPrice RECALCULATES after partial closes (CRITICAL - FINANCIAL LOSS BUG - Fixed Nov 16, 2025):**
|
||||
- **Symptom:** Breakeven SL set $1.50+ ABOVE actual entry price, guaranteeing loss if triggered
|
||||
- **Root Cause:** Drift SDK's `position.entryPrice` returns COST BASIS of remaining position after TP1, NOT original entry
|
||||
- **Real incident (Nov 16, 02:47 CET):**
|
||||
* SHORT opened at $138.52 entry
|
||||
* TP1 hit, 70% closed at profit
|
||||
* System queried Drift for "actual entry": returned $140.01 (runner's cost basis)
|
||||
* Breakeven SL set at $140.01 (instead of $138.52)
|
||||
* Result: "Breakeven" SL $1.50 ABOVE entry = guaranteed $2.52 loss if hit
|
||||
* Position closed by ghost detection before SL could trigger (lucky)
|
||||
- **Why Drift recalculates:**
|
||||
* After partial close, remaining position has different realized P&L
|
||||
* SDK calculates: `position.entryPrice = quoteAssetAmount / baseAssetAmount`
|
||||
* This gives AVERAGE price of remaining position, not ORIGINAL entry
|
||||
* For runners after TP1, this is ALWAYS wrong for breakeven calculation
|
||||
- **Impact:** Every TP1 → breakeven SL transition uses wrong price, locks in losses instead of breakeven
|
||||
- **Fix:** Always use database `trade.entryPrice` for breakeven SL (line 513 in position-manager.ts)
|
||||
```typescript
|
||||
// BEFORE (BROKEN):
|
||||
const actualEntryPrice = position.entryPrice || trade.entryPrice
|
||||
trade.stopLossPrice = actualEntryPrice
|
||||
|
||||
// AFTER (FIXED):
|
||||
const breakevenPrice = trade.entryPrice // Use ORIGINAL entry from database
|
||||
console.log(`📊 Breakeven SL: Using original entry price $${breakevenPrice.toFixed(4)} (Drift shows $${position.entryPrice.toFixed(4)} for remaining position)`)
|
||||
trade.stopLossPrice = breakevenPrice
|
||||
```
|
||||
- **Common Pitfall #44 context:** Original fix (528a0f4) tried to use Drift's entry for "accuracy" but introduced this bug
|
||||
- **Lesson:** Drift SDK data is authoritative for CURRENT state, but database is authoritative for ORIGINAL entry
|
||||
- **Verification:** After TP1, logs now show: "Using original entry price $138.52 (Drift shows $140.01 for remaining position)"
|
||||
- **Git commit:** [pending] "critical: Use database entry price for breakeven SL, not Drift's recalculated value"
|
||||
|
||||
44. **Drift account leverage must be set in UI, not via API (CRITICAL - Nov 16, 2025):**
|
||||
- **Symptom:** InsufficientCollateral errors when opening positions despite bot configured for 15x leverage
|
||||
- **Root Cause:** Drift Protocol account leverage is an on-chain account setting, cannot be changed via SDK/API
|
||||
- **Error message:** `AnchorError occurred. Error Code: InsufficientCollateral. Error Number: 6003. Error Message: Insufficient collateral.`
|
||||
@@ -1874,39 +1906,14 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
|
||||
- **Drift documentation:** Account leverage must be set in UI, is persistent on-chain setting
|
||||
- **Lesson:** On-chain account settings cannot be changed via API - always verify account state matches bot assumptions before production trading
|
||||
|
||||
44. **Breakeven SL using database entry price instead of Drift's actual entry (Fixed Nov 16, 2025):**
|
||||
- **Symptom:** After TP1 hits, SL moved to "breakeven" but at wrong price - SHORT with $139.07 entry gets SL at $139.18 ($0.11 loss if hit)
|
||||
- **Root Cause:** Position Manager used `trade.entryPrice` from database, which can differ from Drift's actual fill price by $0.10-0.15
|
||||
- **Price discrepancy sources:**
|
||||
* Database stores initial expected price
|
||||
* Drift fills at market price (slippage)
|
||||
* Orphaned position restorations use stale database price (see Common Pitfall #33)
|
||||
* Oracle price delays during execution
|
||||
- **Real incident (Nov 16, 01:37 CET):**
|
||||
* User screenshot: Entry $139.07, SL $139.18, Current $138.58
|
||||
* Database: entryPrice=$139.18291
|
||||
* Drift UI: Entry Price=$139.07
|
||||
* Discrepancy: $0.11 (would lose money if SL hit on "breakeven")
|
||||
- **Impact:** Breakeven SL not truly breakeven - gives back profit or locks in unnecessary loss
|
||||
- **Fix:** Query Drift SDK for actual entry price when setting breakeven SL
|
||||
```typescript
|
||||
// In lib/trading/position-manager.ts (line ~511):
|
||||
// BEFORE:
|
||||
trade.stopLossPrice = trade.entryPrice // Uses database value
|
||||
|
||||
// AFTER:
|
||||
const actualEntryPrice = position.entryPrice || trade.entryPrice
|
||||
console.log(`📊 Breakeven calculation: DB entry=$${trade.entryPrice.toFixed(4)}, Drift entry=$${actualEntryPrice.toFixed(4)}`)
|
||||
trade.stopLossPrice = actualEntryPrice // Uses Drift's on-chain calculated price
|
||||
```
|
||||
- **Drift SDK accuracy:** `position.entryPrice` calculated from on-chain data (quoteAssetAmount / baseAssetAmount) = authoritative
|
||||
- **Fallback:** If position.entryPrice unavailable, falls back to database entry (rare edge case)
|
||||
- **Logging:** Now shows both DB and Drift entry prices for verification
|
||||
- **Related issues:** Common Pitfall #33 (orphaned position restoration with wrong entry)
|
||||
- **Git commit:** 528a0f4 "fix: Use Drift's actual entry price for breakeven SL"
|
||||
- **Lesson:** For critical price calculations (breakeven, TP, SL), always prefer on-chain data over cached database values
|
||||
45. **DEPRECATED - See Common Pitfall #43 for the actual bug (Nov 16, 2025):**
|
||||
- **Original diagnosis was WRONG:** Thought database entry was stale, so used Drift's position.entryPrice
|
||||
- **Reality:** Drift's position.entryPrice RECALCULATES after partial closes (cost basis of runner, not original entry)
|
||||
- **Real fix:** Always use DATABASE entry price for breakeven - it's authoritative for original entry
|
||||
- **This "fix" (commit 528a0f4) INTRODUCED the critical bug in Common Pitfall #43**
|
||||
- **See Common Pitfall #43 for full details of the financial loss bug this caused**
|
||||
|
||||
45. **100% position sizing causes InsufficientCollateral (Fixed Nov 16, 2025):**
|
||||
46. **100% position sizing causes InsufficientCollateral (Fixed Nov 16, 2025):**
|
||||
- **Symptom:** Bot configured for 100% position size gets InsufficientCollateral errors, but Drift UI can open same size position
|
||||
- **Root Cause:** Drift's margin calculation includes fees, slippage buffers, and rounding - exact 100% leaves no room
|
||||
- **Error details:**
|
||||
|
||||
Reference in New Issue
Block a user