Fix multi-timeframe analysis display and database issues
- Fixed analysis-details API to display multi-timeframe analysis results - Added comprehensive timeframe breakdown (15m, 1h, 2h, 4h) with confidence levels - Fixed database field recognition issues with Prisma client - Enhanced analysis display with entry/exit levels and technical analysis - Added proper stop loss and take profit calculations from AI analysis - Improved multi-layout analysis display (AI + DIY layouts) - Fixed automation service to handle database schema sync issues - Added detailed momentum, trend, and volume analysis display - Enhanced decision visibility on automation dashboard
This commit is contained in:
@@ -2,6 +2,7 @@ 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'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
@@ -71,7 +72,16 @@ export class AutomationService {
|
||||
}
|
||||
})
|
||||
|
||||
// Create automation session in database
|
||||
// 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,
|
||||
@@ -163,14 +173,17 @@ export class AutomationService {
|
||||
// Step 3: Store analysis for learning
|
||||
await this.storeAnalysisForLearning(analysisResult)
|
||||
|
||||
// Step 4: Make trading decision
|
||||
// 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')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 5: Execute trade
|
||||
// Step 6: Execute trade
|
||||
await this.executeTrade(tradeDecision)
|
||||
|
||||
} catch (error) {
|
||||
@@ -185,30 +198,206 @@ export class AutomationService {
|
||||
analysis: AnalysisResult | null
|
||||
} | null> {
|
||||
try {
|
||||
console.log('📸 Taking screenshot and analyzing...')
|
||||
console.log('📸 Starting multi-timeframe analysis with dual layouts...')
|
||||
|
||||
const screenshotConfig = {
|
||||
symbol: this.config!.symbol,
|
||||
timeframe: this.config!.timeframe,
|
||||
layouts: ['ai', 'diy']
|
||||
// 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`)
|
||||
|
||||
// Analyze each timeframe with both AI and DIY layouts
|
||||
const multiTimeframeResults = await this.analyzeMultiTimeframeWithDualLayouts(symbol, timeframes)
|
||||
|
||||
if (multiTimeframeResults.length === 0) {
|
||||
console.log('❌ No multi-timeframe analysis results')
|
||||
return null
|
||||
}
|
||||
|
||||
const result = await aiAnalysisService.captureAndAnalyzeWithConfig(screenshotConfig)
|
||||
|
||||
if (!result.analysis || result.screenshots.length === 0) {
|
||||
console.log('❌ No analysis or screenshots captured')
|
||||
// Process and combine multi-timeframe results
|
||||
const combinedResult = this.combineMultiTimeframeAnalysis(multiTimeframeResults)
|
||||
|
||||
if (!combinedResult.analysis) {
|
||||
console.log('❌ Failed to combine multi-timeframe analysis')
|
||||
return null
|
||||
}
|
||||
|
||||
console.log(`✅ Analysis completed: ${result.analysis.recommendation} with ${result.analysis.confidence}% confidence`)
|
||||
return result
|
||||
console.log(`✅ Multi-timeframe analysis completed: ${combinedResult.analysis.recommendation} with ${combinedResult.analysis.confidence}% confidence`)
|
||||
console.log(`📊 Timeframe alignment: ${this.analyzeTimeframeAlignment(multiTimeframeResults)}`)
|
||||
|
||||
return combinedResult
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error performing analysis:', error)
|
||||
console.error('Error performing multi-timeframe analysis:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private async analyzeMultiTimeframeWithDualLayouts(
|
||||
symbol: string,
|
||||
timeframes: string[]
|
||||
): Promise<Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>> {
|
||||
const results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }> = []
|
||||
|
||||
for (const timeframe of timeframes) {
|
||||
try {
|
||||
console.log(`📊 Analyzing ${symbol} ${timeframe} with AI + DIY layouts...`)
|
||||
|
||||
// Use the dual-layout configuration for each timeframe
|
||||
const screenshotConfig = {
|
||||
symbol: symbol,
|
||||
timeframe: timeframe,
|
||||
layouts: ['ai', 'diy']
|
||||
}
|
||||
|
||||
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}
|
||||
<EFBFBD> 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
|
||||
@@ -237,6 +426,41 @@ export class AutomationService {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateSessionWithAnalysis(result: {
|
||||
screenshots: string[]
|
||||
analysis: AnalysisResult | null
|
||||
}): Promise<void> {
|
||||
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
|
||||
@@ -284,10 +508,15 @@ export class AutomationService {
|
||||
}
|
||||
|
||||
private calculateStopLoss(analysis: any): number {
|
||||
const currentPrice = analysis.currentPrice || 0
|
||||
// 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.direction === 'LONG') {
|
||||
if (analysis.recommendation === 'BUY') {
|
||||
return currentPrice * (1 - stopLossPercent)
|
||||
} else {
|
||||
return currentPrice * (1 + stopLossPercent)
|
||||
@@ -295,10 +524,15 @@ export class AutomationService {
|
||||
}
|
||||
|
||||
private calculateTakeProfit(analysis: any): number {
|
||||
const currentPrice = analysis.currentPrice || 0
|
||||
// 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.direction === 'LONG') {
|
||||
if (analysis.recommendation === 'BUY') {
|
||||
return currentPrice * (1 + takeProfitPercent)
|
||||
} else {
|
||||
return currentPrice * (1 - takeProfitPercent)
|
||||
@@ -429,6 +663,29 @@ export class AutomationService {
|
||||
async stopAutomation(): Promise<boolean> {
|
||||
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')
|
||||
@@ -471,23 +728,35 @@ export class AutomationService {
|
||||
|
||||
async getStatus(): Promise<AutomationStatus | null> {
|
||||
try {
|
||||
if (!this.config) {
|
||||
// If automation is not running in memory, return null regardless of database state
|
||||
if (!this.isRunning || !this.config) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get the latest active automation session from database
|
||||
const session = await prisma.automationSession.findFirst({
|
||||
where: { status: 'ACTIVE' },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
isActive: this.isRunning,
|
||||
mode: this.config.mode,
|
||||
symbol: this.config.symbol,
|
||||
timeframe: this.config.timeframe,
|
||||
totalTrades: this.stats.totalTrades,
|
||||
successfulTrades: this.stats.successfulTrades,
|
||||
winRate: this.stats.winRate,
|
||||
totalPnL: this.stats.totalPnL,
|
||||
errorCount: this.stats.errorCount,
|
||||
lastError: this.stats.lastError || undefined,
|
||||
lastAnalysis: new Date(),
|
||||
lastTrade: new Date()
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user