fix: implement proper multi-timeframe batch analysis
- Create /api/batch-analysis endpoint that collects ALL screenshots first - Then sends all screenshots to AI for comprehensive analysis - Fixes issue where individual timeframes were analyzed immediately - Multi-timeframe analysis now provides cross-timeframe consensus - Update AIAnalysisPanel to use batch analysis for multiple timeframes - Maintains backward compatibility with single timeframe analysis
This commit is contained in:
@@ -251,68 +251,57 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
onAnalysisComplete(data.analysis, analysisSymbol)
|
||||
}
|
||||
} else {
|
||||
// Multiple timeframe analysis
|
||||
const results = []
|
||||
// Multiple timeframe analysis - use batch analysis endpoint
|
||||
console.log(`🧪 Starting batch analysis for ${analysisTimeframes.length} timeframes`)
|
||||
|
||||
for (let i = 0; i < analysisTimeframes.length; i++) {
|
||||
const tf = analysisTimeframes[i]
|
||||
const timeframeLabel = timeframes.find(t => t.value === tf)?.label || tf
|
||||
|
||||
console.log(`🧪 Analyzing timeframe: ${timeframeLabel}`)
|
||||
|
||||
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,
|
||||
success: response.ok,
|
||||
result
|
||||
const response = await fetch('/api/batch-analysis', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
symbol: analysisSymbol,
|
||||
timeframes: analysisTimeframes,
|
||||
layouts: selectedLayouts,
|
||||
analyze: true
|
||||
})
|
||||
})
|
||||
|
||||
// Start progress tracking for the first timeframe session
|
||||
if (i === 0 && result.sessionId) {
|
||||
startProgressTracking(result.sessionId)
|
||||
}
|
||||
|
||||
// Update timeframe progress manually for multi-timeframe
|
||||
setProgress(prev => prev ? {
|
||||
...prev,
|
||||
timeframeProgress: {
|
||||
current: i + 1,
|
||||
total: analysisTimeframes.length,
|
||||
currentTimeframe: timeframeLabel
|
||||
}
|
||||
} : null)
|
||||
|
||||
// Small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
const data = await response.json()
|
||||
|
||||
const multiResult = {
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Batch analysis failed')
|
||||
}
|
||||
|
||||
// Start real-time progress tracking if sessionId is provided
|
||||
if (data.sessionId) {
|
||||
startProgressTracking(data.sessionId)
|
||||
}
|
||||
|
||||
// Convert batch analysis result to compatible format
|
||||
const batchResult = {
|
||||
type: 'multi_timeframe',
|
||||
symbol: analysisSymbol,
|
||||
summary: `Analyzed ${results.length} timeframes for ${analysisSymbol}`,
|
||||
results
|
||||
summary: data.summary,
|
||||
analysis: data.analysis, // Comprehensive analysis of ALL screenshots
|
||||
totalScreenshots: data.totalScreenshots,
|
||||
results: data.screenshotResults?.map((sr: any) => ({
|
||||
timeframe: sr.timeframe,
|
||||
timeframeLabel: sr.timeframeLabel,
|
||||
success: sr.success,
|
||||
result: {
|
||||
screenshots: sr.screenshots?.map((path: string) => ({
|
||||
url: `/screenshots/${path.split('/').pop()}`,
|
||||
timestamp: Date.now()
|
||||
})) || [],
|
||||
analysis: sr.timeframe === analysisTimeframes[0] ? data.analysis : null // Only show comprehensive analysis on first timeframe
|
||||
}
|
||||
})) || []
|
||||
}
|
||||
|
||||
setResult(multiResult)
|
||||
setResult(batchResult)
|
||||
|
||||
// Call the callback with the first successful analysis result if provided
|
||||
if (onAnalysisComplete) {
|
||||
const firstSuccessfulResult = results.find(r => r.success && r.result?.analysis)
|
||||
if (firstSuccessfulResult) {
|
||||
onAnalysisComplete(firstSuccessfulResult.result.analysis, analysisSymbol)
|
||||
}
|
||||
// Call the callback with the comprehensive analysis result if provided
|
||||
if (onAnalysisComplete && data.analysis) {
|
||||
onAnalysisComplete(data.analysis, analysisSymbol)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1397,15 +1386,28 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Position Calculator */}
|
||||
{result && result.analysis && (
|
||||
<div className="mt-6">
|
||||
{/* Position Calculator - Always Show */}
|
||||
<div className="mt-6">
|
||||
<div className="card card-gradient">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold text-white flex items-center">
|
||||
<span className="w-8 h-8 bg-gradient-to-br from-green-400 to-emerald-600 rounded-lg flex items-center justify-center mr-3">
|
||||
📊
|
||||
</span>
|
||||
Position Calculator
|
||||
</h2>
|
||||
<div className="flex items-center space-x-2 text-sm text-gray-400">
|
||||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||
<span>Live Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PositionCalculator
|
||||
analysis={result.analysis}
|
||||
analysis={result?.analysis || null}
|
||||
currentPrice={
|
||||
result.analysis.entry?.price ||
|
||||
result.analysis.entry ||
|
||||
(typeof result.analysis.entry === 'string' ? parseFloat(result.analysis.entry.replace(/[^0-9.-]+/g, '')) : 0) ||
|
||||
result?.analysis?.entry?.price ||
|
||||
result?.analysis?.entry ||
|
||||
(typeof result?.analysis?.entry === 'string' ? parseFloat(result?.analysis?.entry.replace(/[^0-9.-]+/g, '')) : 0) ||
|
||||
currentPrice ||
|
||||
0
|
||||
}
|
||||
@@ -1415,7 +1417,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{result && !result.analysis && result.screenshots && (
|
||||
<div className="mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
||||
|
||||
@@ -42,6 +42,9 @@ export default function PositionCalculator({
|
||||
const [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
|
||||
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
|
||||
|
||||
// Debug logging
|
||||
console.log('📊 PositionCalculator mounted with:', { analysis, currentPrice, symbol })
|
||||
|
||||
// Auto-detect position type from analysis
|
||||
useEffect(() => {
|
||||
if (analysis?.recommendation) {
|
||||
@@ -57,7 +60,10 @@ export default function PositionCalculator({
|
||||
// Fetch current market price if not provided
|
||||
useEffect(() => {
|
||||
const fetchPrice = async () => {
|
||||
console.log('🔍 Fetching price for:', symbol, 'currentPrice:', currentPrice)
|
||||
|
||||
if (currentPrice > 0) {
|
||||
console.log('✅ Using provided currentPrice:', currentPrice)
|
||||
setMarketPrice(currentPrice)
|
||||
return
|
||||
}
|
||||
@@ -65,13 +71,21 @@ export default function PositionCalculator({
|
||||
try {
|
||||
const response = await fetch(`/api/price?symbol=${symbol}`)
|
||||
const data = await response.json()
|
||||
console.log('📊 Price API response:', data)
|
||||
|
||||
if (data.price) {
|
||||
console.log('✅ Setting market price to:', data.price)
|
||||
setMarketPrice(data.price)
|
||||
} else {
|
||||
console.error('❌ No price in API response')
|
||||
setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch price:', error)
|
||||
console.error('❌ Failed to fetch price:', error)
|
||||
// Fallback to a reasonable default for testing
|
||||
setMarketPrice(symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100)
|
||||
const fallbackPrice = symbol.includes('BTC') ? 100000 : symbol.includes('ETH') ? 4000 : 100
|
||||
console.log('🔄 Using fallback price:', fallbackPrice)
|
||||
setMarketPrice(fallbackPrice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,20 +94,29 @@ export default function PositionCalculator({
|
||||
|
||||
// Calculate position metrics
|
||||
const calculatePosition = () => {
|
||||
const priceToUse = marketPrice || currentPrice
|
||||
if (!priceToUse || priceToUse <= 0) return null
|
||||
let priceToUse = marketPrice || currentPrice
|
||||
console.log('📊 Calculating position with:', { priceToUse, marketPrice, currentPrice, investmentAmount, leverage })
|
||||
|
||||
// Use fallback price if no price is available
|
||||
if (!priceToUse || priceToUse <= 0) {
|
||||
priceToUse = symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100
|
||||
console.log('🔄 Using fallback price:', priceToUse)
|
||||
}
|
||||
|
||||
const positionSize = investmentAmount * leverage
|
||||
const marginRequired = investmentAmount
|
||||
const fee = positionSize * (tradingFee / 100)
|
||||
const netInvestment = investmentAmount + fee
|
||||
|
||||
console.log('📈 Position calculation:', { positionSize, marginRequired, fee, netInvestment })
|
||||
|
||||
// Get AI analysis targets if available
|
||||
let entryPrice = priceToUse
|
||||
let stopLoss = 0
|
||||
let takeProfit = 0
|
||||
|
||||
if (analysis && analysis.analysis) {
|
||||
console.log('🤖 Using AI analysis:', analysis.analysis)
|
||||
// Try to extract entry, stop loss, and take profit from AI analysis
|
||||
const analysisText = analysis.analysis.toLowerCase()
|
||||
|
||||
@@ -101,18 +124,21 @@ export default function PositionCalculator({
|
||||
const entryMatch = analysisText.match(/entry[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||
if (entryMatch) {
|
||||
entryPrice = parseFloat(entryMatch[1])
|
||||
console.log('📍 Found entry price:', entryPrice)
|
||||
}
|
||||
|
||||
// Look for stop loss
|
||||
const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||
if (stopMatch) {
|
||||
stopLoss = parseFloat(stopMatch[1])
|
||||
console.log('🛑 Found stop loss:', stopLoss)
|
||||
}
|
||||
|
||||
// Look for take profit
|
||||
const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||
if (profitMatch) {
|
||||
takeProfit = parseFloat(profitMatch[1])
|
||||
console.log('🎯 Found take profit:', takeProfit)
|
||||
}
|
||||
|
||||
// If no specific targets found, use percentage-based defaults
|
||||
@@ -120,14 +146,17 @@ export default function PositionCalculator({
|
||||
stopLoss = positionType === 'long'
|
||||
? entryPrice * 0.95 // 5% stop loss for long
|
||||
: entryPrice * 1.05 // 5% stop loss for short
|
||||
console.log('🔄 Using default stop loss:', stopLoss)
|
||||
}
|
||||
|
||||
if (!takeProfit) {
|
||||
takeProfit = positionType === 'long'
|
||||
? entryPrice * 1.10 // 10% take profit for long
|
||||
: entryPrice * 0.90 // 10% take profit for short
|
||||
console.log('🔄 Using default take profit:', takeProfit)
|
||||
}
|
||||
} else {
|
||||
console.log('📊 No AI analysis, using defaults')
|
||||
// Default targets if no analysis
|
||||
stopLoss = positionType === 'long'
|
||||
? priceToUse * 0.95
|
||||
@@ -170,6 +199,7 @@ export default function PositionCalculator({
|
||||
netInvestment
|
||||
}
|
||||
|
||||
console.log('✅ Position calculation result:', result)
|
||||
setCalculation(result)
|
||||
onPositionChange?.(result)
|
||||
return result
|
||||
@@ -177,8 +207,15 @@ export default function PositionCalculator({
|
||||
|
||||
// Recalculate when parameters change
|
||||
useEffect(() => {
|
||||
console.log('🔄 Recalculating position...')
|
||||
calculatePosition()
|
||||
}, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin])
|
||||
}, [investmentAmount, leverage, positionType, currentPrice, analysis, tradingFee, maintenanceMargin, marketPrice])
|
||||
|
||||
// Force initial calculation
|
||||
useEffect(() => {
|
||||
console.log('🚀 Position Calculator initialized, forcing calculation...')
|
||||
calculatePosition()
|
||||
}, [])
|
||||
|
||||
const formatCurrency = (amount: number, decimals: number = 2) => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
@@ -219,7 +256,10 @@ export default function PositionCalculator({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-gray-400">Current Market Price</div>
|
||||
<div className="text-lg font-bold text-white">
|
||||
{symbol}: ${formatPrice(marketPrice || currentPrice || 0)}
|
||||
{symbol}: ${formatPrice(marketPrice || currentPrice || (symbol.includes('BTC') ? 117000 : symbol.includes('ETH') ? 4000 : symbol.includes('SOL') ? 200 : 100))}
|
||||
{(!marketPrice && !currentPrice) && (
|
||||
<span className="text-xs text-yellow-400 ml-2">(fallback)</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{analysis?.recommendation && (
|
||||
|
||||
Reference in New Issue
Block a user