diff --git a/lib/monitoring/drift-state-verifier.ts b/lib/monitoring/drift-state-verifier.ts index 4580589..89a8ee6 100644 --- a/lib/monitoring/drift-state-verifier.ts +++ b/lib/monitoring/drift-state-verifier.ts @@ -215,11 +215,39 @@ class DriftStateVerifier { /** * Retry closing a position that should be closed but isn't + * CRITICAL FIX (Dec 9, 2025): Stop retry loop if close transaction confirms */ private async retryClose(mismatch: DriftStateMismatch): Promise { console.log(`🔄 Retrying close for ${mismatch.symbol}...`) try { + // CRITICAL: Check if this trade already has a close attempt in progress + // If we recently tried to close (within 5 minutes), SKIP to avoid retry loop + const prisma = getPrismaClient() + const trade = await prisma.trade.findUnique({ + where: { id: mismatch.tradeId }, + select: { + exitOrderTx: true, + exitReason: true, + configSnapshot: true + } + }) + + if (trade?.configSnapshot) { + const snapshot = trade.configSnapshot as any + const lastRetryTime = snapshot.retryCloseTime ? new Date(snapshot.retryCloseTime) : null + + if (lastRetryTime) { + const timeSinceRetry = Date.now() - lastRetryTime.getTime() + + // If we retried within last 5 minutes, SKIP (Drift propagation delay) + if (timeSinceRetry < 5 * 60 * 1000) { + console.log(` ⏳ Skipping retry - last attempt ${(timeSinceRetry / 1000).toFixed(0)}s ago (Drift propagation delay)`) + return + } + } + } + const result = await closePosition({ symbol: mismatch.symbol, percentToClose: 100, @@ -227,21 +255,18 @@ class DriftStateVerifier { }) if (result.success) { - console.log(` ✅ Successfully closed ${mismatch.symbol}`) + console.log(` ✅ Close transaction confirmed: ${result.transactionSignature}`) console.log(` P&L: $${result.realizedPnL?.toFixed(2) || 0}`) + console.log(` ⏳ Drift API may take up to 5 minutes to reflect closure`) - // Update database with retry close info - const prisma = getPrismaClient() + // Update database with retry close timestamp to prevent loop await prisma.trade.update({ where: { id: mismatch.tradeId }, data: { exitOrderTx: result.transactionSignature || 'RETRY_CLOSE', realizedPnL: result.realizedPnL || 0, configSnapshot: { - ...(await prisma.trade.findUnique({ - where: { id: mismatch.tradeId }, - select: { configSnapshot: true } - }))?.configSnapshot as any, + ...trade?.configSnapshot as any, retryCloseAttempted: true, retryCloseTime: new Date().toISOString(), }