Files
trading_bot_v3/components/RealTimePriceMonitor.tsx
mindesbunister 7de3eaf7b8 feat: implement real-time price monitoring with automatic analysis triggering
New Features:
- Real-time price monitoring service with 5-minute update cycles
- Automatic analysis triggering when prices approach TP/SL levels (15%/25% thresholds)
- Comprehensive price monitoring UI component with live updates
- Integration with automation service for smart analysis scheduling
- PnL tracking and position status monitoring

- EventEmitter-based real-time updates
- CoinGecko API integration with rate limiting
- TP/SL approach detection with configurable thresholds
- Alert system for critical price movements
- Database integration for trade tracking

- Price monitor startup/shutdown with automation lifecycle
- Event listeners for TP_APPROACH, SL_APPROACH, CRITICAL alerts
- Automatic screenshot capture and AI analysis on price triggers
- Enhanced progress tracking for price-based analysis
- Intelligent analysis context with price movement data

- RealTimePriceMonitor component with live price display
- Trade monitoring cards with P&L and distance to TP/SL
- Active alerts panel with price threshold notifications
- Monitoring service controls (start/stop/force update)
- Integration with automation page for comprehensive oversight

- GET: Retrieve monitoring data, alerts, and current prices
- POST: Control monitoring service and force price updates
- Real-time data formatting and status management

- Comprehensive price monitor integration tests
- Basic functionality validation scripts
- API endpoint testing capabilities

This implements the user's request for real-time price monitoring with automatic analysis triggering when prices approach critical levels, providing enhanced oversight of active trading positions.
2025-07-21 10:31:49 +02:00

331 lines
12 KiB
TypeScript

