import { NextResponse } from 'next/server' import { getPrismaClient } from '../../../../lib/database/trades' export const dynamic = 'force-dynamic' export async function GET() { const prisma = getPrismaClient() const analyses = [] try { // ============================================================================ // 1. QUALITY SCORE DISTRIBUTION // ============================================================================ try { const qualityDistribution = await prisma.$queryRaw` SELECT CASE WHEN "signalQualityScore" >= 95 THEN '95-100 (Excellent)' WHEN "signalQualityScore" >= 90 THEN '90-94 (Very Good)' WHEN "signalQualityScore" >= 85 THEN '85-89 (Good)' WHEN "signalQualityScore" >= 80 THEN '80-84 (Fair)' WHEN "signalQualityScore" >= 70 THEN '70-79 (Marginal)' WHEN "signalQualityScore" >= 60 THEN '60-69 (Weak)' ELSE '<60 (Very Weak)' END as tier, COUNT(*) as trades, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_score, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN "realizedPnL" <= 0 THEN 1 ELSE 0 END) as losses, ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, ROUND(SUM("realizedPnL")::numeric, 2) as total_pnl, ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl FROM "Trade" WHERE "exitReason" IS NOT NULL AND "signalQualityScore" IS NOT NULL GROUP BY tier ORDER BY MIN("signalQualityScore") DESC ` const formattedData = qualityDistribution.map(row => ({ tier: row.tier, trades: Number(row.trades), avg_score: Number(row.avg_score), win_rate: Number(row.win_rate), total_pnl: Number(row.total_pnl), avg_pnl: Number(row.avg_pnl) })) // Find best performing tier const bestTier = formattedData.reduce((best, current) => current.win_rate > best.win_rate ? current : best ) analyses.push({ name: 'Quality Score Distribution', description: 'Win rate and P&L across quality score tiers', status: 'success', data: formattedData, recommendation: bestTier.win_rate >= 70 ? `Quality ${bestTier.tier.split(' ')[0]} shows ${bestTier.win_rate}% WR. Consider raising threshold to this tier.` : 'Continue collecting data for reliable quality threshold optimization.' }) } catch (error) { analyses.push({ name: 'Quality Score Distribution', description: 'Win rate and P&L across quality score tiers', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 2. DIRECTION PERFORMANCE (Long vs Short) // ============================================================================ try { const directionPerformance = await prisma.$queryRaw` SELECT direction, COUNT(*) as trades, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, ROUND(SUM("realizedPnL")::numeric, 2) as total_pnl, ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality FROM "Trade" WHERE "exitReason" IS NOT NULL AND direction IS NOT NULL GROUP BY direction ORDER BY win_rate DESC ` const formattedData = directionPerformance.map(row => ({ direction: String(row.direction).toUpperCase(), trades: Number(row.trades), wins: Number(row.wins), win_rate: Number(row.win_rate), total_pnl: Number(row.total_pnl), avg_pnl: Number(row.avg_pnl), avg_quality: Number(row.avg_quality) })) const longData = formattedData.find(d => d.direction === 'LONG') const shortData = formattedData.find(d => d.direction === 'SHORT') let recommendation = '' if (longData && shortData) { const wrDiff = Math.abs(longData.win_rate - shortData.win_rate) if (wrDiff > 15) { const better = longData.win_rate > shortData.win_rate ? 'LONG' : 'SHORT' const worse = better === 'LONG' ? 'SHORT' : 'LONG' recommendation = `${better} signals perform ${wrDiff.toFixed(1)}% better. Consider raising ${worse} quality threshold or reducing ${worse} position size.` } else { recommendation = 'Direction performance is balanced. No threshold adjustment needed.' } } analyses.push({ name: 'Direction Performance', description: 'Compare LONG vs SHORT trade outcomes', status: 'success', data: formattedData, recommendation }) } catch (error) { analyses.push({ name: 'Direction Performance', description: 'Compare LONG vs SHORT trade outcomes', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 3. BLOCKED SIGNALS ANALYSIS // ============================================================================ try { const blockedSignals = await prisma.$queryRaw` SELECT "blockReason", COUNT(*) as count, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_score, ROUND(AVG(adx)::numeric, 1) as avg_adx, ROUND(AVG(atr)::numeric, 3) as avg_atr FROM "BlockedSignal" WHERE "blockReason" IS NOT NULL GROUP BY "blockReason" ORDER BY count DESC ` const formattedData = blockedSignals.map(row => ({ reason: String(row.blockReason), count: Number(row.count), avg_score: Number(row.avg_score), avg_adx: Number(row.avg_adx), avg_atr: Number(row.avg_atr) })) const qualityBlocked = formattedData.find(d => d.reason === 'QUALITY_SCORE_TOO_LOW') let recommendation = '' if (qualityBlocked && qualityBlocked.count >= 20) { recommendation = `${qualityBlocked.count} signals blocked by quality threshold. Ready for Phase 2 analysis - check if these would have been profitable.` } else { const needed = qualityBlocked ? 20 - qualityBlocked.count : 20 recommendation = `Need ${needed} more blocked signals for reliable analysis. Keep collecting data.` } analyses.push({ name: 'Blocked Signals Analysis', description: 'Signals rejected by quality filters', status: 'success', data: formattedData, recommendation, action: qualityBlocked && qualityBlocked.count >= 20 ? 'Run price tracking analysis to determine if blocked signals would have been profitable' : undefined }) } catch (error) { analyses.push({ name: 'Blocked Signals Analysis', description: 'Signals rejected by quality filters', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 4. RUNNER PERFORMANCE ANALYSIS // ============================================================================ try { const runnerPerformance = await prisma.$queryRaw` SELECT COUNT(*) as total_trades, SUM(CASE WHEN "tp1Hit" = true THEN 1 ELSE 0 END) as tp1_hits, SUM(CASE WHEN "tp2Hit" = true THEN 1 ELSE 0 END) as tp2_hits, ROUND(100.0 * SUM(CASE WHEN "tp1Hit" = true THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as tp1_rate, ROUND(100.0 * SUM(CASE WHEN "tp2Hit" = true THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as tp2_rate, ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae FROM "Trade" WHERE "exitReason" IS NOT NULL AND "createdAt" >= NOW() - INTERVAL '30 days' ` const data = runnerPerformance[0] const formattedData = { total_trades: Number(data.total_trades), tp1_hits: Number(data.tp1_hits), tp2_hits: Number(data.tp2_hits), tp1_rate: Number(data.tp1_rate), tp2_rate: Number(data.tp2_rate), avg_mfe: Number(data.avg_mfe), avg_mae: Number(data.avg_mae) } let recommendation = '' if (formattedData.avg_mfe > formattedData.tp1_rate * 1.5) { recommendation = `Avg MFE (${formattedData.avg_mfe.toFixed(2)}%) significantly exceeds TP1 rate. Consider widening TP1 or increasing runner size.` } else if (formattedData.tp2_rate > 50) { recommendation = `TP2 hit rate is ${formattedData.tp2_rate}%. Trailing stop working well - keep current settings.` } else { recommendation = 'Runner performance is within expected range. Continue monitoring.' } analyses.push({ name: 'Runner Performance', description: 'TP1/TP2 hit rates and max excursion analysis', status: 'success', data: formattedData, recommendation }) } catch (error) { analyses.push({ name: 'Runner Performance', description: 'TP1/TP2 hit rates and max excursion analysis', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 5. ATR CORRELATION WITH MFE // ============================================================================ try { const atrCorrelation = await prisma.$queryRaw` SELECT CASE WHEN atr < 0.3 THEN '<0.3 (Low Vol)' WHEN atr < 0.5 THEN '0.3-0.5 (Med Vol)' WHEN atr < 0.7 THEN '0.5-0.7 (High Vol)' ELSE '0.7+ (Very High Vol)' END as atr_range, COUNT(*) as trades, ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate FROM "Trade" WHERE "exitReason" IS NOT NULL AND atr IS NOT NULL GROUP BY atr_range ORDER BY MIN(atr) ` const formattedData = atrCorrelation.map(row => ({ atr_range: String(row.atr_range), trades: Number(row.trades), avg_mfe: Number(row.avg_mfe), avg_pnl: Number(row.avg_pnl), win_rate: Number(row.win_rate) })) let recommendation = 'ATR-based targets already implemented. Monitor correlation over time.' if (formattedData.length >= 3) { const highestMFE = formattedData.reduce((best, current) => current.avg_mfe > best.avg_mfe ? current : best ) recommendation = `${highestMFE.atr_range} shows highest avg MFE (${highestMFE.avg_mfe}%). ATR-based targets adapting correctly.` } analyses.push({ name: 'ATR vs MFE Correlation', description: 'How volatility affects profit potential', status: 'success', data: formattedData, recommendation }) } catch (error) { analyses.push({ name: 'ATR vs MFE Correlation', description: 'How volatility affects profit potential', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 6. INDICATOR VERSION COMPARISON // ============================================================================ try { const indicatorComparison = await prisma.$queryRaw` SELECT "indicatorVersion", COUNT(*) as trades, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, ROUND(SUM("realizedPnL")::numeric, 2) as total_pnl, ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality FROM "Trade" WHERE "exitReason" IS NOT NULL AND "indicatorVersion" IS NOT NULL GROUP BY "indicatorVersion" ORDER BY "indicatorVersion" DESC ` const formattedData = indicatorComparison.map(row => ({ version: String(row.indicatorVersion), trades: Number(row.trades), wins: Number(row.wins), win_rate: Number(row.win_rate), total_pnl: Number(row.total_pnl), avg_pnl: Number(row.avg_pnl), avg_quality: Number(row.avg_quality) })) const v8Data = formattedData.find(d => d.version === 'v8') let recommendation = '' if (v8Data && v8Data.trades >= 20) { recommendation = `v8 has ${v8Data.trades} trades with ${v8Data.win_rate}% WR. Sufficient data for statistical confidence.` } else if (v8Data) { recommendation = `v8 has ${v8Data.trades} trades. Need ${20 - v8Data.trades} more for statistical validation.` } else { recommendation = 'No v8 indicator data yet. Ensure TradingView alerts include IND:v8 field.' } analyses.push({ name: 'Indicator Version Comparison', description: 'Performance across TradingView strategy versions', status: 'success', data: formattedData, recommendation }) } catch (error) { analyses.push({ name: 'Indicator Version Comparison', description: 'Performance across TradingView strategy versions', status: 'error', data: { error: (error as Error).message } }) } // ============================================================================ // 7. DATA COLLECTION STATUS // ============================================================================ try { const dataStatus = await prisma.$queryRaw` SELECT COUNT(*) FILTER (WHERE "exitReason" IS NOT NULL) as completed_trades, COUNT(*) FILTER (WHERE "signalQualityScore" IS NOT NULL) as with_quality, COUNT(*) FILTER (WHERE atr IS NOT NULL) as with_atr, COUNT(*) FILTER (WHERE "maxFavorableExcursion" IS NOT NULL) as with_mfe, COUNT(*) FILTER (WHERE "indicatorVersion" = 'v8') as v8_trades, (SELECT COUNT(*) FROM "BlockedSignal" WHERE "blockReason" = 'QUALITY_SCORE_TOO_LOW') as blocked_quality FROM "Trade" ` const data = dataStatus[0] const formattedData = { completed_trades: Number(data.completed_trades), with_quality: Number(data.with_quality), with_atr: Number(data.with_atr), with_mfe: Number(data.with_mfe), v8_trades: Number(data.v8_trades), blocked_quality: Number(data.blocked_quality) } const blockedNeeded = Math.max(0, 20 - formattedData.blocked_quality) const v8Needed = Math.max(0, 50 - formattedData.v8_trades) let action = '' if (blockedNeeded > 0) { action = `Need ${blockedNeeded} more blocked signals for Phase 2 quality threshold analysis.` } if (v8Needed > 0) { if (action) action += ' ' action += `Need ${v8Needed} more v8 indicator trades for statistical validation.` } analyses.push({ name: 'Data Collection Status', description: 'Progress toward analysis milestones', status: 'success', data: formattedData, recommendation: action || 'Data collection milestones reached! Ready for optimization decisions.', action: action || undefined }) } catch (error) { analyses.push({ name: 'Data Collection Status', description: 'Progress toward analysis milestones', status: 'error', data: { error: (error as Error).message } }) } return NextResponse.json({ success: true, analyses, timestamp: new Date().toISOString() }) } catch (error) { console.error('❌ Optimization analysis failed:', error) return NextResponse.json({ success: false, error: 'Failed to run optimization analyses', message: (error as Error).message }, { status: 500 }) } }