critical: Fix runner trailing stop protection after TP1
Three critical fixes to Position Manager runner protection system: 1. **TP2 pre-check before external closure (MAIN FIX):** - Added check for TP2 price trigger BEFORE external closure detection - Activates trailing stop even if position fully closes before monitoring detects it - Sets tp2Hit and trailingStopActive flags when price reaches TP2 - Initializes peakPrice for trailing calculations - Lines 776-799: New TP2 pre-check logic 2. **Runner closure diagnostics:** - Added detailed logging when runner closes externally after TP1 - Shows if price reached TP2 (trailing should be active) - Identifies if runner hit SL before reaching TP2 - Helps diagnose why trailing stop didn't activate - Lines 803-821: Enhanced external closure logging 3. **Trailing stop exit reason detection:** - Checks if trailing stop was active when position closed - Compares current price to peak price for pullback detection - Correctly labels runner exits as trailing stop (SL) vs TP2 - Prevents misclassification of profitable runner exits - Lines 858-877: Trailing stop state-aware exit reason logic **Problem Solved:** - Previous: TP1 moved runner SL to breakeven/ADX-based level, but never activated trailing - Result: Runner exposed to full reversal (e.g., 24 profit → -.55 loss possible) - Root cause: Position closed before monitoring detected TP2 price trigger - Impact: User forced to manually close at 43.50 instead of system managing **How It Works Now:** 1. TP1 closes 60% at 36.26 → Runner SL moves to 34.48 (ADX 26.9 = -0.55%) 2. Price rises to 37.30 (TP2 trigger) → System detects and activates trailing 3. As price rises to 43.50 → Trailing stop moves SL up dynamically 4. If pullback occurs → Trailing stop triggers, locks in most profit 5. No manual intervention needed → Fully autonomous runner management **Next Trade Will:** - Continue monitoring after TP1 instead of stopping - Activate trailing stop when price reaches TP2 - Trail SL upward as price rises (ADX-based multiplier) - Close runner automatically via trailing stop if pullback occurs - Allow user to sleep while bot protects runner profit Files: lib/trading/position-manager.ts (3 strategic fixes) Impact: Runner system now fully autonomous with trailing stop protection
This commit is contained in:
@@ -775,8 +775,59 @@ export class PositionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL FIX (Nov 20, 2025): Check if price hit TP2 BEFORE external closure detection
|
||||
// This activates trailing stop even if position fully closes before we detect TP2
|
||||
if (trade.tp1Hit && !trade.tp2Hit && !trade.closingInProgress) {
|
||||
const reachedTP2 = this.shouldTakeProfit2(currentPrice, trade)
|
||||
if (reachedTP2) {
|
||||
// Calculate profit percent for logging
|
||||
const profitPercent = this.calculateProfitPercent(
|
||||
trade.entryPrice,
|
||||
currentPrice,
|
||||
trade.direction
|
||||
)
|
||||
|
||||
console.log(`🎊 TP2 PRICE REACHED: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
|
||||
console.log(` Activating trailing stop for runner protection`)
|
||||
trade.tp2Hit = true
|
||||
trade.trailingStopActive = true
|
||||
|
||||
// Initialize peak price for trailing if not set
|
||||
if (trade.peakPrice === 0 ||
|
||||
(trade.direction === 'long' && currentPrice > trade.peakPrice) ||
|
||||
(trade.direction === 'short' && currentPrice < trade.peakPrice)) {
|
||||
trade.peakPrice = currentPrice
|
||||
}
|
||||
|
||||
// Save state
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
}
|
||||
|
||||
if ((position === null || position.size === 0) && !trade.closingInProgress) {
|
||||
|
||||
// CRITICAL FIX (Nov 20, 2025): If TP1 already hit, this is RUNNER closure
|
||||
// We should have been monitoring with trailing stop active
|
||||
// Check if we should have had trailing stop protection
|
||||
if (trade.tp1Hit && !trade.tp2Hit) {
|
||||
console.log(`⚠️ RUNNER CLOSED EXTERNALLY: ${trade.symbol}`)
|
||||
console.log(` TP1 hit: true, TP2 hit: false`)
|
||||
console.log(` This runner should have had trailing stop protection!`)
|
||||
console.log(` Likely cause: Monitoring detected full closure before TP2 price check`)
|
||||
|
||||
// Check if price reached TP2 - if so, trailing should have been active
|
||||
const reachedTP2 = trade.direction === 'long'
|
||||
? currentPrice >= (trade.tp2Price || 0)
|
||||
: currentPrice <= (trade.tp2Price || 0)
|
||||
|
||||
if (reachedTP2) {
|
||||
console.log(` ⚠️ Price reached TP2 ($${trade.tp2Price?.toFixed(4)}) but tp2Hit was false!`)
|
||||
console.log(` Trailing stop should have been active but wasn't`)
|
||||
} else {
|
||||
console.log(` Runner hit SL before reaching TP2 ($${trade.tp2Price?.toFixed(4)})`)
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Use original position size for P&L calculation on external closures
|
||||
// trade.currentSize may already be 0 if on-chain orders closed the position before
|
||||
// Position Manager detected it, causing zero P&L bug
|
||||
@@ -818,11 +869,30 @@ export class PositionManager {
|
||||
const totalRealizedPnL = runnerRealized
|
||||
console.log(` External closure P&L → ${runnerProfitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} = $${totalRealizedPnL.toFixed(2)}`)
|
||||
|
||||
// Determine exit reason from P&L percentage
|
||||
// Determine exit reason from P&L percentage and trade state
|
||||
// Use actual profit percent to determine what order filled
|
||||
let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL'
|
||||
|
||||
if (runnerProfitPercent > 0.3) {
|
||||
// CRITICAL (Nov 20, 2025): Check if trailing stop was active
|
||||
// If so, this is a trailing stop exit, not regular SL
|
||||
if (trade.tp2Hit && trade.trailingStopActive) {
|
||||
console.log(` 🏃 Runner closed with TRAILING STOP active`)
|
||||
console.log(` Peak price: $${trade.peakPrice.toFixed(4)}, Current: $${currentPrice.toFixed(4)}`)
|
||||
|
||||
// Check if price dropped from peak (trailing stop hit)
|
||||
const isPullback = trade.direction === 'long'
|
||||
? currentPrice < trade.peakPrice * 0.99 // More than 1% below peak
|
||||
: currentPrice > trade.peakPrice * 1.01 // More than 1% above peak
|
||||
|
||||
if (isPullback) {
|
||||
exitReason = 'SL' // Trailing stop counts as SL
|
||||
console.log(` ✅ Confirmed: Trailing stop hit (pulled back from peak)`)
|
||||
} else {
|
||||
// Very close to peak - might be emergency close or manual
|
||||
exitReason = 'TP2' // Give credit for reaching runner profit target
|
||||
console.log(` ✅ Closed near peak - counting as TP2`)
|
||||
}
|
||||
} else if (runnerProfitPercent > 0.3) {
|
||||
// Positive profit - was a TP order
|
||||
if (runnerProfitPercent >= 1.2) {
|
||||
// Large profit (>1.2%) - TP2 range
|
||||
|
||||
Reference in New Issue
Block a user