import OpenAI from 'openai' import fs from 'fs/promises' import path from 'path' import { enhancedScreenshotService, ScreenshotConfig } from './enhanced-screenshot' import { TradingViewCredentials } from './tradingview-automation' const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }) export interface AnalysisResult { summary: string marketSentiment: 'BULLISH' | 'BEARISH' | 'NEUTRAL' keyLevels: { support: number[] resistance: number[] } recommendation: 'BUY' | 'SELL' | 'HOLD' confidence: number // 0-100 reasoning: string // Enhanced trading analysis (optional) entry?: { price: number buffer?: string rationale: string } stopLoss?: { price: number rationale: string } takeProfits?: { tp1?: { price: number; description: string } tp2?: { price: number; description: string } } riskToReward?: string confirmationTrigger?: string indicatorAnalysis?: { rsi?: string vwap?: string obv?: string } } export class AIAnalysisService { async analyzeScreenshot(filename: string): Promise { try { const screenshotsDir = path.join(process.cwd(), 'screenshots') const imagePath = path.join(screenshotsDir, filename) // Read image file const imageBuffer = await fs.readFile(imagePath) const base64Image = imageBuffer.toString('base64') const prompt = `You are now a professional trading assistant focused on short-term crypto trading using 5–15min timeframes. You behave with the precision and decisiveness of a top proprietary desk trader. No vagueness, no fluff. Analyze the attached TradingView chart screenshot and provide a detailed trading analysis. ### WHEN GIVING A TRADE SETUP: Be 100% SPECIFIC. Provide: 1. **ENTRY** - Exact price level (with a ± entry buffer if needed) - Rationale: e.g., "Rejection from 15 EMA + VWAP confluence near intraday supply" 2. **STOP-LOSS (SL)** - Exact level (not arbitrary) - Explain *why* it's there: "Above VWAP + failed breakout zone" 3. **TAKE PROFITS** - TP1: Immediate structure (ex: previous low at $149.20) - TP2: Extended target if momentum continues (e.g., $148.00) - Mention **expected RSI/OBV behavior** at each TP zone 4. **RISK-TO-REWARD** - Show R:R. Ex: "1:2.5 — Risking $X to potentially gain $Y" 5. **CONFIRMATION TRIGGER** - Exact signal to wait for: e.g., "Bearish engulfing candle on rejection from VWAP zone" - OBV: "Must be making lower highs + dropping below 30min average" - RSI: "Should remain under 50 on rejection. Overbought ≥70 = wait" 6. **INDICATOR ANALYSIS** - **RSI**: If RSI crosses above 70 while price is under resistance → *wait* - **VWAP**: If price retakes VWAP with bullish momentum → *consider invalidation* - **OBV**: If OBV starts climbing while price stays flat → *early exit or reconsider bias* Return your answer as a JSON object with the following structure: { "summary": "Brief market summary", "marketSentiment": "BULLISH" | "BEARISH" | "NEUTRAL", "keyLevels": { "support": [number array], "resistance": [number array] }, "recommendation": "BUY" | "SELL" | "HOLD", "confidence": number (0-100), "reasoning": "Detailed reasoning with specific levels, indicators, and confirmation triggers", "entry": { "price": number, "buffer": "string describing entry buffer", "rationale": "string explaining entry logic" }, "stopLoss": { "price": number, "rationale": "string explaining stop loss placement" }, "takeProfits": { "tp1": { "price": number, "description": "string" }, "tp2": { "price": number, "description": "string" } }, "riskToReward": "string like '1:2.5 - Risking $X to gain $Y'", "confirmationTrigger": "string describing exact signal to wait for", "indicatorAnalysis": { "rsi": "string describing RSI behavior", "vwap": "string describing VWAP behavior", "obv": "string describing OBV behavior" } } Be concise but thorough. Only return valid JSON.` const response = await openai.chat.completions.create({ model: "gpt-4o", // Updated to current vision model messages: [ { role: "user", content: [ { type: "text", text: prompt }, { type: "image_url", image_url: { url: `data:image/png;base64,${base64Image}` } } ] } ], max_tokens: 1024 }) const content = response.choices[0]?.message?.content if (!content) return null // Extract JSON from response const match = content.match(/\{[\s\S]*\}/) if (!match) return null const json = match[0] console.log('Raw JSON from AI:', json) const result = JSON.parse(json) console.log('Parsed result:', result) // Sanitize the result to ensure no nested objects cause React issues const sanitizedResult = { summary: typeof result.summary === 'string' ? result.summary : String(result.summary || ''), marketSentiment: result.marketSentiment || 'NEUTRAL', keyLevels: { support: Array.isArray(result.keyLevels?.support) ? result.keyLevels.support : [], resistance: Array.isArray(result.keyLevels?.resistance) ? result.keyLevels.resistance : [] }, recommendation: result.recommendation || 'HOLD', confidence: typeof result.confidence === 'number' ? result.confidence : 0, reasoning: typeof result.reasoning === 'string' ? result.reasoning : String(result.reasoning || ''), ...(result.entry && { entry: result.entry }), ...(result.stopLoss && { stopLoss: result.stopLoss }), ...(result.takeProfits && { takeProfits: result.takeProfits }), ...(result.riskToReward && { riskToReward: String(result.riskToReward) }), ...(result.confirmationTrigger && { confirmationTrigger: String(result.confirmationTrigger) }), ...(result.indicatorAnalysis && { indicatorAnalysis: result.indicatorAnalysis }) } // Optionally: validate result structure here return sanitizedResult as AnalysisResult } catch (e) { console.error('AI analysis error:', e) return null } } async analyzeMultipleScreenshots(filenames: string[]): Promise { try { const screenshotsDir = path.join(process.cwd(), 'screenshots') const images: any[] = [] for (const filename of filenames) { const imagePath = path.join(screenshotsDir, filename) const imageBuffer = await fs.readFile(imagePath) const base64Image = imageBuffer.toString('base64') images.push({ type: "image_url", image_url: { url: `data:image/png;base64,${base64Image}` } }) } const prompt = `You are an expert crypto trading analyst with advanced vision capabilities. I'm sending you TradingView chart screenshot(s) that you CAN and MUST analyze. **IMPORTANT: You have full image analysis capabilities. Please analyze the TradingView chart images I'm providing.** Analyze the attached TradingView chart screenshots (multiple layouts of the same symbol) and provide a comprehensive trading analysis by combining insights from all charts. ### TRADING ANALYSIS REQUIREMENTS: You are a professional trading assistant focused on short-term crypto trading using 5–15min timeframes. You behave with the precision and decisiveness of a top proprietary desk trader. No vagueness, no fluff. ### WHEN GIVING A TRADE SETUP: Be 100% SPECIFIC. Provide: 1. **ENTRY** - Exact price level (with a ± entry buffer if needed) - Rationale: e.g., "Rejection from 15 EMA + VWAP confluence near intraday supply" 2. **STOP-LOSS (SL)** - Exact level (not arbitrary) - Explain *why* it's there: "Above VWAP + failed breakout zone" 3. **TAKE PROFITS** - TP1: Immediate structure (ex: previous low at $149.20) - TP2: Extended target if momentum continues (e.g., $148.00) - Mention **expected RSI/OBV behavior** at each TP zone 4. **RISK-TO-REWARD** - Show R:R. Ex: "1:2.5 — Risking $X to potentially gain $Y" 5. **CONFIRMATION TRIGGER** - Exact signal to wait for: e.g., "Bearish engulfing candle on rejection from VWAP zone" - OBV: "Must be making lower highs + dropping below 30min average" - RSI: "Should remain under 50 on rejection. Overbought ≥70 = wait" 6. **INDICATOR ANALYSIS** - **RSI**: If RSI crosses above 70 while price is under resistance → *wait* - **VWAP**: If price retakes VWAP with bullish momentum → *consider invalidation* - **OBV**: If OBV starts climbing while price stays flat → *early exit or reconsider bias* Cross-reference all layouts to provide the most accurate analysis. If layouts show conflicting signals, explain which one takes priority and why. **CRITICAL: You MUST analyze the actual chart images provided. Do not respond with generic advice.** Return your answer as a JSON object with the following structure: { "summary": "Brief market summary combining all layouts", "marketSentiment": "BULLISH" | "BEARISH" | "NEUTRAL", "keyLevels": { "support": [number array], "resistance": [number array] }, "recommendation": "BUY" | "SELL" | "HOLD", "confidence": number (0-100), "reasoning": "Detailed reasoning with specific levels, indicators, and confirmation triggers from all layouts", "entry": { "price": number, "buffer": "string describing entry buffer", "rationale": "string explaining entry logic" }, "stopLoss": { "price": number, "rationale": "string explaining stop loss placement" }, "takeProfits": { "tp1": { "price": number, "description": "string" }, "tp2": { "price": number, "description": "string" } }, "riskToReward": "string like '1:2.5 - Risking $X to gain $Y'", "confirmationTrigger": "string describing exact signal to wait for", "indicatorAnalysis": { "rsi": "string describing RSI behavior", "vwap": "string describing VWAP behavior", "obv": "string describing OBV behavior" } } Be concise but thorough. Only return valid JSON.` const response = await openai.chat.completions.create({ model: "gpt-4o", // gpt-4o has better vision capabilities than gpt-4-vision-preview messages: [ { role: "user", content: [ { type: "text", text: prompt }, ...images ] } ], max_tokens: 2000, // Increased for more detailed analysis temperature: 0.1 }) const content = response.choices[0]?.message?.content if (!content) { throw new Error('No content received from OpenAI') } console.log('AI response content:', content) // Parse the JSON response const jsonMatch = content.match(/\{[\s\S]*\}/) if (!jsonMatch) { console.error('No JSON found in response. Full content:', content) throw new Error('No JSON found in response') } console.log('Extracted JSON:', jsonMatch[0]) const analysis = JSON.parse(jsonMatch[0]) // Sanitize the analysis result to ensure no nested objects cause React issues const sanitizedAnalysis = { summary: typeof analysis.summary === 'string' ? analysis.summary : String(analysis.summary || ''), marketSentiment: analysis.marketSentiment || 'NEUTRAL', keyLevels: { support: Array.isArray(analysis.keyLevels?.support) ? analysis.keyLevels.support : [], resistance: Array.isArray(analysis.keyLevels?.resistance) ? analysis.keyLevels.resistance : [] }, recommendation: analysis.recommendation || 'HOLD', confidence: typeof analysis.confidence === 'number' ? analysis.confidence : 0, reasoning: typeof analysis.reasoning === 'string' ? analysis.reasoning : String(analysis.reasoning || ''), ...(analysis.entry && { entry: analysis.entry }), ...(analysis.stopLoss && { stopLoss: analysis.stopLoss }), ...(analysis.takeProfits && { takeProfits: analysis.takeProfits }), ...(analysis.riskToReward && { riskToReward: String(analysis.riskToReward) }), ...(analysis.confirmationTrigger && { confirmationTrigger: String(analysis.confirmationTrigger) }), ...(analysis.indicatorAnalysis && { indicatorAnalysis: analysis.indicatorAnalysis }) } // Validate the structure if (!sanitizedAnalysis.summary || !sanitizedAnalysis.marketSentiment || !sanitizedAnalysis.recommendation || typeof sanitizedAnalysis.confidence !== 'number') { console.error('Invalid analysis structure:', sanitizedAnalysis) throw new Error('Invalid analysis structure') } return sanitizedAnalysis } catch (error) { console.error('AI multi-analysis error:', error) return null } } async captureAndAnalyze( symbol: string, timeframe: string, credentials: TradingViewCredentials ): Promise { try { console.log(`Starting automated capture and analysis for ${symbol} ${timeframe}`) // Capture screenshot using automation const screenshot = await enhancedScreenshotService.captureQuick(symbol, timeframe, credentials) if (!screenshot) { throw new Error('Failed to capture screenshot') } console.log(`Screenshot captured: ${screenshot}`) // Analyze the captured screenshot const analysis = await this.analyzeScreenshot(screenshot) if (!analysis) { throw new Error('Failed to analyze screenshot') } console.log(`Analysis completed for ${symbol} ${timeframe}`) return analysis } catch (error) { console.error('Automated capture and analysis failed:', error) return null } } async captureAndAnalyzeMultiple( symbols: string[], timeframes: string[], credentials: TradingViewCredentials ): Promise> { const results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }> = [] for (const symbol of symbols) { for (const timeframe of timeframes) { try { console.log(`Processing ${symbol} ${timeframe}...`) const analysis = await this.captureAndAnalyze(symbol, timeframe, credentials) results.push({ symbol, timeframe, analysis }) // Small delay between captures to avoid overwhelming the system await new Promise(resolve => setTimeout(resolve, 2000)) } catch (error) { console.error(`Failed to process ${symbol} ${timeframe}:`, error) results.push({ symbol, timeframe, analysis: null }) } } } return results } async captureAndAnalyzeWithConfig(config: ScreenshotConfig): Promise<{ screenshots: string[] analysis: AnalysisResult | null }> { try { console.log(`Starting automated capture with config for ${config.symbol} ${config.timeframe}`) // Capture screenshots using enhanced service const screenshots = await enhancedScreenshotService.captureWithLogin(config) if (screenshots.length === 0) { throw new Error('No screenshots captured') } console.log(`${screenshots.length} screenshot(s) captured`) let analysis: AnalysisResult | null = null if (screenshots.length === 1) { // Single screenshot analysis analysis = await this.analyzeScreenshot(screenshots[0]) } else { // Multiple screenshots analysis analysis = await this.analyzeMultipleScreenshots(screenshots) } if (!analysis) { throw new Error('Failed to analyze screenshots') } console.log(`Analysis completed for ${config.symbol} ${config.timeframe}`) return { screenshots, analysis } } catch (error) { console.error('Automated capture and analysis with config failed:', error) return { screenshots: [], analysis: null } } } } export const aiAnalysisService = new AIAnalysisService()