/** * Trading Bot v4 - Signal Quality Version Comparison API * * Returns performance metrics comparing different signal quality scoring versions */ import { NextResponse } from 'next/server' import { getPrismaClient } from '@/lib/database/trades' export const dynamic = 'force-dynamic' interface VersionStats { version: string tradeCount: number winRate: number totalPnL: number avgPnL: number avgQualityScore: number | null avgMFE: number | null avgMAE: number | null extremePositions: { count: number avgADX: number | null weakADXCount: number winRate: number avgPnL: number } } export async function GET() { try { const prisma = getPrismaClient() // Get overall stats by version const versionStats = await prisma.$queryRaw>` SELECT COALESCE("indicatorVersion", 'unknown') as version, COUNT(*) as trades, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM("realizedPnL") as total_pnl, AVG("realizedPnL") as avg_pnl, AVG("realizedPnL") as avg_pnl, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score, ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae FROM "Trade" WHERE "exitReason" IS NOT NULL AND "exitReason" NOT LIKE '%CLEANUP%' AND "isTestTrade" = false AND ("signalSource" IS NULL OR "signalSource" != 'manual') GROUP BY "indicatorVersion" ORDER BY version DESC ` // Get extreme position stats by version (< 15% price position) const extremePositionStats = await prisma.$queryRaw>` SELECT COALESCE("indicatorVersion", 'unknown') as version, COUNT(*) as trades, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM("realizedPnL") as total_pnl, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score FROM "Trade" WHERE "exitReason" IS NOT NULL AND "exitReason" NOT LIKE '%CLEANUP%' AND "isTestTrade" = false AND ("signalSource" IS NULL OR "signalSource" != 'manual') AND "pricePositionAtEntry" < 15 GROUP BY "indicatorVersion" ORDER BY version DESC ` // Build combined results const results: VersionStats[] = versionStats.map(stat => { const extremeStats = extremePositionStats.find(e => (e.version || 'unknown') === (stat.version || 'unknown') ) const trades = Number(stat.trades) const wins = Number(stat.wins) const extremeCount = extremeStats ? Number(extremeStats.trades) : 0 const extremeWins = extremeStats ? Number(extremeStats.wins) : 0 return { version: stat.version || 'v1', tradeCount: trades, winRate: trades > 0 ? Math.round((wins / trades) * 100 * 10) / 10 : 0, totalPnL: Number(stat.total_pnl) || 0, avgPnL: Number(stat.avg_pnl) || 0, avgQualityScore: stat.avg_quality_score ? Number(stat.avg_quality_score) : null, avgMFE: stat.avg_mfe ? Number(stat.avg_mfe) : null, avgMAE: stat.avg_mae ? Number(stat.avg_mae) : null, extremePositions: { count: extremeCount, avgADX: null, weakADXCount: 0, winRate: extremeCount > 0 ? Math.round((extremeWins / extremeCount) * 100 * 10) / 10 : 0, avgPnL: extremeStats?.total_pnl ? Number(extremeStats.total_pnl) / extremeCount : 0, } } }) // Sort versions: v6 first, then v5, then unknown const versionOrder: Record = { 'v6': 0, 'v5': 1, 'unknown': 2 } results.sort((a, b) => { const orderA = versionOrder[a.version] ?? 999 const orderB = versionOrder[b.version] ?? 999 return orderA - orderB }) // Get version descriptions const versionDescriptions: Record = { 'v5': 'Buy/Sell Signal strategy (pre-Nov 12)', 'v6': 'HalfTrend + BarColor strategy (Nov 12+)', 'unknown': 'No indicator version tracked (pre-Nov 12)' } return NextResponse.json({ success: true, versions: results, descriptions: versionDescriptions, timestamp: new Date().toISOString() }) } catch (error) { console.error('❌ Failed to fetch version comparison:', error) return NextResponse.json( { success: false, error: 'Failed to fetch version comparison data' }, { status: 500 } ) } }