refactor: Remove time-based ghost detection, rely purely on Drift API

User feedback: Time-based cleanup (6 hours) too aggressive for legitimate long-running positions.
Drift API is the authoritative source of truth.

Changes:
- Removed cleanupStalePositions() method entirely
- Removed age-based Layer 1 from validatePositions()
- Updated Layer 2: Now verifies with Drift API before removing position
- All ghost detection now uses Drift blockchain as source of truth

Ghost detection methods:
- Layer 2: Queries Drift after 20 failed close attempts
- Layer 3: Queries Drift every 40 seconds during monitoring
- Periodic validation: Queries Drift every 5 minutes

Result: No premature closures, more reliable ghost detection.
This commit is contained in:
mindesbunister
2025-11-16 00:22:19 +01:00
parent bbab693cc1
commit 9db5f8566d

View File

@@ -237,18 +237,15 @@ export class PositionManager {
console.log('🔍 Validating positions against Drift...')
// LAYER 1: Database-based age check (doesn't require RPC - always works)
await this.cleanupStalePositions()
try {
const driftService = getDriftService()
// If Drift service not ready, use database-only validation
// If Drift service not ready, skip this validation cycle
if (!driftService || !(driftService as any).isInitialized) {
console.log('⚠️ Drift service not ready - using database-only validation')
console.log('⚠️ Drift service not ready - skipping validation this cycle')
console.log(` Positions in memory: ${this.activeTrades.size}`)
console.log(` These will be checked against database on next monitoring cycle`)
return // Database cleanup already ran above
console.log(` Will retry on next cycle (5 minutes) or during monitoring (40 seconds)`)
return
}
// Check each tracked trade individually
@@ -283,30 +280,6 @@ export class PositionManager {
}
}
/**
* LAYER 1: Database-based stale position cleanup
*
* Removes positions from memory that are older than 6 hours
* Doesn't require RPC calls - always works even during rate limiting
*
* CRITICAL: This prevents ghost accumulation during rate limit death spirals
*/
private async cleanupStalePositions(): Promise<void> {
const sixHoursAgo = Date.now() - (6 * 60 * 60 * 1000)
for (const [tradeId, trade] of this.activeTrades) {
// If position is >6 hours old, it's likely a ghost (max trade duration should be ~2-3 hours)
if (trade.entryTime < sixHoursAgo) {
console.log(`🔴 STALE GHOST DETECTED: ${trade.symbol} (age: ${Math.floor((Date.now() - trade.entryTime) / 3600000)}h)`)
console.log(` Entry time: ${new Date(trade.entryTime).toISOString()}`)
console.log(` Removing from memory - likely closed externally hours ago`)
await this.handleExternalClosure(trade, 'Stale position cleanup (>6h old)')
console.log(`✅ Stale ghost cleaned up: ${trade.symbol}`)
}
}
}
/**
* Handle external closure for ghost position cleanup
*
@@ -1178,13 +1151,25 @@ export class PositionManager {
console.error(`⚠️ Rate limited while closing ${trade.symbol} - will retry on next price update`)
// LAYER 2: Death spiral detector (Nov 15, 2025)
// If we've failed to close this position 20+ times (40+ seconds of retries),
// force remove from monitoring to prevent infinite rate limit storms
// If we've failed 20+ times, check Drift API to see if it's a ghost position
if (trade.priceCheckCount > 20) {
console.log(`🔴 DEATH SPIRAL DETECTED: ${trade.symbol} failed 20+ close attempts`)
console.log(` Forcing removal from monitoring to prevent rate limit exhaustion`)
await this.handleExternalClosure(trade, 'Death spiral prevention (20+ failed close attempts)')
return
try {
const driftService = getDriftService()
const marketConfig = getMarketConfig(trade.symbol)
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
// If position doesn't exist on Drift, it's a ghost - remove immediately
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`)
await this.handleExternalClosure(trade, 'Layer 2: Ghost detected via Drift API')
return
} else {
console.log(` Position verified on Drift (size: ${position.size}) - will keep retrying`)
}
} catch (checkError) {
console.error(` Could not verify position on Drift:`, checkError)
}
}
// DON'T remove trade from monitoring - let it retry naturally