From b19f1568226378d307ceb26ce71b82379bd91798 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sat, 22 Nov 2025 14:09:24 +0100 Subject: [PATCH] critical: Fix Layer 2 ghost detection causing duplicate Telegram notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: Trade #8 (SHORT SOL-PERP) sent 13 duplicate 'POSITION CLOSED' notifications - P&L compounded: $11.50 → $38.56 → $64.70 → ... → $155.05 - Root cause: Layer 2 ghost detection (failureCount > 20) didn't check closingInProgress flag - Called handleExternalClosure() every 2 seconds during rate limit storm (6,581 failures) - Each call sent Telegram notification with compounding P&L Fix: - Added closingInProgress check before Layer 2 ghost detection - Mark trade as closing BEFORE calling handleExternalClosure() - Prevents duplicate processing during async database updates Location: lib/trading/position-manager.ts lines 1477-1490 Prevents: Common Pitfall #49 (P&L compounding) in Layer 2 death spiral scenario Related: Common Pitfall #40 (ghost death spiral), #48 (closingInProgress flag) Impact: No more duplicate notifications, accurate P&L reporting --- lib/trading/position-manager.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 99faaa5..148f445 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -1477,7 +1477,7 @@ export class PositionManager { // LAYER 2: Death spiral detector (Nov 15, 2025) // If we've failed 20+ times, check Drift API to see if it's a ghost position - if (trade.priceCheckCount > 20) { + if (trade.priceCheckCount > 20 && !trade.closingInProgress) { try { const driftService = getDriftService() const marketConfig = getMarketConfig(trade.symbol) @@ -1487,6 +1487,11 @@ export class PositionManager { if (!position || Math.abs(position.size) < 0.01) { console.log(`🔴 LAYER 2: Ghost detected after ${trade.priceCheckCount} failures`) console.log(` Drift shows position closed/missing - removing from monitoring`) + + // CRITICAL: Mark as closing to prevent duplicate processing + trade.closingInProgress = true + trade.closeConfirmedAt = Date.now() + await this.handleExternalClosure(trade, 'Layer 2: Ghost detected via Drift API') return } else {