Frontend changes: - Pass mode, tradingAmount, balancePercentage, dexProvider to optimized API - Send user's actual trading mode choice (LIVE/SIMULATION) Backend changes: - Accept mode and trading parameters from frontend request - Use passed mode instead of hardcoded 'SIMULATION' - Apply user's trading amount and balance percentage settings This fixes the issue where optimized automation always used SIMULATION regardless of user's LIVE trading selection.
831 lines
36 KiB
JavaScript
831 lines
36 KiB
JavaScript
'use client'
|
||
import React, { useState, useEffect } from 'react'
|
||
|
||
// Available timeframes for automation (matching analysis page format)
|
||
const timeframes = [
|
||
{ label: '5m', value: '5' },
|
||
{ label: '15m', value: '15' },
|
||
{ label: '30m', value: '30' },
|
||
{ label: '1h', value: '60' },
|
||
{ label: '2h', value: '120' },
|
||
{ label: '4h', value: '240' },
|
||
{ label: '1d', value: 'D' },
|
||
]
|
||
|
||
export default function AutomationPageV2() {
|
||
const [config, setConfig] = useState({
|
||
mode: 'SIMULATION',
|
||
dexProvider: 'DRIFT',
|
||
symbol: 'SOLUSD',
|
||
timeframe: '1h', // Primary timeframe for backwards compatibility
|
||
selectedTimeframes: ['60'], // Multi-timeframe support
|
||
tradingAmount: 100,
|
||
balancePercentage: 50, // Default to 50% of available balance
|
||
// stopLossPercent and takeProfitPercent removed - AI calculates these automatically
|
||
})
|
||
|
||
const [status, setStatus] = useState(null)
|
||
const [balance, setBalance] = useState(null)
|
||
const [positions, setPositions] = useState([])
|
||
const [loading, setLoading] = useState(false)
|
||
const [nextAnalysisCountdown, setNextAnalysisCountdown] = useState(0)
|
||
|
||
useEffect(() => {
|
||
fetchStatus()
|
||
fetchBalance()
|
||
fetchPositions()
|
||
|
||
const interval = setInterval(() => {
|
||
fetchStatus()
|
||
fetchBalance()
|
||
fetchPositions()
|
||
}, 30000)
|
||
return () => clearInterval(interval)
|
||
}, [])
|
||
|
||
// Timer effect for countdown
|
||
useEffect(() => {
|
||
let countdownInterval = null
|
||
|
||
if (status?.isActive && status?.nextAnalysisIn > 0) {
|
||
setNextAnalysisCountdown(status.nextAnalysisIn)
|
||
|
||
countdownInterval = setInterval(() => {
|
||
setNextAnalysisCountdown(prev => {
|
||
if (prev <= 1) {
|
||
// Refresh status when timer reaches 0
|
||
fetchStatus()
|
||
return 0
|
||
}
|
||
return prev - 1
|
||
})
|
||
}, 1000)
|
||
} else {
|
||
setNextAnalysisCountdown(0)
|
||
}
|
||
|
||
return () => {
|
||
if (countdownInterval) {
|
||
clearInterval(countdownInterval)
|
||
}
|
||
}
|
||
}, [status?.nextAnalysisIn, status?.isActive])
|
||
|
||
// Helper function to format countdown time
|
||
const formatCountdown = (seconds) => {
|
||
if (seconds <= 0) return 'Analyzing now...'
|
||
|
||
const hours = Math.floor(seconds / 3600)
|
||
const minutes = Math.floor((seconds % 3600) / 60)
|
||
const secs = seconds % 60
|
||
|
||
if (hours > 0) {
|
||
return `${hours}h ${minutes}m ${secs}s`
|
||
} else if (minutes > 0) {
|
||
return `${minutes}m ${secs}s`
|
||
} else {
|
||
return `${secs}s`
|
||
}
|
||
}
|
||
|
||
const toggleTimeframe = (timeframe) => {
|
||
setConfig(prev => ({
|
||
...prev,
|
||
selectedTimeframes: prev.selectedTimeframes.includes(timeframe)
|
||
? prev.selectedTimeframes.filter(tf => tf !== timeframe)
|
||
: [...prev.selectedTimeframes, timeframe]
|
||
}))
|
||
}
|
||
|
||
const fetchStatus = async () => {
|
||
try {
|
||
const response = await fetch('/api/automation/status')
|
||
const data = await response.json()
|
||
console.log('Status fetched:', data) // Debug log
|
||
if (data.success) {
|
||
setStatus(data.status)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch status:', error)
|
||
}
|
||
}
|
||
|
||
const fetchBalance = async () => {
|
||
try {
|
||
const response = await fetch('/api/drift/balance')
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setBalance(data)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch balance:', error)
|
||
}
|
||
}
|
||
|
||
const fetchPositions = async () => {
|
||
try {
|
||
const response = await fetch('/api/drift/positions')
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setPositions(data.positions || [])
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch positions:', error)
|
||
}
|
||
}
|
||
|
||
const handleStart = async () => {
|
||
console.log('🚀 Starting OPTIMIZED automation with batch processing!')
|
||
setLoading(true)
|
||
try {
|
||
// Ensure we have selectedTimeframes before starting
|
||
if (config.selectedTimeframes.length === 0) {
|
||
alert('Please select at least one timeframe for analysis')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
console.log('🔥 Starting OPTIMIZED automation with config:', {
|
||
...config,
|
||
selectedTimeframes: config.selectedTimeframes
|
||
})
|
||
|
||
// 🔥 USE THE NEW FANCY OPTIMIZED ENDPOINT! 🔥
|
||
const optimizedConfig = {
|
||
symbol: config.symbol, // FIX: Use config.symbol not config.asset
|
||
timeframes: config.selectedTimeframes,
|
||
layouts: ['ai', 'diy'],
|
||
analyze: true,
|
||
automationMode: true, // Flag to indicate this is automation, not just testing
|
||
mode: config.mode, // Pass the user's trading mode choice
|
||
tradingAmount: config.tradingAmount,
|
||
balancePercentage: config.balancePercentage,
|
||
dexProvider: config.dexProvider
|
||
}
|
||
|
||
const startTime = Date.now()
|
||
const response = await fetch('/api/analysis-optimized', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(optimizedConfig)
|
||
})
|
||
|
||
const duration = ((Date.now() - startTime) / 1000).toFixed(1)
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
console.log(`🚀 OPTIMIZED automation completed in ${duration}s!`)
|
||
console.log(`📸 Screenshots: ${data.screenshots?.length || 0}`)
|
||
console.log(`🤖 Analysis: ${data.analysis ? 'Yes' : 'No'}`)
|
||
|
||
// Show clean success message without performance spam
|
||
const message = data.mode === 'automation'
|
||
? `🚀 Optimized Automation Started!\n\n⏱️ Duration: ${duration}s\n<EFBFBD> Analysis: ${data.analysis ? `${data.analysis.overallRecommendation} (${data.analysis.confidence}% confidence)` : 'Completed'}\n💰 Trade: ${data.trade?.executed ? `${data.trade.direction} executed` : 'No trade executed'}`
|
||
: `✅ Analysis Complete!\n\n⏱️ Duration: ${duration}s\n📊 Recommendation: ${data.analysis ? `${data.analysis.overallRecommendation} (${data.analysis.confidence}% confidence)` : 'No analysis'}`
|
||
|
||
alert(message)
|
||
|
||
fetchStatus() // Refresh to show automation status
|
||
} else {
|
||
alert('Failed to start optimized automation: ' + data.error)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to start automation:', error)
|
||
alert('Failed to start automation')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleStop = async () => {
|
||
console.log('Stop button clicked') // Debug log
|
||
setLoading(true)
|
||
try {
|
||
const response = await fetch('/api/automation/stop', {
|
||
method: 'POST'
|
||
})
|
||
const data = await response.json()
|
||
console.log('Stop response:', data) // Debug log
|
||
if (data.success) {
|
||
fetchStatus()
|
||
} else {
|
||
alert('Failed to stop automation: ' + data.error)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to stop automation:', error)
|
||
alert('Failed to stop automation')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleOptimizedTest = async () => {
|
||
console.log('🚀 Testing optimized analysis...')
|
||
setLoading(true)
|
||
try {
|
||
// Ensure we have selectedTimeframes before testing
|
||
if (config.selectedTimeframes.length === 0) {
|
||
alert('Please select at least one timeframe for optimized analysis test')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
const testConfig = {
|
||
symbol: config.symbol, // FIX: Use config.symbol not config.asset
|
||
timeframes: config.selectedTimeframes,
|
||
layouts: ['ai', 'diy'],
|
||
analyze: true
|
||
}
|
||
|
||
console.log('🔬 Testing with config:', testConfig)
|
||
|
||
const startTime = Date.now()
|
||
const response = await fetch('/api/analysis-optimized', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(testConfig)
|
||
})
|
||
|
||
const duration = ((Date.now() - startTime) / 1000).toFixed(1)
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
console.log('✅ Optimized analysis completed!')
|
||
console.log(`⏱️ Duration: ${duration}s`)
|
||
console.log(`📸 Screenshots: ${data.screenshots?.length || 0}`)
|
||
console.log(`🤖 Analysis: ${data.analysis ? 'Yes' : 'No'}`)
|
||
console.log(`🚀 Efficiency: ${data.optimization?.efficiency || 'N/A'}`)
|
||
|
||
alert(`✅ Optimized Analysis Complete!\n\n⏱️ Duration: ${duration}s\n📸 Screenshots: ${data.screenshots?.length || 0}\n🚀 Efficiency: ${data.optimization?.efficiency || 'N/A'}\n\n${data.analysis ? `📊 Recommendation: ${data.analysis.overallRecommendation} (${data.analysis.confidence}% confidence)` : ''}`)
|
||
} else {
|
||
console.error('❌ Optimized analysis failed:', data.error)
|
||
alert(`❌ Optimized analysis failed: ${data.error}`)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to run optimized analysis:', error)
|
||
alert('Failed to run optimized analysis: ' + error.message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="bg-green-500 p-3 text-white text-center font-bold rounded">
|
||
🚀 NEW AUTOMATION V2 - MULTI-TIMEFRAME READY 🚀
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-r from-purple-600 to-blue-600 p-4 text-white rounded-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="font-bold text-lg">⚡ NEW: Optimized Multi-Timeframe Analysis</h3>
|
||
<p className="text-sm opacity-90">70% faster processing • Single AI call • Parallel screenshot capture</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-2xl font-bold">70%</div>
|
||
<div className="text-xs">FASTER</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-white">Automated Trading V2 ⚡ OPTIMIZED</h1>
|
||
<p className="text-gray-400 mt-1">Drift Protocol - Multi-Timeframe Batch Analysis (70% Faster)</p>
|
||
</div>
|
||
<div className="flex space-x-3">
|
||
{status?.isActive ? (
|
||
<button
|
||
onClick={handleStop}
|
||
disabled={loading}
|
||
className="px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50 font-semibold"
|
||
>
|
||
{loading ? 'Stopping...' : 'STOP'}
|
||
</button>
|
||
) : (
|
||
<button
|
||
onClick={handleStart}
|
||
disabled={loading}
|
||
className="px-6 py-3 bg-gradient-to-r from-green-600 to-cyan-600 text-white rounded-lg hover:from-green-700 hover:to-cyan-700 transition-all disabled:opacity-50 font-semibold shadow-lg"
|
||
title="Start OPTIMIZED automation with 70% faster batch processing"
|
||
>
|
||
{loading ? '⚡ Starting...' : '🚀 START OPTIMIZED'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||
{/* Configuration Panel */}
|
||
<div className="xl:col-span-2 space-y-6">
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-xl font-bold text-white mb-6">Configuration</h3>
|
||
|
||
{/* Trading Mode */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||
<div className="space-y-3">
|
||
<label className="block text-sm font-bold text-blue-400">Trading Mode</label>
|
||
<div className="space-y-2">
|
||
<label className="flex items-center space-x-3 cursor-pointer p-3 rounded-lg border border-gray-600 hover:border-blue-500 transition-colors">
|
||
<input
|
||
type="radio"
|
||
className="w-4 h-4 text-blue-600"
|
||
name="mode"
|
||
checked={config.mode === 'SIMULATION'}
|
||
onChange={() => setConfig({...config, mode: 'SIMULATION'})}
|
||
disabled={status?.isActive}
|
||
/>
|
||
<span className="text-white">Paper Trading</span>
|
||
</label>
|
||
<label className="flex items-center space-x-3 cursor-pointer p-3 rounded-lg border border-gray-600 hover:border-green-500 transition-colors">
|
||
<input
|
||
type="radio"
|
||
className="w-4 h-4 text-green-600"
|
||
name="mode"
|
||
checked={config.mode === 'LIVE'}
|
||
onChange={() => setConfig({...config, mode: 'LIVE'})}
|
||
disabled={status?.isActive}
|
||
/>
|
||
<span className="text-white font-semibold">Live Trading</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{/* Symbol and Position Size */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-300 mb-2">Symbol</label>
|
||
<select
|
||
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:border-blue-500"
|
||
value={config.symbol}
|
||
onChange={(e) => setConfig({...config, symbol: e.target.value})}
|
||
disabled={status?.isActive}
|
||
>
|
||
<option value="SOLUSD">SOL/USD</option>
|
||
<option value="BTCUSD">BTC/USD</option>
|
||
<option value="ETHUSD">ETH/USD</option>
|
||
<option value="APTUSD">APT/USD</option>
|
||
<option value="AVAXUSD">AVAX/USD</option>
|
||
<option value="DOGEUSD">DOGE/USD</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||
Balance to Use: {config.balancePercentage}%
|
||
{balance && ` ($${(parseFloat(balance.availableBalance) * config.balancePercentage / 100).toFixed(2)})`}
|
||
</label>
|
||
<input
|
||
type="range"
|
||
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||
style={{
|
||
background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${config.balancePercentage}%, #374151 ${config.balancePercentage}%, #374151 100%)`
|
||
}}
|
||
min="10"
|
||
max="100"
|
||
step="5"
|
||
value={config.balancePercentage}
|
||
onChange={(e) => {
|
||
const percentage = parseFloat(e.target.value);
|
||
const newAmount = balance ? (parseFloat(balance.availableBalance) * percentage / 100) : 100;
|
||
setConfig({
|
||
...config,
|
||
balancePercentage: percentage,
|
||
tradingAmount: Math.round(newAmount)
|
||
});
|
||
}}
|
||
disabled={status?.isActive}
|
||
/>
|
||
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
||
<span>10%</span>
|
||
<span>50%</span>
|
||
<span>100%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* MULTI-TIMEFRAME SELECTION */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||
Analysis Timeframes
|
||
<span className="text-xs text-cyan-400 ml-2">({config.selectedTimeframes.length} selected)</span>
|
||
{config.selectedTimeframes.length === 0 && (
|
||
<span className="text-xs text-red-400 ml-2">⚠️ At least one timeframe required</span>
|
||
)}
|
||
</label>
|
||
|
||
{/* Timeframe Checkboxes */}
|
||
<div className="grid grid-cols-4 gap-2 mb-3">
|
||
{timeframes.map(tf => (
|
||
<label key={tf.value} className="group relative cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={config.selectedTimeframes.includes(tf.value)}
|
||
onChange={() => toggleTimeframe(tf.value)}
|
||
disabled={status?.isActive}
|
||
className="sr-only"
|
||
/>
|
||
<div className={`flex items-center justify-center p-2 rounded-lg border transition-all text-xs font-medium ${
|
||
config.selectedTimeframes.includes(tf.value)
|
||
? 'border-cyan-500 bg-cyan-500/10 text-cyan-300 shadow-lg shadow-cyan-500/20'
|
||
: status?.isActive
|
||
? 'border-gray-700 bg-gray-800/30 text-gray-500 cursor-not-allowed'
|
||
: 'border-gray-700 bg-gray-800/30 text-gray-400 hover:border-gray-600 hover:bg-gray-800/50 hover:text-gray-300'
|
||
}`}>
|
||
{tf.label}
|
||
{config.selectedTimeframes.includes(tf.value) && (
|
||
<div className="absolute top-0.5 right-0.5 w-1.5 h-1.5 bg-cyan-400 rounded-full"></div>
|
||
)}
|
||
</div>
|
||
</label>
|
||
))}
|
||
</div>
|
||
|
||
{/* Selected Timeframes Display */}
|
||
{config.selectedTimeframes.length > 0 && (
|
||
<div className="p-2 bg-gray-800/30 rounded-lg mb-3">
|
||
<div className="text-xs text-gray-400">
|
||
Selected: <span className="text-cyan-400">
|
||
{config.selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label || tf).filter(Boolean).join(', ')}
|
||
</span>
|
||
</div>
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
💡 Multiple timeframes provide more robust analysis
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Quick Selection Buttons */}
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['5', '15', '30']})}
|
||
disabled={status?.isActive}
|
||
className="py-1 px-2 rounded text-xs font-medium bg-green-600/20 text-green-300 hover:bg-green-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
📈 Scalping
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['60', '120']})}
|
||
disabled={status?.isActive}
|
||
className="py-1 px-2 rounded text-xs font-medium bg-blue-600/20 text-blue-300 hover:bg-blue-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
⚡ Day Trading
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['240', 'D']})}
|
||
disabled={status?.isActive}
|
||
className="py-1 px-2 rounded text-xs font-medium bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
🎯 Swing Trading
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* AI Risk Management Notice */}
|
||
<div className="bg-blue-600/10 border border-blue-600/30 rounded-lg p-4">
|
||
<div className="flex items-center space-x-2 mb-2">
|
||
<span className="text-blue-400 text-lg">🧠</span>
|
||
<h4 className="text-blue-300 font-semibold">AI-Powered Risk Management</h4>
|
||
</div>
|
||
<p className="text-gray-300 text-sm">
|
||
Stop loss and take profit levels are automatically calculated by the AI based on:
|
||
</p>
|
||
<ul className="text-gray-400 text-xs mt-2 space-y-1 ml-4">
|
||
<li>• Multi-timeframe technical analysis</li>
|
||
<li>• Market volatility and support/resistance levels</li>
|
||
<li>• Real-time risk assessment and position sizing</li>
|
||
<li>• Learning from previous trade outcomes</li>
|
||
</ul>
|
||
<div className="mt-3 p-2 bg-green-600/10 border border-green-600/30 rounded">
|
||
<p className="text-green-300 text-xs">
|
||
✅ Ultra-tight scalping enabled (0.5%+ stop losses proven effective)
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Status Panels */}
|
||
<div className="space-y-6">
|
||
{/* Account Status */}
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-xl font-bold text-white">Account Status</h3>
|
||
<button
|
||
onClick={fetchBalance}
|
||
className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:opacity-50 text-sm"
|
||
>
|
||
Sync
|
||
</button>
|
||
</div>
|
||
{balance ? (
|
||
<div className="space-y-3">
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Available Balance:</span>
|
||
<span className="text-green-400 font-semibold">${parseFloat(balance.availableBalance).toFixed(2)}</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Account Value:</span>
|
||
<span className="text-green-400 font-semibold">${parseFloat(balance.accountValue || balance.availableBalance).toFixed(2)}</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Unrealized P&L:</span>
|
||
<span className={`font-semibold ${balance.unrealizedPnl > 0 ? 'text-green-400' : balance.unrealizedPnl < 0 ? 'text-red-400' : 'text-gray-400'}`}>
|
||
${parseFloat(balance.unrealizedPnl || 0).toFixed(2)}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Open Positions:</span>
|
||
<span className="text-yellow-400 font-semibold">{positions.length}</span>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="text-center py-4">
|
||
<div className="text-gray-400">Loading account data...</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Bot Status */}
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-xl font-bold text-white mb-4">Bot Status</h3>
|
||
{status ? (
|
||
<div className="space-y-3">
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Status:</span>
|
||
<span className={`font-semibold px-2 py-1 rounded ${
|
||
status.isActive ? 'bg-green-600 text-white' : 'bg-red-600 text-white'
|
||
}`}>
|
||
{status.isActive ? 'ACTIVE' : 'STOPPED'}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Mode:</span>
|
||
<span className={`font-semibold ${
|
||
status.mode === 'LIVE' ? 'text-red-400' : 'text-blue-400'
|
||
}`}>
|
||
{status.mode}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Protocol:</span>
|
||
<span className="text-green-400 font-semibold">DRIFT</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Symbol:</span>
|
||
<span className="text-white font-semibold">{status.symbol}</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Timeframes:</span>
|
||
<span className="text-cyan-400 font-semibold text-xs">
|
||
{status && status.selectedTimeframes ?
|
||
status.selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label || tf).filter(Boolean).join(', ') :
|
||
status && status.timeframe ?
|
||
(timeframes.find(t => t.value === status.timeframe)?.label || status.timeframe) :
|
||
'N/A'
|
||
}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<p className="text-gray-400">Loading bot status...</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Analysis Progress */}
|
||
{status?.analysisProgress && (
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-xl font-bold text-white">Analysis Progress</h3>
|
||
<div className="text-xs text-blue-400">
|
||
Session: {status.analysisProgress.sessionId.split('-').pop()}
|
||
</div>
|
||
</div>
|
||
<div className="space-y-4">
|
||
{/* Overall Progress */}
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-gray-300">Step {status.analysisProgress.currentStep} of {status.analysisProgress.totalSteps}</span>
|
||
<span className="text-blue-400 font-semibold">
|
||
{Math.round((status.analysisProgress.currentStep / status.analysisProgress.totalSteps) * 100)}%
|
||
</span>
|
||
</div>
|
||
<div className="bg-gray-700 rounded-full h-2">
|
||
<div
|
||
className="bg-blue-500 h-2 rounded-full transition-all duration-500"
|
||
style={{
|
||
width: `${(status.analysisProgress.currentStep / status.analysisProgress.totalSteps) * 100}%`
|
||
}}
|
||
></div>
|
||
</div>
|
||
|
||
{/* Timeframe Progress */}
|
||
{status.analysisProgress.timeframeProgress && (
|
||
<div className="p-3 bg-blue-600/10 border border-blue-600/30 rounded-lg">
|
||
<div className="flex items-center justify-between text-sm">
|
||
<span className="text-blue-400">
|
||
Analyzing {status.analysisProgress.timeframeProgress.currentTimeframe || 'timeframes'}
|
||
</span>
|
||
<span className="text-blue-300">
|
||
{status.analysisProgress.timeframeProgress.current}/{status.analysisProgress.timeframeProgress.total}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Detailed Steps */}
|
||
<div className="space-y-2">
|
||
{status.analysisProgress.steps.map((step, index) => (
|
||
<div key={step.id} className={`flex items-center space-x-3 p-2 rounded-lg ${
|
||
step.status === 'active' ? 'bg-blue-600/20 border border-blue-600/30' :
|
||
step.status === 'completed' ? 'bg-green-600/20 border border-green-600/30' :
|
||
step.status === 'error' ? 'bg-red-600/20 border border-red-600/30' :
|
||
'bg-gray-700/30'
|
||
}`}>
|
||
{/* Status Icon */}
|
||
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
|
||
step.status === 'active' ? 'bg-blue-500 text-white animate-pulse' :
|
||
step.status === 'completed' ? 'bg-green-500 text-white' :
|
||
step.status === 'error' ? 'bg-red-500 text-white' :
|
||
'bg-gray-600 text-gray-300'
|
||
}`}>
|
||
{step.status === 'active' ? '⏳' :
|
||
step.status === 'completed' ? '✓' :
|
||
step.status === 'error' ? '✗' :
|
||
index + 1}
|
||
</div>
|
||
|
||
{/* Step Info */}
|
||
<div className="flex-1">
|
||
<div className={`font-semibold text-sm ${
|
||
step.status === 'active' ? 'text-blue-300' :
|
||
step.status === 'completed' ? 'text-green-300' :
|
||
step.status === 'error' ? 'text-red-300' :
|
||
'text-gray-400'
|
||
}`}>
|
||
{step.title}
|
||
</div>
|
||
<div className="text-xs text-gray-400">
|
||
{step.details || step.description}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Duration */}
|
||
{step.duration && (
|
||
<div className="text-xs text-gray-500">
|
||
{(step.duration / 1000).toFixed(1)}s
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Analysis Timer */}
|
||
{status?.isActive && !status?.analysisProgress && (
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-xl font-bold text-white">Analysis Timer</h3>
|
||
<div className="text-xs text-gray-400">
|
||
Cycle #{status.currentCycle || 0}
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="text-center">
|
||
<div className="text-3xl font-bold text-blue-400 mb-2">
|
||
{formatCountdown(nextAnalysisCountdown)}
|
||
</div>
|
||
<div className="text-sm text-gray-400">
|
||
{nextAnalysisCountdown > 0 ? 'Next Analysis In' : 'Analysis Starting Soon'}
|
||
</div>
|
||
</div>
|
||
<div className="bg-gray-700 rounded-full h-2">
|
||
<div
|
||
className="bg-blue-500 h-2 rounded-full transition-all duration-1000"
|
||
style={{
|
||
width: status?.analysisInterval > 0 ?
|
||
`${Math.max(0, 100 - (nextAnalysisCountdown / status.analysisInterval) * 100)}%` :
|
||
'0%'
|
||
}}
|
||
></div>
|
||
</div>
|
||
<div className="text-xs text-gray-400 text-center">
|
||
Analysis Interval: {(() => {
|
||
const intervalSec = status?.analysisInterval || 0
|
||
const intervalMin = Math.floor(intervalSec / 60)
|
||
|
||
// Determine strategy type for display
|
||
if (status?.selectedTimeframes) {
|
||
const timeframes = status.selectedTimeframes
|
||
const isScalping = timeframes.includes('5') || timeframes.includes('3') ||
|
||
(timeframes.length > 1 && timeframes.every(tf => ['1', '3', '5', '15', '30'].includes(tf)))
|
||
|
||
if (isScalping) {
|
||
return '2m (Scalping Mode)'
|
||
}
|
||
|
||
const isDayTrading = timeframes.includes('60') || timeframes.includes('120')
|
||
if (isDayTrading) {
|
||
return '5m (Day Trading Mode)'
|
||
}
|
||
|
||
const isSwingTrading = timeframes.includes('240') || timeframes.includes('D')
|
||
if (isSwingTrading) {
|
||
return '15m (Swing Trading Mode)'
|
||
}
|
||
}
|
||
|
||
return `${intervalMin}m`
|
||
})()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Individual Timeframe Results */}
|
||
{status?.individualTimeframeResults && status.individualTimeframeResults.length > 0 && (
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-xl font-bold text-white mb-4">Timeframe Analysis</h3>
|
||
<div className="space-y-2">
|
||
{status.individualTimeframeResults.map((result, index) => (
|
||
<div key={index} className="flex items-center justify-between p-3 bg-gray-700/50 rounded-lg">
|
||
<div className="flex items-center space-x-3">
|
||
<span className="text-cyan-400 font-bold text-sm w-8">
|
||
{timeframes.find(tf => tf.value === result.timeframe)?.label || result.timeframe}
|
||
</span>
|
||
<span className={`font-semibold text-sm px-2 py-1 rounded ${
|
||
result.recommendation === 'BUY' ? 'bg-green-600/20 text-green-400' :
|
||
result.recommendation === 'SELL' ? 'bg-red-600/20 text-red-400' :
|
||
'bg-gray-600/20 text-gray-400'
|
||
}`}>
|
||
{result.recommendation}
|
||
</span>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-white font-semibold text-sm">
|
||
{result.confidence}%
|
||
</div>
|
||
<div className="text-xs text-gray-400">
|
||
confidence
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="mt-4 p-3 bg-blue-600/10 border border-blue-600/30 rounded-lg">
|
||
<div className="text-xs text-blue-400">
|
||
✅ Last Updated: {status.individualTimeframeResults[0]?.timestamp ?
|
||
new Date(status.individualTimeframeResults[0].timestamp).toLocaleTimeString() :
|
||
'N/A'
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Trading Metrics */}
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-xl font-bold text-white mb-4">Trading Metrics</h3>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="text-center">
|
||
<div className="text-2xl font-bold text-green-400">
|
||
${balance ? parseFloat(balance.accountValue || balance.availableBalance).toFixed(2) : '0.00'}
|
||
</div>
|
||
<div className="text-xs text-gray-400">Portfolio</div>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="text-2xl font-bold text-blue-400">
|
||
{balance ? parseFloat(balance.leverage || 0).toFixed(1) : '0.0'}%
|
||
</div>
|
||
<div className="text-xs text-gray-400">Leverage Used</div>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="text-2xl font-bold text-red-400">
|
||
${balance ? parseFloat(balance.unrealizedPnl || 0).toFixed(2) : '0.00'}
|
||
</div>
|
||
<div className="text-xs text-gray-400">Unrealized P&L</div>
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="text-2xl font-bold text-yellow-400">
|
||
{positions.length}
|
||
</div>
|
||
<div className="text-xs text-gray-400">Open Positions</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|