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.
445 lines
18 KiB
JavaScript
445 lines
18 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()
|
||
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 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">
|
||
{/* Header with Start/Stop */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-white">Automated Trading</h1>
|
||
<p className="text-gray-400 mt-1">Multi-Timeframe Analysis</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-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>
|
||
|
||
{/* 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>
|
||
</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>
|
||
</>
|
||
)}
|
||
</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>
|
||
)
|
||
}
|