/** * Trading Bot v4 - Indicator Version Comparison API * * Primary: v8 Money Line (Nov 18+) - Production system * Archived: v5/v6/unknown - Historical baseline for comparison * * Returns performance metrics for statistical validation and future v9 testing */ 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, } } }) // Define version metadata FIRST (before usage) const versionDescriptions: Record = { 'v8': 'Money Line Sticky Trend (Nov 18+) - PRODUCTION', 'v7': 'HalfTrend with toggles (deprecated)', 'v6': 'HalfTrend + BarColor (Nov 12-18) - ARCHIVED', 'v5': 'Buy/Sell Signal (pre-Nov 12) - ARCHIVED', 'unknown': 'No version tracked (pre-Nov 12) - ARCHIVED' } const archivedVersions = ['v5', 'v6', 'v7', 'unknown'] // Sort versions: v8 first (production), then v7, v6, v5, unknown (archived) const versionOrder: Record = { 'v8': 0, 'v7': 1, 'v6': 2, 'v5': 3, 'unknown': 4 } results.sort((a, b) => { const orderA = versionOrder[a.version] ?? 999 const orderB = versionOrder[b.version] ?? 999 return orderA - orderB }) // Mark archived versions (archivedVersions now defined above) const resultsWithArchived = results.map(r => ({ ...r, archived: archivedVersions.includes(r.version) })) return NextResponse.json({ success: true, versions: resultsWithArchived, descriptions: versionDescriptions, production: 'v8', archived: archivedVersions, 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 } ) } }