Files
trading_bot_v3/lib/ai-analysis.ts
mindesbunister a8fcb33ec8 🚀 Major TradingView Automation Improvements
 SUCCESSFUL FEATURES:
- Fixed TradingView login automation by implementing Email button click detection
- Added comprehensive Playwright-based automation with Docker support
- Implemented robust chart navigation and symbol switching
- Added timeframe detection with interval legend clicking and keyboard fallbacks
- Created enhanced screenshot capture with multiple layout support
- Built comprehensive debug tools and error handling

🔧 KEY TECHNICAL IMPROVEMENTS:
- Enhanced login flow: Email button → input detection → form submission
- Improved navigation with flexible wait strategies and fallbacks
- Advanced timeframe changing with interval legend and keyboard shortcuts
- Robust element detection with multiple selector strategies
- Added extensive logging and debug screenshot capabilities
- Docker-optimized with proper Playwright setup

📁 NEW FILES:
- lib/tradingview-automation.ts: Complete Playwright automation
- lib/enhanced-screenshot.ts: Advanced screenshot service
- debug-*.js: Debug scripts for TradingView UI analysis
- Docker configurations and automation scripts

🐛 FIXES:
- Solved dynamic TradingView login form issue with Email button detection
- Fixed navigation timeouts with multiple wait strategies
- Implemented fallback systems for all critical automation steps
- Added proper error handling and recovery mechanisms

📊 CURRENT STATUS:
- Login: 100% working 
- Navigation: 100% working 
- Timeframe change: 95% working 
- Screenshot capture: 100% working 
- Docker integration: 100% working 

Next: Fix AI analysis JSON response format
2025-07-12 14:50:24 +02:00

444 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<AnalysisResult | null> {
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 515min 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<AnalysisResult | null> {
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 515min 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<AnalysisResult | null> {
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<Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>> {
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()