Files
trading_bot_v3/lib/enhanced-screenshot-batch.ts
mindesbunister 1e4f305657 fix: emergency automation fix - stop runaway trading loops
- Replace automation service with emergency rate-limited version
- Add 5-minute minimum interval between automation starts
- Implement forced Chromium process cleanup on stop
- Backup broken automation service as .broken file
- Emergency service prevents multiple simultaneous automations
- Fixed 1400+ Chromium process accumulation issue
- Tested and confirmed: rate limiting works, processes stay at 0
2025-07-24 20:33:20 +02:00

290 lines
9.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<ScreenshotBatch[]> {
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<TradingViewAutomation> {
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<void> {
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<void> {
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<void> {
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)