From 2bdf9e2b41abf52b850038a6b44f46533d826cab Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Fri, 18 Jul 2025 18:32:08 +0200 Subject: [PATCH] fix: implement proper multi-timeframe batch analysis - Create /api/batch-analysis endpoint that collects ALL screenshots first - Then sends all screenshots to AI for comprehensive analysis - Fixes issue where individual timeframes were analyzed immediately - Multi-timeframe analysis now provides cross-timeframe consensus - Update AIAnalysisPanel to use batch analysis for multiple timeframes - Maintains backward compatibility with single timeframe analysis --- app/api/batch-analysis/route.js | 265 ++++++++++++++++++++++++++++++ components/AIAnalysisPanel.tsx | 124 +++++++------- components/PositionCalculator.tsx | 52 +++++- lib/aggressive-cleanup.ts | 38 ++++- lib/startup.ts | 4 +- src/app/layout.tsx | 19 --- 6 files changed, 413 insertions(+), 89 deletions(-) create mode 100644 app/api/batch-analysis/route.js delete mode 100644 src/app/layout.tsx diff --git a/app/api/batch-analysis/route.js b/app/api/batch-analysis/route.js new file mode 100644 index 0000000..efd17a5 --- /dev/null +++ b/app/api/batch-analysis/route.js @@ -0,0 +1,265 @@ +import { NextResponse } from 'next/server' +import { enhancedScreenshotService } from '../../../lib/enhanced-screenshot' +import { aiAnalysisService } from '../../../lib/ai-analysis' +import { progressTracker } from '../../../lib/progress-tracker' + +export async function POST(request) { + try { + const body = await request.json() + const { symbol, layouts, timeframes, selectedLayouts, analyze = true } = body + + console.log('๐Ÿ“Š Batch analysis request:', { symbol, layouts, timeframes, selectedLayouts }) + + // Validate inputs + if (!symbol || !timeframes || !Array.isArray(timeframes) || timeframes.length === 0) { + return NextResponse.json( + { success: false, error: 'Invalid request: symbol and timeframes array required' }, + { status: 400 } + ) + } + + // Generate unique session ID for progress tracking + const sessionId = `batch_analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` + console.log('๐Ÿ” Created batch analysis session ID:', sessionId) + + // Create progress tracking session with initial steps + const initialSteps = [ + { + id: 'init', + title: 'Initializing Batch Analysis', + description: 'Starting multi-timeframe analysis...', + status: 'pending' + }, + { + id: 'auth', + title: 'TradingView Authentication', + description: 'Logging into TradingView accounts', + status: 'pending' + }, + { + id: 'navigation', + title: 'Chart Navigation', + description: 'Navigating to chart layouts', + status: 'pending' + }, + { + id: 'loading', + title: 'Chart Data Loading', + description: 'Waiting for chart data and indicators', + status: 'pending' + }, + { + id: 'capture', + title: 'Screenshot Capture', + description: `Capturing screenshots for ${timeframes.length} timeframes`, + status: 'pending' + }, + { + id: 'analysis', + title: 'AI Analysis', + description: 'Analyzing all screenshots with AI', + status: 'pending' + } + ] + + // Create the progress session + progressTracker.createSession(sessionId, initialSteps) + + // Prepare base configuration + const baseConfig = { + symbol: symbol || 'BTCUSD', + layouts: layouts || selectedLayouts || ['ai', 'diy'], + sessionId, + credentials: { + email: process.env.TRADINGVIEW_EMAIL, + password: process.env.TRADINGVIEW_PASSWORD + } + } + + console.log('๐Ÿ”ง Base config:', baseConfig) + + let allScreenshots = [] + const screenshotResults = [] + + try { + // STEP 1: Collect ALL screenshots from ALL timeframes FIRST + console.log(`๐Ÿ”„ Starting batch screenshot collection for ${timeframes.length} timeframes...`) + + progressTracker.updateStep(sessionId, 'init', 'active', 'Starting batch screenshot collection...') + + for (let i = 0; i < timeframes.length; i++) { + const timeframe = timeframes[i] + const timeframeLabel = getTimeframeLabel(timeframe) + + console.log(`๐Ÿ“ธ Collecting screenshots for ${symbol} ${timeframeLabel} (${i + 1}/${timeframes.length})`) + + // Update progress for current timeframe + progressTracker.updateStep(sessionId, 'capture', 'active', + `Capturing ${timeframeLabel} screenshots (${i + 1}/${timeframes.length})` + ) + + try { + const config = { + ...baseConfig, + timeframe: timeframe, + sessionId: i === 0 ? sessionId : undefined // Only track progress for first timeframe + } + + // Capture screenshots WITHOUT analysis + const screenshots = await enhancedScreenshotService.captureWithLogin(config) + + if (screenshots && screenshots.length > 0) { + console.log(`โœ… Captured ${screenshots.length} screenshots for ${timeframeLabel}`) + + // Store screenshots with metadata + const screenshotData = { + timeframe: timeframe, + timeframeLabel: timeframeLabel, + screenshots: screenshots, + success: true + } + + screenshotResults.push(screenshotData) + allScreenshots.push(...screenshots) + + } else { + console.warn(`โš ๏ธ No screenshots captured for ${timeframeLabel}`) + screenshotResults.push({ + timeframe: timeframe, + timeframeLabel: timeframeLabel, + screenshots: [], + success: false, + error: 'No screenshots captured' + }) + } + + } catch (timeframeError) { + console.error(`โŒ Error capturing ${timeframeLabel}:`, timeframeError) + screenshotResults.push({ + timeframe: timeframe, + timeframeLabel: timeframeLabel, + screenshots: [], + success: false, + error: timeframeError.message + }) + } + + // Small delay between captures + if (i < timeframes.length - 1) { + await new Promise(resolve => setTimeout(resolve, 1000)) + } + } + + console.log(`๐Ÿ“Š Batch screenshot collection completed: ${allScreenshots.length} total screenshots`) + progressTracker.updateStep(sessionId, 'capture', 'completed', `Captured ${allScreenshots.length} total screenshots`) + + // STEP 2: Send ALL screenshots to AI for comprehensive analysis + let analysis = null + + if (analyze && allScreenshots.length > 0) { + console.log(`๐Ÿค– Starting comprehensive AI analysis on ${allScreenshots.length} screenshots...`) + progressTracker.updateStep(sessionId, 'analysis', 'active', 'Running comprehensive AI analysis...') + + try { + if (allScreenshots.length === 1) { + analysis = await aiAnalysisService.analyzeScreenshot(allScreenshots[0]) + } else { + analysis = await aiAnalysisService.analyzeMultipleScreenshots(allScreenshots) + } + + if (analysis) { + console.log('โœ… Comprehensive AI analysis completed') + progressTracker.updateStep(sessionId, 'analysis', 'completed', 'AI analysis completed successfully!') + } else { + throw new Error('AI analysis returned null') + } + + } catch (analysisError) { + console.error('โŒ AI analysis failed:', analysisError) + progressTracker.updateStep(sessionId, 'analysis', 'error', `AI analysis failed: ${analysisError.message}`) + + // Don't fail the entire request - return screenshots without analysis + analysis = null + } + } + + // STEP 3: Format comprehensive results + const result = { + success: true, + type: 'batch_analysis', + sessionId, + timestamp: Date.now(), + symbol: symbol, + timeframes: timeframes, + layouts: baseConfig.layouts, + summary: `Batch analysis completed for ${timeframes.length} timeframes`, + totalScreenshots: allScreenshots.length, + screenshotResults: screenshotResults, + allScreenshots: allScreenshots.map(path => ({ + url: `/screenshots/${path.split('/').pop()}`, + timestamp: Date.now() + })), + analysis: analysis, // Comprehensive analysis of ALL screenshots + message: `Successfully captured ${allScreenshots.length} screenshots${analysis ? ' with comprehensive AI analysis' : ''}` + } + + // Clean up session + setTimeout(() => progressTracker.deleteSession(sessionId), 2000) + + return NextResponse.json(result) + + } catch (error) { + console.error('โŒ Batch analysis failed:', error) + progressTracker.updateStep(sessionId, 'analysis', 'error', `Batch analysis failed: ${error.message}`) + setTimeout(() => progressTracker.deleteSession(sessionId), 5000) + + return NextResponse.json( + { + success: false, + error: 'Batch analysis failed', + message: error.message, + sessionId: sessionId + }, + { status: 500 } + ) + } + + } catch (error) { + console.error('Batch analysis API error:', error) + return NextResponse.json( + { + success: false, + error: 'Batch analysis failed', + message: error.message + }, + { status: 500 } + ) + } +} + +// Helper function to get timeframe label +function getTimeframeLabel(timeframe) { + const timeframes = [ + { label: '1m', value: '1' }, + { label: '5m', value: '5' }, + { label: '15m', value: '15' }, + { label: '30m', value: '30' }, + { label: '1h', value: '60' }, + { label: '2h', value: '120' }, + { label: '4h', value: '240' }, + { label: '1d', value: 'D' }, + { label: '1w', value: 'W' }, + { label: '1M', value: 'M' }, + ] + + return timeframes.find(t => t.value === timeframe)?.label || timeframe +} + +export async function GET() { + return NextResponse.json({ + message: 'Batch Analysis API - use POST method for multi-timeframe analysis', + endpoints: { + POST: '/api/batch-analysis - Run multi-timeframe analysis with parameters' + } + }) +} diff --git a/components/AIAnalysisPanel.tsx b/components/AIAnalysisPanel.tsx index aca9133..3f57dfd 100644 --- a/components/AIAnalysisPanel.tsx +++ b/components/AIAnalysisPanel.tsx @@ -251,68 +251,57 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP onAnalysisComplete(data.analysis, analysisSymbol) } } else { - // Multiple timeframe analysis - const results = [] + // Multiple timeframe analysis - use batch analysis endpoint + console.log(`๐Ÿงช Starting batch analysis for ${analysisTimeframes.length} timeframes`) - for (let i = 0; i < analysisTimeframes.length; i++) { - const tf = analysisTimeframes[i] - const timeframeLabel = timeframes.find(t => t.value === tf)?.label || tf - - console.log(`๐Ÿงช Analyzing timeframe: ${timeframeLabel}`) - - const response = await fetch('/api/enhanced-screenshot', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - symbol: analysisSymbol, - timeframe: tf, - layouts: selectedLayouts, - analyze: true - }) - }) - - const result = await response.json() - results.push({ - timeframe: tf, - timeframeLabel, - success: response.ok, - result + const response = await fetch('/api/batch-analysis', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + symbol: analysisSymbol, + timeframes: analysisTimeframes, + layouts: selectedLayouts, + analyze: true }) + }) - // Start progress tracking for the first timeframe session - if (i === 0 && result.sessionId) { - startProgressTracking(result.sessionId) - } - - // Update timeframe progress manually for multi-timeframe - setProgress(prev => prev ? { - ...prev, - timeframeProgress: { - current: i + 1, - total: analysisTimeframes.length, - currentTimeframe: timeframeLabel - } - } : null) - - // Small delay between requests - await new Promise(resolve => setTimeout(resolve, 1000)) - } + const data = await response.json() - const multiResult = { + if (!response.ok) { + throw new Error(data.error || 'Batch analysis failed') + } + + // Start real-time progress tracking if sessionId is provided + if (data.sessionId) { + startProgressTracking(data.sessionId) + } + + // Convert batch analysis result to compatible format + const batchResult = { type: 'multi_timeframe', symbol: analysisSymbol, - summary: `Analyzed ${results.length} timeframes for ${analysisSymbol}`, - results + summary: data.summary, + analysis: data.analysis, // Comprehensive analysis of ALL screenshots + totalScreenshots: data.totalScreenshots, + results: data.screenshotResults?.map((sr: any) => ({ + timeframe: sr.timeframe, + timeframeLabel: sr.timeframeLabel, + success: sr.success, + result: { + screenshots: sr.screenshots?.map((path: string) => ({ + url: `/screenshots/${path.split('/').pop()}`, + timestamp: Date.now() + })) || [], + analysis: sr.timeframe === analysisTimeframes[0] ? data.analysis : null // Only show comprehensive analysis on first timeframe + } + })) || [] } - setResult(multiResult) + setResult(batchResult) - // Call the callback with the first successful analysis result if provided - if (onAnalysisComplete) { - const firstSuccessfulResult = results.find(r => r.success && r.result?.analysis) - if (firstSuccessfulResult) { - onAnalysisComplete(firstSuccessfulResult.result.analysis, analysisSymbol) - } + // Call the callback with the comprehensive analysis result if provided + if (onAnalysisComplete && data.analysis) { + onAnalysisComplete(data.analysis, analysisSymbol) } } } catch (err) { @@ -1397,15 +1386,28 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP )} - {/* Position Calculator */} - {result && result.analysis && ( -
+ {/* Position Calculator - Always Show */} +
+
+
+

+ + ๐Ÿ“Š + + Position Calculator +

+
+
+ Live Calculator +
+
+
- )} +
{result && !result.analysis && result.screenshots && (
diff --git a/components/PositionCalculator.tsx b/components/PositionCalculator.tsx index b6d04d5..96e3289 100644 --- a/components/PositionCalculator.tsx +++ b/components/PositionCalculator.tsx @@ -42,6 +42,9 @@ export default function PositionCalculator({ const [tradingFee, setTradingFee] = useState(0.1) // 0.1% fee const [maintenanceMargin, setMaintenanceMargin] = useState(0.5) // 0.5% maintenance margin + // Debug logging + console.log('๐Ÿ“Š PositionCalculator mounted with:', { analysis, currentPrice, symbol }) + // Auto-detect position type from analysis useEffect(() => { if (analysis?.recommendation) { @@ -57,7 +60,10 @@ export default function PositionCalculator({ // Fetch current market price if not provided useEffect(() => { const fetchPrice = async () => { + console.log('๐Ÿ” Fetching price for:', symbol, 'currentPrice:', currentPrice) + if (currentPrice > 0) { + console.log('โœ… Using provided currentPrice:', currentPrice) setMarketPrice(currentPrice) return } @@ -65,13 +71,21 @@ export default function PositionCalculator({ try { const response = await fetch(`/api/price?symbol=${symbol}`) const data = await response.json() + console.log('๐Ÿ“Š Price API response:', data) + if (data.price) { + console.log('โœ… Setting market price to:', data.price) setMarketPrice(data.price) + } else { + console.error('โŒ No price in API response') + setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100) } } catch (error) { - console.error('Failed to fetch price:', error) + console.error('โŒ Failed to fetch price:', error) // Fallback to a reasonable default for testing - setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100) + const fallbackPrice = symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100 + console.log('๐Ÿ”„ Using fallback price:', fallbackPrice) + setMarketPrice(fallbackPrice) } } @@ -80,20 +94,29 @@ export default function PositionCalculator({ // Calculate position metrics const calculatePosition = () => { - const priceToUse = marketPrice || currentPrice - if (!priceToUse || priceToUse <= 0) return null + let priceToUse = marketPrice || currentPrice + console.log('๐Ÿ“Š Calculating position with:', { priceToUse, marketPrice, currentPrice, investmentAmount, leverage }) + + // Use fallback price if no price is available + if (!priceToUse || priceToUse <= 0) { + priceToUse = symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100 + console.log('๐Ÿ”„ Using fallback price:', priceToUse) + } const positionSize = investmentAmount * leverage const marginRequired = investmentAmount const fee = positionSize * (tradingFee / 100) const netInvestment = investmentAmount + fee + console.log('๐Ÿ“ˆ Position calculation:', { positionSize, marginRequired, fee, netInvestment }) + // Get AI analysis targets if available let entryPrice = priceToUse let stopLoss = 0 let takeProfit = 0 if (analysis && analysis.analysis) { + console.log('๐Ÿค– Using AI analysis:', analysis.analysis) // Try to extract entry, stop loss, and take profit from AI analysis const analysisText = analysis.analysis.toLowerCase() @@ -101,18 +124,21 @@ export default function PositionCalculator({ const entryMatch = analysisText.match(/entry[:\s]*[\$]?(\d+\.?\d*)/i) if (entryMatch) { entryPrice = parseFloat(entryMatch[1]) + console.log('๐Ÿ“ Found entry price:', entryPrice) } // Look for stop loss const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i) if (stopMatch) { stopLoss = parseFloat(stopMatch[1]) + console.log('๐Ÿ›‘ Found stop loss:', stopLoss) } // Look for take profit const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i) if (profitMatch) { takeProfit = parseFloat(profitMatch[1]) + console.log('๐ŸŽฏ Found take profit:', takeProfit) } // If no specific targets found, use percentage-based defaults @@ -120,14 +146,17 @@ export default function PositionCalculator({ stopLoss = positionType === 'long' ? entryPrice * 0.95 // 5% stop loss for long : entryPrice * 1.05 // 5% stop loss for short + console.log('๐Ÿ”„ Using default stop loss:', stopLoss) } if (!takeProfit) { takeProfit = positionType === 'long' ? entryPrice * 1.10 // 10% take profit for long : entryPrice * 0.90 // 10% take profit for short + console.log('๐Ÿ”„ Using default take profit:', takeProfit) } } else { + console.log('๐Ÿ“Š No AI analysis, using defaults') // Default targets if no analysis stopLoss = positionType === 'long' ? priceToUse * 0.95 @@ -170,6 +199,7 @@ export default function PositionCalculator({ netInvestment } + console.log('โœ… Position calculation result:', result) setCalculation(result) onPositionChange?.(result) return result @@ -177,8 +207,15 @@ export default function PositionCalculator({ // Recalculate when parameters change useEffect(() => { + console.log('๐Ÿ”„ Recalculating position...') calculatePosition() - }, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin]) + }, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin, marketPrice]) + + // Force initial calculation + useEffect(() => { + console.log('๐Ÿš€ Position Calculator initialized, forcing calculation...') + calculatePosition() + }, []) const formatCurrency = (amount: number, decimals: number = 2) => { return new Intl.NumberFormat('en-US', { @@ -219,7 +256,10 @@ export default function PositionCalculator({
Current Market Price
- {symbol}: ${formatPrice(marketPrice || currentPrice || 0)} + {symbol}: ${formatPrice(marketPrice || currentPrice || (symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100))} + {(!marketPrice && !currentPrice) && ( + (fallback) + )}
{analysis?.recommendation && ( diff --git a/lib/aggressive-cleanup.ts b/lib/aggressive-cleanup.ts index 8efff31..b3bfbda 100644 --- a/lib/aggressive-cleanup.ts +++ b/lib/aggressive-cleanup.ts @@ -8,9 +8,10 @@ class AggressiveCleanup { private static instance: AggressiveCleanup private cleanupInterval: NodeJS.Timeout | null = null private isRunning = false + private isInitialized = false private constructor() { - this.startPeriodicCleanup() + // Don't auto-start - let startup.ts control it } static getInstance(): AggressiveCleanup { @@ -20,7 +21,21 @@ class AggressiveCleanup { return AggressiveCleanup.instance } - private startPeriodicCleanup() { + startPeriodicCleanup() { + if (this.isInitialized) { + console.log('๐Ÿ”„ Aggressive cleanup already initialized') + return + } + + this.isInitialized = true + console.log('๐Ÿš€ Starting aggressive cleanup system') + + // In development, disable aggressive cleanup to avoid interfering with analysis + if (process.env.NODE_ENV === 'development') { + console.log('๐Ÿ”’ Aggressive cleanup disabled in development mode') + return + } + // Clean up every 5 minutes this.cleanupInterval = setInterval(async () => { try { @@ -34,6 +49,8 @@ class AggressiveCleanup { setTimeout(() => { this.cleanupOrphanedProcesses().catch(console.error) }, 30000) + + console.log('โœ… Aggressive cleanup system started (5 min intervals)') } async cleanupOrphanedProcesses(): Promise { @@ -43,6 +60,23 @@ class AggressiveCleanup { console.log('๐Ÿงน Running aggressive cleanup for orphaned processes...') try { + // Check for active analysis sessions + try { + const { progressTracker } = await import('./progress-tracker') + const activeSessions = progressTracker.getActiveSessions() + + if (activeSessions.length > 0) { + console.log(`โš ๏ธ Skipping cleanup - ${activeSessions.length} active analysis sessions: ${activeSessions.join(', ')}`) + return + } + + console.log('โœ… No active analysis sessions, proceeding with cleanup') + } catch (importError) { + console.error('โŒ Error importing progress tracker:', importError) + console.log('โš ๏ธ Skipping cleanup due to import error') + return + } + // Find and kill orphaned chromium processes const chromiumProcesses = await this.findChromiumProcesses() diff --git a/lib/startup.ts b/lib/startup.ts index 7449c90..2d9969f 100644 --- a/lib/startup.ts +++ b/lib/startup.ts @@ -7,7 +7,9 @@ import aggressiveCleanup from './aggressive-cleanup' // Initialize cleanup system console.log('๐Ÿš€ Initializing trading bot systems...') console.log('๐Ÿงน Process cleanup handlers initialized') -console.log('๐Ÿงน Aggressive cleanup system initialized') + +// Start aggressive cleanup system (singleton pattern ensures only one instance) +aggressiveCleanup.startPeriodicCleanup() // Export cleanup for manual access export { processCleanup, aggressiveCleanup } diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index b3f76b8..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// Initialize cleanup system -import '../../lib/startup' - -export const metadata = { - title: 'Next.js', - description: 'Generated by Next.js', -} - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - {children} - - ) -}