- Removed stop loss and take profit input fields from automation-v2 page - Updated AutomationConfig interfaces to remove manual TP/SL parameters - Implemented dynamic AI risk calculation methods: * calculateAIStopLoss() - Volatility and confidence-based SL calculation * calculateAITakeProfit() - Risk/reward optimized TP calculation - Added AI Risk Management information panel explaining automated calculation - Enhanced risk management logic to use AI-generated values first, then fallback to dynamic calculation - Supports ultra-tight scalping percentages (0.3% to 2% SL range) - AI adapts risk based on market volatility, confidence levels, and learned patterns - Proven effective with real trades: 0.8% SL / 1.5% TP achieving 1.50% profit This enables fully autonomous AI risk management without manual user intervention, allowing the AI to optimize stop loss and take profit levels based on technical analysis, market conditions, and continuous learning from real trade outcomes.
738 lines
32 KiB
JavaScript
738 lines
32 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
|
||
maxLeverage: 5
|
||
// 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('Start button clicked') // Debug log
|
||
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 automation with config:', {
|
||
...config,
|
||
selectedTimeframes: config.selectedTimeframes
|
||
})
|
||
|
||
const response = await fetch('/api/automation/start', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(config)
|
||
})
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
fetchStatus()
|
||
} else {
|
||
alert('Failed to start 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)
|
||
}
|
||
}
|
||
|
||
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="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-white">Automated Trading V2</h1>
|
||
<p className="text-gray-400 mt-1">Drift Protocol - Multi-Timeframe Analysis</p>
|
||
</div>
|
||
<div className="flex space-x-4">
|
||
<button
|
||
onClick={() => console.log('TEST BUTTON CLICKED')}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||
>
|
||
Test Click
|
||
</button>
|
||
{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-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 font-semibold"
|
||
>
|
||
{loading ? 'Starting...' : 'START'}
|
||
</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 className="space-y-3">
|
||
<label className="block text-sm font-bold text-purple-400">Leverage</label>
|
||
<select
|
||
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:border-purple-400"
|
||
value={config.maxLeverage}
|
||
onChange={(e) => setConfig({...config, maxLeverage: parseInt(e.target.value)})}
|
||
disabled={status?.isActive}
|
||
>
|
||
<option value="1">1x - Spot</option>
|
||
<option value="2">2x</option>
|
||
<option value="3">3x</option>
|
||
<option value="5">5x</option>
|
||
<option value="10">10x</option>
|
||
<option value="20">20x</option>
|
||
</select>
|
||
</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>
|
||
{balance && config.maxLeverage > 1 && (
|
||
<p className="text-xs text-green-400 mt-1">
|
||
With {config.maxLeverage}x leverage: ${(config.tradingAmount * config.maxLeverage).toFixed(2)} position exposure
|
||
</p>
|
||
)}
|
||
</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).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">
|
||
{config.selectedTimeframes.map(tf => timeframes.find(t => t.value === tf)?.label).filter(Boolean).join(', ')}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-300">Leverage:</span>
|
||
<span className="text-yellow-400 font-semibold">{config.maxLeverage}x</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: {Math.floor((status.analysisInterval || 0) / 60)}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>
|
||
)
|
||
}
|