import { PrismaClient } from '@prisma/client' import { aiAnalysisService, AnalysisResult } from './ai-analysis' import { enhancedScreenshotService } from './enhanced-screenshot-simple' import { TradingViewCredentials } from './tradingview-automation' import { progressTracker, ProgressStatus } from './progress-tracker' import aggressiveCleanup from './aggressive-cleanup' import { analysisCompletionFlag } from './analysis-completion-flag' import priceMonitorService from './price-monitor' const prisma = new PrismaClient() export interface AutomationConfig { userId: string mode: 'SIMULATION' | 'LIVE' symbol: string timeframe: string selectedTimeframes?: string[] // Multi-timeframe support from UI tradingAmount: number maxLeverage: number // stopLossPercent and takeProfitPercent removed - AI calculates these automatically maxDailyTrades: number riskPercentage: number dexProvider?: string // DEX provider (DRIFT or JUPITER) } 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 nextAnalysisIn?: number // Seconds until next analysis analysisInterval?: number // Analysis interval in seconds currentCycle?: number // Current automation cycle } 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 and takeProfitPercent removed - AI calculates these automatically maxDailyTrades: config.maxDailyTrades, riskPercentage: config.riskPercentage }, startBalance: config.tradingAmount, currentBalance: config.tradingAmount, createdAt: new Date(), updatedAt: new Date() } }) // Start automation cycle this.startAutomationCycle() // Start price monitoring await priceMonitorService.startMonitoring() // Set up price monitor event listeners for automatic analysis triggering priceMonitorService.on('tp_approach', async (data) => { if (data.symbol === this.config?.symbol) { console.log(`🎯 TP approach detected for ${data.symbol}, triggering analysis...`) await this.triggerPriceBasedAnalysis('TP_APPROACH', data) } }) priceMonitorService.on('sl_approach', async (data) => { if (data.symbol === this.config?.symbol) { console.log(`⚠️ SL approach detected for ${data.symbol}, triggering analysis...`) await this.triggerPriceBasedAnalysis('SL_APPROACH', data) } }) priceMonitorService.on('critical_level', async (data) => { if (data.symbol === this.config?.symbol) { console.log(`🚨 Critical level reached for ${data.symbol}, triggering urgent analysis...`) await this.triggerPriceBasedAnalysis('CRITICAL', data) } }) 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}`) // Update next scheduled time in database for timer display const intervalMs = this.getIntervalFromTimeframe(this.config.timeframe) const nextScheduled = new Date(Date.now() + intervalMs) try { await prisma.automationSession.updateMany({ where: { userId: this.config.userId, status: 'ACTIVE' }, data: { nextScheduled: nextScheduled, lastAnalysis: new Date() } }) console.log(`⏰ Next analysis scheduled for: ${nextScheduled.toLocaleTimeString()}`) } catch (dbError) { console.error('Failed to update next scheduled time:', dbError) } // 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 { // Use the new post-analysis cleanup that respects completion flags await aggressiveCleanup.runPostAnalysisCleanup() 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)}` // Mark the start of analysis cycle to prevent cleanup interruption analysisCompletionFlag.startAnalysisCycle(sessionId) 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...') // Use selected timeframes from UI, fallback to default if not provided const timeframes = this.config!.selectedTimeframes || ['1h'] 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) // Mark analysis as complete to allow cleanup analysisCompletionFlag.markAnalysisComplete(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) // Mark analysis as complete to allow cleanup analysisCompletionFlag.markAnalysisComplete(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) // Mark analysis as complete to allow cleanup analysisCompletionFlag.markAnalysisComplete(sessionId) 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) // Mark analysis as complete even on error to allow cleanup analysisCompletionFlag.markAnalysisComplete(sessionId) 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 (first selected or default) as base const selectedTimeframes = this.config!.selectedTimeframes || ['1h'] const primaryTimeframe = selectedTimeframes[0] || '1h' const primaryResult = validResults.find(r => r.timeframe === primaryTimeframe) || 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 primary timeframe levels (first selected) as primary, or first available const selectedTimeframes = this.config!.selectedTimeframes || ['1h'] const primaryTimeframe = selectedTimeframes[0] || '1h' const primaryLevels = results.find(r => r.timeframe === primaryTimeframe)?.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 primary timeframe sentiment (first selected) as primary, or first available const selectedTimeframes = this.config!.selectedTimeframes || ['1h'] const primaryTimeframe = selectedTimeframes[0] || '1h' const primarySentiment = results.find(r => r.timeframe === primaryTimeframe)?.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 } // βœ… ENHANCED: Support both BUY and SELL signals if (analysis.recommendation === 'SELL') { // Check if we have SOL position to sell const hasPosition = await this.checkCurrentPosition() if (!hasPosition) { console.log('πŸ“Š SELL signal but no SOL position to sell - skipping') return null } console.log('πŸ“‰ SELL signal detected with existing SOL position') } else if (analysis.recommendation === 'BUY') { console.log('πŸ“ˆ BUY signal detected') } // Calculate position size based on risk percentage const positionSize = await this.calculatePositionSize(analysis) return { direction: analysis.recommendation, confidence: analysis.confidence, positionSize, stopLoss: this.calculateStopLoss(analysis), takeProfit: this.calculateTakeProfit(analysis), marketSentiment: analysis.marketSentiment, currentPrice: analysis.entry?.price || 190 // Store current price for calculations } } catch (error) { console.error('Error making trade decision:', error) return null } } // βœ… NEW: Check if we have SOL position available to sell private async checkCurrentPosition(): Promise { try { // Check recent trades to see current position const recentTrades = await prisma.trade.findMany({ where: { userId: this.config!.userId, symbol: this.config!.symbol, status: 'OPEN' }, orderBy: { createdAt: 'desc' }, take: 5 }) // Count open positions let netPosition = 0 for (const trade of recentTrades) { if (trade.side === 'BUY') { netPosition += trade.amount } else if (trade.side === 'SELL') { netPosition -= trade.amount } } console.log(`πŸ” Current SOL position: ${netPosition.toFixed(4)} SOL`) return netPosition > 0.001 // Have at least 0.001 SOL to sell } catch (error) { console.error('❌ Error checking current position:', error) // If we can't check, default to allowing the trade (fail-safe) return true } } private async calculatePositionSize(analysis: any): Promise { const baseAmount = this.config!.tradingAmount // This is the USD amount to invest const riskAdjustment = this.config!.riskPercentage / 100 const confidenceAdjustment = analysis.confidence / 100 // βœ… ENHANCED: Handle both BUY and SELL position sizing if (analysis.recommendation === 'SELL') { // For SELL orders, calculate how much SOL to sell based on current holdings return await this.calculateSellAmount(analysis) } // For BUY orders, calculate USD amount to invest const usdAmount = baseAmount * riskAdjustment * confidenceAdjustment // Get current price to convert USD to token amount let currentPrice = analysis.entry?.price || analysis.currentPrice if (!currentPrice) { try { const { default: PriceFetcher } = await import('./price-fetcher') currentPrice = await PriceFetcher.getCurrentPrice(this.config?.symbol || 'SOLUSD') console.log(`πŸ“Š Using current ${this.config?.symbol || 'SOLUSD'} price for position size: $${currentPrice}`) } catch (error) { console.error('Error fetching price for position size, using fallback:', error) currentPrice = this.config?.symbol === 'SOLUSD' ? 189 : 100 } } // Calculate token amount: USD investment / token price const tokenAmount = usdAmount / currentPrice console.log(`πŸ’° BUY Position calculation: $${usdAmount} Γ· $${currentPrice} = ${tokenAmount.toFixed(4)} tokens`) return tokenAmount } // βœ… NEW: Calculate SOL amount to sell for SELL orders private async calculateSellAmount(analysis: any): Promise { try { // Get current SOL holdings from recent open trades const openTrades = await prisma.trade.findMany({ where: { userId: this.config!.userId, symbol: this.config!.symbol, status: 'OPEN', side: 'BUY' // Only BUY trades represent SOL holdings }, orderBy: { createdAt: 'desc' } }) let totalSOLHoldings = 0 for (const trade of openTrades) { totalSOLHoldings += trade.amount } // Risk-adjusted sell amount (don't sell everything at once) const riskAdjustment = this.config!.riskPercentage / 100 const confidenceAdjustment = analysis.confidence / 100 const sellAmount = totalSOLHoldings * riskAdjustment * confidenceAdjustment console.log(`πŸ’° SELL Position calculation: ${totalSOLHoldings.toFixed(4)} SOL holdings Γ— ${(riskAdjustment * confidenceAdjustment * 100).toFixed(1)}% = ${sellAmount.toFixed(4)} SOL to sell`) return Math.max(sellAmount, 0.001) // Minimum 0.001 SOL } catch (error) { console.error('❌ Error calculating sell amount:', error) return 0.01 // Fallback: sell 0.01 SOL } } private calculateStopLoss(analysis: any): number { // βœ… AI-FIRST: Use AI analysis stopLoss if available if (analysis.stopLoss?.price) { const currentPrice = analysis.entry?.price || 189 const stopLossPrice = analysis.stopLoss.price // Convert absolute price to percentage if (analysis.recommendation === 'BUY') { return ((currentPrice - stopLossPrice) / currentPrice) * 100 } else if (analysis.recommendation === 'SELL') { return ((stopLossPrice - currentPrice) / currentPrice) * 100 } } // If AI provides explicit stop loss percentage, use it if (analysis.stopLossPercent) { return analysis.stopLossPercent } // Fallback: Dynamic stop loss based on market volatility (AI-calculated) // AI determines volatility-based stop loss (0.5% to 2% range) return this.calculateAIStopLoss(analysis) } private calculateTakeProfit(analysis: any): number { // βœ… AI-FIRST: Use AI analysis takeProfit if available if (analysis.takeProfits?.tp1?.price) { const currentPrice = analysis.entry?.price || 150 const takeProfitPrice = analysis.takeProfits.tp1.price // Convert absolute price to percentage if (analysis.recommendation === 'BUY') { return ((takeProfitPrice - currentPrice) / currentPrice) * 100 } else if (analysis.recommendation === 'SELL') { return ((currentPrice - takeProfitPrice) / currentPrice) * 100 } } // If AI provides explicit take profit percentage, use it if (analysis.takeProfitPercent) { return analysis.takeProfitPercent } // Fallback: Dynamic take profit based on AI risk/reward optimization return this.calculateAITakeProfit(analysis) } // AI-calculated dynamic stop loss based on volatility and market conditions private calculateAIStopLoss(analysis: any): number { // Extract confidence and market sentiment for adaptive stop loss const confidence = analysis.confidence || 70 const volatility = analysis.marketConditions?.volatility || 'MEDIUM' // Base stop loss percentages (proven to work from our testing) let baseStopLoss = 0.8 // 0.8% base (proven effective) // Adjust based on volatility if (volatility === 'HIGH') { baseStopLoss = 1.2 // Wider stop loss for high volatility } else if (volatility === 'LOW') { baseStopLoss = 0.5 // Tighter stop loss for low volatility } // Adjust based on confidence (higher confidence = tighter stop loss) if (confidence > 85) { baseStopLoss *= 0.8 // 20% tighter for high confidence } else if (confidence < 70) { baseStopLoss *= 1.3 // 30% wider for low confidence } return Math.max(0.3, Math.min(2.0, baseStopLoss)) // Cap between 0.3% and 2% } // AI-calculated dynamic take profit based on market conditions and risk/reward private calculateAITakeProfit(analysis: any): number { const stopLossPercent = this.calculateAIStopLoss(analysis) const confidence = analysis.confidence || 70 // Target minimum 1.5:1 risk/reward ratio, scaled by confidence let baseRiskReward = 1.5 if (confidence > 85) { baseRiskReward = 2.0 // Higher reward target for high confidence } else if (confidence < 70) { baseRiskReward = 1.2 // Lower reward target for low confidence } const takeProfitPercent = stopLossPercent * baseRiskReward return Math.max(0.5, Math.min(5.0, takeProfitPercent)) // Cap between 0.5% and 5% } 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 Drift Protocol console.log(`πŸ’° LIVE TRADE: $${this.config!.tradingAmount} trading amount configured`) tradeResult = await this.executeLiveTrade(decision) // If live trade failed, fall back to simulation for data consistency if (!tradeResult || !tradeResult.success) { console.log('⚠️ Live trade failed, falling back to simulation for record keeping') tradeResult = await this.executeSimulationTrade(decision) tradeResult.status = 'FAILED' tradeResult.error = 'Drift Protocol execution failed' } } // Store trade in database await this.storeTrade(decision, tradeResult) // Update stats this.updateStats(tradeResult) console.log(`βœ… Trade executed successfully: ${tradeResult.transactionId || 'SIMULATION'}`) // Force cleanup after successful trade execution if (tradeResult.status !== 'FAILED') { setTimeout(async () => { try { await aggressiveCleanup.forceCleanupAfterTrade() } catch (error) { console.error('Error in post-trade cleanup:', error) } }, 2000) // 2 second delay } } 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 let currentPrice = decision.currentPrice // If no current price provided, fetch real price if (!currentPrice) { try { const { default: PriceFetcher } = await import('./price-fetcher') currentPrice = await PriceFetcher.getCurrentPrice(this.config?.symbol || 'SOLUSD') console.log(`πŸ“Š Fetched real ${this.config?.symbol || 'SOLUSD'} price: $${currentPrice}`) } catch (error) { console.error('Error fetching real price, using fallback:', error) // Use a more realistic fallback based on symbol currentPrice = this.config?.symbol === 'SOLUSD' ? 189 : 100 } } 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: 'OPEN', // Trades start as OPEN, not 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 Drift Protocol console.log(`🌊 Executing Drift trade: ${decision.direction} ${this.config!.symbol}`) // Calculate AI-generated stop loss and take profit from analysis const stopLossPercent = decision.stopLoss || this.calculateAIStopLoss(decision) const takeProfitPercent = decision.takeProfit || this.calculateAITakeProfit(decision) console.log(`🎯 AI Risk Management: SL=${stopLossPercent}%, TP=${takeProfitPercent}%`) // Call the unified trading API endpoint that routes to Drift const tradeResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'}/api/automation/trade`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dexProvider: this.config!.dexProvider || 'DRIFT', action: 'place_order', symbol: this.config!.symbol, amount: this.config!.tradingAmount, side: decision.direction.toLowerCase(), leverage: this.config!.maxLeverage || 2, stopLoss: true, takeProfit: true, stopLossPercent: stopLossPercent, takeProfitPercent: takeProfitPercent, mode: this.config!.mode || 'SIMULATION' }) }) const tradeResult = await tradeResponse.json() // Convert Drift result to standard trade result format if (tradeResult.success) { return { transactionId: tradeResult.result?.transactionId || tradeResult.result?.txId, executionPrice: tradeResult.result?.executionPrice, amount: tradeResult.result?.amount, direction: decision.direction, status: 'COMPLETED', timestamp: new Date(), leverage: tradeResult.leverageUsed || this.config!.maxLeverage, stopLoss: stopLossPercent, takeProfit: takeProfitPercent, tradingAmount: this.config!.tradingAmount, dexProvider: 'DRIFT' } } else { throw new Error(tradeResult.error || 'Drift trade execution failed') } } private async storeTrade(decision: any, result: any): Promise { try { // Ensure we have a valid price for database storage const executionPrice = result.executionPrice || decision.currentPrice || decision.entryPrice if (!executionPrice) { console.error('❌ No valid price available for trade storage. Result:', result) console.error('❌ Decision data:', { currentPrice: decision.currentPrice, entryPrice: decision.entryPrice }) return } // For live trades, use the actual amounts from Drift const tradeAmount = result.tradingAmount ? this.config!.tradingAmount : decision.positionSize const actualAmount = result.amount || decision.positionSize console.log(`πŸ’Ύ Storing trade: ${decision.direction} ${actualAmount} ${this.config!.symbol} at $${executionPrice}`) await prisma.trade.create({ data: { userId: this.config!.userId, symbol: this.config!.symbol, side: decision.direction, amount: actualAmount, price: executionPrice, status: result.status || 'COMPLETED', 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(), // Add Drift-specific fields for live trades ...(this.config!.mode === 'LIVE' && result.tradingAmount && { realTradingAmount: this.config!.tradingAmount, leverage: result.leverage, driftTxId: result.transactionId }) } }) console.log('βœ… Trade stored in database successfully') } 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 } // Stop price monitoring try { await priceMonitorService.stopMonitoring() console.log('πŸ“Š Price monitoring stopped') } catch (error) { console.error('Failed to stop price monitoring:', error) } // 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) } // Calculate next analysis timing const analysisInterval = Math.floor(this.getIntervalFromTimeframe(session.timeframe) / 1000) // Convert to seconds let nextAnalysisIn = 0 if (this.isRunning && session.nextScheduled) { const nextScheduledTime = new Date(session.nextScheduled).getTime() const currentTime = Date.now() nextAnalysisIn = Math.max(0, Math.floor((nextScheduledTime - currentTime) / 1000)) } 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, nextAnalysisIn: nextAnalysisIn, analysisInterval: analysisInterval, currentCycle: session.totalTrades || 0 } } 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 and takeProfitPercent removed - AI calculates these automatically 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 with dynamic timeframe const selectedTimeframes = this.config?.selectedTimeframes || ['1h'] const primaryTimeframe = selectedTimeframes[0] || '1h' return { totalAnalyses: 150, avgAccuracy: 0.72, bestTimeframe: primaryTimeframe, worstTimeframe: '15m', commonFailures: [ 'Low confidence predictions', 'Missed support/resistance levels', 'Timeframe misalignment' ], recommendations: [ `Focus on ${primaryTimeframe} 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: [] } } } /** * Trigger analysis based on price movement alerts */ private async triggerPriceBasedAnalysis( trigger: 'TP_APPROACH' | 'SL_APPROACH' | 'CRITICAL', data: any ): Promise { if (!this.config || !this.isRunning) { console.log('❌ Cannot trigger price-based analysis: automation not running') return } const sessionId = `price-trigger-${Date.now()}` try { console.log(`πŸ”₯ Price-based analysis triggered by ${trigger} for ${data.symbol}`) console.log(`πŸ“Š Current price: $${data.currentPrice}, Target: $${data.targetPrice}`) // Create progress tracker for this analysis const steps = [ { id: 'trigger', title: 'Triggered by price movement', description: 'Analysis initiated by price alert', status: 'pending' as ProgressStatus }, { id: 'screenshot', title: 'Capturing screenshots', description: 'Taking fresh market screenshots', status: 'pending' as ProgressStatus }, { id: 'analysis', title: 'Running AI analysis', description: 'Analyzing current market conditions', status: 'pending' as ProgressStatus }, { id: 'evaluation', title: 'Evaluating position', description: 'Checking position adjustments', status: 'pending' as ProgressStatus }, { id: 'complete', title: 'Analysis complete', description: 'Price-based analysis finished', status: 'pending' as ProgressStatus } ] progressTracker.createSession(sessionId, steps) progressTracker.updateStep(sessionId, 'trigger', 'active', `${trigger}: ${data.symbol} at $${data.currentPrice}`) // Run enhanced screenshot capture with current symbol/timeframe progressTracker.updateStep(sessionId, 'screenshot', 'active') const screenshotConfig = { symbol: this.config.symbol, timeframe: this.config.timeframe, layouts: ['ai', 'diy'], sessionId } const screenshots = await enhancedScreenshotService.captureWithLogin(screenshotConfig) if (!screenshots || screenshots.length === 0) { throw new Error('Failed to capture screenshots for price-based analysis') } progressTracker.updateStep(sessionId, 'screenshot', 'completed', `Captured ${screenshots.length} screenshots`) progressTracker.updateStep(sessionId, 'analysis', 'active') // Simplified analysis call - just use the first screenshot const analysisResult = await aiAnalysisService.analyzeScreenshot(screenshots[0]) if (!analysisResult) { throw new Error('AI analysis returned null result') } progressTracker.updateStep(sessionId, 'analysis', 'completed', `Analysis: ${analysisResult.recommendation}`) progressTracker.updateStep(sessionId, 'evaluation', 'active') // Store the triggered analysis in trading journal await prisma.tradingJournal.create({ data: { userId: this.config.userId, screenshotUrl: screenshots[0] || '', aiAnalysis: analysisResult.reasoning || 'No analysis available', confidence: analysisResult.confidence || 0, recommendation: analysisResult.recommendation || 'HOLD', symbol: this.config.symbol, timeframe: this.config.timeframe, sessionId, notes: `Price-triggered analysis: ${trigger} - Current: $${data.currentPrice}, Target: $${data.targetPrice}`, marketSentiment: analysisResult.marketSentiment || 'Unknown', tradingMode: this.config.mode, isAutomated: true, priceAtAnalysis: data.currentPrice, marketCondition: trigger, createdAt: new Date() } }) // Log important insights for potential position adjustments if (analysisResult.recommendation === 'SELL' && trigger === 'SL_APPROACH') { console.log('⚠️ AI recommends SELL while approaching Stop Loss - consider early exit') } else if (analysisResult.recommendation === 'BUY' && trigger === 'TP_APPROACH') { console.log('🎯 AI recommends BUY while approaching Take Profit - consider extending position') } progressTracker.updateStep(sessionId, 'evaluation', 'completed') progressTracker.updateStep(sessionId, 'complete', 'completed', `${analysisResult.recommendation} (${analysisResult.confidence}% confidence)`) console.log(`βœ… Price-based analysis completed (${trigger}): ${analysisResult.recommendation} with ${analysisResult.confidence}% confidence`) } catch (error) { console.error(`❌ Price-based analysis failed (${trigger}):`, error) progressTracker.updateStep(sessionId, 'complete', 'error', `Error: ${error instanceof Error ? error.message : 'Unknown error'}`) this.stats.errorCount++ this.stats.lastError = error instanceof Error ? error.message : 'Unknown error' } } } export const automationService = new AutomationService()