feat: ATR-based trailing stop + rate limit monitoring
MAJOR FIXES: - ATR-based trailing stop for runners (was fixed 0.3%, now adapts to volatility) - Fixes runners with +7-9% MFE exiting for losses - Typical improvement: 2.24x more room (0.3% → 0.67% at 0.45% ATR) - Enhanced rate limit logging with database tracking - New /api/analytics/rate-limits endpoint for monitoring DETAILS: - Position Manager: Calculate trailing as (atrAtEntry / price × 100) × multiplier - Config: TRAILING_STOP_ATR_MULTIPLIER=1.5, MIN=0.25%, MAX=0.9% - Settings UI: Added ATR multiplier controls - Rate limits: Log hits/recoveries/exhaustions to SystemEvent table - Documentation: ATR_TRAILING_STOP_FIX.md + RATE_LIMIT_MONITORING.md IMPACT: - Runners can now capture big moves (like morning's $172→$162 SOL drop) - Rate limit visibility prevents silent failures - Data-driven optimization for RPC endpoint health
This commit is contained in:
@@ -632,19 +632,76 @@ async function retryWithBackoff<T>(
|
||||
maxRetries: number = 3,
|
||||
baseDelay: number = 2000
|
||||
): Promise<T> {
|
||||
const startTime = Date.now()
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn()
|
||||
const result = await fn()
|
||||
|
||||
// Log successful execution time for rate limit monitoring
|
||||
if (attempt > 0) {
|
||||
const totalTime = Date.now() - startTime
|
||||
console.log(`✅ Retry successful after ${totalTime}ms (${attempt} retries)`)
|
||||
|
||||
// Log to database for analytics
|
||||
try {
|
||||
const { logSystemEvent } = await import('../database/trades')
|
||||
await logSystemEvent('rate_limit_recovered', 'Drift RPC rate limit recovered after retries', {
|
||||
retriesNeeded: attempt,
|
||||
totalTimeMs: totalTime,
|
||||
recoveredAt: new Date().toISOString(),
|
||||
})
|
||||
} catch (dbError) {
|
||||
console.error('Failed to log rate limit recovery:', dbError)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const isRateLimit = errorMessage.includes('429') || errorMessage.includes('rate limit')
|
||||
|
||||
if (!isRateLimit || attempt === maxRetries) {
|
||||
// Log final failure with full context
|
||||
if (isRateLimit && attempt === maxRetries) {
|
||||
const totalTime = Date.now() - startTime
|
||||
console.error(`❌ RATE LIMIT EXHAUSTED: Failed after ${maxRetries} retries and ${totalTime}ms`)
|
||||
console.error(` Error: ${errorMessage}`)
|
||||
|
||||
// Log to database for analytics
|
||||
try {
|
||||
const { logSystemEvent } = await import('../database/trades')
|
||||
await logSystemEvent('rate_limit_exhausted', 'Drift RPC rate limit exceeded max retries', {
|
||||
maxRetries,
|
||||
totalTimeMs: totalTime,
|
||||
errorMessage: errorMessage.substring(0, 500),
|
||||
failedAt: new Date().toISOString(),
|
||||
})
|
||||
} catch (dbError) {
|
||||
console.error('Failed to log rate limit exhaustion:', dbError)
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
const delay = baseDelay * Math.pow(2, attempt)
|
||||
console.log(`⏳ Rate limited, retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`)
|
||||
console.log(`⏳ Rate limited (429), retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`)
|
||||
console.log(` Error context: ${errorMessage.substring(0, 100)}`)
|
||||
|
||||
// Log rate limit hit to database
|
||||
try {
|
||||
const { logSystemEvent } = await import('../database/trades')
|
||||
await logSystemEvent('rate_limit_hit', 'Drift RPC rate limit encountered', {
|
||||
attempt: attempt + 1,
|
||||
maxRetries,
|
||||
delayMs: delay,
|
||||
errorSnippet: errorMessage.substring(0, 200),
|
||||
hitAt: new Date().toISOString(),
|
||||
})
|
||||
} catch (dbError) {
|
||||
console.error('Failed to log rate limit hit:', dbError)
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user