Fix automated trading display calculations

Fixed position size calculation: 00 investment now shows 00 position (was 04.76)
 Fixed token amount display: Now shows correct tokens (~0.996) for 00 investment (was 2.04)
 Corrected API route: /api/automation/analysis-details now returns 200 instead of 405

Technical changes:
- Updated route calculation logic: tradingAmount / trade.price for correct token amounts
- Fixed displayPositionSize to show intended investment amount
- Used Docker Compose v2 for container management
- Resolved Next.js module export issues

The API now correctly displays trade details matching user investment intentions.
This commit is contained in:
mindesbunister
2025-07-20 22:32:16 +02:00
parent 6ce4f364a9
commit 55cea00e5e
22 changed files with 1180 additions and 189 deletions

View File

@@ -60,6 +60,12 @@ class AggressiveCleanup {
return
}
// Check if auto cleanup is disabled (for development)
if (process.env.DISABLE_AUTO_CLEANUP === 'true') {
console.log('🚫 Auto cleanup disabled via DISABLE_AUTO_CLEANUP environment variable')
return
}
this.isRunning = true
const isDevelopment = process.env.NODE_ENV === 'development'
const cleanupType = isDevelopment ? 'gentle' : 'aggressive'
@@ -95,8 +101,8 @@ class AggressiveCleanup {
// In case of import errors, be extra cautious - only clean very old processes
if (isDevelopment) {
console.log('🔧 Development mode with import issues - skipping cleanup for safety')
return
console.log('🔧 Development mode with import issues - using aggressive cleanup to clear stuck processes')
// In development, if we can't check sessions, assume they're stuck and clean aggressively
}
}
@@ -248,48 +254,95 @@ class AggressiveCleanup {
// New method for on-demand cleanup after complete automation cycle
async runPostAnalysisCleanup(): Promise<void> {
// Check if auto cleanup is disabled (for development)
if (process.env.DISABLE_AUTO_CLEANUP === 'true') {
console.log('🚫 Post-analysis cleanup disabled via DISABLE_AUTO_CLEANUP environment variable')
return
}
console.log('🧹 Post-cycle cleanup triggered (analysis + decision complete)...')
// Wait for all browser processes to fully close
console.log('⏳ Waiting 5 seconds for all processes to close gracefully...')
await new Promise(resolve => setTimeout(resolve, 5000))
console.log('⏳ Waiting 3 seconds for all processes to close gracefully...')
await new Promise(resolve => setTimeout(resolve, 3000))
// Check if there are still active sessions before cleaning
// Always run cleanup after complete automation cycle - don't check for active sessions
// since the analysis is complete and we need to ensure all processes are cleaned up
console.log('🧹 Running comprehensive post-cycle cleanup (ignoring session status)...')
try {
// Find all chromium processes
const chromiumProcesses = await this.findChromiumProcesses()
if (chromiumProcesses.length === 0) {
console.log('✅ No chromium processes found to clean up')
return
}
console.log(`🔍 Found ${chromiumProcesses.length} chromium processes for post-analysis cleanup`)
// In post-analysis cleanup, we're more aggressive since analysis is complete
// Try graceful shutdown first
for (const pid of chromiumProcesses) {
try {
console.log(`🔧 Attempting graceful shutdown of process ${pid}`)
await execAsync(`kill -TERM ${pid}`)
} catch (error) {
console.log(` Process ${pid} may already be terminated`)
}
}
// Wait for graceful shutdown
await new Promise(resolve => setTimeout(resolve, 5000))
// Check which processes are still running and force kill them
const stillRunning = await this.findStillRunningProcesses(chromiumProcesses)
if (stillRunning.length > 0) {
console.log(`🗡️ Force killing ${stillRunning.length} stubborn processes`)
for (const pid of stillRunning) {
try {
await execAsync(`kill -9 ${pid}`)
console.log(`💀 Force killed process ${pid}`)
} catch (error) {
console.log(` Process ${pid} already terminated`)
}
}
} else {
console.log('✅ All processes shut down gracefully')
}
// Clean up temp directories and shared memory
try {
await execAsync('rm -rf /tmp/puppeteer_dev_chrome_profile-* 2>/dev/null || true')
await execAsync('rm -rf /dev/shm/.org.chromium.* 2>/dev/null || true')
await execAsync('rm -rf /tmp/.org.chromium.* 2>/dev/null || true')
console.log('✅ Cleaned up temporary files and shared memory')
} catch (error) {
console.error('Warning: Could not clean up temporary files:', error)
}
console.log('✅ Post-analysis cleanup completed successfully')
} catch (error) {
console.error('Error in post-analysis cleanup:', error)
}
// Clear any stuck progress sessions
try {
const { progressTracker } = await import('./progress-tracker')
const activeSessions = progressTracker.getActiveSessions()
if (activeSessions.length > 0) {
console.log(`⚠️ Post-cycle cleanup: Still ${activeSessions.length} active sessions detected`)
console.log(`🧹 Force clearing ${activeSessions.length} potentially stuck sessions`)
activeSessions.forEach(session => {
const progress = progressTracker.getProgress(session)
if (progress) {
const activeStep = progress.steps.find(step => step.status === 'active')
const currentStep = activeStep ? activeStep.title : 'Unknown'
console.log(` - ${session}: ${currentStep} (Step ${progress.currentStep}/${progress.totalSteps})`)
}
})
// Force cleanup anyway since cycle is complete
console.log('<27> Forcing cleanup - analysis cycle is complete regardless of session status')
// Clean up the session tracker entries that might be stuck
activeSessions.forEach(session => {
console.log(`🧹 Force clearing stuck session: ${session}`)
console.log(`🧹 Force clearing session: ${session}`)
progressTracker.deleteSession(session)
})
} else {
console.log('✅ No active sessions detected - proceeding with post-cycle cleanup')
}
} catch (error) {
console.warn('⚠️ Could not check active sessions for post-cycle cleanup:', error)
console.warn('Could not clear progress sessions:', error)
}
// Always run cleanup after complete automation cycle
console.log('🧹 Running comprehensive post-cycle cleanup...')
await this.cleanupOrphanedProcesses()
console.log('✅ Post-cycle cleanup completed - all analysis processes should be cleaned up')
}
// Signal that an analysis cycle is complete and all processes should be cleaned up
@@ -397,6 +450,40 @@ class AggressiveCleanup {
return stillRunning
}
// Method to get detailed process information for debugging
async getProcessInfo(): Promise<void> {
try {
console.log('🔍 Current browser process information:')
// Get all chromium processes with detailed info
const { stdout } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep')
const processes = stdout.trim().split('\n').filter(line => line.length > 0)
if (processes.length === 0) {
console.log('✅ No browser processes currently running')
return
}
console.log(`📊 Found ${processes.length} browser processes:`)
processes.forEach((process, index) => {
const parts = process.split(/\s+/)
const pid = parts[1]
const cpu = parts[2]
const mem = parts[3]
const command = parts.slice(10).join(' ')
console.log(` ${index + 1}. PID: ${pid}, CPU: ${cpu}%, MEM: ${mem}%, CMD: ${command.substring(0, 100)}...`)
})
// Get memory usage
const { stdout: memInfo } = await execAsync('free -h')
console.log('💾 Memory usage:')
console.log(memInfo)
} catch (error) {
console.error('Error getting process info:', error)
}
}
stop(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval)

View File

@@ -714,21 +714,32 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
if (sessionId) {
progressTracker.updateStep(sessionId, 'analysis', 'completed', 'AI analysis completed successfully!')
// Mark session as complete
setTimeout(() => progressTracker.deleteSession(sessionId), 1000)
}
// Trigger post-analysis cleanup in development mode
if (process.env.NODE_ENV === 'development') {
try {
// Dynamic import to avoid circular dependencies
const aggressiveCleanupModule = await import('./aggressive-cleanup')
const aggressiveCleanup = aggressiveCleanupModule.default
// Run cleanup in background, don't block the response
aggressiveCleanup.runPostAnalysisCleanup().catch(console.error)
} catch (cleanupError) {
console.error('Error triggering post-analysis cleanup:', cleanupError)
}
// Trigger browser cleanup immediately after analysis completes
try {
console.log('🧹 Triggering browser cleanup after analysis completion...')
const { enhancedScreenshotService } = await import('./enhanced-screenshot')
await enhancedScreenshotService.cleanup()
console.log('✅ Browser cleanup completed')
} catch (cleanupError) {
console.error('Error in browser cleanup:', cleanupError)
}
// Trigger system-wide cleanup
try {
// Dynamic import to avoid circular dependencies
const aggressiveCleanupModule = await import('./aggressive-cleanup')
const aggressiveCleanup = aggressiveCleanupModule.default
// Run cleanup in background, don't block the response
aggressiveCleanup.runPostAnalysisCleanup().catch(console.error)
} catch (cleanupError) {
console.error('Error triggering post-analysis cleanup:', cleanupError)
}
if (sessionId) {
// Mark session as complete after cleanup is initiated
setTimeout(() => progressTracker.deleteSession(sessionId), 1000)
}
return {
@@ -739,6 +750,16 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
} catch (error) {
console.error('Automated capture and analysis with config failed:', error)
// Trigger browser cleanup even on error
try {
console.log('🧹 Triggering browser cleanup after analysis error...')
const { enhancedScreenshotService } = await import('./enhanced-screenshot')
await enhancedScreenshotService.cleanup()
console.log('✅ Browser cleanup completed after error')
} catch (cleanupError) {
console.error('Error in browser cleanup after error:', cleanupError)
}
if (sessionId) {
// Find the active step and mark it as error
const progress = progressTracker.getProgress(sessionId)

View File

@@ -0,0 +1,56 @@
// Analysis completion flag manager
// This ensures cleanup only happens after the entire analysis cycle is complete
class AnalysisCompletionFlag {
private static instance: AnalysisCompletionFlag
private isAnalysisComplete = false
private currentSessionId: string | null = null
private constructor() {}
static getInstance(): AnalysisCompletionFlag {
if (!AnalysisCompletionFlag.instance) {
AnalysisCompletionFlag.instance = new AnalysisCompletionFlag()
}
return AnalysisCompletionFlag.instance
}
// Called at the start of each analysis cycle
startAnalysisCycle(sessionId: string) {
console.log(`🚀 Starting analysis cycle: ${sessionId}`)
this.isAnalysisComplete = false
this.currentSessionId = sessionId
}
// Called at the end of each analysis cycle
markAnalysisComplete(sessionId: string) {
if (sessionId === this.currentSessionId) {
console.log(`✅ Analysis cycle complete: ${sessionId}`)
this.isAnalysisComplete = true
} else {
console.log(`⚠️ Session ID mismatch: expected ${this.currentSessionId}, got ${sessionId}`)
}
}
// Check if analysis is complete and cleanup can proceed
canCleanup(): boolean {
return this.isAnalysisComplete
}
// Get current session info
getCurrentSession(): { sessionId: string | null; isComplete: boolean } {
return {
sessionId: this.currentSessionId,
isComplete: this.isAnalysisComplete
}
}
// Reset flag (for manual cleanup or new cycles)
reset() {
console.log(`🔄 Resetting analysis completion flag`)
this.isAnalysisComplete = false
this.currentSessionId = null
}
}
export const analysisCompletionFlag = AnalysisCompletionFlag.getInstance()

View File

@@ -5,6 +5,7 @@ import { enhancedScreenshotService } from './enhanced-screenshot-simple'
import { TradingViewCredentials } from './tradingview-automation'
import { progressTracker } from './progress-tracker'
import aggressiveCleanup from './aggressive-cleanup'
import { analysisCompletionFlag } from './analysis-completion-flag'
const prisma = new PrismaClient()
@@ -214,8 +215,8 @@ export class AutomationService {
await new Promise(resolve => setTimeout(resolve, 10000)) // 10 seconds
try {
// Signal that the complete analysis cycle is done
await aggressiveCleanup.signalAnalysisCycleComplete()
// 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)
@@ -229,6 +230,9 @@ export class AutomationService {
// 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})`)
@@ -259,6 +263,8 @@ export class AutomationService {
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
}
@@ -272,6 +278,8 @@ export class AutomationService {
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
}
@@ -286,6 +294,9 @@ export class AutomationService {
progressTracker.deleteSession(sessionId)
}, 2000)
// Mark analysis as complete to allow cleanup
analysisCompletionFlag.markAnalysisComplete(sessionId)
return combinedResult
} catch (error) {
@@ -295,6 +306,9 @@ export class AutomationService {
progressTracker.deleteSession(sessionId)
}, 5000)
// Mark analysis as complete even on error to allow cleanup
analysisCompletionFlag.markAnalysisComplete(sessionId)
return null
}
}

View File

@@ -259,8 +259,10 @@ export class AutomationService {
return
}
// Step 2: Store analysis in database for learning
await this.storeAnalysisForLearning(config, result, sessionId)
// 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)
@@ -306,7 +308,7 @@ export class AutomationService {
data: {
userId: config.userId,
sessionId: sessionId,
analysisData: result.analysis,
analysisData: result.analysis as any,
marketConditions: {
timeframe: config.timeframe,
symbol: config.symbol,
@@ -626,11 +628,11 @@ export class AutomationService {
successfulTrades: session.successfulTrades,
winRate: session.winRate,
totalPnL: session.totalPnL,
lastAnalysis: session.lastAnalysis,
lastTrade: session.lastTrade,
nextScheduled: session.nextScheduled,
lastAnalysis: session.lastAnalysis || undefined,
lastTrade: session.lastTrade || undefined,
nextScheduled: session.nextScheduled || undefined,
errorCount: session.errorCount,
lastError: session.lastError
lastError: session.lastError || undefined
}
} catch (error) {
console.error('Failed to get automation status:', error)

View File

@@ -83,8 +83,6 @@ export class EnhancedScreenshotService {
// Check login status and login if needed
const isLoggedIn = await layoutSession.checkLoginStatus()
console.log(`🔍 ${layout.toUpperCase()}: Login status check result: ${isLoggedIn}`)
if (!isLoggedIn) {
console.log(`🔐 Logging in to ${layout} session...`)
if (sessionId && index === 0) {
@@ -104,12 +102,55 @@ export class EnhancedScreenshotService {
progressTracker.updateStep(sessionId, 'navigation', 'active', `Navigating to ${config.symbol} chart...`)
}
// Use the new navigateToLayout method instead of manual URL construction
console.log(`🌐 ${layout.toUpperCase()}: Using navigateToLayout method with ${layoutUrl}`)
const navigationSuccess = await layoutSession.navigateToLayout(layoutUrl, config.symbol, config.timeframe)
// Navigate directly to the specific layout URL with symbol and timeframe
const directUrl = `https://www.tradingview.com/chart/${layoutUrl}/?symbol=${config.symbol}&interval=${config.timeframe}`
console.log(`🌐 ${layout.toUpperCase()}: Navigating directly to ${directUrl}`)
// Get page from the session
const page = (layoutSession as any).page
if (!page) {
throw new Error(`Failed to get page for ${layout} session`)
}
// Navigate directly to the layout URL with retries and progressive timeout strategy
let navigationSuccess = false
for (let attempt = 1; attempt <= 3; attempt++) {
try {
console.log(`🔄 ${layout.toUpperCase()}: Navigation attempt ${attempt}/3`)
// Progressive waiting strategy: first try domcontentloaded, then networkidle if that fails
const waitUntilStrategy = attempt === 1 ? 'domcontentloaded' : 'networkidle0'
const timeoutDuration = attempt === 1 ? 30000 : (60000 + (attempt - 1) * 30000)
console.log(`📋 ${layout.toUpperCase()}: Using waitUntil: ${waitUntilStrategy}, timeout: ${timeoutDuration}ms`)
await page.goto(directUrl, {
waitUntil: waitUntilStrategy,
timeout: timeoutDuration
})
// If we used domcontentloaded, wait a bit more for dynamic content
if (waitUntilStrategy === 'domcontentloaded') {
console.log(`${layout.toUpperCase()}: Waiting additional 5s for dynamic content...`)
await new Promise(resolve => setTimeout(resolve, 5000))
}
navigationSuccess = true
break
} catch (navError: any) {
console.warn(`⚠️ ${layout.toUpperCase()}: Navigation attempt ${attempt} failed:`, navError?.message || navError)
if (attempt === 3) {
throw new Error(`Failed to navigate to ${layout} layout after 3 attempts: ${navError?.message || navError}`)
}
// Progressive backoff
const waitTime = 2000 * attempt
console.log(`${layout.toUpperCase()}: Waiting ${waitTime}ms before retry...`)
await new Promise(resolve => setTimeout(resolve, waitTime))
}
}
if (!navigationSuccess) {
throw new Error(`Failed to navigate to ${layout} layout ${layoutUrl}`)
throw new Error(`Failed to navigate to ${layout} layout`)
}
console.log(`${layout.toUpperCase()}: Successfully navigated to layout`)
@@ -142,12 +183,9 @@ export class EnhancedScreenshotService {
// Strategy 2: Look for chart elements manually
try {
console.log(`🔍 ${layout.toUpperCase()}: Checking for chart elements manually...`)
const page = (layoutSession as any).page
if (page) {
await page.waitForSelector('.layout__area--center', { timeout: 15000 })
console.log(`${layout.toUpperCase()}: Chart area found via selector`)
chartLoadSuccess = true
}
await page.waitForSelector('.layout__area--center', { timeout: 15000 })
console.log(`${layout.toUpperCase()}: Chart area found via selector`)
chartLoadSuccess = true
} catch (selectorError: any) {
console.warn(`⚠️ ${layout.toUpperCase()}: Chart selector check failed:`, selectorError?.message || selectorError)
}
@@ -370,8 +408,27 @@ export class EnhancedScreenshotService {
)
)
// Wait for all cleanup operations to complete
await Promise.allSettled(cleanupPromises)
// Give browsers time to fully close
await new Promise(resolve => setTimeout(resolve, 3000))
console.log('✅ All parallel browser sessions cleaned up')
// Force kill any remaining browser processes
try {
const { exec } = require('child_process')
const { promisify } = require('util')
const execAsync = promisify(exec)
console.log('🔧 Force killing any remaining browser processes...')
await execAsync('pkill -f "chromium.*--remote-debugging-port" 2>/dev/null || true')
await execAsync('pkill -f "chrome.*--remote-debugging-port" 2>/dev/null || true')
console.log('✅ Remaining browser processes terminated')
} catch (processKillError) {
console.error('Error force killing browser processes:', processKillError)
}
}
async healthCheck(): Promise<{ status: 'healthy' | 'error', message?: string }> {