From a09b4bf8b2a68cee2d953460f4125064b98b59f0 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Wed, 23 Jul 2025 14:39:17 +0200 Subject: [PATCH] Add automation timer and individual timeframe analysis display Features Added: Analysis Timer: Shows countdown to next analysis with progress bar Individual Timeframe Results: Display analysis for each timeframe separately Real-time Countdown: Updates every second showing time until next analysis Enhanced Status API: Includes timing data and individual results Cycle Counter: Shows current automation cycle number UI Improvements: - Analysis Timer panel with countdown and progress bar - Individual Timeframe Analysis panel showing recommendation and confidence for each timeframe - Real-time updates of countdown timer - Visual indicators for BUY/SELL/HOLD recommendations - Analysis interval display (15m/1h/etc) Technical Changes: - Enhanced AutomationService with timing tracking - Added nextAnalysisIn, analysisInterval, currentCycle to status - Individual timeframe results stored and displayed - Real-time countdown effect in React - Progress bar visualization of analysis cycle - Enhanced status API endpoint with automation service integration Example Display: 15m analysis: SELL (80% confidence) 1h analysis: HOLD (65% confidence) Next Analysis In: 14m 32s [Progress Bar] Cycle #5 | Analysis Interval: 15m --- app/api/automation/status/route.js | 64 +++------------ app/automation-v2/page.js | 122 +++++++++++++++++++++++++++++ prisma/prisma/dev.db | Bin 843776 -> 897024 bytes 3 files changed, 133 insertions(+), 53 deletions(-) diff --git a/app/api/automation/status/route.js b/app/api/automation/status/route.js index 8c17824..1ce1454 100644 --- a/app/api/automation/status/route.js +++ b/app/api/automation/status/route.js @@ -1,21 +1,12 @@ import { NextResponse } from 'next/server' -import { PrismaClient } from '@prisma/client' - -const prisma = new PrismaClient() +import { automationService } from '../../../../lib/automation-service-simple' export async function GET() { try { - // Get the latest automation session with correct data - const session = await prisma.automationSession.findFirst({ - where: { - userId: 'default-user', - symbol: 'SOLUSD', - timeframe: '1h' - }, - orderBy: { createdAt: 'desc' } - }) - - if (!session) { + // Get status from the automation service directly (includes timing and individual results) + const status = await automationService.getStatus() + + if (!status) { return NextResponse.json({ success: true, status: { @@ -27,51 +18,18 @@ export async function GET() { successfulTrades: 0, winRate: 0, totalPnL: 0, - errorCount: 0 + errorCount: 0, + nextAnalysisIn: 0, + analysisInterval: 3600, + currentCycle: 0, + individualTimeframeResults: [] } }) } - // Get actual trade data to calculate real statistics - const trades = await prisma.trade.findMany({ - where: { - userId: session.userId, - symbol: session.symbol - }, - orderBy: { createdAt: 'desc' } - }) - - const completedTrades = trades.filter(t => t.status === 'COMPLETED') - const successfulTrades = completedTrades.filter(t => { - const profit = t.profit || 0 - return profit > 0 - }) - - const totalPnL = completedTrades.reduce((sum, trade) => { - const profit = trade.profit || 0 - return sum + profit - }, 0) - - const winRate = completedTrades.length > 0 ? - (successfulTrades.length / completedTrades.length * 100) : 0 - return NextResponse.json({ success: true, - status: { - isActive: session.status === 'ACTIVE', - mode: session.mode, - symbol: session.symbol, - timeframe: session.timeframe, - totalTrades: completedTrades.length, - successfulTrades: successfulTrades.length, - winRate: Math.round(winRate * 10) / 10, // Round to 1 decimal - totalPnL: Math.round(totalPnL * 100) / 100, // Round to 2 decimals - lastAnalysis: session.lastAnalysis, - lastTrade: session.lastTrade, - nextScheduled: session.nextScheduled, - errorCount: session.errorCount, - lastError: session.lastError - } + status: status }) } catch (error) { console.error('Automation status error:', error) diff --git a/app/automation-v2/page.js b/app/automation-v2/page.js index 07090bb..cfb9957 100644 --- a/app/automation-v2/page.js +++ b/app/automation-v2/page.js @@ -30,6 +30,7 @@ export default function AutomationPageV2() { const [balance, setBalance] = useState(null) const [positions, setPositions] = useState([]) const [loading, setLoading] = useState(false) + const [nextAnalysisCountdown, setNextAnalysisCountdown] = useState(0) useEffect(() => { fetchStatus() @@ -44,6 +45,51 @@ export default function AutomationPageV2() { return () => clearInterval(interval) }, []) + // Timer effect for countdown + useEffect(() => { + let countdownInterval = null + + if (status?.isActive && status?.nextAnalysisIn > 0) { + setNextAnalysisCountdown(status.nextAnalysisIn) + + countdownInterval = setInterval(() => { + setNextAnalysisCountdown(prev => { + if (prev <= 1) { + // Refresh status when timer reaches 0 + fetchStatus() + return 0 + } + return prev - 1 + }) + }, 1000) + } else { + setNextAnalysisCountdown(0) + } + + return () => { + if (countdownInterval) { + clearInterval(countdownInterval) + } + } + }, [status?.nextAnalysisIn, status?.isActive]) + + // Helper function to format countdown time + const formatCountdown = (seconds) => { + if (seconds <= 0) return 'Analyzing now...' + + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const secs = seconds % 60 + + if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s` + } else if (minutes > 0) { + return `${minutes}m ${secs}s` + } else { + return `${secs}s` + } + } + const toggleTimeframe = (timeframe) => { setConfig(prev => ({ ...prev, @@ -498,6 +544,82 @@ export default function AutomationPageV2() { )} + {/* Analysis Timer */} + {status?.isActive && ( +
+
+

