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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user