"use client"
import React, { useState, useEffect } from 'react'
interface TradeMonitoring {
tradeId: string
symbol: string
side: 'BUY' | 'SELL'
entryPrice: number
stopLoss?: number
takeProfit?: number
currentPrice?: number
currentPnL?: number
pnlPercentage?: number
distanceToTP?: number
distanceToSL?: number
status: 'ACTIVE' | 'APPROACHING_TP' | 'APPROACHING_SL' | 'CRITICAL'
}
interface PriceAlert {
id: string
symbol: string
tradeId: string
alertType: 'TP_APPROACH' | 'SL_APPROACH' | 'BREAKOUT' | 'REVERSAL'
currentPrice: number
targetPrice: number
threshold: number
triggered: boolean
createdAt: string
}
export default function RealTimePriceMonitor() {
const [monitoringData, setMonitoringData] = useState<TradeMonitoring[]>([])
const [alerts, setAlerts] = useState<PriceAlert[]>([])
const [prices, setPrices] = useState<Record<string, number>>({})
const [lastUpdated, setLastUpdated] = useState<string>('')
const [monitoringActive, setMonitoringActive] = useState(false)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchMonitoringData()
// Update every 30 seconds for UI refresh
const interval = setInterval(fetchMonitoringData, 30000)
return () => clearInterval(interval)
}, [])
const fetchMonitoringData = async () => {
try {
const response = await fetch('/api/price-monitor')
const data = await response.json()
if (data.success) {
setMonitoringData(data.data.trades || [])
setAlerts(data.data.alerts || [])
setPrices(data.data.prices || {})
setLastUpdated(data.data.lastUpdated || '')
setMonitoringActive(data.data.monitoringActive || false)
}
} catch (error) {
console.error('Error fetching monitoring data:', error)
} finally {
setLoading(false)
}
}
const startMonitoring = async () => {
try {
const response = await fetch('/api/price-monitor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'start_monitoring' })
})
if (response.ok) {
setMonitoringActive(true)
await fetchMonitoringData()
}
} catch (error) {
console.error('Error starting monitoring:', error)
}
}
const stopMonitoring = async () => {
try {
const response = await fetch('/api/price-monitor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'stop_monitoring' })
})
if (response.ok) {
setMonitoringActive(false)
}
} catch (error) {
console.error('Error stopping monitoring:', error)
}
}
const forceUpdatePrice = async (symbol: string) => {
try {
const response = await fetch('/api/price-monitor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'force_update', symbol })
})
if (response.ok) {
await fetchMonitoringData()
}
} catch (error) {
console.error('Error force updating price:', error)
}
}
const getStatusColor = (status: TradeMonitoring['status']) => {
switch (status) {
case 'CRITICAL': return 'text-red-400 bg-red-900/20 border-red-600'
case 'APPROACHING_TP': return 'text-green-400 bg-green-900/20 border-green-600'
case 'APPROACHING_SL': return 'text-yellow-400 bg-yellow-900/20 border-yellow-600'
default: return 'text-blue-400 bg-blue-900/20 border-blue-600'
}
}
const getPnLColor = (pnl?: number) => {
if (!pnl) return 'text-gray-400'
return pnl >= 0 ? 'text-green-400' : 'text-red-400'
}
const formatCurrency = (amount?: number) => {
if (amount === undefined || amount === null) return 'N/A'
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 4
}).format(amount)
}
const formatPercentage = (pct?: number) => {
if (pct === undefined || pct === null) return 'N/A'
return `${pct >= 0 ? '+' : ''}${pct.toFixed(2)}%`
}
const formatDistance = (distance?: number) => {
if (distance === undefined || distance === null) return 'N/A'
return `${(distance * 100).toFixed(1)}%`
}
if (loading) {
return (
<div className="card card-gradient p-6">
<div className="animate-pulse">
<div className="h-6 bg-gray-700 rounded mb-4"></div>
<div className="space-y-3">
<div className="h-4 bg-gray-700 rounded"></div>
<div className="h-4 bg-gray-700 rounded"></div>
</div>
</div>
</div>
)
}
return (
<div className="space-y-6">
{/* Header Controls */}
<div className="card card-gradient p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-xl font-bold text-white">Real-Time Price Monitor</h2>
<p className="text-gray-400 text-sm">
Tracks active trades and triggers analysis when approaching TP/SL
</p>
</div>
<div className="flex items-center space-x-3">
<div className={`px-3 py-1 rounded-full text-xs font-medium ${
monitoringActive ? 'bg-green-600 text-white' : 'bg-gray-600 text-gray-300'
}`}>
{monitoringActive ? '🟢 Active' : '🔴 Stopped'}
</div>
{monitoringActive ? (
<button
onClick={stopMonitoring}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium"
>
Stop Monitor
</button>
) : (
<button
onClick={startMonitoring}
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg text-sm font-medium"
>
Start Monitor
</button>
)}
</div>
</div>
{lastUpdated && (
<div className="text-xs text-gray-400">
Last updated: {new Date(lastUpdated).toLocaleString()}
</div>
)}
</div>
{/* Active Alerts */}
{alerts.length > 0 && (
<div className="card card-gradient p-6">
<h3 className="text-lg font-bold text-white mb-4">🚨 Active Alerts</h3>
<div className="space-y-3">
{alerts.map((alert) => (
<div key={alert.id} className="p-4 bg-red-900/20 border border-red-600 rounded-lg">
<div className="flex items-center justify-between">
<div>
<span className="font-semibold text-red-400">{alert.symbol}</span>
<span className="ml-2 text-sm text-gray-300">
{alert.alertType.replace('_', ' ')}
</span>
</div>
<div className="text-sm text-gray-300">
Current: {formatCurrency(alert.currentPrice)} Target: {formatCurrency(alert.targetPrice)}
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Current Prices */}
<div className="card card-gradient p-6">
<h3 className="text-lg font-bold text-white mb-4">💰 Current Prices</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Object.entries(prices).map(([symbol, price]) => (
<div key={symbol} className="p-3 bg-gray-800 rounded-lg border border-gray-700">
<div className="flex items-center justify-between">
<span className="text-gray-300 text-sm">{symbol}</span>
<button
onClick={() => forceUpdatePrice(symbol)}
className="text-xs text-blue-400 hover:text-blue-300"
>
🔄
</button>
</div>
<div className="text-white font-bold text-lg">
{formatCurrency(price)}
</div>
</div>
))}
</div>
</div>
{/* Active Trades Monitoring */}
<div className="card card-gradient p-6">
<h3 className="text-lg font-bold text-white mb-4">📊 Active Trades Monitor</h3>
{monitoringData.length === 0 ? (
<p className="text-gray-400">No active trades to monitor</p>
) : (
<div className="space-y-4">
{monitoringData.map((trade) => (
<div key={trade.tradeId} className="p-4 bg-gray-800 rounded-lg border border-gray-700">
{/* Trade Header */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-3">
<span className={`font-semibold px-2 py-1 rounded text-xs ${
trade.side === 'BUY' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'
}`}>
{trade.side}
</span>
<span className="text-white font-bold">{trade.symbol}</span>
<span className={`px-2 py-1 rounded text-xs border ${getStatusColor(trade.status)}`}>
{trade.status.replace('_', ' ')}
</span>
</div>
<div className="text-right">
<div className="text-white font-semibold">
Current: {formatCurrency(trade.currentPrice)}
</div>
<div className="text-xs text-gray-400">
Entry: {formatCurrency(trade.entryPrice)}
</div>
</div>
</div>
{/* Trade Details Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<div className="text-gray-400">P&L</div>
<div className={`font-semibold ${getPnLColor(trade.currentPnL)}`}>
{formatCurrency(trade.currentPnL)}
</div>
<div className={`text-xs ${getPnLColor(trade.currentPnL)}`}>
{formatPercentage(trade.pnlPercentage)}
</div>
</div>
<div>
<div className="text-gray-400">Take Profit</div>
<div className="text-white">{formatCurrency(trade.takeProfit)}</div>
<div className="text-xs text-green-400">
{trade.distanceToTP ? `${formatDistance(trade.distanceToTP)} away` : 'N/A'}
</div>
</div>
<div>
<div className="text-gray-400">Stop Loss</div>
<div className="text-white">{formatCurrency(trade.stopLoss)}</div>
<div className="text-xs text-red-400">
{trade.distanceToSL ? `${formatDistance(trade.distanceToSL)} away` : 'N/A'}
</div>
</div>
<div>
<div className="text-gray-400">Status</div>
<div className="text-white font-medium">
{trade.status === 'CRITICAL' ? '🚨 Critical' :
trade.status === 'APPROACHING_TP' ? '🎯 Near TP' :
trade.status === 'APPROACHING_SL' ? '⚠️ Near SL' : '✅ Normal'}
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}