docs: Add Common Pitfall #43 - Runner trailing stop protection

Documented critical bug where runner trailing stop never activated after TP1:
- Symptom: Runner exposed to full reversal with static SL
- Real incident: 24 profit at risk, user forced manual close
- Root cause: External closure detected before TP2 price check
- Fix: Three-part solution with TP2 pre-check, diagnostics, exit reason detection
- Impact: Runner now fully autonomous with trailing protection

Also renumbered subsequent pitfalls 43→45, 44→46, etc. for consistency.

Commit: 55582a4 "critical: Fix runner trailing stop protection after TP1"
This commit is contained in:
mindesbunister
2025-11-20 08:07:59 +01:00
parent 55582a4e69
commit c4c5c84c24

View File

@@ -2149,7 +2149,67 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
- **Git commit:** b1ca454 "feat: Add Telegram notifications for position closures"
- **Lesson:** User feedback channels (notifications) are as important as monitoring logic
42. **Telegram bot DNS resolution failures (Fixed Nov 16, 2025):**
43. **Runner trailing stop never activates after TP1 (CRITICAL - Fixed Nov 20, 2025):**
- **Symptom:** Runner after TP1 exposed to full reversal with only static SL, no trailing protection
- **Real incident (Nov 20, $224 profit at risk):**
* LONG SOL-PERP: Entry $135.22, quality score 95, ADX 26.9
* TP1 hit: 60% closed at $136.26 for $6.28 profit
* Runner: 40% remaining, price rose to $143.50 (+$224.49 profit)
* Runner SL: Stayed at $134.48 (-0.55% ADX-based) - NEVER trailed
* Risk: $224 profit exposed to full reversal back to -$1.55 loss
* User action: Manually closed at $143.50 to protect profit
- **Root Cause:** Position Manager detected full closure before checking TP2 price trigger
- **Bug sequence:**
1. TP1 filled on-chain at $136.26 (60% closed)
2. Position Manager detected size reduction, moved runner SL to $134.48 ✅
3. Price rose to $143.50 but monitoring detected position fully gone ❌
4. External closure handler stopped all monitoring before TP2 check ❌
5. Trailing stop NEVER activated (requires tp2Hit flag set)
6. Runner had static SL $9.02 below current price with NO trailing
- **Three-part fix (Nov 20, 2025):**
```typescript
// Part 1: TP2 pre-check BEFORE external closure detection (lines 776-799)
if (trade.tp1Hit && !trade.tp2Hit && !trade.closingInProgress) {
const reachedTP2 = this.shouldTakeProfit2(currentPrice, trade)
if (reachedTP2) {
console.log(`🎊 TP2 PRICE REACHED: Activating trailing stop for runner`)
trade.tp2Hit = true
trade.trailingStopActive = true
trade.peakPrice = currentPrice // Initialize for trailing
await this.saveTradeState(trade)
}
}
// Part 2: Enhanced diagnostics (lines 803-821)
if ((position === null) && trade.tp1Hit && !trade.tp2Hit) {
console.log(`⚠️ RUNNER CLOSED EXTERNALLY without reaching TP2`)
const reachedTP2 = currentPrice >= trade.tp2Price
if (reachedTP2) {
console.log(` Price reached TP2 but tp2Hit was false!`)
console.log(` Trailing stop should have been active`)
}
}
// Part 3: Trailing stop-aware exit reason (lines 858-877)
if (trade.tp2Hit && trade.trailingStopActive) {
const isPullback = currentPrice < trade.peakPrice * 0.99
exitReason = isPullback ? 'SL' : 'TP2' // Trailing hit vs peak close
}
```
- **How it works now:**
1. TP1 closes 60% → Runner SL moves to $134.48 (ADX-based)
2. **Before external closure check**: System checks if price ≥ TP2 ($137.30)
3. If yes: Sets tp2Hit=true, trailingStopActive=true, initializes peakPrice
4. As price rises: Trailing stop moves SL up dynamically (ATR × ADX multiplier)
5. On pullback: Trailing stop triggers, locks in profit
6. Fully autonomous: No manual intervention needed
- **Impact:** Runner system now works as designed - "let winners run" with protection
- **User requirement:** "bot has to work all the time especially when i am not on my laptop"
- **Files:** lib/trading/position-manager.ts (3 strategic fixes)
- **Git commit:** 55582a4 "critical: Fix runner trailing stop protection after TP1"
- **Lesson:** When detecting external closures, always check for intermediate state triggers (TP2) BEFORE assuming trade is fully done. Trailing stop requires tp2Hit flag but position can close before monitoring detects TP2 price crossed.
44. **Telegram bot DNS resolution failures (Fixed Nov 16, 2025):**
- **Symptom:** Telegram bot throws "Failed to resolve 'trading-bot-v4'" errors on /status and manual trades
- **Root Cause:** Python urllib3 has transient DNS resolution failures (same as Node.js fetch failures)
- **Error message:** `urllib3.exceptions.NameResolutionError: <urllib3.connection.HTTPConnection object> Failed to resolve 'trading-bot-v4'`
@@ -2193,7 +2253,7 @@ 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 SDK position.entryPrice RECALCULATES after partial closes (CRITICAL - FINANCIAL LOSS BUG - Fixed Nov 16, 2025):**
45. **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):**