critical: Fix FALSE TP1 detection - add price verification (Pitfall #63)
CRITICAL BUG FIXED (Nov 30, 2025): Position Manager was setting tp1Hit=true based ONLY on size mismatch, without verifying price actually reached TP1 target. This caused: - Premature order cancellation (on-chain TP1 removed before fill) - Lost profit potential (optimal exits missed) - Ghost orders after container restarts ROOT CAUSE (line 1086 in position-manager.ts): trade.tp1Hit = true // Set without checking this.shouldTakeProfit1() FIX IMPLEMENTED: - Added price verification: this.shouldTakeProfit1(currentPrice, trade) - Only set tp1Hit when BOTH conditions met: 1. Size reduced by 5%+ (positionSizeUSD < trade.currentSize * 0.95) 2. Price crossed TP1 target (this.shouldTakeProfit1 returns true) - Verbose logging for debugging (shows price vs target, size ratio) - Fallback: Update tracked size but don't trigger TP1 logic REAL INCIDENT: - Trade cmim4ggkr00canv07pgve2to9 (SHORT SOL-PERP Nov 30) - TP1 target: $137.07, actual exit: $136.84 - False detection triggered premature order cancellation - Position closed successfully but system integrity compromised FILES CHANGED: - lib/trading/position-manager.ts (lines 1082-1111) - CRITICAL_TP1_FALSE_DETECTION_BUG.md (comprehensive incident report) TESTING REQUIRED: - Monitor next trade with TP1 for correct detection - Verify logs show TP1 VERIFIED or TP1 price NOT reached - Confirm no premature order cancellation ALSO FIXED: - Restarted telegram-trade-bot to fix /status command conflict See: Common Pitfall #63 in copilot-instructions.md (to be added)
This commit is contained in:
@@ -1079,10 +1079,36 @@ export class PositionManager {
|
||||
return
|
||||
}
|
||||
|
||||
// Update current size to match reality (already in USD)
|
||||
trade.currentSize = positionSizeUSD
|
||||
trade.tp1Hit = true
|
||||
await this.saveTradeState(trade)
|
||||
// CRITICAL FIX (Nov 30, 2025): MUST verify price reached TP1 before setting flag
|
||||
// BUG: Setting tp1Hit=true based ONLY on size mismatch caused premature order cancellation
|
||||
// Size reduction could be: partial fill, slippage, external action, RPC staleness
|
||||
// ONLY set tp1Hit when BOTH conditions met: size reduced AND price target reached
|
||||
|
||||
const tp1PriceReached = this.shouldTakeProfit1(currentPrice, trade)
|
||||
|
||||
if (tp1PriceReached) {
|
||||
console.log(`✅ TP1 VERIFIED: Size mismatch + price target reached`)
|
||||
console.log(` Size: $${trade.currentSize.toFixed(2)} → $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`)
|
||||
console.log(` Price: ${currentPrice.toFixed(4)} crossed TP1 target ${trade.tp1Price.toFixed(4)}`)
|
||||
|
||||
// Update current size to match reality (already in USD)
|
||||
trade.currentSize = positionSizeUSD
|
||||
trade.tp1Hit = true
|
||||
await this.saveTradeState(trade)
|
||||
|
||||
console.log(`🎉 TP1 HIT: ${trade.symbol} via on-chain order (detected by size reduction)`)
|
||||
} else {
|
||||
console.log(`⚠️ Size reduced but TP1 price NOT reached yet - NOT triggering TP1 logic`)
|
||||
console.log(` Current: ${currentPrice.toFixed(4)}, TP1 target: ${trade.tp1Price.toFixed(4)} (${trade.direction === 'long' ? 'need higher' : 'need lower'})`)
|
||||
console.log(` Size: $${trade.currentSize.toFixed(2)} → $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`)
|
||||
console.log(` Likely: Partial fill, slippage, or external action`)
|
||||
|
||||
// Update tracked size but DON'T trigger TP1 logic
|
||||
trade.currentSize = positionSizeUSD
|
||||
await this.saveTradeState(trade)
|
||||
|
||||
// Continue monitoring - TP1 logic will trigger when price actually crosses target
|
||||
}
|
||||
}
|
||||
} // End of: if (position && position.size !== 0 && !trade.closingInProgress)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user