Key Features: - ✅ Drift SDK v2.126.0-beta.14 integration with Helius RPC - ✅ User account initialization and balance reading - ✅ Leverage trading API with real trades executed - ✅ Support for SOL, BTC, ETH, APT, AVAX, BNB, MATIC, ARB, DOGE, OP - ✅ Transaction confirmed: gNmaWVqcE4qNK31ksoUsK6pcHqdDTaUtJXY52ZoXRF API Endpoints: - POST /api/drift/trade - Main trading endpoint - Actions: get_balance, place_order - Successfully tested with 0.01 SOL buy order at 2x leverage Technical Fixes: - Fixed RPC endpoint blocking with Helius API key - Resolved wallet signing compatibility issues - Implemented proper BigNumber handling for amounts - Added comprehensive error handling and logging Trading Bot Status: 🚀 FULLY OPERATIONAL with leverage trading!
353 lines
13 KiB
TypeScript
353 lines
13 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 {
|
|
console.log('🔄 RealTimePriceMonitor: Fetching data...')
|
|
const response = await fetch('/api/price-monitor')
|
|
const data = await response.json()
|
|
|
|
console.log('📊 RealTimePriceMonitor: API Response:', {
|
|
success: data.success,
|
|
tradesCount: data.data?.trades?.length || 0,
|
|
monitoringActive: data.data?.monitoringActive,
|
|
prices: Object.keys(data.data?.prices || {})
|
|
})
|
|
|
|
if (data.success) {
|
|
setMonitoringData(data.data.trades || [])
|
|
setAlerts(data.data.alerts || [])
|
|
setPrices(data.data.prices || {})
|
|
setLastUpdated(data.data.lastUpdated || '')
|
|
setMonitoringActive(data.data.monitoringActive || false)
|
|
console.log('✅ RealTimePriceMonitor: State updated with', data.data.trades?.length || 0, 'trades')
|
|
} else {
|
|
console.error('❌ RealTimePriceMonitor: API returned error:', data.error)
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ RealTimePriceMonitor: Error fetching 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>
|
|
|
|
{(() => {
|
|
console.log('🎯 RealTimePriceMonitor: Rendering with', monitoringData.length, 'trades', monitoringData)
|
|
return null
|
|
})()}
|
|
|
|
{monitoringData.length === 0 ? (
|
|
<div>
|
|
<p className="text-gray-400">No active trades to monitor</p>
|
|
<p className="text-xs text-yellow-400 mt-2">
|
|
Debug: Monitoring Active: {monitoringActive ? 'Yes' : 'No'} |
|
|
Last Updated: {lastUpdated ? new Date(lastUpdated).toLocaleTimeString() : 'Never'}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<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>
|
|
)
|
|
}
|