Files
trading_bot_v3/components/TradeFollowUpPanel.tsx
mindesbunister 47d7b8b364 feat: Add interactive Trade Follow-up Assistant with ChatGPT integration
- Interactive chat interface to ask questions about active trades
- Automatic position detection and context-aware responses
- Fresh screenshot capture with updated market analysis
- Smart conversation flow with trade-specific insights
- Quick action buttons for common trade management questions

- TradeFollowUpPanel.tsx: Full-featured chat interface with position tracking
- /api/trade-followup: GPT-4o mini integration with screenshot analysis
- Enhanced AIAnalysisPanel with Follow-up button integration

- 'Should I exit now?' - Real-time exit recommendations
- 'Update my stop loss' - SL adjustment guidance based on current conditions
- 'Move to break even' - Risk-free position management
- 'Current market analysis' - Fresh chart analysis with updated screenshots
- 'Risk assessment' - Position risk evaluation
- 'Take profit strategy' - TP optimization recommendations

- Enter trade based on AI analysis → Use Follow-up for ongoing management
- Ask specific questions: 'Is this still a valid setup?'
- Get updated analysis: 'What do the charts look like now?'
- Risk management: 'Should I move my stop loss?'
- Exit timing: 'Is this a good time to take profits?'

The assistant provides context-aware guidance by:
 Tracking your current position details (entry, size, P&L)
 Capturing fresh screenshots when needed for updated analysis
 Combining position context with current market conditions
 Providing specific price levels and actionable advice
 Maintaining conversation history for continuity

Perfect for traders who want ongoing AI guidance throughout their trades!
2025-07-17 15:29:52 +02:00

347 lines
13 KiB
TypeScript

