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:
99
app/api/analytics/rate-limits/route.ts
Normal file
99
app/api/analytics/rate-limits/route.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Rate Limit Analytics Endpoint
|
||||
* GET /api/analytics/rate-limits
|
||||
*
|
||||
* View Drift RPC rate limit occurrences for monitoring and optimization
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getPrismaClient } from '@/lib/database/trades'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const prisma = getPrismaClient()
|
||||
|
||||
// Get rate limit events from last 7 days
|
||||
const sevenDaysAgo = new Date()
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
|
||||
|
||||
const rateLimitEvents = await prisma.systemEvent.findMany({
|
||||
where: {
|
||||
eventType: {
|
||||
in: ['rate_limit_hit', 'rate_limit_recovered', 'rate_limit_exhausted']
|
||||
},
|
||||
createdAt: {
|
||||
gte: sevenDaysAgo
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
take: 100
|
||||
})
|
||||
|
||||
// Calculate statistics
|
||||
const stats = {
|
||||
total_hits: rateLimitEvents.filter(e => e.eventType === 'rate_limit_hit').length,
|
||||
total_recovered: rateLimitEvents.filter(e => e.eventType === 'rate_limit_recovered').length,
|
||||
total_exhausted: rateLimitEvents.filter(e => e.eventType === 'rate_limit_exhausted').length,
|
||||
|
||||
// Group by hour to see patterns
|
||||
by_hour: {} as Record<number, number>,
|
||||
|
||||
// Average recovery time
|
||||
avg_recovery_time_ms: 0,
|
||||
max_recovery_time_ms: 0,
|
||||
}
|
||||
|
||||
// Process recovery times
|
||||
const recoveredEvents = rateLimitEvents.filter(e => e.eventType === 'rate_limit_recovered')
|
||||
if (recoveredEvents.length > 0) {
|
||||
const recoveryTimes = recoveredEvents
|
||||
.map(e => (e.details as any)?.totalTimeMs)
|
||||
.filter(t => typeof t === 'number')
|
||||
|
||||
if (recoveryTimes.length > 0) {
|
||||
stats.avg_recovery_time_ms = recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length
|
||||
stats.max_recovery_time_ms = Math.max(...recoveryTimes)
|
||||
}
|
||||
}
|
||||
|
||||
// Group by hour
|
||||
rateLimitEvents.forEach(event => {
|
||||
const hour = event.createdAt.getHours()
|
||||
stats.by_hour[hour] = (stats.by_hour[hour] || 0) + 1
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
stats,
|
||||
recent_events: rateLimitEvents.slice(0, 20).map(e => ({
|
||||
type: e.eventType,
|
||||
message: e.message,
|
||||
details: e.details,
|
||||
timestamp: e.createdAt.toISOString(),
|
||||
})),
|
||||
analysis: {
|
||||
recovery_rate: stats.total_hits > 0
|
||||
? `${((stats.total_recovered / stats.total_hits) * 100).toFixed(1)}%`
|
||||
: 'N/A',
|
||||
failure_rate: stats.total_hits > 0
|
||||
? `${((stats.total_exhausted / stats.total_hits) * 100).toFixed(1)}%`
|
||||
: 'N/A',
|
||||
avg_recovery_time: stats.avg_recovery_time_ms > 0
|
||||
? `${(stats.avg_recovery_time_ms / 1000).toFixed(1)}s`
|
||||
: 'N/A',
|
||||
max_recovery_time: stats.max_recovery_time_ms > 0
|
||||
? `${(stats.max_recovery_time_ms / 1000).toFixed(1)}s`
|
||||
: 'N/A',
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Rate limit analytics error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user