import { PrismaClient } from '@prisma/client' import { aiAnalysisService, AnalysisResult } from './ai-analysis' import { jupiterDEXService } from './jupiter-dex-service' import { enhancedScreenshotService } from './enhanced-screenshot-simple' import { TradingViewCredentials } from './tradingview-automation' import { progressTracker } from './progress-tracker' import aggressiveCleanup from './aggressive-cleanup' 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 isRunning = false private config: AutomationConfig | null = null private intervalId: NodeJS.Timeout | null = null private stats = { totalTrades: 0, successfulTrades: 0, winRate: 0, totalPnL: 0, errorCount: 0, lastError: null as string | null } async startAutomation(config: AutomationConfig): Promise { try { if (this.isRunning) { throw new Error('Automation is already running') } this.config = config this.isRunning = true console.log(`πŸ€– Starting automation for ${config.symbol} ${config.timeframe} in ${config.mode} mode`) // Ensure user exists in database await prisma.user.upsert({ where: { id: config.userId }, update: {}, create: { id: config.userId, email: `${config.userId}@example.com`, name: config.userId, createdAt: new Date(), updatedAt: new Date() } }) // Delete any existing automation session for this user/symbol/timeframe await prisma.automationSession.deleteMany({ where: { userId: config.userId, symbol: config.symbol, timeframe: config.timeframe } }) // Create new automation session in database await prisma.automationSession.create({ data: { userId: config.userId, status: 'ACTIVE', mode: config.mode, symbol: config.symbol, timeframe: config.timeframe, settings: { tradingAmount: config.tradingAmount, maxLeverage: config.maxLeverage, stopLossPercent: config.stopLossPercent, takeProfitPercent: config.takeProfitPercent, maxDailyTrades: config.maxDailyTrades, riskPercentage: config.riskPercentage }, startBalance: config.tradingAmount, currentBalance: config.tradingAmount, createdAt: new Date(), updatedAt: new Date() } }) // Start automation cycle this.startAutomationCycle() return true } catch (error) { console.error('Failed to start automation:', error) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' return false } } private startAutomationCycle(): void { if (!this.config) return // Get interval in milliseconds based on timeframe const intervalMs = this.getIntervalFromTimeframe(this.config.timeframe) console.log(`πŸ”„ Starting automation cycle every ${intervalMs/1000} seconds`) this.intervalId = setInterval(async () => { if (this.isRunning && this.config) { await this.runAutomationCycle() } }, intervalMs) // Run first cycle immediately this.runAutomationCycle() } private getIntervalFromTimeframe(timeframe: string): number { const intervals: { [key: string]: number } = { '1m': 60 * 1000, '3m': 3 * 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, '1d': 24 * 60 * 60 * 1000 } return intervals[timeframe] || intervals['1h'] // Default to 1 hour } private async runAutomationCycle(): Promise { if (!this.config) return try { console.log(`πŸ” Running automation cycle for ${this.config.symbol} ${this.config.timeframe}`) // Step 1: Check daily trade limit const todayTrades = await this.getTodayTradeCount(this.config.userId) if (todayTrades >= this.config.maxDailyTrades) { console.log(`πŸ“Š Daily trade limit reached (${todayTrades}/${this.config.maxDailyTrades})`) // Run cleanup even when trade limit is reached await this.runPostCycleCleanup('trade_limit_reached') return } // Step 2: Take screenshot and analyze const analysisResult = await this.performAnalysis() if (!analysisResult) { console.log('❌ Analysis failed, skipping cycle') // Run cleanup when analysis fails await this.runPostCycleCleanup('analysis_failed') return } // Step 3: Store analysis for learning await this.storeAnalysisForLearning(analysisResult) // Step 4: Update session with latest analysis await this.updateSessionWithAnalysis(analysisResult) // Step 5: Make trading decision const tradeDecision = await this.makeTradeDecision(analysisResult) if (!tradeDecision) { console.log('πŸ“Š No trading opportunity found') // Run cleanup when no trading opportunity await this.runPostCycleCleanup('no_opportunity') return } // Step 6: Execute trade await this.executeTrade(tradeDecision) // Run cleanup after successful trade execution await this.runPostCycleCleanup('trade_executed') } catch (error) { console.error('Error in automation cycle:', error) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' // Run cleanup on error await this.runPostCycleCleanup('error') } } private async runPostCycleCleanup(reason: string): Promise { console.log(`🧹 Running post-cycle cleanup (reason: ${reason})`) // Longer delay to ensure all analysis processes AND trading decision have finished await new Promise(resolve => setTimeout(resolve, 10000)) // 10 seconds try { // Signal that the complete analysis cycle is done await aggressiveCleanup.signalAnalysisCycleComplete() console.log(`βœ… Post-cycle cleanup completed for: ${reason}`) } catch (error) { console.error('Error in post-cycle cleanup:', error) } } private async performAnalysis(): Promise<{ screenshots: string[] analysis: AnalysisResult | null } | null> { // Generate unique session ID for this analysis const sessionId = `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` try { console.log(`πŸ“Έ Starting multi-timeframe analysis with dual layouts... (Session: ${sessionId})`) // Create progress tracking session const progressSteps = [ { id: 'init', title: 'Initialize', description: 'Starting multi-timeframe analysis', status: 'pending' as const }, { id: 'capture', title: 'Capture', description: 'Capturing screenshots for all timeframes', status: 'pending' as const }, { id: 'analysis', title: 'Analysis', description: 'Running AI analysis on screenshots', status: 'pending' as const }, { id: 'complete', title: 'Complete', description: 'Analysis complete', status: 'pending' as const } ] progressTracker.createSession(sessionId, progressSteps) progressTracker.updateStep(sessionId, 'init', 'active', 'Starting multi-timeframe analysis...') // Multi-timeframe analysis: 15m, 1h, 2h, 4h const timeframes = ['15', '1h', '2h', '4h'] const symbol = this.config!.symbol console.log(`πŸ” Analyzing ${symbol} across timeframes: ${timeframes.join(', ')} with AI + DIY layouts`) progressTracker.updateStep(sessionId, 'init', 'completed', `Starting analysis for ${timeframes.length} timeframes`) progressTracker.updateStep(sessionId, 'capture', 'active', 'Capturing screenshots...') // Analyze each timeframe with both AI and DIY layouts const multiTimeframeResults = await this.analyzeMultiTimeframeWithDualLayouts(symbol, timeframes, sessionId) if (multiTimeframeResults.length === 0) { console.log('❌ No multi-timeframe analysis results') progressTracker.updateStep(sessionId, 'capture', 'error', 'No analysis results captured') progressTracker.deleteSession(sessionId) return null } progressTracker.updateStep(sessionId, 'capture', 'completed', `Captured ${multiTimeframeResults.length} timeframe analyses`) progressTracker.updateStep(sessionId, 'analysis', 'active', 'Processing multi-timeframe results...') // Process and combine multi-timeframe results const combinedResult = this.combineMultiTimeframeAnalysis(multiTimeframeResults) if (!combinedResult.analysis) { console.log('❌ Failed to combine multi-timeframe analysis') progressTracker.updateStep(sessionId, 'analysis', 'error', 'Failed to combine analysis results') progressTracker.deleteSession(sessionId) return null } console.log(`βœ… Multi-timeframe analysis completed: ${combinedResult.analysis.recommendation} with ${combinedResult.analysis.confidence}% confidence`) console.log(`πŸ“Š Timeframe alignment: ${this.analyzeTimeframeAlignment(multiTimeframeResults)}`) progressTracker.updateStep(sessionId, 'analysis', 'completed', `Analysis complete: ${combinedResult.analysis.recommendation}`) progressTracker.updateStep(sessionId, 'complete', 'completed', 'Multi-timeframe analysis finished') // Clean up session after successful completion setTimeout(() => { progressTracker.deleteSession(sessionId) }, 2000) return combinedResult } catch (error) { console.error('Error performing multi-timeframe analysis:', error) progressTracker.updateStep(sessionId, 'analysis', 'error', error instanceof Error ? error.message : 'Unknown error') setTimeout(() => { progressTracker.deleteSession(sessionId) }, 5000) return null } } private async analyzeMultiTimeframeWithDualLayouts( symbol: string, timeframes: string[], sessionId: string ): Promise> { const results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }> = [] for (let i = 0; i < timeframes.length; i++) { const timeframe = timeframes[i] try { console.log(`πŸ“Š Analyzing ${symbol} ${timeframe} with AI + DIY layouts... (${i + 1}/${timeframes.length})`) // Update progress for timeframe progressTracker.updateTimeframeProgress(sessionId, i + 1, timeframes.length, timeframe) // Use the dual-layout configuration for each timeframe const screenshotConfig = { symbol: symbol, timeframe: timeframe, layouts: ['ai', 'diy'], sessionId: sessionId } const result = await aiAnalysisService.captureAndAnalyzeWithConfig(screenshotConfig) if (result.analysis) { console.log(`βœ… ${timeframe} analysis: ${result.analysis.recommendation} (${result.analysis.confidence}% confidence)`) results.push({ symbol, timeframe, analysis: result.analysis }) } else { console.log(`❌ ${timeframe} analysis failed`) results.push({ symbol, timeframe, analysis: null }) } // Small delay between captures to avoid overwhelming the system await new Promise(resolve => setTimeout(resolve, 3000)) } catch (error) { console.error(`Failed to analyze ${symbol} ${timeframe}:`, error) results.push({ symbol, timeframe, analysis: null }) } } return results } private combineMultiTimeframeAnalysis(results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>): { screenshots: string[] analysis: AnalysisResult | null } { const validResults = results.filter(r => r.analysis !== null) if (validResults.length === 0) { return { screenshots: [], analysis: null } } // Get the primary timeframe (1h) as base const primaryResult = validResults.find(r => r.timeframe === '1h') || validResults[0] const screenshots = validResults.length > 0 ? [primaryResult.timeframe] : [] // Calculate weighted confidence based on timeframe alignment const alignment = this.calculateTimeframeAlignment(validResults) const baseAnalysis = primaryResult.analysis! // Adjust confidence based on timeframe alignment const adjustedConfidence = Math.round(baseAnalysis.confidence * alignment.score) // Create combined analysis with multi-timeframe reasoning const combinedAnalysis: AnalysisResult = { ...baseAnalysis, confidence: adjustedConfidence, reasoning: `Multi-timeframe Dual-Layout Analysis (${results.map(r => r.timeframe).join(', ')}): ${baseAnalysis.reasoning} πŸ“Š Each timeframe analyzed with BOTH AI layout (RSI, MACD, EMAs) and DIY layout (Stochastic RSI, VWAP, OBV) ⏱️ Timeframe Alignment: ${alignment.description} οΏ½ Signal Strength: ${alignment.strength} 🎯 Confidence Adjustment: ${baseAnalysis.confidence}% β†’ ${adjustedConfidence}% (${alignment.score >= 1 ? 'Enhanced' : 'Reduced'} due to timeframe ${alignment.score >= 1 ? 'alignment' : 'divergence'}) πŸ”¬ Analysis Details: ${validResults.map(r => `β€’ ${r.timeframe}: ${r.analysis?.recommendation} (${r.analysis?.confidence}% confidence)`).join('\n')}`, keyLevels: this.consolidateKeyLevels(validResults), marketSentiment: this.consolidateMarketSentiment(validResults) } return { screenshots, analysis: combinedAnalysis } } private calculateTimeframeAlignment(results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>): { score: number description: string strength: string } { const recommendations = results.map(r => r.analysis?.recommendation).filter(Boolean) const buySignals = recommendations.filter(r => r === 'BUY').length const sellSignals = recommendations.filter(r => r === 'SELL').length const holdSignals = recommendations.filter(r => r === 'HOLD').length const total = recommendations.length const maxSignals = Math.max(buySignals, sellSignals, holdSignals) const alignmentRatio = maxSignals / total let score = 1.0 let description = '' let strength = '' if (alignmentRatio >= 0.75) { score = 1.2 // Boost confidence description = `Strong alignment (${maxSignals}/${total} timeframes agree)` strength = 'Strong' } else if (alignmentRatio >= 0.5) { score = 1.0 // Neutral description = `Moderate alignment (${maxSignals}/${total} timeframes agree)` strength = 'Moderate' } else { score = 0.8 // Reduce confidence description = `Weak alignment (${maxSignals}/${total} timeframes agree)` strength = 'Weak' } return { score, description, strength } } private consolidateKeyLevels(results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>): any { const allLevels = results.map(r => r.analysis?.keyLevels).filter(Boolean) if (allLevels.length === 0) return {} // Use the 1h timeframe levels as primary, or first available const primaryLevels = results.find(r => r.timeframe === '1h')?.analysis?.keyLevels || allLevels[0] return { ...primaryLevels, note: `Consolidated from ${allLevels.length} timeframes` } } private consolidateMarketSentiment(results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>): 'BULLISH' | 'BEARISH' | 'NEUTRAL' { const sentiments = results.map(r => r.analysis?.marketSentiment).filter(Boolean) if (sentiments.length === 0) return 'NEUTRAL' // Use the 1h timeframe sentiment as primary, or first available const primarySentiment = results.find(r => r.timeframe === '1h')?.analysis?.marketSentiment || sentiments[0] return primarySentiment || 'NEUTRAL' } private analyzeTimeframeAlignment(results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>): string { const recommendations = results.map(r => ({ timeframe: r.timeframe, recommendation: r.analysis?.recommendation, confidence: r.analysis?.confidence || 0 })) const summary = recommendations.map(r => `${r.timeframe}: ${r.recommendation} (${r.confidence}%)`).join(', ') return summary } private async storeAnalysisForLearning(result: { screenshots: string[] analysis: AnalysisResult | null }): Promise { try { if (!result.analysis) return await prisma.aILearningData.create({ data: { userId: this.config!.userId, symbol: this.config!.symbol, timeframe: this.config!.timeframe, screenshot: result.screenshots[0] || '', analysisData: JSON.stringify(result.analysis), marketConditions: JSON.stringify({ marketSentiment: result.analysis.marketSentiment, keyLevels: result.analysis.keyLevels, timestamp: new Date().toISOString() }), confidenceScore: result.analysis.confidence, createdAt: new Date() } }) } catch (error) { console.error('Error storing analysis for learning:', error) } } private async updateSessionWithAnalysis(result: { screenshots: string[] analysis: AnalysisResult | null }): Promise { try { if (!result.analysis) return // Store the analysis decision in a field that works for now const analysisDecision = `${result.analysis.recommendation} with ${result.analysis.confidence}% confidence - ${result.analysis.summary}` // Update the current session with the latest analysis await prisma.automationSession.updateMany({ where: { userId: this.config!.userId, symbol: this.config!.symbol, timeframe: this.config!.timeframe, status: 'ACTIVE' }, data: { lastAnalysis: new Date(), lastError: analysisDecision // Temporarily store analysis here } }) // Also log the analysis for debugging console.log('πŸ“Š Analysis stored in database:', { recommendation: result.analysis.recommendation, confidence: result.analysis.confidence, summary: result.analysis.summary }) } catch (error) { console.error('Failed to update session with analysis:', error) } } private async makeTradeDecision(result: { screenshots: string[] analysis: AnalysisResult | null }): Promise { try { const analysis = result.analysis if (!analysis) return null // Only trade if confidence is high enough if (analysis.confidence < 70) { console.log(`πŸ“Š Confidence too low: ${analysis.confidence}%`) return null } // Only trade if direction is clear if (analysis.recommendation === 'HOLD') { console.log('πŸ“Š No clear direction signal') return null } // Calculate position size based on risk percentage const positionSize = this.calculatePositionSize(analysis) return { direction: analysis.recommendation, confidence: analysis.confidence, positionSize, stopLoss: this.calculateStopLoss(analysis), takeProfit: this.calculateTakeProfit(analysis), marketSentiment: analysis.marketSentiment } } catch (error) { console.error('Error making trade decision:', error) return null } } private calculatePositionSize(analysis: any): number { const baseAmount = this.config!.tradingAmount const riskAdjustment = this.config!.riskPercentage / 100 const confidenceAdjustment = analysis.confidence / 100 return baseAmount * riskAdjustment * confidenceAdjustment } private calculateStopLoss(analysis: any): number { // Use AI analysis stopLoss if available, otherwise calculate from entry price if (analysis.stopLoss?.price) { return analysis.stopLoss.price } const currentPrice = analysis.entry?.price || 150 // Default SOL price const stopLossPercent = this.config!.stopLossPercent / 100 if (analysis.recommendation === 'BUY') { return currentPrice * (1 - stopLossPercent) } else { return currentPrice * (1 + stopLossPercent) } } private calculateTakeProfit(analysis: any): number { // Use AI analysis takeProfit if available, otherwise calculate from entry price if (analysis.takeProfits?.tp1?.price) { return analysis.takeProfits.tp1.price } const currentPrice = analysis.entry?.price || 150 // Default SOL price const takeProfitPercent = this.config!.takeProfitPercent / 100 if (analysis.recommendation === 'BUY') { return currentPrice * (1 + takeProfitPercent) } else { return currentPrice * (1 - takeProfitPercent) } } private async executeTrade(decision: any): Promise { try { console.log(`🎯 Executing ${this.config!.mode} trade: ${decision.direction} ${decision.positionSize} ${this.config!.symbol}`) let tradeResult: any if (this.config!.mode === 'SIMULATION') { // Execute simulation trade tradeResult = await this.executeSimulationTrade(decision) } else { // Execute live trade via Jupiter tradeResult = await this.executeLiveTrade(decision) } // Store trade in database await this.storeTrade(decision, tradeResult) // Update stats this.updateStats(tradeResult) console.log(`βœ… Trade executed successfully: ${tradeResult.transactionId || 'SIMULATION'}`) } catch (error) { console.error('Error executing trade:', error) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Trade execution failed' } } private async executeSimulationTrade(decision: any): Promise { // Simulate trade execution with realistic parameters const currentPrice = decision.currentPrice || 100 // Mock price const slippage = Math.random() * 0.005 // 0-0.5% slippage const executionPrice = currentPrice * (1 + (Math.random() > 0.5 ? slippage : -slippage)) return { transactionId: `SIM_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, executionPrice, amount: decision.positionSize, direction: decision.direction, status: 'COMPLETED', timestamp: new Date(), fees: decision.positionSize * 0.001, // 0.1% fee slippage: slippage * 100 } } private async executeLiveTrade(decision: any): Promise { // Execute real trade via Jupiter DEX const inputToken = decision.direction === 'BUY' ? 'USDC' : 'SOL' const outputToken = decision.direction === 'BUY' ? 'SOL' : 'USDC' const tokens = { SOL: 'So11111111111111111111111111111111111111112', USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', } return await jupiterDEXService.executeSwap( tokens[inputToken as keyof typeof tokens], tokens[outputToken as keyof typeof tokens], decision.positionSize, 50 // 0.5% slippage ) } private async storeTrade(decision: any, result: any): Promise { try { await prisma.trade.create({ data: { userId: this.config!.userId, symbol: this.config!.symbol, side: decision.direction, amount: decision.positionSize, price: result.executionPrice, status: result.status, driftTxId: result.transactionId || result.txId, fees: result.fees || 0, stopLoss: decision.stopLoss, takeProfit: decision.takeProfit, isAutomated: true, tradingMode: this.config!.mode, confidence: decision.confidence, marketSentiment: decision.marketSentiment, createdAt: new Date() } }) } catch (error) { console.error('Error storing trade:', error) } } private updateStats(result: any): void { this.stats.totalTrades++ if (result.status === 'COMPLETED') { this.stats.successfulTrades++ this.stats.winRate = (this.stats.successfulTrades / this.stats.totalTrades) * 100 // Update PnL (simplified calculation) const pnl = result.amount * 0.01 * (Math.random() > 0.5 ? 1 : -1) // Random PnL for demo this.stats.totalPnL += pnl } } private async getTodayTradeCount(userId: string): Promise { const today = new Date() today.setHours(0, 0, 0, 0) const count = await prisma.trade.count({ where: { userId, isAutomated: true, createdAt: { gte: today } } }) return count } async stopAutomation(): Promise { try { this.isRunning = false // Clear the interval if it exists if (this.intervalId) { clearInterval(this.intervalId) this.intervalId = null } // Update database session status to STOPPED if (this.config) { await prisma.automationSession.updateMany({ where: { userId: this.config.userId, symbol: this.config.symbol, timeframe: this.config.timeframe, status: 'ACTIVE' }, data: { status: 'STOPPED', updatedAt: new Date() } }) } this.config = 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) { return false } this.isRunning = false console.log('⏸️ Automation paused') return true } catch (error) { console.error('Failed to pause automation:', error) return false } } async resumeAutomation(): Promise { try { if (!this.config) { return false } this.isRunning = true console.log('▢️ Automation resumed') return true } catch (error) { console.error('Failed to resume automation:', error) return false } } async getStatus(): Promise { try { // Get the latest active automation session from database first const session = await prisma.automationSession.findFirst({ where: { status: 'ACTIVE' }, orderBy: { createdAt: 'desc' } }) if (!session) { return null } // If we have a session but automation is not running in memory, // it means the server was restarted but the session is still active const isActiveInMemory = this.isRunning && this.config !== null // Auto-restart automation if session exists but not running in memory if (!isActiveInMemory) { console.log('πŸ”„ Found active session but automation not running, attempting auto-restart...') await this.autoRestartFromSession(session) } return { isActive: this.isRunning && this.config !== null, mode: session.mode as 'SIMULATION' | 'LIVE', symbol: session.symbol, timeframe: session.timeframe, totalTrades: session.totalTrades, successfulTrades: session.successfulTrades, winRate: session.winRate, totalPnL: session.totalPnL, errorCount: session.errorCount, lastError: session.lastError || undefined, lastAnalysis: session.lastAnalysis || undefined, lastTrade: session.lastTrade || undefined, nextScheduled: session.nextScheduled || undefined } } catch (error) { console.error('Failed to get automation status:', error) return null } } private async autoRestartFromSession(session: any): Promise { try { const settings = session.settings || {} const config: AutomationConfig = { userId: session.userId, mode: session.mode, symbol: session.symbol, timeframe: session.timeframe, tradingAmount: settings.tradingAmount || 100, maxLeverage: settings.maxLeverage || 3, stopLossPercent: settings.stopLossPercent || 2, takeProfitPercent: settings.takeProfitPercent || 6, maxDailyTrades: settings.maxDailyTrades || 5, riskPercentage: settings.riskPercentage || 2 } await this.startAutomation(config) console.log('βœ… Automation auto-restarted successfully') } catch (error) { console.error('Failed to auto-restart automation:', error) } } async getLearningInsights(userId: string): Promise<{ totalAnalyses: number avgAccuracy: number bestTimeframe: string worstTimeframe: string commonFailures: string[] recommendations: string[] }> { try { // For now, return mock data return { totalAnalyses: 150, avgAccuracy: 0.72, bestTimeframe: '1h', worstTimeframe: '15m', commonFailures: [ 'Low confidence predictions', 'Missed support/resistance levels', 'Timeframe misalignment' ], recommendations: [ 'Focus on 1h timeframe for better accuracy', 'Wait for higher confidence signals (>75%)', '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()