critical: Bug #89 - Detect and handle Drift fractional position remnants (3-part fix)

- Part 1: Position Manager fractional remnant detection after close attempts
  * Check if position < 1.5× minOrderSize after close transaction
  * Log to persistent logger with FRACTIONAL_REMNANT_DETECTED
  * Track closeAttempts, limit to 3 maximum
  * Mark exitReason='FRACTIONAL_REMNANT' in database
  * Remove from monitoring after 3 failed attempts

- Part 2: Pre-close validation in closePosition()
  * Check if position viable before attempting close
  * Reject positions < 1.5× minOrderSize with specific error
  * Prevent wasted transaction attempts on too-small positions
  * Return POSITION_TOO_SMALL_TO_CLOSE error with manual instructions

- Part 3: Health monitor detection for fractional remnants
  * Query Trade table for FRACTIONAL_REMNANT exits in last 24h
  * Alert operators with position details and manual cleanup instructions
  * Provide trade IDs, symbols, and Drift UI link

- Database schema: Added closeAttempts Int? field to Track attempts

Root cause: Drift protocol exchange constraints can leave fractional positions
Evidence: 3 close transactions confirmed but 0.15 SOL remnant persisted
Financial impact: ,000+ risk from unprotected fractional positions
Status: Fix implemented, awaiting deployment verification

See: docs/COMMON_PITFALLS.md Bug #89 for complete incident details
This commit is contained in:
mindesbunister
2025-12-16 22:05:12 +01:00
parent 7c8f1688aa
commit b11da009eb
6 changed files with 321 additions and 1 deletions

View File

@@ -193,6 +193,37 @@ export async function checkPositionManagerHealth(): Promise<HealthCheckResult> {
}
}
// BUG #89 FIX PART 3 (Dec 16, 2025): Check for fractional position remnants
// Query database for positions marked as FRACTIONAL_REMNANT in last 24 hours
// These require manual intervention via Drift UI
const prisma = getPrismaClient()
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000)
const fractionalRemnants = await prisma.trade.findMany({
where: {
exitReason: 'FRACTIONAL_REMNANT' as any,
exitTime: { gte: twentyFourHoursAgo }
},
select: {
id: true,
symbol: true,
positionSizeUSD: true,
exitTime: true
}
})
if (fractionalRemnants.length > 0) {
issues.push(`🛑 CRITICAL: ${fractionalRemnants.length} fractional position remnant(s) detected!`)
issues.push(` These are positions that confirmed close but left small remnants on Drift`)
issues.push(` Close attempts exhausted (3 max) - MANUAL INTERVENTION REQUIRED`)
for (const remnant of fractionalRemnants) {
issues.push(`${remnant.symbol}: Size $${remnant.positionSizeUSD.toFixed(2)} (Trade ID: ${remnant.id})`)
issues.push(` Detected at: ${remnant.exitTime?.toISOString()}`)
issues.push(` Action: Close manually via Drift UI at https://app.drift.trade`)
}
}
const isHealthy = issues.length === 0
return {