import { PrismaClient } from '@prisma/client' import { aiAnalysisService, AnalysisResult } from './ai-analysis' import { jupiterDEXService } from './jupiter-dex-service' import { TradingViewCredentials } from './tradingview-automation' const prisma = new PrismaClient() export interface AutomationConfig { userId: string mode: 'SIMULATION' | 'LIVE' symbol: string timeframe: string tradingAmount: number maxLeverage: number stopLossPercent: number takeProfitPercent: number maxDailyTrades: number riskPercentage: number } export interface AutomationStatus { isActive: boolean mode: 'SIMULATION' | 'LIVE' symbol: string timeframe: string totalTrades: number successfulTrades: number winRate: number totalPnL: number lastAnalysis?: Date lastTrade?: Date nextScheduled?: Date errorCount: number lastError?: string } export class AutomationService { private activeSession: any = null private intervalId: NodeJS.Timeout | null = null private isRunning = false private credentials: TradingViewCredentials | null = null constructor() { this.initialize() } private async initialize() { // Load credentials from environment or database this.credentials = { email: process.env.TRADINGVIEW_EMAIL || '', password: process.env.TRADINGVIEW_PASSWORD || '' } } async startAutomation(config: AutomationConfig): Promise { try { if (this.isRunning) { throw new Error('Automation is already running') } // Validate configuration if (!config.userId || !config.symbol || !config.timeframe) { throw new Error('Invalid automation configuration') } // Create or update automation session const existingSession = await prisma.automationSession.findFirst({ where: { userId: config.userId, symbol: config.symbol, timeframe: config.timeframe } }) let session if (existingSession) { session = await prisma.automationSession.update({ where: { id: existingSession.id }, data: { status: 'ACTIVE', mode: config.mode, settings: config as any, updatedAt: new Date() } }) } else { session = await prisma.automationSession.create({ data: { userId: config.userId, status: 'ACTIVE', mode: config.mode, symbol: config.symbol, timeframe: config.timeframe, settings: config as any } }) } this.activeSession = session this.isRunning = true // Start the automation loop this.startAutomationLoop(config) console.log(`πŸ€– Automation started for ${config.symbol} ${config.timeframe} in ${config.mode} mode`) return true } catch (error) { console.error('Failed to start automation:', error) return false } } async stopAutomation(): Promise { try { if (!this.isRunning) { return true } // Clear interval if (this.intervalId) { clearInterval(this.intervalId) this.intervalId = null } // Update session status if (this.activeSession) { await prisma.automationSession.update({ where: { id: this.activeSession.id }, data: { status: 'STOPPED', updatedAt: new Date() } }) } this.isRunning = false this.activeSession = null console.log('πŸ›‘ Automation stopped') return true } catch (error) { console.error('Failed to stop automation:', error) return false } } async pauseAutomation(): Promise { try { if (!this.isRunning || !this.activeSession) { return false } // Clear interval but keep session if (this.intervalId) { clearInterval(this.intervalId) this.intervalId = null } // Update session status await prisma.automationSession.update({ where: { id: this.activeSession.id }, data: { status: 'PAUSED', updatedAt: new Date() } }) console.log('⏸️ Automation paused') return true } catch (error) { console.error('Failed to pause automation:', error) return false } } async resumeAutomation(): Promise { try { if (!this.activeSession) { return false } // Update session status await prisma.automationSession.update({ where: { id: this.activeSession.id }, data: { status: 'ACTIVE', updatedAt: new Date() } }) // Restart automation loop const config = this.activeSession.settings as AutomationConfig this.startAutomationLoop(config) console.log('▢️ Automation resumed') return true } catch (error) { console.error('Failed to resume automation:', error) return false } } private startAutomationLoop(config: AutomationConfig) { // Calculate interval based on timeframe const intervalMs = this.getIntervalFromTimeframe(config.timeframe) console.log(`πŸ”„ Starting automation loop every ${intervalMs/1000/60} minutes`) this.intervalId = setInterval(async () => { try { await this.executeAutomationCycle(config) } catch (error) { console.error('Automation cycle error:', error) await this.handleAutomationError(error) } }, intervalMs) // Execute first cycle immediately setTimeout(async () => { try { await this.executeAutomationCycle(config) } catch (error) { console.error('Initial automation cycle error:', error) await this.handleAutomationError(error) } }, 5000) // 5 second delay for initialization } private async executeAutomationCycle(config: AutomationConfig) { console.log(`πŸ”„ Executing automation cycle for ${config.symbol} ${config.timeframe}`) // Check if we've reached daily trade limit const todayTrades = await this.getTodayTradeCount(config.userId) if (todayTrades >= config.maxDailyTrades) { console.log(`πŸ“Š Daily trade limit reached (${todayTrades}/${config.maxDailyTrades})`) return } // Generate session ID for progress tracking const sessionId = `auto_${Date.now()}_${Math.random().toString(36).substr(2, 8)}` // Step 1: Capture screenshot and analyze const screenshotConfig = { symbol: config.symbol, timeframe: config.timeframe, layouts: ['ai', 'diy'], sessionId, analyze: true } const result = await aiAnalysisService.captureAndAnalyzeWithConfig(screenshotConfig) if (!result.analysis || result.screenshots.length === 0) { console.log('❌ Failed to capture or analyze chart') return } // Step 2: Store analysis in database for learning (only if analysis exists) if (result.analysis) { await this.storeAnalysisForLearning(config, { ...result, analysis: result.analysis }, sessionId) } // Step 3: Check if we should execute trade const shouldTrade = await this.shouldExecuteTrade(result.analysis, config) if (!shouldTrade) { console.log('πŸ“Š Analysis does not meet trading criteria') return } // Step 4: Execute trade based on analysis await this.executeTrade(config, result.analysis, result.screenshots[0]) // Step 5: Update session statistics await this.updateSessionStats(config.userId) } private async storeAnalysisForLearning( config: AutomationConfig, result: { screenshots: string[], analysis: AnalysisResult }, sessionId: string ) { try { // Store in trading journal await prisma.tradingJournal.create({ data: { userId: config.userId, screenshotUrl: result.screenshots.join(','), aiAnalysis: JSON.stringify(result.analysis), marketSentiment: result.analysis.marketSentiment, keyLevels: result.analysis.keyLevels, recommendation: result.analysis.recommendation, confidence: result.analysis.confidence, symbol: config.symbol, timeframe: config.timeframe, tradingMode: config.mode, sessionId: sessionId, priceAtAnalysis: result.analysis.entry?.price } }) // Store in AI learning data await prisma.aILearningData.create({ data: { userId: config.userId, sessionId: sessionId, analysisData: result.analysis as any, marketConditions: { timeframe: config.timeframe, symbol: config.symbol, timestamp: new Date().toISOString() }, confidenceScore: result.analysis.confidence, timeframe: config.timeframe, symbol: config.symbol, screenshot: result.screenshots[0], predictedPrice: result.analysis.entry?.price } }) console.log('πŸ“š Analysis stored for learning') } catch (error) { console.error('Failed to store analysis for learning:', error) } } private async shouldExecuteTrade(analysis: AnalysisResult, config: AutomationConfig): Promise { // Check minimum confidence threshold if (analysis.confidence < 70) { console.log(`πŸ“Š Confidence too low: ${analysis.confidence}%`) return false } // Check if recommendation is actionable if (analysis.recommendation === 'HOLD') { console.log('πŸ“Š Recommendation is HOLD') return false } // Check if we have required trading levels if (!analysis.entry || !analysis.stopLoss) { console.log('πŸ“Š Missing entry or stop loss levels') return false } // Check risk/reward ratio if (analysis.riskToReward) { const rr = this.parseRiskReward(analysis.riskToReward) if (rr < 2) { console.log(`πŸ“Š Risk/reward ratio too low: ${rr}`) return false } } // Check recent performance for dynamic adjustments const recentPerformance = await this.getRecentPerformance(config.userId) if (recentPerformance.winRate < 0.4 && recentPerformance.totalTrades > 10) { console.log('πŸ“Š Recent performance too poor, requiring higher confidence') return analysis.confidence > 80 } return true } private async executeTrade(config: AutomationConfig, analysis: AnalysisResult, screenshotUrl: string) { try { console.log(`πŸš€ Executing ${config.mode} trade: ${analysis.recommendation} ${config.symbol}`) const side = analysis.recommendation === 'BUY' ? 'BUY' : 'SELL' const amount = this.calculateTradeAmount(config, analysis) const leverage = Math.min(config.maxLeverage, 3) // Cap at 3x for safety let tradeResult: any = null if (config.mode === 'SIMULATION') { // Simulate trade tradeResult = await this.simulateTrade({ symbol: config.symbol, side, amount, price: analysis.entry?.price || 0, stopLoss: analysis.stopLoss?.price, takeProfit: analysis.takeProfits?.tp1?.price, leverage }) } else { // Execute real trade via Jupiter DEX tradeResult = await jupiterDEXService.executeTrade({ symbol: config.symbol, side, amount, stopLoss: analysis.stopLoss?.price, takeProfit: analysis.takeProfits?.tp1?.price }) } // Store trade in database await prisma.trade.create({ data: { userId: config.userId, symbol: config.symbol, side, amount, price: analysis.entry?.price || 0, status: tradeResult?.success ? 'FILLED' : 'FAILED', isAutomated: true, entryPrice: analysis.entry?.price, stopLoss: analysis.stopLoss?.price, takeProfit: analysis.takeProfits?.tp1?.price, leverage, timeframe: config.timeframe, tradingMode: config.mode, confidence: analysis.confidence, marketSentiment: analysis.marketSentiment, screenshotUrl, aiAnalysis: JSON.stringify(analysis), driftTxId: tradeResult?.txId, executedAt: new Date() } }) console.log(`βœ… Trade executed: ${tradeResult?.success ? 'SUCCESS' : 'FAILED'}`) } catch (error) { console.error('Trade execution error:', error) // Store failed trade await prisma.trade.create({ data: { userId: config.userId, symbol: config.symbol, side: analysis.recommendation === 'BUY' ? 'BUY' : 'SELL', amount: config.tradingAmount, price: analysis.entry?.price || 0, status: 'FAILED', isAutomated: true, timeframe: config.timeframe, tradingMode: config.mode, confidence: analysis.confidence, marketSentiment: analysis.marketSentiment, screenshotUrl, aiAnalysis: JSON.stringify(analysis) } }) } } private async simulateTrade(params: { symbol: string side: string amount: number price: number stopLoss?: number takeProfit?: number leverage?: number }): Promise<{ success: boolean; txId?: string }> { // Simulate realistic execution with small random variation const priceVariation = 0.001 * (Math.random() - 0.5) // Β±0.1% const executedPrice = params.price * (1 + priceVariation) // Simulate network delay await new Promise(resolve => setTimeout(resolve, 500)) return { success: true, txId: `sim_${Date.now()}_${Math.random().toString(36).substr(2, 8)}` } } private calculateTradeAmount(config: AutomationConfig, analysis: AnalysisResult): number { // Base amount from config let amount = config.tradingAmount // Adjust based on confidence const confidenceMultiplier = Math.min(analysis.confidence / 100, 1) amount *= confidenceMultiplier // Adjust based on risk percentage const riskAdjustment = config.riskPercentage / 100 amount *= riskAdjustment // Ensure minimum trade amount return Math.max(amount, 10) } private parseRiskReward(rrString: string): number { // Parse "1:2.5" format const parts = rrString.split(':') if (parts.length === 2) { return parseFloat(parts[1]) / parseFloat(parts[0]) } return 0 } private getIntervalFromTimeframe(timeframe: string): number { const intervals: { [key: string]: number } = { '1m': 1 * 60 * 1000, '5m': 5 * 60 * 1000, '15m': 15 * 60 * 1000, '30m': 30 * 60 * 1000, '1h': 60 * 60 * 1000, '2h': 2 * 60 * 60 * 1000, '4h': 4 * 60 * 60 * 1000, '6h': 6 * 60 * 60 * 1000, '12h': 12 * 60 * 60 * 1000, '1d': 24 * 60 * 60 * 1000 } return intervals[timeframe] || intervals['1h'] // Default to 1 hour } private async getTodayTradeCount(userId: string): Promise { const today = new Date() today.setHours(0, 0, 0, 0) const tomorrow = new Date(today) tomorrow.setDate(tomorrow.getDate() + 1) const count = await prisma.trade.count({ where: { userId, isAutomated: true, createdAt: { gte: today, lt: tomorrow } } }) return count } private async getRecentPerformance(userId: string): Promise<{ winRate: number totalTrades: number avgRR: number }> { const thirtyDaysAgo = new Date() thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30) const trades = await prisma.trade.findMany({ where: { userId, isAutomated: true, createdAt: { gte: thirtyDaysAgo }, status: 'FILLED' } }) const totalTrades = trades.length const winningTrades = trades.filter(t => (t.pnlPercent || 0) > 0).length const winRate = totalTrades > 0 ? winningTrades / totalTrades : 0 const avgRR = trades.reduce((sum, t) => sum + (t.actualRR || 0), 0) / Math.max(totalTrades, 1) return { winRate, totalTrades, avgRR } } private async updateSessionStats(userId: string) { try { if (!this.activeSession) return const stats = await this.getRecentPerformance(userId) await prisma.automationSession.update({ where: { id: this.activeSession.id }, data: { totalTrades: stats.totalTrades, successfulTrades: Math.round(stats.totalTrades * stats.winRate), winRate: stats.winRate, avgRiskReward: stats.avgRR, lastAnalysis: new Date() } }) } catch (error) { console.error('Failed to update session stats:', error) } } private async handleAutomationError(error: any) { try { if (this.activeSession) { await prisma.automationSession.update({ where: { id: this.activeSession.id }, data: { errorCount: { increment: 1 }, lastError: error.message || 'Unknown error', updatedAt: new Date() } }) // Stop automation if too many errors if (this.activeSession.errorCount >= 5) { console.log('❌ Too many errors, stopping automation') await this.stopAutomation() } } } catch (dbError) { console.error('Failed to handle automation error:', dbError) } } async getStatus(): Promise { try { if (!this.activeSession) { return null } const session = await prisma.automationSession.findUnique({ where: { id: this.activeSession.id } }) if (!session) { return null } return { isActive: this.isRunning, mode: session.mode as 'SIMULATION' | 'LIVE', symbol: session.symbol, timeframe: session.timeframe, totalTrades: session.totalTrades, successfulTrades: session.successfulTrades, winRate: session.winRate, totalPnL: session.totalPnL, lastAnalysis: session.lastAnalysis || undefined, lastTrade: session.lastTrade || undefined, nextScheduled: session.nextScheduled || undefined, errorCount: session.errorCount, lastError: session.lastError || undefined } } catch (error) { console.error('Failed to get automation status:', error) return null } } async getLearningInsights(userId: string): Promise<{ totalAnalyses: number avgAccuracy: number bestTimeframe: string worstTimeframe: string commonFailures: string[] recommendations: string[] }> { try { const learningData = await prisma.aILearningData.findMany({ where: { userId }, orderBy: { createdAt: 'desc' }, take: 100 }) const totalAnalyses = learningData.length const avgAccuracy = learningData.reduce((sum, d) => sum + (d.accuracyScore || 0), 0) / Math.max(totalAnalyses, 1) // Group by timeframe to find best/worst const timeframeStats = learningData.reduce((acc, d) => { if (!acc[d.timeframe]) { acc[d.timeframe] = { count: 0, accuracy: 0 } } acc[d.timeframe].count += 1 acc[d.timeframe].accuracy += d.accuracyScore || 0 return acc }, {} as { [key: string]: { count: number, accuracy: number } }) const timeframes = Object.entries(timeframeStats).map(([tf, stats]) => ({ timeframe: tf, avgAccuracy: stats.accuracy / stats.count })) const bestTimeframe = timeframes.sort((a, b) => b.avgAccuracy - a.avgAccuracy)[0]?.timeframe || 'Unknown' const worstTimeframe = timeframes.sort((a, b) => a.avgAccuracy - b.avgAccuracy)[0]?.timeframe || 'Unknown' return { totalAnalyses, avgAccuracy, bestTimeframe, worstTimeframe, commonFailures: [ 'Low confidence predictions', 'Missed support/resistance levels', 'Timeframe misalignment' ], recommendations: [ 'Focus on higher timeframes for better accuracy', 'Wait for higher confidence signals', 'Use multiple timeframe confirmation' ] } } catch (error) { console.error('Failed to get learning insights:', error) return { totalAnalyses: 0, avgAccuracy: 0, bestTimeframe: 'Unknown', worstTimeframe: 'Unknown', commonFailures: [], recommendations: [] } } } } export const automationService = new AutomationService()