From 11ae0938ba4daf8b08fec310e36d38dce1046e3e Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 23 Nov 2025 20:07:24 +0100 Subject: [PATCH] feat: Add comprehensive optimization analytics dashboard - Created /api/optimization/analyze endpoint with 7 SQL analyses - Replaced old TP/SL page with comprehensive dashboard - Analyses: Quality Score Distribution, Direction Performance, Blocked Signals, Runner Performance, ATR vs MFE, Indicator Versions, Data Collection Status - Real-time refresh capability - Actionable recommendations based on data thresholds - Roadmap links at bottom - Addresses user request for automated SQL analysis dashboard --- app/analytics/optimization/page.tsx | 698 +++++++------------------- app/api/optimization/analyze/route.ts | 419 ++++++++++++++++ 2 files changed, 591 insertions(+), 526 deletions(-) create mode 100644 app/api/optimization/analyze/route.ts diff --git a/app/analytics/optimization/page.tsx b/app/analytics/optimization/page.tsx index 3f3b653..a2c0e5a 100644 --- a/app/analytics/optimization/page.tsx +++ b/app/analytics/optimization/page.tsx @@ -1,572 +1,218 @@ 'use client' import { useEffect, useState } from 'react' -import Link from 'next/link' -interface TPSLAnalysis { - success: boolean - analysis?: { - totalTrades: number - winningTrades: number - losingTrades: number - winRate: number - avgWin: number - avgLoss: number - profitFactor: number - - maeAnalysis: { - avgMAE: number - medianMAE: number - percentile25MAE: number - percentile75MAE: number - worstMAE: number - } - - mfeAnalysis: { - avgMFE: number - medianMFE: number - percentile25MFE: number - percentile75MFE: number - bestMFE: number - } - - currentLevels: { - tp1Percent: number - tp2Percent: number - slPercent: number - tp1HitRate: number - tp2HitRate: number - slHitRate: number - moneyLeftOnTable: number - } - - recommendations: { - optimalTP1: number - optimalTP2: number - optimalSL: number - - reasoning: { - tp1: string - tp2: string - sl: string - } - - projectedImpact: { - expectedWinRateChange: number - expectedProfitFactorChange: number - estimatedProfitImprovement: number - } - } - - tradesByOutcome: { - tp1Exits: number - tp2Exits: number - slExits: number - manualExits: number - } - - dynamicATRAnalysis?: { - available: boolean - sampleSize: number - minSampleSize: number - sufficientData: boolean - avgATRPercent: number - dynamicTP2Percent: number - dynamicSLPercent: number - actualPnL: number - fixedSimulatedPnL: number - dynamicSimulatedPnL: number - dynamicAdvantage: number - dynamicAdvantagePercent: number - dynamicTP2HitRate: number - dynamicSLHitRate: number - recommendation: string - reasoning: string - } - } - error?: string +interface AnalysisResult { + name: string + description: string + status: 'loading' | 'success' | 'error' + data: any + recommendation?: string + action?: string } export default function OptimizationPage() { - const [analysis, setAnalysis] = useState(null) + const [analyses, setAnalyses] = useState([]) const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) + const [lastRefresh, setLastRefresh] = useState(null) - useEffect(() => { - fetchAnalysis() - }, []) - - const fetchAnalysis = async () => { + const loadAnalyses = async () => { + setLoading(true) try { - setLoading(true) - setError(null) - - const response = await fetch('/api/analytics/tp-sl-optimization') + const response = await fetch('/api/optimization/analyze') const data = await response.json() - - setAnalysis(data) - - if (!data.success) { - setError(data.error || 'Failed to load analysis') - } - } catch (err) { - setError('Failed to fetch analytics: ' + (err as Error).message) + setAnalyses(data.analyses) + setLastRefresh(new Date()) + } catch (error) { + console.error('Failed to load analyses:', error) } finally { setLoading(false) } } - if (loading) { - return ( -
-
-
-
-
-
-

Loading optimization analysis...

-
-
-
-
- ) - } - - if (error || !analysis?.success) { - return ( -
-
-
-
-

⚠️ Insufficient Data

-

{error || analysis?.error}

-

- Need at least 10 closed trades with MAE/MFE tracking data. - The next trades you take will automatically track this data. -

- -
-
-
- ) - } - - const data = analysis.analysis! + useEffect(() => { + loadAnalyses() + }, []) return ( -
-
- -
- {/* Header with Refresh */} -
-
-

💡 TP/SL Optimization

-

Based on {data.totalTrades} trades with MAE/MFE data

-
+
+
+ {/* Header */} +
+

🔬 Optimization Analytics Dashboard

+

+ Data-driven analysis for trading system improvements based on roadmaps and SQL queries +

+ {lastRefresh && ( +

+ Last refresh: {lastRefresh.toLocaleTimeString()} +

+ )} +
+ + {/* Refresh Button */} +
- {/* Overview Stats */} -
- - - - -
- - {/* MAE/MFE Analysis */} -
-
-

- 📈 Maximum Favorable Excursion (MFE) -

-
- - - - -
- -
-
-
- -
-

- 📉 Maximum Adverse Excursion (MAE) -

-
- - - - -
- -
-
-
-
- - {/* Current Configuration Performance */} -
-

🎯 Current Configuration Performance

- -
- - - -
-
- - {/* Dynamic ATR Analysis */} - {data.dynamicATRAnalysis?.available && ( -
-
-

- 🎯 Dynamic ATR-Based TP/SL Analysis -

-
- {data.dynamicATRAnalysis.sampleSize}/{data.dynamicATRAnalysis.minSampleSize} trades collected -
-
- - {/* Progress Bar */} -
-
- Data Collection Progress - - {((data.dynamicATRAnalysis.sampleSize / data.dynamicATRAnalysis.minSampleSize) * 100).toFixed(1)}% - -
-
-
-
-
- - {/* Comparison Metrics */} -
- {/* Left: Current Fixed Targets */} -
-

📌 Fixed Targets (Current)

-
-
- TP2: - {data.currentLevels.tp2Percent}% -
-
- SL: - {data.currentLevels.slPercent}% -
-
-
- Simulated P&L: - - ${data.dynamicATRAnalysis.fixedSimulatedPnL.toFixed(2)} - -
-
-
-
- - {/* Right: Dynamic ATR Targets */} -
-

⚡ Dynamic ATR-Based

-
-
- TP2 (2x ATR): - - {data.dynamicATRAnalysis.dynamicTP2Percent.toFixed(2)}% - -
-
- SL (1.5x ATR): - - {data.dynamicATRAnalysis.dynamicSLPercent.toFixed(2)}% - -
-
-
- Simulated P&L: - - ${data.dynamicATRAnalysis.dynamicSimulatedPnL.toFixed(2)} - -
-
-
-
-
- - {/* Advantage Highlight */} -
= 0 - ? 'bg-green-900/30 border border-green-600' - : 'bg-red-900/30 border border-red-600' - }`}> -
-
-
Dynamic ATR Advantage
-
- {data.dynamicATRAnalysis.dynamicAdvantage >= 0 ? '+' : ''} - ${data.dynamicATRAnalysis.dynamicAdvantage.toFixed(2)} - - ({data.dynamicATRAnalysis.dynamicAdvantagePercent >= 0 ? '+' : ''} - {data.dynamicATRAnalysis.dynamicAdvantagePercent.toFixed(1)}%) - -
-
-
-
Avg ATR
-
- {data.dynamicATRAnalysis.avgATRPercent.toFixed(2)}% -
-
-
-
- - {/* Recommendation */} -
-
- {data.dynamicATRAnalysis.recommendation} -
-
- {data.dynamicATRAnalysis.reasoning} -
-
+ {/* Loading State */} + {loading && analyses.length === 0 && ( +
+
+

Running SQL analyses...

)} - {/* Recommendations */} -
-

💡 Optimization Recommendations

- -
- + {analyses.map((analysis, index) => ( + + ))} +
+ + {/* Roadmap Links */} +
+

📚 Optimization Roadmaps

+
+ - -
+
+
+
+ ) +} - {/* Reasoning */} -
- - - -
+function AnalysisCard({ analysis }: { analysis: AnalysisResult }) { + const statusColors = { + loading: 'border-yellow-500 bg-yellow-500/10', + success: 'border-green-500 bg-green-500/10', + error: 'border-red-500 bg-red-500/10' + } - {/* Projected Impact */} -
-

📊 Projected Impact

-
- = 0} - /> - = 0} - /> - = 0} - /> + const statusIcons = { + loading: '⏳', + success: '✅', + error: '❌' + } + + return ( +
+
+
+

+ {statusIcons[analysis.status]} {analysis.name} +

+

{analysis.description}

+
+
+ + {/* Data Display */} + {analysis.status === 'success' && analysis.data && ( +
+ {/* Table */} + {Array.isArray(analysis.data) && analysis.data.length > 0 && ( +
+ + + + {Object.keys(analysis.data[0]).map(key => ( + + ))} + + + + {analysis.data.map((row: any, i: number) => ( + + {Object.values(row).map((value: any, j: number) => ( + + ))} + + ))} + +
+ {key} +
+ {typeof value === 'number' ? value.toFixed(2) : String(value)} +
-
+ )} + + {/* Summary Stats */} + {typeof analysis.data === 'object' && !Array.isArray(analysis.data) && ( +
+ {Object.entries(analysis.data).map(([key, value]) => ( +
+
{key}
+
+ {typeof value === 'number' ? value.toFixed(2) : String(value)} +
+
+ ))} +
+ )}
+ )} - {/* Action Button */} -
-

- Ready to apply these optimized levels? Update your configuration in Settings. -

- - ⚙️ Go to Settings - + {/* Recommendation */} + {analysis.recommendation && ( +
+
💡 Recommendation
+

{analysis.recommendation}

-
-
- ) -} + )} -// Component helpers -function Header() { - return ( -
-
-
- - - - - -
-

🎯 TP/SL Optimization

-

Data-driven recommendations for optimal exit levels

-
+ {/* Action */} + {analysis.action && ( +
+
🎯 Action Required
+

{analysis.action}

-
+ )} + + {/* Error */} + {analysis.status === 'error' && ( +
+

{analysis.data?.error || 'Analysis failed'}

+
+ )}
) } -function StatCard({ title, value, valueColor = 'text-white' }: { title: string, value: string, valueColor?: string }) { +function RoadmapLink({ title, file, status, progress }: { + title: string + file: string + status: string + progress: string +}) { return ( -
-
{title}
-
{value}
-
- ) -} - -function MetricRow({ label, value, valueColor = 'text-white' }: { label: string, value: string, valueColor?: string }) { - return ( -
- {label}: - {value} -
- ) -} - -function HitRateBar({ label, hitRate, exits, color }: { label: string, hitRate: number, exits: number, color: string }) { - return ( -
-
{label}
-
-
-
-
-
Hit Rate: {hitRate.toFixed(1)}%
-
{exits} exits
-
-
- ) -} - -function RecommendationCard({ label, value, current, color }: { label: string, value: string, current: string, color: string }) { - return ( -
-
{label}
-
{value}
-
Current: {current}
-
- ) -} - -function ReasoningCard({ label, text, color }: { label: string, text: string, color: string }) { - return ( -
-
{label}
-
{text}
-
- ) -} - -function ImpactMetric({ label, value, positive }: { label: string, value: string, positive: boolean }) { - return ( -
-
{label}
-
- {positive ? '+' : ''}{value} -
+
+

{title}

+

{status}

+

{progress}

) } diff --git a/app/api/optimization/analyze/route.ts b/app/api/optimization/analyze/route.ts new file mode 100644 index 0000000..d2615fd --- /dev/null +++ b/app/api/optimization/analyze/route.ts @@ -0,0 +1,419 @@ +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 }) + } +}