import { tradingViewAutomation, TradingViewAutomation, TradingViewCredentials } from './tradingview-automation' import { progressTracker } from './progress-tracker' import fs from 'fs/promises' import path from 'path' export interface BatchScreenshotConfig { symbol: string timeframes: string[] // Multiple timeframes layouts?: string[] // Multiple chart layouts credentials?: TradingViewCredentials sessionId?: string } export interface ScreenshotBatch { symbol: string timeframe: string layout: string filepath: string timestamp: number } // Layout URL mappings for direct navigation const LAYOUT_URLS: { [key: string]: string } = { 'ai': 'Z1TzpUrf', // RSI + EMAs + MACD 'diy': 'vWVvjLhP' // Stochastic RSI + VWAP + OBV } export class BatchScreenshotService { private static readonly OPERATION_TIMEOUT = 180000 // 3 minutes for batch operations private aiSession: TradingViewAutomation | null = null private diySession: TradingViewAutomation | null = null private sessionId: string constructor(sessionId?: string) { this.sessionId = sessionId || `batch_${Date.now()}` } /** * Capture screenshots for multiple timeframes and layouts in parallel * This dramatically speeds up analysis by batching all screenshots */ async captureMultipleTimeframes(config: BatchScreenshotConfig): Promise { console.log('๐Ÿš€ Batch Screenshot Service - Multi-Timeframe Capture') console.log('๐Ÿ“‹ Config:', config) const { symbol, timeframes, layouts = ['ai', 'diy'], sessionId } = config const screenshotBatches: ScreenshotBatch[] = [] if (sessionId) { progressTracker.updateStep(sessionId, 'init', 'active', `Initializing batch capture for ${timeframes.length} timeframes`) } try { // Ensure screenshots directory exists const screenshotsDir = path.join(process.cwd(), 'screenshots') await fs.mkdir(screenshotsDir, { recursive: true }) console.log(`\n๐Ÿ”„ Starting batch capture: ${timeframes.length} timeframes ร— ${layouts.length} layouts = ${timeframes.length * layouts.length} screenshots`) if (sessionId) { progressTracker.updateStep(sessionId, 'auth', 'active', 'Initializing browser sessions') } // Create parallel promises for each layout const layoutPromises = layouts.map(async (layout) => { const session = await this.getOrCreateSession(layout, config.credentials) const layoutResults: ScreenshotBatch[] = [] console.log(`๐Ÿ“Š Starting ${layout.toUpperCase()} session for ${timeframes.length} timeframes`) if (sessionId) { progressTracker.updateStep(sessionId, 'navigation', 'active', `Navigating ${layout} layout to ${symbol}`) } // Navigate to first timeframe to establish base chart const firstTimeframe = timeframes[0] await this.navigateToChart(session, symbol, firstTimeframe, layout) console.log(`โœ… ${layout.toUpperCase()} session established on ${symbol} ${firstTimeframe}`) // Now capture all timeframes for this layout sequentially (but layouts run in parallel) for (let i = 0; i < timeframes.length; i++) { const timeframe = timeframes[i] try { if (sessionId) { progressTracker.updateStep(sessionId, 'capture', 'active', `Capturing ${layout} ${timeframe} (${i + 1}/${timeframes.length})`) } console.log(`๐Ÿ“ธ ${layout.toUpperCase()}: Capturing ${symbol} ${timeframe}...`) // Change timeframe if not the first one if (i > 0) { await this.changeTimeframe(session, timeframe, symbol) } // Take screenshot const timestamp = Date.now() const filename = `${symbol}_${timeframe}_${layout}_${timestamp}.png` const filepath = path.join(screenshotsDir, filename) await session.takeScreenshot({ filename }) const batch: ScreenshotBatch = { symbol, timeframe, layout, filepath: filename, // Store relative filename for compatibility timestamp } layoutResults.push(batch) console.log(`โœ… ${layout.toUpperCase()}: ${timeframe} captured โ†’ ${filename}`) // Small delay between timeframe changes to ensure chart loads if (i < timeframes.length - 1) { await new Promise(resolve => setTimeout(resolve, 2000)) } } catch (error) { console.error(`โŒ ${layout.toUpperCase()}: Failed to capture ${timeframe}:`, error) } } console.log(`๐ŸŽฏ ${layout.toUpperCase()} session completed: ${layoutResults.length}/${timeframes.length} screenshots`) return layoutResults }) // Wait for all layout sessions to complete const allLayoutResults = await Promise.all(layoutPromises) // Flatten results screenshotBatches.push(...allLayoutResults.flat()) if (sessionId) { progressTracker.updateStep(sessionId, 'capture', 'completed', `Batch capture completed: ${screenshotBatches.length} screenshots`) } console.log(`\n๐ŸŽฏ BATCH CAPTURE COMPLETED`) console.log(`๐Ÿ“Š Total Screenshots: ${screenshotBatches.length}`) console.log(`โฑ๏ธ Efficiency: ${timeframes.length * layouts.length} screenshots captured with ${layouts.length} parallel sessions`) return screenshotBatches } catch (error: any) { console.error('โŒ Batch screenshot capture failed:', error) if (sessionId) { progressTracker.updateStep(sessionId, 'capture', 'error', `Batch capture failed: ${error?.message || 'Unknown error'}`) } throw error } } /** * Get or create a persistent session for a layout */ private async getOrCreateSession(layout: string, credentials?: TradingViewCredentials): Promise { if (layout === 'ai' && this.aiSession) { return this.aiSession } if (layout === 'diy' && this.diySession) { return this.diySession } // Create new session console.log(`๐Ÿ”ง Creating new ${layout.toUpperCase()} session...`) const session = new TradingViewAutomation() // Initialize and login await session.init() await session.login(credentials || { email: process.env.TRADINGVIEW_EMAIL || '', password: process.env.TRADINGVIEW_PASSWORD || '' }) // Store session if (layout === 'ai') { this.aiSession = session } else { this.diySession = session } return session } /** * Navigate to a specific chart with symbol, timeframe, and layout */ private async navigateToChart(session: TradingViewAutomation, symbol: string, timeframe: string, layout: string): Promise { const layoutId = LAYOUT_URLS[layout] if (!layoutId) { throw new Error(`Unknown layout: ${layout}`) } // Use the navigateToLayout method console.log(`๐ŸŒ ${layout.toUpperCase()}: Navigating to layout ${layoutId} with ${symbol}`) const success = await session.navigateToLayout(layoutId, symbol, this.normalizeTimeframe(timeframe)) if (!success) { throw new Error(`Failed to navigate to ${layout} layout`) } // Wait for chart to fully load await new Promise(resolve => setTimeout(resolve, 5000)) } /** * Change timeframe on an existing chart session */ private async changeTimeframe(session: TradingViewAutomation, timeframe: string, symbol: string): Promise { console.log(`โฑ๏ธ Changing timeframe to ${timeframe}`) // Use navigateToSymbol with timeframe parameter to change timeframe const success = await session.navigateToSymbol(symbol, this.normalizeTimeframe(timeframe)) if (!success) { console.warn(`Failed to change timeframe to ${timeframe}, continuing...`) } // Wait for chart to reload with new timeframe await new Promise(resolve => setTimeout(resolve, 3000)) } /** * Normalize timeframe for TradingView URL compatibility */ private normalizeTimeframe(timeframe: string): string { const timeframeMap: { [key: string]: string } = { '5m': '5', '15m': '15', '30m': '30', '1h': '60', '2h': '120', '4h': '240', '1d': 'D', '1w': 'W', '1M': 'M' } return timeframeMap[timeframe] || timeframe } /** * Clean up all sessions */ async cleanup(): Promise { console.log('๐Ÿงน Cleaning up batch screenshot sessions...') try { if (this.aiSession) { await this.aiSession.forceCleanup() this.aiSession = null } if (this.diySession) { await this.diySession.forceCleanup() this.diySession = null } console.log('โœ… Batch screenshot cleanup completed') } catch (error) { console.error('โŒ Batch screenshot cleanup failed:', error) } } /** * Convert batch results to format expected by existing systems */ static formatBatchForAnalysis(batches: ScreenshotBatch[]): { [timeframe: string]: string[] } { const timeframeGroups: { [timeframe: string]: string[] } = {} for (const batch of batches) { if (!timeframeGroups[batch.timeframe]) { timeframeGroups[batch.timeframe] = [] } timeframeGroups[batch.timeframe].push(batch.filepath) } return timeframeGroups } } // Export a factory function instead of a singleton instance export const createBatchScreenshotService = (sessionId?: string) => new BatchScreenshotService(sessionId)