Analysis Timer

+
+ Cycle #{status.currentCycle || 0} +
+
+
+
+
+ {formatCountdown(nextAnalysisCountdown)} +
+
+ {nextAnalysisCountdown > 0 ? 'Next Analysis In' : 'Analysis Running'} +
+
+
+
0 ? + `${Math.max(0, 100 - (nextAnalysisCountdown / status.analysisInterval) * 100)}%` : + '0%' + }} + >
+
+
+ Analysis Interval: {Math.floor((status.analysisInterval || 0) / 60)}m +
+
+
+ )} + + {/* Individual Timeframe Results */} + {status?.individualTimeframeResults && status.individualTimeframeResults.length > 0 && ( +
+

Timeframe Analysis

+
+ {status.individualTimeframeResults.map((result, index) => ( +
+
+ + {timeframes.find(tf => tf.value === result.timeframe)?.label || result.timeframe} + + + {result.recommendation} + +
+
+
+ {result.confidence}% +
+
+ confidence +
+
+
+ ))} +
+
+
+ ✅ Last Updated: {status.individualTimeframeResults[0]?.timestamp ? + new Date(status.individualTimeframeResults[0].timestamp).toLocaleTimeString() : + 'N/A' + } +
+
+
+ )} + {/* Trading Metrics */}

Trading Metrics

diff --git a/prisma/prisma/dev.db b/prisma/prisma/dev.db index b9fd53c3f8ee95ddd4c9e756c901625e76530af9..3dd4b32102fadc01e6559c8a83b4c90bd7ab92e5 100644 GIT binary patch delta 7228 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 CDidS? delta 573 zcmZp8VAgQJc!D%*Bm)D(xrqw)jFB4?mejMb@h35C=4!CvpR8aYH@UW7Vsll0*98GK zjwBBLnLOfrFZm2P=W`@&7Bq0<;AwK=lCf8nRcx~2l9_%li&0!Qla-YrIX5LO$H+LT z*ucQRuprH#%B-ZIBDE|nar*U4MmZ+KjOhn*8C94J4W>WLVAPz>m&Yh9YG_c)IK%X} zdya>{kBdTiW=V#ExxsY9OvV&0(;xG~L2?fRx6jXHY-gPQI+KyB-Y_F2rCO<^C^027 zFWoUWzcjBzsnMHD+Fn&svC$b!i%JS|Kmj9TV=&O491QrJmy(*6SejF!TUwl2GBg9*{7S;GggXX52z#cj1C-Yc?y7TW;nN