ADVANCED SYSTEM KNOWLEDGE: - Superior parallel screenshot system (60% performance gain) - AI learning system architecture and decision flow - Orphaned order cleanup integration patterns - Critical technical fixes and troubleshooting guide - Database schema best practices - Memory leak prevention strategies - AI learning system patterns and functions - Error handling best practices for trading systems - Integration patterns for position monitoring - Performance optimization rules - UI/UX consistency requirements - Critical anti-patterns to avoid - Added links to new knowledge base documents - Comprehensive documentation structure - Development guides and best practices - Performance optimizations summary - 60% screenshot performance improvement techniques - AI learning system that adapts trading decisions - Container stability and crash prevention - Frontend-backend consistency requirements - Integration strategies for existing infrastructure This documentation preserves critical insights from complex debugging sessions and provides patterns for future development.
469 lines
19 KiB
JavaScript
469 lines
19 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',
|
||
selectedTimeframes: ['60'], // Multi-timeframe support
|
||
tradingAmount: 100,
|
||
balancePercentage: 50, // Default to 50% of available balance
|
||
})
|
||
|
||
const [status, setStatus] = useState(null)
|
||
const [balance, setBalance] = useState(null)
|
||
const [positions, setPositions] = useState([])
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
useEffect(() => {
|
||
fetchStatus()
|
||
fetchBalance()
|
||
fetchPositions()
|
||
|
||
const interval = setInterval(() => {
|
||
fetchStatus()
|
||
fetchBalance()
|
||
fetchPositions()
|
||
}, 30000)
|
||
return () => clearInterval(interval)
|
||
}, [])
|
||
|
||
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 response:', data) // Debug log
|
||
|
||
if (response.ok && !data.error) {
|
||
setStatus(data) // Status data is returned directly, not wrapped in 'success'
|
||
} else {
|
||
console.error('Status API error:', data.error || 'Unknown error')
|
||
}
|
||
} 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 automation...')
|
||
setLoading(true)
|
||
try {
|
||
if (config.selectedTimeframes.length === 0) {
|
||
console.error('No timeframes selected')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
const automationConfig = {
|
||
symbol: config.symbol,
|
||
selectedTimeframes: config.selectedTimeframes,
|
||
mode: config.mode,
|
||
tradingAmount: config.tradingAmount,
|
||
leverage: config.leverage,
|
||
stopLoss: config.stopLoss,
|
||
takeProfit: config.takeProfit
|
||
}
|
||
|
||
const response = await fetch('/api/automation/start', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(automationConfig)
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
console.log('✅ Automation started successfully')
|
||
fetchStatus()
|
||
} else {
|
||
console.error('Failed to start automation:', data.error)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to start automation:', error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleStop = async () => {
|
||
console.log('🛑 Stopping automation...')
|
||
setLoading(true)
|
||
try {
|
||
const response = await fetch('/api/automation/stop', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' }
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
console.log('✅ Automation stopped successfully')
|
||
fetchStatus()
|
||
} else {
|
||
console.error('Failed to stop automation:', data.error)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to stop automation:', error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<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">
|
||
{/* Header with Start/Stop Button */}
|
||
<div className="flex items-center justify-between mb-6">
|
||
<h3 className="text-xl font-bold text-white">Configuration</h3>
|
||
<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-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 font-semibold"
|
||
>
|
||
{loading ? 'Starting...' : status?.rateLimitHit ? 'RESTART' : 'START'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Trading Mode - Side by Side Radio Buttons with Logos */}
|
||
<div className="mb-6">
|
||
<label className="block text-sm font-bold text-blue-400 mb-3">Trading Mode</label>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<label className="flex items-center space-x-3 cursor-pointer p-4 rounded-lg border border-gray-600 hover:border-blue-500 transition-colors">
|
||
<input
|
||
type="radio"
|
||
className="w-5 h-5 text-blue-600"
|
||
name="mode"
|
||
checked={config.mode === 'SIMULATION'}
|
||
onChange={() => setConfig({...config, mode: 'SIMULATION'})}
|
||
disabled={status?.isActive}
|
||
/>
|
||
<div className="flex items-center space-x-2">
|
||
<span className="text-2xl">📊</span>
|
||
<span className="text-white font-medium">Paper Trading</span>
|
||
</div>
|
||
</label>
|
||
<label className="flex items-center space-x-3 cursor-pointer p-4 rounded-lg border border-gray-600 hover:border-green-500 transition-colors">
|
||
<input
|
||
type="radio"
|
||
className="w-5 h-5 text-green-600"
|
||
name="mode"
|
||
checked={config.mode === 'LIVE'}
|
||
onChange={() => setConfig({...config, mode: 'LIVE'})}
|
||
disabled={status?.isActive}
|
||
/>
|
||
<div className="flex items-center space-x-2">
|
||
<span className="text-2xl">💰</span>
|
||
<span className="text-white font-semibold">Live Trading</span>
|
||
</div>
|
||
</label>
|
||
</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 - Made Bigger */}
|
||
<div className="grid grid-cols-3 gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['5', '15', '30']})}
|
||
disabled={status?.isActive}
|
||
className="py-3 px-4 rounded-lg text-sm font-medium bg-green-600/20 text-green-300 hover:bg-green-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-green-600/30 hover:border-green-600/50"
|
||
>
|
||
<div className="text-lg mb-1">📈</div>
|
||
<div>Scalping</div>
|
||
<div className="text-xs opacity-75">5m, 15m, 30m</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['60', '120']})}
|
||
disabled={status?.isActive}
|
||
className="py-3 px-4 rounded-lg text-sm font-medium bg-blue-600/20 text-blue-300 hover:bg-blue-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-blue-600/30 hover:border-blue-600/50"
|
||
>
|
||
<div className="text-lg mb-1">⚡</div>
|
||
<div>Day Trading</div>
|
||
<div className="text-xs opacity-75">1h, 2h</div>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setConfig({...config, selectedTimeframes: ['240', 'D']})}
|
||
disabled={status?.isActive}
|
||
className="py-3 px-4 rounded-lg text-sm font-medium bg-purple-600/20 text-purple-300 hover:bg-purple-600/30 transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-purple-600/30 hover:border-purple-600/50"
|
||
>
|
||
<div className="text-lg mb-1">🎯</div>
|
||
<div>Swing Trading</div>
|
||
<div className="text-xs opacity-75">4h, 1d</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Status and Info Panel */}
|
||
<div className="space-y-6">
|
||
{/* Status */}
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-lg font-bold text-white mb-4">Bot Status</h3>
|
||
|
||
<div className="space-y-3">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Status:</span>
|
||
<span className={`px-2 py-1 rounded text-xs font-semibold ${
|
||
status?.isActive ? 'bg-green-600 text-white' : 'bg-gray-600 text-gray-300'
|
||
}`}>
|
||
{status?.isActive ? 'RUNNING' : 'STOPPED'}
|
||
</span>
|
||
</div>
|
||
|
||
{status?.isActive && (
|
||
<>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Symbol:</span>
|
||
<span className="text-white font-medium">{status.symbol}</span>
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Mode:</span>
|
||
<span className={`px-2 py-1 rounded text-xs font-semibold ${
|
||
status.mode === 'LIVE' ? 'bg-red-600 text-white' : 'bg-blue-600 text-white'
|
||
}`}>
|
||
{status.mode}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Timeframes:</span>
|
||
<span className="text-cyan-400 text-xs">
|
||
{status.timeframes?.map(tf => timeframes.find(t => t.value === tf)?.label || tf).join(', ')}
|
||
</span>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Rate Limit Notification */}
|
||
{status?.rateLimitHit && (
|
||
<div className="mt-4 p-3 bg-red-900 border border-red-600 rounded-lg">
|
||
<div className="flex items-center space-x-2">
|
||
<span className="text-red-400 font-semibold">⚠️ Rate Limit Reached</span>
|
||
</div>
|
||
{status.rateLimitMessage && (
|
||
<p className="text-red-300 text-sm mt-1">{status.rateLimitMessage}</p>
|
||
)}
|
||
<p className="text-red-200 text-xs mt-2">
|
||
Automation stopped automatically. Please recharge your OpenAI account to continue.
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Balance */}
|
||
{balance && (
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-lg font-bold text-white mb-4">Account Balance</h3>
|
||
|
||
<div className="space-y-3">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Available:</span>
|
||
<span className="text-green-400 font-semibold">${balance.availableBalance}</span>
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Total:</span>
|
||
<span className="text-white font-medium">${balance.totalCollateral}</span>
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-400">Positions:</span>
|
||
<span className="text-yellow-400">{balance.positions || 0}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Positions */}
|
||
{positions.length > 0 && (
|
||
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
|
||
<h3 className="text-lg font-bold text-white mb-4">Open Positions</h3>
|
||
|
||
<div className="space-y-2">
|
||
{positions.map((position, index) => (
|
||
<div key={index} className="flex justify-between items-center p-2 bg-gray-700 rounded">
|
||
<span className="text-white">{position.symbol}</span>
|
||
<span className={`font-semibold ${
|
||
position.side === 'LONG' ? 'text-green-400' : 'text-red-400'
|
||
}`}>
|
||
{position.side} ${position.size}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|