"use client"
import React, { useState, useRef, useEffect } from 'react'
interface TradePosition {
id: string
symbol: string
side: 'LONG' | 'SHORT'
entryPrice: number
currentPrice: number
size: number
pnl: number
stopLoss?: number
takeProfit?: number
entryTime: string
entryAnalysis?: string
}
interface ChatMessage {
id: string
type: 'user' | 'assistant' | 'system'
content: string
timestamp: string
analysis?: any
screenshots?: string[]
}
interface TradeFollowUpPanelProps {
onClose: () => void
}
export default function TradeFollowUpPanel({ onClose }: TradeFollowUpPanelProps) {
const [activePosition, setActivePosition] = useState<TradePosition | null>(null)
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([])
const [currentMessage, setCurrentMessage] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isAnalyzing, setIsAnalyzing] = useState(false)
const chatEndRef = useRef<HTMLDivElement>(null)
// Auto-scroll to bottom of chat
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [chatMessages])
// Load active positions on mount
useEffect(() => {
loadActivePositions()
}, [])
const loadActivePositions = async () => {
try {
const response = await fetch('/api/trading/positions')
const data = await response.json()
if (data.success && data.positions?.length > 0) {
// For now, take the first active position
// TODO: Add position selector if multiple positions
setActivePosition(data.positions[0])
// Add welcome message
setChatMessages([{
id: Date.now().toString(),
type: 'system',
content: `🎯 **Trade Follow-up Assistant**\n\nI'm here to help you manage your active ${data.positions[0].symbol} ${data.positions[0].side} position.\n\n**Current Position:**\n• Entry: $${data.positions[0].entryPrice}\n• Size: ${data.positions[0].size}\n• Current P&L: ${data.positions[0].pnl > 0 ? '+' : ''}$${data.positions[0].pnl.toFixed(2)}\n\nAsk me anything about your trade!`,
timestamp: new Date().toISOString()
}])
} else {
setChatMessages([{
id: Date.now().toString(),
type: 'system',
content: '⚠️ **No Active Positions Found**\n\nI don\'t see any active positions to analyze. Please enter a trade first, then come back for follow-up analysis.',
timestamp: new Date().toISOString()
}])
}
} catch (error) {
console.error('Error loading positions:', error)
setChatMessages([{
id: Date.now().toString(),
type: 'system',
content: '❌ **Error Loading Positions**\n\nCouldn\'t load your active positions. Please try again.',
timestamp: new Date().toISOString()
}])
}
}
const handleSendMessage = async () => {
if (!currentMessage.trim() || isLoading) return
const userMessage: ChatMessage = {
id: Date.now().toString(),
type: 'user',
content: currentMessage,
timestamp: new Date().toISOString()
}
setChatMessages(prev => [...prev, userMessage])
setCurrentMessage('')
setIsLoading(true)
try {
// Check if user is asking for updated analysis
const needsScreenshot = currentMessage.toLowerCase().includes('analysis') ||
currentMessage.toLowerCase().includes('update') ||
currentMessage.toLowerCase().includes('current') ||
currentMessage.toLowerCase().includes('now')
let screenshots: string[] = []
if (needsScreenshot && activePosition) {
setIsAnalyzing(true)
// Add thinking message
const thinkingMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
type: 'assistant',
content: '🔄 **Getting Updated Analysis...**\n\nCapturing fresh screenshots and analyzing current market conditions for your position...',
timestamp: new Date().toISOString()
}
setChatMessages(prev => [...prev, thinkingMessage])
// Get fresh screenshots
const screenshotResponse = await fetch('/api/enhanced-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: activePosition.symbol,
timeframe: '240', // 4h default for trade follow-up
layouts: ['ai', 'diy'],
analyze: false // We'll analyze separately with trade context
})
})
const screenshotData = await screenshotResponse.json()
if (screenshotData.success && screenshotData.screenshots) {
screenshots = screenshotData.screenshots
}
setIsAnalyzing(false)
}
// Send to trade follow-up API
const response = await fetch('/api/trade-followup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: currentMessage,
position: activePosition,
screenshots: screenshots,
chatHistory: chatMessages.slice(-5) // Last 5 messages for context
})
})
const data = await response.json()
if (data.success) {
const assistantMessage: ChatMessage = {
id: (Date.now() + 2).toString(),
type: 'assistant',
content: data.response,
timestamp: new Date().toISOString(),
analysis: data.analysis,
screenshots: screenshots
}
setChatMessages(prev => [...prev.filter(m => !m.content.includes('Getting Updated Analysis')), assistantMessage])
} else {
throw new Error(data.error || 'Failed to get response')
}
} catch (error) {
console.error('Error sending message:', error)
const errorMessage: ChatMessage = {
id: (Date.now() + 3).toString(),
type: 'assistant',
content: '❌ **Error**\n\nSorry, I encountered an error. Please try again.',
timestamp: new Date().toISOString()
}
setChatMessages(prev => [...prev.filter(m => !m.content.includes('Getting Updated Analysis')), errorMessage])
} finally {
setIsLoading(false)
setIsAnalyzing(false)
}
}
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
}
}
const formatMessage = (content: string) => {
// Convert markdown-style formatting to JSX
return content.split('\n').map((line, index) => {
if (line.startsWith('**') && line.endsWith('**')) {
return <div key={index} className="font-bold text-purple-300 mb-2">{line.slice(2, -2)}</div>
}
if (line.startsWith('• ')) {
return <div key={index} className="ml-4 text-gray-300">{line}</div>
}
return <div key={index} className="text-gray-300">{line}</div>
})
}
const quickActions = [
"Should I exit now?",
"Update my stop loss",
"Move to break even",
"Current market analysis",
"Risk assessment",
"Take profit strategy"
]
return (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-gradient-to-br from-gray-900 to-purple-900/20 border border-purple-500/30 rounded-xl w-full max-w-4xl h-[80vh] flex flex-col">
{/* Header */}
<div className="p-4 border-b border-purple-500/30 flex items-center justify-between">
<div className="flex items-center">
<span className="w-8 h-8 bg-gradient-to-br from-purple-400 to-purple-600 rounded-lg flex items-center justify-center mr-3 text-lg">
💬
</span>
<div>
<h3 className="text-lg font-bold text-white">Trade Follow-up Assistant</h3>
{activePosition && (
<p className="text-sm text-purple-300">
{activePosition.symbol} {activePosition.side} Entry: ${activePosition.entryPrice}
</p>
)}
</div>
</div>
<button
onClick={onClose}
className="w-8 h-8 bg-red-500/20 hover:bg-red-500/40 rounded-lg flex items-center justify-center text-red-400 transition-colors"
>
</button>
</div>
{/* Chat Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{chatMessages.map((message) => (
<div
key={message.id}
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] p-3 rounded-lg ${
message.type === 'user'
? 'bg-purple-600 text-white'
: message.type === 'system'
? 'bg-blue-600/20 border border-blue-500/30 text-blue-300'
: 'bg-gray-800 text-gray-300'
}`}
>
{formatMessage(message.content)}
{/* Show screenshots if available */}
{message.screenshots && message.screenshots.length > 0 && (
<div className="mt-3 grid grid-cols-2 gap-2">
{message.screenshots.map((screenshot, index) => (
<img
key={index}
src={`/api/image?file=${screenshot.split('/').pop()}`}
alt={`Analysis screenshot ${index + 1}`}
className="w-full rounded border border-gray-600 cursor-pointer hover:border-purple-500/50 transition-colors"
onClick={() => {
// TODO: Open enlarged view
}}
/>
))}
</div>
)}
<div className="text-xs opacity-60 mt-2">
{new Date(message.timestamp).toLocaleTimeString()}
</div>
</div>
</div>
))}
{isAnalyzing && (
<div className="flex justify-start">
<div className="bg-purple-600/20 border border-purple-500/30 p-3 rounded-lg">
<div className="flex items-center space-x-2">
<div className="animate-spin w-4 h-4 border-2 border-purple-400 border-t-transparent rounded-full"></div>
<span className="text-purple-300">Analyzing market conditions...</span>
</div>
</div>
</div>
)}
<div ref={chatEndRef} />
</div>
{/* Quick Actions */}
{activePosition && (
<div className="p-4 border-t border-purple-500/30">
<div className="mb-3">
<div className="text-xs text-gray-400 mb-2">Quick Actions:</div>
<div className="flex flex-wrap gap-2">
{quickActions.map((action, index) => (
<button
key={index}
onClick={() => setCurrentMessage(action)}
className="px-3 py-1 bg-purple-600/20 hover:bg-purple-600/40 border border-purple-500/30 rounded-full text-xs text-purple-300 transition-colors"
>
{action}
</button>
))}
</div>
</div>
</div>
)}
{/* Message Input */}
<div className="p-4 border-t border-purple-500/30">
<div className="flex items-center space-x-3">
<textarea
value={currentMessage}
onChange={(e) => setCurrentMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={
activePosition
? "Ask about your trade: 'Should I exit?', 'Update analysis', 'Move stop loss'..."
: "No active positions to analyze"
}
disabled={!activePosition || isLoading}
className="flex-1 bg-gray-800 border border-gray-600 rounded-lg px-4 py-2 text-white placeholder-gray-400 focus:border-purple-500 focus:outline-none resize-none"
rows={2}
/>
<button
onClick={handleSendMessage}
disabled={!currentMessage.trim() || isLoading || !activePosition}
className="w-10 h-10 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center text-white transition-colors"
>
{isLoading ? (
<div className="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full"></div>
) : (
'📤'
)}
</button>
</div>
</div>
</div>
</div>
)
}