import { NextRequest, NextResponse } from 'next/server' import { getMarketDataCache } from '@/lib/trading/market-data-cache' import { getPrismaClient } from '@/lib/database/trades' import { scoreSignalQuality } from '@/lib/trading/signal-quality' /** * Re-Entry Analytics Endpoint * * Validates manual trades using: * 1. Fresh TradingView market data (if available) * 2. Recent trade performance (last 3 trades for symbol + direction) * 3. Signal quality scoring with performance modifiers * * Called by Telegram bot before executing manual "long sol" / "short eth" commands */ interface ReentryAnalytics { should_enter: boolean score: number reason: string data_source: 'tradingview_real' | 'fallback_historical' | 'no_data' data_age_seconds?: number metrics: { atr: number adx: number rsi: number volumeRatio: number pricePosition: number timeframe: string recentTradeStats: { last3Trades: number winRate: number avgPnL: number } } } export async function POST(request: NextRequest) { try { const body = await request.json() const { symbol, direction } = body if (!symbol || !direction) { return NextResponse.json( { error: 'Missing symbol or direction' }, { status: 400 } ) } if (!['long', 'short'].includes(direction)) { return NextResponse.json( { error: 'Direction must be "long" or "short"' }, { status: 400 } ) } console.log(`🔍 Analyzing re-entry for ${direction.toUpperCase()} ${symbol}`) // 1. Try to get REAL market data from TradingView cache const marketCache = getMarketDataCache() const cachedData = marketCache.get(symbol) let metrics: any let dataSource: 'tradingview_real' | 'fallback_historical' | 'no_data' let dataAgeSeconds: number | undefined if (cachedData) { // Use REAL TradingView data (less than 5min old) dataAgeSeconds = Math.round((Date.now() - cachedData.timestamp) / 1000) dataSource = 'tradingview_real' console.log(`✅ Using real TradingView data (${dataAgeSeconds}s old)`) metrics = { atr: cachedData.atr, adx: cachedData.adx, rsi: cachedData.rsi, volumeRatio: cachedData.volumeRatio, pricePosition: cachedData.pricePosition, timeframe: cachedData.timeframe } } else { // Fallback to most recent trade metrics console.log(`⚠️ No fresh TradingView data, using historical metrics from last trade`) const prisma = getPrismaClient() const lastTrade = await prisma.trade.findFirst({ where: { symbol }, orderBy: { createdAt: 'desc' } }) as any // Trade type has optional metric fields if (lastTrade && lastTrade.atr && lastTrade.adx && lastTrade.rsi) { dataSource = 'fallback_historical' const tradeAge = Math.round((Date.now() - lastTrade.createdAt.getTime()) / 1000) console.log(`📊 Using metrics from last trade (${tradeAge}s ago)`) metrics = { atr: lastTrade.atr, adx: lastTrade.adx, rsi: lastTrade.rsi, volumeRatio: lastTrade.volumeRatio || 1.2, pricePosition: lastTrade.pricePosition || 50, timeframe: '5' } } else { // No data available at all console.log(`❌ No market data available for ${symbol}`) dataSource = 'no_data' metrics = { atr: 1.0, adx: 20, rsi: direction === 'long' ? 45 : 55, volumeRatio: 1.2, pricePosition: 50, timeframe: '5' } } } // 2. Get recent trade performance for this symbol + direction const prisma = getPrismaClient() const recentTrades = await prisma.trade.findMany({ where: { symbol, direction, exitTime: { not: null }, createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24h } }, orderBy: { createdAt: 'desc' }, take: 3 }) const last3Count = recentTrades.length const winningTrades = recentTrades.filter((t: any) => (t.realizedPnL || 0) > 0) const winRate = last3Count > 0 ? (winningTrades.length / last3Count) * 100 : 0 const avgPnL = last3Count > 0 ? recentTrades.reduce((sum: number, t: any) => sum + (t.realizedPnL || 0), 0) / last3Count : 0 console.log(`📊 Recent performance: ${last3Count} trades, ${winRate.toFixed(0)}% WR, ${avgPnL.toFixed(2)}% avg P&L`) // 3. Score the re-entry with real/fallback metrics const qualityResult = await scoreSignalQuality({ atr: metrics.atr, adx: metrics.adx, rsi: metrics.rsi, volumeRatio: metrics.volumeRatio, pricePosition: metrics.pricePosition, direction: direction as 'long' | 'short', symbol: symbol, skipFrequencyCheck: true, // Re-entry check already considers recent trades }) let finalScore = qualityResult.score // 4. Apply recent performance modifiers if (last3Count >= 2 && avgPnL < -5) { finalScore -= 20 console.log(`⚠️ Recent trades losing (${avgPnL.toFixed(2)}% avg) - applying -20 penalty`) } if (last3Count >= 2 && avgPnL > 5 && winRate >= 66) { finalScore += 10 console.log(`✨ Recent trades winning (${winRate.toFixed(0)}% WR) - applying +10 bonus`) } // 5. Penalize if using stale/no data if (dataSource === 'fallback_historical') { finalScore -= 5 console.log(`⚠️ Using historical data - applying -5 penalty`) } else if (dataSource === 'no_data') { finalScore -= 10 console.log(`⚠️ No market data available - applying -10 penalty`) } // 6. Determine if should enter const MIN_REENTRY_SCORE = 55 const should_enter = finalScore >= MIN_REENTRY_SCORE let reason = '' if (!should_enter) { if (dataSource === 'no_data') { reason = `No market data available (score: ${finalScore})` } else if (dataSource === 'fallback_historical') { reason = `Using stale data (score: ${finalScore})` } else if (finalScore < MIN_REENTRY_SCORE) { reason = `Quality score too low (${finalScore} < ${MIN_REENTRY_SCORE})` } if (last3Count >= 2 && avgPnL < -5) { reason += `. Recent ${direction} trades losing (${avgPnL.toFixed(2)}% avg)` } } else { reason = `Quality score acceptable (${finalScore}/${MIN_REENTRY_SCORE})` if (dataSource === 'tradingview_real') { reason += ` [✅ FRESH TradingView data: ${dataAgeSeconds}s old]` } else if (dataSource === 'fallback_historical') { reason += ` [⚠️ Historical data - consider waiting for fresh signal]` } else { reason += ` [❌ No data - risky entry]` } if (winRate >= 66 && last3Count >= 2) { reason += `. Recent win rate: ${winRate.toFixed(0)}%` } } const response: ReentryAnalytics = { should_enter, score: finalScore, reason, data_source: dataSource, data_age_seconds: dataAgeSeconds, metrics: { ...metrics, recentTradeStats: { last3Trades: last3Count, winRate, avgPnL } } } console.log(`📊 Re-entry analysis complete:`, { should_enter, score: finalScore, data_source: dataSource }) return NextResponse.json(response) } catch (error) { console.error('❌ Re-entry analysis error:', error) return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ) } }