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:
mindesbunister
2025-11-30 23:08:34 +01:00
parent 887ae3b924
commit 78757d2111
2 changed files with 296 additions and 4 deletions

View File

@@ -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)