import { PrismaClient } from '@prisma/client' import { enhancedScreenshotService } from './enhanced-screenshot-robust' import { progressTracker } from './progress-tracker' import { analysisCompletionFlag } from './analysis-completion-flag' import { driftTradingService } from './drift-trading-final' import { automatedCleanupService } from './automated-cleanup-service' const prisma = new PrismaClient() interface AutomationConfig { userId: string mode: 'SIMULATION' | 'LIVE' symbol: string timeframe: string selectedTimeframes: string[] tradingAmount: number maxLeverage: number stopLossPercent: number takeProfitPercent: number maxDailyTrades: number riskPercentage: number dexProvider: string } class SafeAutomationService { private isRunning = false private config: AutomationConfig | null = null private intervalId: NodeJS.Timeout | null = null private lastTradeTime = 0 private tradeCount = 0 // SAFETY LIMITS private readonly MIN_TRADE_INTERVAL = 10 * 60 * 1000 // 10 minutes minimum between trades private readonly MAX_TRADES_PER_HOUR = 2 private readonly ANALYSIS_COOLDOWN = 5 * 60 * 1000 // 5 minutes between analyses private readonly MAX_DAILY_TRADES = 6 private lastAnalysisTime = 0 private stats = { totalTrades: 0, successfulTrades: 0, winRate: 0, totalPnL: 0, errorCount: 0, lastError: null as string | null, lastAnalysis: null as string | null, nextScheduled: null as string | null, nextAnalysisIn: 0, analysisInterval: 0, currentCycle: 0 } async startAutomation(config: AutomationConfig): Promise<{ success: boolean, message?: string }> { try { if (this.isRunning) { return { success: false, message: 'Automation is already running' } } // SAFETY CHECK: Rate limiting const now = Date.now() if (now - this.lastAnalysisTime < this.ANALYSIS_COOLDOWN) { const remaining = Math.ceil((this.ANALYSIS_COOLDOWN - (now - this.lastAnalysisTime)) / 1000) return { success: false, message: `Rate limit: Wait ${remaining} seconds before starting automation` } } // SAFETY CHECK: Check for recent trades const recentTrades = await this.checkRecentTrades() if (recentTrades >= this.MAX_TRADES_PER_HOUR) { return { success: false, message: `Rate limit exceeded: ${recentTrades} trades in last hour (max: ${this.MAX_TRADES_PER_HOUR})` } } // SAFETY CHECK: Daily trade limit const dailyTrades = await this.checkDailyTrades() if (dailyTrades >= this.MAX_DAILY_TRADES) { return { success: false, message: `Daily limit exceeded: ${dailyTrades} trades today (max: ${this.MAX_DAILY_TRADES})` } } this.config = config this.isRunning = true this.lastAnalysisTime = now this.tradeCount = 0 console.log(`πŸ€– SAFE: Starting automation for ${config.symbol} in ${config.mode} mode`) console.log(`πŸ›‘οΈ SAFETY: Rate limiting enabled - max ${this.MAX_TRADES_PER_HOUR} trades/hour`) console.log(`⏱️ SAFETY: Minimum ${this.MIN_TRADE_INTERVAL/1000/60} minutes between trades`) // Start SAFE automation cycle with longer intervals this.startSafeAutomationCycle() return { success: true, message: 'Safe automation started with rate limiting' } } catch (error) { console.error('Failed to start safe automation:', error) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' return { success: false, message: `Startup failed: ${error instanceof Error ? error.message : 'Unknown error'}` } } } private startSafeAutomationCycle(): void { if (!this.config) return // SAFETY: Use much longer intervals (minimum 5 minutes) const baseInterval = this.getIntervalFromTimeframe(this.config.timeframe) const safeInterval = Math.max(baseInterval, this.ANALYSIS_COOLDOWN) console.log(`πŸ”„ SAFE: Starting automation cycle every ${safeInterval/1000/60} minutes`) this.stats.analysisInterval = safeInterval this.stats.nextScheduled = new Date(Date.now() + safeInterval).toISOString() this.stats.nextAnalysisIn = safeInterval this.intervalId = setInterval(async () => { if (this.isRunning && this.config) { const now = Date.now() // SAFETY: Check cooldown before each cycle if (now - this.lastAnalysisTime < this.ANALYSIS_COOLDOWN) { console.log(`⏸️ SAFETY: Analysis cooldown active, skipping cycle`) return } await this.runSafeAutomationCycle() this.lastAnalysisTime = now this.stats.currentCycle++ // Update next scheduled time this.stats.nextScheduled = new Date(Date.now() + safeInterval).toISOString() this.stats.nextAnalysisIn = safeInterval } }, safeInterval) // Run first cycle after delay to prevent immediate execution const initialDelay = 30000 // 30 seconds setTimeout(() => { if (this.isRunning && this.config) { this.runSafeAutomationCycle() this.lastAnalysisTime = Date.now() this.stats.currentCycle++ } }, initialDelay) } private async runSafeAutomationCycle(): Promise { if (!this.config) return const sessionId = `automation_${Date.now()}` try { console.log(`\nπŸ”„ SAFE: Running automation cycle ${this.stats.currentCycle + 1} for ${this.config.symbol}`) // SAFETY: Check if we can trade const canTrade = await this.canExecuteTrade() if (!canTrade.allowed) { console.log(`β›” SAFETY: Trade blocked - ${canTrade.reason}`) return } progressTracker.createSession(sessionId, `Safe automation cycle for ${this.config.symbol}`) progressTracker.updateStep(sessionId, 'init', 'active', 'Starting safe analysis...') // Perform analysis with enhanced cleanup const analysisResult = await this.performSafeAnalysis(sessionId) if (!analysisResult) { console.log('❌ Analysis failed, skipping trade execution') progressTracker.updateStep(sessionId, 'analysis', 'error', 'Analysis failed') return } this.stats.lastAnalysis = new Date().toISOString() progressTracker.updateStep(sessionId, 'analysis', 'completed', 'Analysis completed successfully') // Execute trade only if analysis is strongly bullish/bearish if (this.shouldExecuteTrade(analysisResult)) { const tradeResult = await this.executeSafeTrade(analysisResult) if (tradeResult?.success) { this.stats.totalTrades++ this.stats.successfulTrades++ this.stats.winRate = (this.stats.successfulTrades / this.stats.totalTrades) * 100 this.lastTradeTime = Date.now() } } else { console.log('πŸ“Š Analysis result does not meet execution criteria') } progressTracker.updateStep(sessionId, 'complete', 'completed', 'Safe automation cycle completed') } catch (error) { console.error('Error in safe automation cycle:', error) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' progressTracker.updateStep(sessionId, 'analysis', 'error', error instanceof Error ? error.message : 'Unknown error') } finally { // GUARANTEED CLEANUP await this.guaranteedCleanup(sessionId) } } private async guaranteedCleanup(sessionId: string): Promise { console.log(`🧹 GUARANTEED: Starting cleanup for session ${sessionId}`) try { // Force cleanup with timeout protection const cleanupPromise = automatedCleanupService.performCleanup() const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Cleanup timeout')), 10000) ) await Promise.race([cleanupPromise, timeoutPromise]) console.log('βœ… GUARANTEED: Cleanup completed successfully') } catch (error) { console.error('⚠️ GUARANTEED: Cleanup failed, forcing manual cleanup', error) // Manual fallback cleanup try { const { execSync } = require('child_process') execSync('pkill -f "chrome|chromium" 2>/dev/null || true') console.log('βœ… GUARANTEED: Manual cleanup completed') } catch (manualError) { console.error('❌ GUARANTEED: Manual cleanup also failed', manualError) } } // Clean up progress tracking setTimeout(() => { progressTracker.deleteSession(sessionId) }, 5000) } private async canExecuteTrade(): Promise<{ allowed: boolean, reason?: string }> { const now = Date.now() // Check time-based cooldown if (now - this.lastTradeTime < this.MIN_TRADE_INTERVAL) { const remaining = Math.ceil((this.MIN_TRADE_INTERVAL - (now - this.lastTradeTime)) / 1000 / 60) return { allowed: false, reason: `Trade cooldown: ${remaining} minutes remaining` } } // Check hourly limit const recentTrades = await this.checkRecentTrades() if (recentTrades >= this.MAX_TRADES_PER_HOUR) { return { allowed: false, reason: `Hourly limit reached: ${recentTrades}/${this.MAX_TRADES_PER_HOUR}` } } // Check daily limit const dailyTrades = await this.checkDailyTrades() if (dailyTrades >= this.MAX_DAILY_TRADES) { return { allowed: false, reason: `Daily limit reached: ${dailyTrades}/${this.MAX_DAILY_TRADES}` } } return { allowed: true } } private async checkRecentTrades(): Promise { const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000) try { const count = await prisma.trade.count({ where: { createdAt: { gte: oneHourAgo }, status: { not: 'CANCELLED' } } }) return count } catch (error) { console.error('Error checking recent trades:', error) return 0 } } private async checkDailyTrades(): Promise { const startOfDay = new Date() startOfDay.setHours(0, 0, 0, 0) try { const count = await prisma.trade.count({ where: { createdAt: { gte: startOfDay }, status: { not: 'CANCELLED' } } }) return count } catch (error) { console.error('Error checking daily trades:', error) return 0 } } private async performSafeAnalysis(sessionId: string): Promise { try { if (!this.config) return null progressTracker.updateStep(sessionId, 'analysis', 'active', 'Performing safe screenshot analysis...') const analysisResult = await enhancedScreenshotService.captureAndAnalyze({ symbol: this.config.symbol, timeframe: this.config.timeframe, layouts: ['ai', 'diy'], analyze: true, sessionId }) return analysisResult } catch (error) { console.error('Error in safe analysis:', error) throw error } } private shouldExecuteTrade(analysisResult: any): boolean { if (!analysisResult?.analysis?.recommendation) return false const recommendation = analysisResult.analysis.recommendation.toLowerCase() // Only execute on strong signals const strongBullish = recommendation.includes('strong buy') || recommendation.includes('very bullish') const strongBearish = recommendation.includes('strong sell') || recommendation.includes('very bearish') return strongBullish || strongBearish } private async executeSafeTrade(analysisResult: any): Promise { if (!this.config) return null try { console.log('πŸ’° SAFE: Executing trade with enhanced safety checks...') const tradeParams = { mode: this.config.mode, symbol: this.config.symbol, amount: this.config.tradingAmount, leverage: this.config.maxLeverage, stopLoss: this.config.stopLossPercent, takeProfit: this.config.takeProfitPercent, analysis: analysisResult.analysis, riskPercentage: this.config.riskPercentage } const tradeResult = await driftTradingService.executeTrade(tradeParams) if (tradeResult?.success) { console.log(`βœ… SAFE: Trade executed successfully`) } else { console.log(`❌ SAFE: Trade execution failed: ${tradeResult?.error}`) } return tradeResult } catch (error) { console.error('Error executing safe trade:', error) return { success: false, error: error.message } } } async stopAutomation(): Promise<{ success: boolean, message?: string }> { try { this.isRunning = false if (this.intervalId) { clearInterval(this.intervalId) this.intervalId = null console.log('β›” SAFE: Automation interval cleared') } // Force cleanup await automatedCleanupService.performCleanup() this.config = null this.stats.nextAnalysisIn = 0 this.stats.nextScheduled = null console.log('βœ… SAFE: Automation stopped successfully') return { success: true, message: 'Safe automation stopped successfully' } } catch (error) { console.error('Error stopping automation:', error) return { success: false, message: error instanceof Error ? error.message : 'Unknown error' } } } getStatus() { return { isActive: this.isRunning, mode: this.config?.mode || 'SIMULATION', symbol: this.config?.symbol || 'SOLUSD', timeframe: this.config?.timeframe || '1h', ...this.stats } } private getIntervalFromTimeframe(timeframe: string): number { // Much longer intervals for safety const intervals: { [key: string]: number } = { '5': 15 * 60 * 1000, // 15 minutes for 5m timeframe '15': 30 * 60 * 1000, // 30 minutes for 15m timeframe '60': 60 * 60 * 1000, // 1 hour for 1h timeframe '240': 2 * 60 * 60 * 1000, // 2 hours for 4h timeframe '1440': 4 * 60 * 60 * 1000 // 4 hours for 1d timeframe } return intervals[timeframe] || 60 * 60 * 1000 // Default 1 hour } } export const automationService = new SafeAutomationService()