🚀 Enhanced Multi-Timeframe Analysis with Smart Defaults

 Major UI/UX Improvements:
- AI + DIY layouts now default selections for comprehensive analysis
- Multi-timeframe selection support for broader market outlook
- Quick timeframe presets: Scalping, Day Trading, Swing, Position
- Combined coin + timeframe selection workflow

📊 Enhanced Analysis Features:
- Multiple timeframe analysis in single request
- Smart timeframe grouping (5m+15m+1h for scalping, etc.)
- Cross-timeframe consensus display
- Individual timeframe result breakdown
- Visual timeframe selection with active indicators

🎨 Improved User Experience:
- Quick action buttons for instant multi-timeframe analysis
- Clear selection indicators and counters
- Preset combinations for different trading styles
- Streamlined workflow: Select presets → Pick coin → Analyze
- Beautiful multi-timeframe result visualization

🔧 Technical Enhancements:
- Support for analyzing 1-8 timeframes simultaneously
- Automatic delay between requests to prevent rate limiting
- Error handling for individual timeframe failures
- Responsive grid layouts for different screen sizes
This commit is contained in:
mindesbunister
2025-07-13 18:07:32 +02:00
parent 4f125ac6b6
commit 85791a2e82

View File

@@ -26,8 +26,8 @@ const popularCoins = [
export default function AIAnalysisPanel() {
const [symbol, setSymbol] = useState('BTCUSD')
const [selectedLayouts, setSelectedLayouts] = useState<string[]>([layouts[0]])
const [timeframe, setTimeframe] = useState('60')
const [selectedLayouts, setSelectedLayouts] = useState<string[]>(['ai', 'diy']) // Default to both AI and DIY
const [selectedTimeframes, setSelectedTimeframes] = useState<string[]>(['60']) // Support multiple timeframes
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
@@ -51,22 +51,32 @@ export default function AIAnalysisPanel() {
)
}
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframe = timeframe) => {
if (loading || selectedLayouts.length === 0) return
const toggleTimeframe = (timeframe: string) => {
setSelectedTimeframes(prev =>
prev.includes(timeframe)
? prev.filter(tf => tf !== timeframe)
: [...prev, timeframe]
)
}
const performAnalysis = async (analysisSymbol = symbol, analysisTimeframes = selectedTimeframes) => {
if (loading || selectedLayouts.length === 0 || analysisTimeframes.length === 0) return
setLoading(true)
setError(null)
setResult(null)
try {
if (analysisTimeframes.length === 1) {
// Single timeframe analysis
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframe: analysisTimeframe,
timeframe: analysisTimeframes[0],
layouts: selectedLayouts,
analyze: true // Request AI analysis of captured screenshots
analyze: true
})
})
@@ -77,6 +87,43 @@ export default function AIAnalysisPanel() {
}
setResult(data)
} else {
// Multiple timeframe analysis
const results = []
for (const tf of analysisTimeframes) {
console.log(`🧪 Analyzing timeframe: ${timeframes.find(t => t.value === tf)?.label || tf}`)
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: analysisSymbol,
timeframe: tf,
layouts: selectedLayouts,
analyze: true
})
})
const result = await response.json()
results.push({
timeframe: tf,
timeframeLabel: timeframes.find(t => t.value === tf)?.label || tf,
success: response.ok,
result
})
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 2000))
}
setResult({
type: 'multi_timeframe',
symbol: analysisSymbol,
summary: `Analyzed ${results.length} timeframes for ${analysisSymbol}`,
results
})
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to perform analysis')
} finally {
@@ -84,59 +131,34 @@ export default function AIAnalysisPanel() {
}
}
const quickAnalyze = async (coinSymbol: string) => {
const quickAnalyze = async (coinSymbol: string, quickTimeframes = selectedTimeframes) => {
setSymbol(coinSymbol)
if (!loading) {
await performAnalysis(coinSymbol)
await performAnalysis(coinSymbol, quickTimeframes)
}
}
const quickTimeframeTest = async (testTimeframe: string) => {
setTimeframe(testTimeframe)
if (!loading && symbol) {
await performAnalysis(symbol, testTimeframe)
// Toggle the timeframe in selection instead of replacing
const newTimeframes = selectedTimeframes.includes(testTimeframe)
? selectedTimeframes.filter(tf => tf !== testTimeframe)
: [...selectedTimeframes, testTimeframe]
setSelectedTimeframes(newTimeframes)
if (!loading && symbol && newTimeframes.length > 0) {
await performAnalysis(symbol, newTimeframes)
}
}
const testAllTimeframes = async () => {
if (loading) return
setLoading(true)
setError(null)
const results = []
const allTimeframeValues = timeframes.map(tf => tf.value)
setSelectedTimeframes(allTimeframeValues)
try {
for (const tf of timeframes) {
console.log(`🧪 Testing timeframe: ${tf.label}`)
setTimeframe(tf.value)
const response = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol,
timeframe: tf.value,
layouts: selectedLayouts,
analyze: true // Request AI analysis for timeframe testing too
})
})
const result = await response.json()
results.push({ timeframe: tf.label, success: response.ok, result })
// Small delay between tests
await new Promise(resolve => setTimeout(resolve, 2000))
}
setResult({
type: 'timeframe_test',
summary: `Tested ${results.length} timeframes`,
results
})
} catch (err) {
setError(err instanceof Error ? err.message : 'Timeframe testing failed')
} finally {
setLoading(false)
if (!loading && symbol) {
await performAnalysis(symbol, allTimeframeValues)
}
}
@@ -159,32 +181,70 @@ export default function AIAnalysisPanel() {
</div>
</div>
{/* Quick Coin Selection */}
{/* Quick Coin & Timeframe Analysis */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-semibold text-gray-300 flex items-center">
<span className="w-4 h-4 bg-yellow-500 rounded-full mr-2"></span>
Quick Analysis
</h3>
<span className="text-xs text-gray-500">Click any coin for instant analysis</span>
<span className="text-xs text-gray-500">Select coin + timeframe combo for instant analysis</span>
</div>
{/* Quick Timeframe Presets */}
<div className="mb-4 p-3 bg-gray-800/30 rounded-lg">
<label className="block text-xs font-medium text-gray-400 mb-2">Quick Timeframe Presets</label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
<button
onClick={() => setSelectedTimeframes(['5', '15', '60'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-all"
>
🕒 Scalping (5m, 15m, 1h)
</button>
<button
onClick={() => setSelectedTimeframes(['60', '240', 'D'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-blue-600/20 text-blue-300 hover:bg-blue-600/30 transition-all"
>
📊 Day Trading (1h, 4h, 1d)
</button>
<button
onClick={() => setSelectedTimeframes(['240', 'D', 'W'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-green-600/20 text-green-300 hover:bg-green-600/30 transition-all"
>
📈 Swing (4h, 1d, 1w)
</button>
<button
onClick={() => setSelectedTimeframes(['D', 'W', 'M'])}
className="py-2 px-3 rounded-lg text-xs font-medium bg-orange-600/20 text-orange-300 hover:bg-orange-600/30 transition-all"
>
🎯 Position (1d, 1w, 1m)
</button>
</div>
</div>
{/* Coin Selection */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{popularCoins.map(coin => (
<button
key={coin.symbol}
onClick={() => quickAnalyze(coin.symbol)}
disabled={loading}
disabled={loading || selectedTimeframes.length === 0}
className={`group relative p-4 rounded-xl border transition-all duration-300 ${
symbol === coin.symbol
? 'border-cyan-500 bg-cyan-500/10 shadow-lg shadow-cyan-500/20'
: 'border-gray-700 bg-gray-800/50 hover:border-gray-600 hover:bg-gray-800'
} ${loading ? 'opacity-50 cursor-not-allowed' : 'hover:scale-105 hover:shadow-lg'}`}
} ${loading || selectedTimeframes.length === 0 ? 'opacity-50 cursor-not-allowed' : 'hover:scale-105 hover:shadow-lg'}`}
>
<div className={`w-10 h-10 bg-gradient-to-br ${coin.color} rounded-lg flex items-center justify-center mb-3 mx-auto text-white font-bold`}>
{coin.icon}
</div>
<div className="text-xs font-semibold text-white">{coin.name}</div>
<div className="text-xs text-gray-400 mt-1">{coin.symbol}</div>
{selectedTimeframes.length > 0 && (
<div className="text-xs text-cyan-400 mt-1 font-medium">
{selectedTimeframes.length} TF{selectedTimeframes.length > 1 ? 's' : ''}
</div>
)}
{symbol === coin.symbol && (
<div className="absolute top-2 right-2 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
)}
@@ -200,9 +260,9 @@ export default function AIAnalysisPanel() {
Advanced Analysis
</h3>
{/* Symbol and Timeframe */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="md:col-span-2">
{/* Symbol Input */}
<div className="grid grid-cols-1 gap-4 mb-6">
<div>
<label className="block text-xs font-medium text-gray-400 mb-2">Trading Pair</label>
<input
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
@@ -211,20 +271,48 @@ export default function AIAnalysisPanel() {
placeholder="e.g., BTCUSD, ETHUSD"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-400 mb-2">Timeframe</label>
<select
className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 transition-all"
value={timeframe}
onChange={e => setTimeframe(e.target.value)}
>
{timeframes.map(tf => (
<option key={tf.value} value={tf.value} className="bg-gray-800">
{tf.label}
</option>
))}
</select>
</div>
{/* Timeframe Selection */}
<div className="mb-6">
<label className="block text-xs font-medium text-gray-400 mb-3">
Analysis Timeframes
<span className="text-xs text-cyan-400 ml-2">({selectedTimeframes.length} selected)</span>
</label>
<div className="grid grid-cols-4 gap-2">
{timeframes.map(tf => (
<label key={tf.value} className="group relative cursor-pointer">
<input
type="checkbox"
checked={selectedTimeframes.includes(tf.value)}
onChange={() => toggleTimeframe(tf.value)}
className="sr-only"
/>
<div className={`flex items-center justify-center p-3 rounded-lg border transition-all ${
selectedTimeframes.includes(tf.value)
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300 shadow-lg shadow-cyan-500/20'
: 'border-gray-700 bg-gray-800/30 text-gray-400 hover:border-gray-600 hover:bg-gray-800/50 hover:text-gray-300'
}`}>
<span className="text-sm font-medium">{tf.label}</span>
{selectedTimeframes.includes(tf.value) && (
<div className="absolute top-1 right-1 w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
)}
</div>
</label>
))}
</div>
{selectedTimeframes.length > 0 && (
<div className="mt-3 p-3 bg-gray-800/30 rounded-lg">
<div className="text-xs text-gray-400">
Selected timeframes: <span className="text-cyan-400">
{selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label).join(', ')}
</span>
</div>
<div className="text-xs text-gray-500 mt-1">
💡 Multiple timeframes provide broader market outlook
</div>
</div>
)}
</div>
{/* Layout Selection */}
@@ -269,9 +357,9 @@ export default function AIAnalysisPanel() {
)}
</div>
{/* Quick Timeframe Testing */}
{/* Quick Timeframe Actions */}
<div className="mb-6">
<label className="block text-xs font-medium text-gray-400 mb-3">Quick Timeframe Tests</label>
<label className="block text-xs font-medium text-gray-400 mb-3">Quick Actions</label>
<div className="grid grid-cols-4 gap-2 mb-3">
{timeframes.map(tf => (
<button
@@ -279,7 +367,7 @@ export default function AIAnalysisPanel() {
onClick={() => quickTimeframeTest(tf.value)}
disabled={loading || selectedLayouts.length === 0}
className={`py-2 px-3 rounded-lg text-xs font-medium transition-all ${
timeframe === tf.value
selectedTimeframes.includes(tf.value)
? 'bg-cyan-500 text-white shadow-lg'
: loading
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
@@ -290,17 +378,30 @@ export default function AIAnalysisPanel() {
</button>
))}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<button
onClick={testAllTimeframes}
disabled={loading || selectedLayouts.length === 0 || !symbol}
className={`w-full py-2 px-4 rounded-lg text-sm font-medium transition-all ${
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
loading
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg'
}`}
>
{loading ? '🔄 Testing...' : '🧪 Test All Timeframes'}
🔍 Analyze All Timeframes
</button>
<button
onClick={() => setSelectedTimeframes([])}
disabled={loading || selectedTimeframes.length === 0}
className={`py-2 px-4 rounded-lg text-sm font-medium transition-all ${
loading || selectedTimeframes.length === 0
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600 hover:text-white transform hover:scale-[1.02] active:scale-[0.98]'
}`}
>
🗑 Clear Selection
</button>
</div>
</div>
{/* Analyze Button */}
@@ -347,13 +448,106 @@ export default function AIAnalysisPanel() {
<div>
<h4 className="text-cyan-400 font-medium text-sm">AI Processing</h4>
<p className="text-cyan-300 text-xs mt-1 opacity-90">
Analyzing {symbol} on {timeframe} timeframe...
Analyzing {symbol} on {selectedTimeframes.length === 1
? `${timeframes.find(tf => tf.value === selectedTimeframes[0])?.label} timeframe`
: `${selectedTimeframes.length} timeframes`
}...
</p>
</div>
</div>
</div>
)}
{result && result.type === 'multi_timeframe' && (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold text-white flex items-center">
<span className="w-6 h-6 bg-gradient-to-br from-purple-400 to-purple-600 rounded-lg flex items-center justify-center mr-2 text-sm">
📊
</span>
Multi-Timeframe Analysis
</h3>
<div className="text-xs text-gray-400">
{result.symbol} {result.results.length} timeframes
</div>
</div>
<div className="p-4 bg-gradient-to-r from-purple-800/30 to-purple-700/30 rounded-lg border border-purple-500/30">
<h4 className="text-sm font-semibold text-purple-300 mb-2">Analysis Summary</h4>
<p className="text-white text-sm">{result.summary}</p>
</div>
<div className="grid gap-4">
{result.results.map((timeframeResult: any, index: number) => (
<div key={index} className={`p-4 rounded-lg border ${
timeframeResult.success
? 'bg-green-500/5 border-green-500/30'
: 'bg-red-500/5 border-red-500/30'
}`}>
<div className="flex items-center justify-between mb-3">
<h5 className={`font-semibold ${
timeframeResult.success ? 'text-green-400' : 'text-red-400'
}`}>
{timeframeResult.success ? '✅' : '❌'} {timeframeResult.timeframeLabel} Timeframe
</h5>
<span className="text-xs text-gray-400">
{timeframeResult.success ? 'Analysis Complete' : 'Failed'}
</span>
</div>
{timeframeResult.success && timeframeResult.result.analysis && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Sentiment</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.marketSentiment)}
</div>
</div>
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Recommendation</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.recommendation)}
</div>
</div>
<div className="text-center p-3 bg-gray-800/30 rounded">
<div className="text-xs text-gray-400">Confidence</div>
<div className="text-sm font-medium text-white">
{safeRender(timeframeResult.result.analysis.confidence)}%
</div>
</div>
</div>
)}
{timeframeResult.success && timeframeResult.result.analysis?.entry && (
<div className="mt-3 p-3 bg-yellow-500/5 border border-yellow-500/20 rounded">
<div className="text-xs text-yellow-400 font-semibold mb-1">Entry Setup</div>
<div className="text-sm text-white">
📍 ${safeRender(timeframeResult.result.analysis.entry.price)}
{timeframeResult.result.analysis.entry.buffer && (
<span className="text-yellow-400 ml-1">
{safeRender(timeframeResult.result.analysis.entry.buffer)}
</span>
)}
</div>
{timeframeResult.result.analysis.stopLoss && (
<div className="text-sm text-red-300 mt-1">
🛑 SL: ${safeRender(timeframeResult.result.analysis.stopLoss.price)}
</div>
)}
</div>
)}
{!timeframeResult.success && (
<div className="text-red-300 text-sm">
Analysis failed for this timeframe
</div>
)}
</div>
))}
</div>
</div>
)}
{result && result.analysis && (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between">