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:
265
app/api/batch-analysis/route.js
Normal file
265
app/api/batch-analysis/route.js
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { enhancedScreenshotService } from '../../../lib/enhanced-screenshot'
|
||||||
|
import { aiAnalysisService } from '../../../lib/ai-analysis'
|
||||||
|
import { progressTracker } from '../../../lib/progress-tracker'
|
||||||
|
|
||||||
|
export async function POST(request) {
|
||||||
|
try {
|
||||||
|
const body = await request.json()
|
||||||
|
const { symbol, layouts, timeframes, selectedLayouts, analyze = true } = body
|
||||||
|
|
||||||
|
console.log('📊 Batch analysis request:', { symbol, layouts, timeframes, selectedLayouts })
|
||||||
|
|
||||||
|
// Validate inputs
|
||||||
|
if (!symbol || !timeframes || !Array.isArray(timeframes) || timeframes.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid request: symbol and timeframes array required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique session ID for progress tracking
|
||||||
|
const sessionId = `batch_analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
console.log('🔍 Created batch analysis session ID:', sessionId)
|
||||||
|
|
||||||
|
// Create progress tracking session with initial steps
|
||||||
|
const initialSteps = [
|
||||||
|
{
|
||||||
|
id: 'init',
|
||||||
|
title: 'Initializing Batch Analysis',
|
||||||
|
description: 'Starting multi-timeframe analysis...',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'auth',
|
||||||
|
title: 'TradingView Authentication',
|
||||||
|
description: 'Logging into TradingView accounts',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'navigation',
|
||||||
|
title: 'Chart Navigation',
|
||||||
|
description: 'Navigating to chart layouts',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'loading',
|
||||||
|
title: 'Chart Data Loading',
|
||||||
|
description: 'Waiting for chart data and indicators',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'capture',
|
||||||
|
title: 'Screenshot Capture',
|
||||||
|
description: `Capturing screenshots for ${timeframes.length} timeframes`,
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'analysis',
|
||||||
|
title: 'AI Analysis',
|
||||||
|
description: 'Analyzing all screenshots with AI',
|
||||||
|
status: 'pending'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Create the progress session
|
||||||
|
progressTracker.createSession(sessionId, initialSteps)
|
||||||
|
|
||||||
|
// Prepare base configuration
|
||||||
|
const baseConfig = {
|
||||||
|
symbol: symbol || 'BTCUSD',
|
||||||
|
layouts: layouts || selectedLayouts || ['ai', 'diy'],
|
||||||
|
sessionId,
|
||||||
|
credentials: {
|
||||||
|
email: process.env.TRADINGVIEW_EMAIL,
|
||||||
|
password: process.env.TRADINGVIEW_PASSWORD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔧 Base config:', baseConfig)
|
||||||
|
|
||||||
|
let allScreenshots = []
|
||||||
|
const screenshotResults = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
// STEP 1: Collect ALL screenshots from ALL timeframes FIRST
|
||||||
|
console.log(`🔄 Starting batch screenshot collection for ${timeframes.length} timeframes...`)
|
||||||
|
|
||||||
|
progressTracker.updateStep(sessionId, 'init', 'active', 'Starting batch screenshot collection...')
|
||||||
|
|
||||||
|
for (let i = 0; i < timeframes.length; i++) {
|
||||||
|
const timeframe = timeframes[i]
|
||||||
|
const timeframeLabel = getTimeframeLabel(timeframe)
|
||||||
|
|
||||||
|
console.log(`📸 Collecting screenshots for ${symbol} ${timeframeLabel} (${i + 1}/${timeframes.length})`)
|
||||||
|
|
||||||
|
// Update progress for current timeframe
|
||||||
|
progressTracker.updateStep(sessionId, 'capture', 'active',
|
||||||
|
`Capturing ${timeframeLabel} screenshots (${i + 1}/${timeframes.length})`
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config = {
|
||||||
|
...baseConfig,
|
||||||
|
timeframe: timeframe,
|
||||||
|
sessionId: i === 0 ? sessionId : undefined // Only track progress for first timeframe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture screenshots WITHOUT analysis
|
||||||
|
const screenshots = await enhancedScreenshotService.captureWithLogin(config)
|
||||||
|
|
||||||
|
if (screenshots && screenshots.length > 0) {
|
||||||
|
console.log(`✅ Captured ${screenshots.length} screenshots for ${timeframeLabel}`)
|
||||||
|
|
||||||
|
// Store screenshots with metadata
|
||||||
|
const screenshotData = {
|
||||||
|
timeframe: timeframe,
|
||||||
|
timeframeLabel: timeframeLabel,
|
||||||
|
screenshots: screenshots,
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
|
||||||
|
screenshotResults.push(screenshotData)
|
||||||
|
allScreenshots.push(...screenshots)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠️ No screenshots captured for ${timeframeLabel}`)
|
||||||
|
screenshotResults.push({
|
||||||
|
timeframe: timeframe,
|
||||||
|
timeframeLabel: timeframeLabel,
|
||||||
|
screenshots: [],
|
||||||
|
success: false,
|
||||||
|
error: 'No screenshots captured'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (timeframeError) {
|
||||||
|
console.error(`❌ Error capturing ${timeframeLabel}:`, timeframeError)
|
||||||
|
screenshotResults.push({
|
||||||
|
timeframe: timeframe,
|
||||||
|
timeframeLabel: timeframeLabel,
|
||||||
|
screenshots: [],
|
||||||
|
success: false,
|
||||||
|
error: timeframeError.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay between captures
|
||||||
|
if (i < timeframes.length - 1) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📊 Batch screenshot collection completed: ${allScreenshots.length} total screenshots`)
|
||||||
|
progressTracker.updateStep(sessionId, 'capture', 'completed', `Captured ${allScreenshots.length} total screenshots`)
|
||||||
|
|
||||||
|
// STEP 2: Send ALL screenshots to AI for comprehensive analysis
|
||||||
|
let analysis = null
|
||||||
|
|
||||||
|
if (analyze && allScreenshots.length > 0) {
|
||||||
|
console.log(`🤖 Starting comprehensive AI analysis on ${allScreenshots.length} screenshots...`)
|
||||||
|
progressTracker.updateStep(sessionId, 'analysis', 'active', 'Running comprehensive AI analysis...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (allScreenshots.length === 1) {
|
||||||
|
analysis = await aiAnalysisService.analyzeScreenshot(allScreenshots[0])
|
||||||
|
} else {
|
||||||
|
analysis = await aiAnalysisService.analyzeMultipleScreenshots(allScreenshots)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analysis) {
|
||||||
|
console.log('✅ Comprehensive AI analysis completed')
|
||||||
|
progressTracker.updateStep(sessionId, 'analysis', 'completed', 'AI analysis completed successfully!')
|
||||||
|
} else {
|
||||||
|
throw new Error('AI analysis returned null')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (analysisError) {
|
||||||
|
console.error('❌ AI analysis failed:', analysisError)
|
||||||
|
progressTracker.updateStep(sessionId, 'analysis', 'error', `AI analysis failed: ${analysisError.message}`)
|
||||||
|
|
||||||
|
// Don't fail the entire request - return screenshots without analysis
|
||||||
|
analysis = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3: Format comprehensive results
|
||||||
|
const result = {
|
||||||
|
success: true,
|
||||||
|
type: 'batch_analysis',
|
||||||
|
sessionId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
symbol: symbol,
|
||||||
|
timeframes: timeframes,
|
||||||
|
layouts: baseConfig.layouts,
|
||||||
|
summary: `Batch analysis completed for ${timeframes.length} timeframes`,
|
||||||
|
totalScreenshots: allScreenshots.length,
|
||||||
|
screenshotResults: screenshotResults,
|
||||||
|
allScreenshots: allScreenshots.map(path => ({
|
||||||
|
url: `/screenshots/${path.split('/').pop()}`,
|
||||||
|
timestamp: Date.now()
|
||||||
|
})),
|
||||||
|
analysis: analysis, // Comprehensive analysis of ALL screenshots
|
||||||
|
message: `Successfully captured ${allScreenshots.length} screenshots${analysis ? ' with comprehensive AI analysis' : ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up session
|
||||||
|
setTimeout(() => progressTracker.deleteSession(sessionId), 2000)
|
||||||
|
|
||||||
|
return NextResponse.json(result)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Batch analysis failed:', error)
|
||||||
|
progressTracker.updateStep(sessionId, 'analysis', 'error', `Batch analysis failed: ${error.message}`)
|
||||||
|
setTimeout(() => progressTracker.deleteSession(sessionId), 5000)
|
||||||
|
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Batch analysis failed',
|
||||||
|
message: error.message,
|
||||||
|
sessionId: sessionId
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Batch analysis API error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Batch analysis failed',
|
||||||
|
message: error.message
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get timeframe label
|
||||||
|
function getTimeframeLabel(timeframe) {
|
||||||
|
const timeframes = [
|
||||||
|
{ label: '1m', value: '1' },
|
||||||
|
{ label: '5m', value: '5' },
|
||||||
|
{ label: '15m', value: '15' },
|
||||||
|
{ label: '30m', value: '30' },
|
||||||
|
{ label: '1h', value: '60' },
|
||||||
|
{ label: '2h', value: '120' },
|
||||||
|
{ label: '4h', value: '240' },
|
||||||
|
{ label: '1d', value: 'D' },
|
||||||
|
{ label: '1w', value: 'W' },
|
||||||
|
{ label: '1M', value: 'M' },
|
||||||
|
]
|
||||||
|
|
||||||
|
return timeframes.find(t => t.value === timeframe)?.label || timeframe
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Batch Analysis API - use POST method for multi-timeframe analysis',
|
||||||
|
endpoints: {
|
||||||
|
POST: '/api/batch-analysis - Run multi-timeframe analysis with parameters'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -251,68 +251,57 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
onAnalysisComplete(data.analysis, analysisSymbol)
|
onAnalysisComplete(data.analysis, analysisSymbol)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Multiple timeframe analysis
|
// Multiple timeframe analysis - use batch analysis endpoint
|
||||||
const results = []
|
console.log(`🧪 Starting batch analysis for ${analysisTimeframes.length} timeframes`)
|
||||||
|
|
||||||
for (let i = 0; i < analysisTimeframes.length; i++) {
|
const response = await fetch('/api/batch-analysis', {
|
||||||
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',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
symbol: analysisSymbol,
|
symbol: analysisSymbol,
|
||||||
timeframe: tf,
|
timeframes: analysisTimeframes,
|
||||||
layouts: selectedLayouts,
|
layouts: selectedLayouts,
|
||||||
analyze: true
|
analyze: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await response.json()
|
const data = await response.json()
|
||||||
results.push({
|
|
||||||
timeframe: tf,
|
|
||||||
timeframeLabel,
|
|
||||||
success: response.ok,
|
|
||||||
result
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start progress tracking for the first timeframe session
|
if (!response.ok) {
|
||||||
if (i === 0 && result.sessionId) {
|
throw new Error(data.error || 'Batch analysis failed')
|
||||||
startProgressTracking(result.sessionId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update timeframe progress manually for multi-timeframe
|
// Start real-time progress tracking if sessionId is provided
|
||||||
setProgress(prev => prev ? {
|
if (data.sessionId) {
|
||||||
...prev,
|
startProgressTracking(data.sessionId)
|
||||||
timeframeProgress: {
|
|
||||||
current: i + 1,
|
|
||||||
total: analysisTimeframes.length,
|
|
||||||
currentTimeframe: timeframeLabel
|
|
||||||
}
|
|
||||||
} : null)
|
|
||||||
|
|
||||||
// Small delay between requests
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiResult = {
|
// Convert batch analysis result to compatible format
|
||||||
|
const batchResult = {
|
||||||
type: 'multi_timeframe',
|
type: 'multi_timeframe',
|
||||||
symbol: analysisSymbol,
|
symbol: analysisSymbol,
|
||||||
summary: `Analyzed ${results.length} timeframes for ${analysisSymbol}`,
|
summary: data.summary,
|
||||||
results
|
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
|
// Call the callback with the comprehensive analysis result if provided
|
||||||
if (onAnalysisComplete) {
|
if (onAnalysisComplete && data.analysis) {
|
||||||
const firstSuccessfulResult = results.find(r => r.success && r.result?.analysis)
|
onAnalysisComplete(data.analysis, analysisSymbol)
|
||||||
if (firstSuccessfulResult) {
|
|
||||||
onAnalysisComplete(firstSuccessfulResult.result.analysis, analysisSymbol)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1397,15 +1386,28 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Position Calculator */}
|
{/* Position Calculator - Always Show */}
|
||||||
{result && result.analysis && (
|
|
||||||
<div className="mt-6">
|
<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
|
<PositionCalculator
|
||||||
analysis={result.analysis}
|
analysis={result?.analysis || null}
|
||||||
currentPrice={
|
currentPrice={
|
||||||
result.analysis.entry?.price ||
|
result?.analysis?.entry?.price ||
|
||||||
result.analysis.entry ||
|
result?.analysis?.entry ||
|
||||||
(typeof result.analysis.entry === 'string' ? parseFloat(result.analysis.entry.replace(/[^0-9.-]+/g, '')) : 0) ||
|
(typeof result?.analysis?.entry === 'string' ? parseFloat(result?.analysis?.entry.replace(/[^0-9.-]+/g, '')) : 0) ||
|
||||||
currentPrice ||
|
currentPrice ||
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@@ -1415,7 +1417,7 @@ export default function AIAnalysisPanel({ onAnalysisComplete }: AIAnalysisPanelP
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{result && !result.analysis && result.screenshots && (
|
{result && !result.analysis && result.screenshots && (
|
||||||
<div className="mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
<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 [tradingFee, setTradingFee] = useState<number>(0.1) // 0.1% fee
|
||||||
const [maintenanceMargin, setMaintenanceMargin] = useState<number>(0.5) // 0.5% maintenance margin
|
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
|
// Auto-detect position type from analysis
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (analysis?.recommendation) {
|
if (analysis?.recommendation) {
|
||||||
@@ -57,7 +60,10 @@ export default function PositionCalculator({
|
|||||||
// Fetch current market price if not provided
|
// Fetch current market price if not provided
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPrice = async () => {
|
const fetchPrice = async () => {
|
||||||
|
console.log('🔍 Fetching price for:', symbol, 'currentPrice:', currentPrice)
|
||||||
|
|
||||||
if (currentPrice > 0) {
|
if (currentPrice > 0) {
|
||||||
|
console.log('✅ Using provided currentPrice:', currentPrice)
|
||||||
setMarketPrice(currentPrice)
|
setMarketPrice(currentPrice)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,13 +71,21 @@ export default function PositionCalculator({
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/price?symbol=${symbol}`)
|
const response = await fetch(`/api/price?symbol=${symbol}`)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
console.log('📊 Price API response:', data)
|
||||||
|
|
||||||
if (data.price) {
|
if (data.price) {
|
||||||
|
console.log('✅ Setting market price to:', data.price)
|
||||||
setMarketPrice(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) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch price:', error)
|
console.error('❌ Failed to fetch price:', error)
|
||||||
// Fallback to a reasonable default for testing
|
// 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
|
// Calculate position metrics
|
||||||
const calculatePosition = () => {
|
const calculatePosition = () => {
|
||||||
const priceToUse = marketPrice || currentPrice
|
let priceToUse = marketPrice || currentPrice
|
||||||
if (!priceToUse || priceToUse <= 0) return null
|
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 positionSize = investmentAmount * leverage
|
||||||
const marginRequired = investmentAmount
|
const marginRequired = investmentAmount
|
||||||
const fee = positionSize * (tradingFee / 100)
|
const fee = positionSize * (tradingFee / 100)
|
||||||
const netInvestment = investmentAmount + fee
|
const netInvestment = investmentAmount + fee
|
||||||
|
|
||||||
|
console.log('📈 Position calculation:', { positionSize, marginRequired, fee, netInvestment })
|
||||||
|
|
||||||
// Get AI analysis targets if available
|
// Get AI analysis targets if available
|
||||||
let entryPrice = priceToUse
|
let entryPrice = priceToUse
|
||||||
let stopLoss = 0
|
let stopLoss = 0
|
||||||
let takeProfit = 0
|
let takeProfit = 0
|
||||||
|
|
||||||
if (analysis && analysis.analysis) {
|
if (analysis && analysis.analysis) {
|
||||||
|
console.log('🤖 Using AI analysis:', analysis.analysis)
|
||||||
// Try to extract entry, stop loss, and take profit from AI analysis
|
// Try to extract entry, stop loss, and take profit from AI analysis
|
||||||
const analysisText = analysis.analysis.toLowerCase()
|
const analysisText = analysis.analysis.toLowerCase()
|
||||||
|
|
||||||
@@ -101,18 +124,21 @@ export default function PositionCalculator({
|
|||||||
const entryMatch = analysisText.match(/entry[:\s]*[\$]?(\d+\.?\d*)/i)
|
const entryMatch = analysisText.match(/entry[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||||
if (entryMatch) {
|
if (entryMatch) {
|
||||||
entryPrice = parseFloat(entryMatch[1])
|
entryPrice = parseFloat(entryMatch[1])
|
||||||
|
console.log('📍 Found entry price:', entryPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for stop loss
|
// Look for stop loss
|
||||||
const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i)
|
const stopMatch = analysisText.match(/stop[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||||
if (stopMatch) {
|
if (stopMatch) {
|
||||||
stopLoss = parseFloat(stopMatch[1])
|
stopLoss = parseFloat(stopMatch[1])
|
||||||
|
console.log('🛑 Found stop loss:', stopLoss)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for take profit
|
// Look for take profit
|
||||||
const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i)
|
const profitMatch = analysisText.match(/(?:take profit|target)[:\s]*[\$]?(\d+\.?\d*)/i)
|
||||||
if (profitMatch) {
|
if (profitMatch) {
|
||||||
takeProfit = parseFloat(profitMatch[1])
|
takeProfit = parseFloat(profitMatch[1])
|
||||||
|
console.log('🎯 Found take profit:', takeProfit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no specific targets found, use percentage-based defaults
|
// If no specific targets found, use percentage-based defaults
|
||||||
@@ -120,14 +146,17 @@ export default function PositionCalculator({
|
|||||||
stopLoss = positionType === 'long'
|
stopLoss = positionType === 'long'
|
||||||
? entryPrice * 0.95 // 5% stop loss for long
|
? entryPrice * 0.95 // 5% stop loss for long
|
||||||
: entryPrice * 1.05 // 5% stop loss for short
|
: entryPrice * 1.05 // 5% stop loss for short
|
||||||
|
console.log('🔄 Using default stop loss:', stopLoss)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!takeProfit) {
|
if (!takeProfit) {
|
||||||
takeProfit = positionType === 'long'
|
takeProfit = positionType === 'long'
|
||||||
? entryPrice * 1.10 // 10% take profit for long
|
? entryPrice * 1.10 // 10% take profit for long
|
||||||
: entryPrice * 0.90 // 10% take profit for short
|
: entryPrice * 0.90 // 10% take profit for short
|
||||||
|
console.log('🔄 Using default take profit:', takeProfit)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log('📊 No AI analysis, using defaults')
|
||||||
// Default targets if no analysis
|
// Default targets if no analysis
|
||||||
stopLoss = positionType === 'long'
|
stopLoss = positionType === 'long'
|
||||||
? priceToUse * 0.95
|
? priceToUse * 0.95
|
||||||
@@ -170,6 +199,7 @@ export default function PositionCalculator({
|
|||||||
netInvestment
|
netInvestment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('✅ Position calculation result:', result)
|
||||||
setCalculation(result)
|
setCalculation(result)
|
||||||
onPositionChange?.(result)
|
onPositionChange?.(result)
|
||||||
return result
|
return result
|
||||||
@@ -177,8 +207,15 @@ export default function PositionCalculator({
|
|||||||
|
|
||||||
// Recalculate when parameters change
|
// Recalculate when parameters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('🔄 Recalculating position...')
|
||||||
calculatePosition()
|
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) => {
|
const formatCurrency = (amount: number, decimals: number = 2) => {
|
||||||
return new Intl.NumberFormat('en-US', {
|
return new Intl.NumberFormat('en-US', {
|
||||||
@@ -219,7 +256,10 @@ export default function PositionCalculator({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-sm text-gray-400">Current Market Price</div>
|
<div className="text-sm text-gray-400">Current Market Price</div>
|
||||||
<div className="text-lg font-bold text-white">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{analysis?.recommendation && (
|
{analysis?.recommendation && (
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ class AggressiveCleanup {
|
|||||||
private static instance: AggressiveCleanup
|
private static instance: AggressiveCleanup
|
||||||
private cleanupInterval: NodeJS.Timeout | null = null
|
private cleanupInterval: NodeJS.Timeout | null = null
|
||||||
private isRunning = false
|
private isRunning = false
|
||||||
|
private isInitialized = false
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.startPeriodicCleanup()
|
// Don't auto-start - let startup.ts control it
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): AggressiveCleanup {
|
static getInstance(): AggressiveCleanup {
|
||||||
@@ -20,7 +21,21 @@ class AggressiveCleanup {
|
|||||||
return AggressiveCleanup.instance
|
return AggressiveCleanup.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
private startPeriodicCleanup() {
|
startPeriodicCleanup() {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
console.log('🔄 Aggressive cleanup already initialized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitialized = true
|
||||||
|
console.log('🚀 Starting aggressive cleanup system')
|
||||||
|
|
||||||
|
// In development, disable aggressive cleanup to avoid interfering with analysis
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('🔒 Aggressive cleanup disabled in development mode')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up every 5 minutes
|
// Clean up every 5 minutes
|
||||||
this.cleanupInterval = setInterval(async () => {
|
this.cleanupInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -34,6 +49,8 @@ class AggressiveCleanup {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cleanupOrphanedProcesses().catch(console.error)
|
this.cleanupOrphanedProcesses().catch(console.error)
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
|
console.log('✅ Aggressive cleanup system started (5 min intervals)')
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanupOrphanedProcesses(): Promise<void> {
|
async cleanupOrphanedProcesses(): Promise<void> {
|
||||||
@@ -43,6 +60,23 @@ class AggressiveCleanup {
|
|||||||
console.log('🧹 Running aggressive cleanup for orphaned processes...')
|
console.log('🧹 Running aggressive cleanup for orphaned processes...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Check for active analysis sessions
|
||||||
|
try {
|
||||||
|
const { progressTracker } = await import('./progress-tracker')
|
||||||
|
const activeSessions = progressTracker.getActiveSessions()
|
||||||
|
|
||||||
|
if (activeSessions.length > 0) {
|
||||||
|
console.log(`⚠️ Skipping cleanup - ${activeSessions.length} active analysis sessions: ${activeSessions.join(', ')}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ No active analysis sessions, proceeding with cleanup')
|
||||||
|
} catch (importError) {
|
||||||
|
console.error('❌ Error importing progress tracker:', importError)
|
||||||
|
console.log('⚠️ Skipping cleanup due to import error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Find and kill orphaned chromium processes
|
// Find and kill orphaned chromium processes
|
||||||
const chromiumProcesses = await this.findChromiumProcesses()
|
const chromiumProcesses = await this.findChromiumProcesses()
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import aggressiveCleanup from './aggressive-cleanup'
|
|||||||
// Initialize cleanup system
|
// Initialize cleanup system
|
||||||
console.log('🚀 Initializing trading bot systems...')
|
console.log('🚀 Initializing trading bot systems...')
|
||||||
console.log('🧹 Process cleanup handlers initialized')
|
console.log('🧹 Process cleanup handlers initialized')
|
||||||
console.log('🧹 Aggressive cleanup system initialized')
|
|
||||||
|
// Start aggressive cleanup system (singleton pattern ensures only one instance)
|
||||||
|
aggressiveCleanup.startPeriodicCleanup()
|
||||||
|
|
||||||
// Export cleanup for manual access
|
// Export cleanup for manual access
|
||||||
export { processCleanup, aggressiveCleanup }
|
export { processCleanup, aggressiveCleanup }
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Initialize cleanup system
|
|
||||||
import '../../lib/startup'
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: 'Next.js',
|
|
||||||
description: 'Generated by Next.js',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user