Files
trading_bot_v3/app/automation-v2/page.js
mindesbunister e0d1344421 fix: reduce aggressive orphaned position monitoring frequency
- Changed position monitor interval from 30 seconds to 5 minutes (300s)
- Reduced verbose logging in position monitor API
- Only log orphaned order checks when orders are actually found
- Prevents log flooding while maintaining functionality

- 90% reduction in monitoring frequency (30s → 300s)
- Cleaner logs with less noise
- Still catches orphaned orders effectively
- Better system performance with reduced API calls

 Verified:
- Automation page now runs monitor every 5 minutes
- Position monitor API only logs when orphaned orders exist
- System still functions correctly for cleanup
2025-07-26 20:26:45 +02:00

603 lines
25 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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)
const [monitorData, setMonitorData] = useState(null)
useEffect(() => {
fetchStatus()
fetchBalance()
fetchPositions()
fetchMonitorData()
fetchMonitorData()
const interval = setInterval(() => {
fetchStatus()
fetchBalance()
fetchPositions()
fetchMonitorData()
}, 300000) // 5 minutes instead of 30 seconds
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 fetchMonitorData = async () => {
try {
const response = await fetch('/api/automation/position-monitor')
const data = await response.json()
if (data.success) {
setMonitorData(data.monitor)
}
} catch (error) {
console.error('Failed to fetch monitor data:', 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)
}
}
const handleEmergencyStop = async () => {
console.log('🚨 Emergency stop triggered!')
setLoading(true)
try {
const response = await fetch('/api/automation/emergency-stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
const data = await response.json()
if (data.success) {
console.log('✅ Emergency stop completed successfully')
fetchStatus()
fetchPositions()
fetchMonitorData()
fetchMonitorData()
} else {
console.error('Emergency stop failed:', data.error)
}
} catch (error) {
console.error('Emergency stop error:', error)
} finally {
setLoading(false)
}
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
{/* 🤖 Automation Control 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 */}
{/* Header with Start/Stop Button */}
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white">🤖 Automation Control</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={handleEmergencyStop}
disabled={loading}
className="px-4 py-3 bg-red-800 text-white rounded-lg hover:bg-red-900 transition-colors disabled:opacity-50 font-semibold border-2 border-red-600"
title="Emergency Stop - Closes all positions immediately"
>
🚨 EMERGENCY
</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>
{/* Position Monitor */}
{monitorData && (
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
<h3 className="text-lg font-bold text-white mb-4">📊 Position Monitor</h3>
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-gray-400">Has Position:</span>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
monitorData.hasPosition ? 'bg-green-600 text-white' : 'bg-gray-600 text-gray-300'
}`}>
{monitorData.hasPosition ? '✅ YES' : '❌ NO'}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">Risk Level:</span>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
monitorData.riskLevel === 'HIGH' ? 'bg-red-600 text-white' :
monitorData.riskLevel === 'MEDIUM' ? 'bg-yellow-600 text-white' :
'bg-green-600 text-white'
}`}>
{monitorData.riskLevel}
</span>
</div>
<div className="text-xs text-gray-400">
<strong>Next Action:</strong> {monitorData.nextAction}
</div>
{monitorData.orphanedOrderCleanup && (
<div className={`mt-3 p-2 rounded-lg ${
monitorData.orphanedOrderCleanup.success ? 'bg-green-900 border border-green-600' : 'bg-red-900 border border-red-600'
}`}>
<div className="text-xs font-semibold">
{monitorData.orphanedOrderCleanup.success ? '✅ Cleanup Success' : '❌ Cleanup Failed'}
</div>
<div className="text-xs mt-1">
{monitorData.orphanedOrderCleanup.message}
</div>
</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"><EFBFBD> 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">${parseFloat(balance.availableBalance).toFixed(2)}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">Total:</span>
<span className="text-white font-medium">${parseFloat(balance.totalCollateral).toFixed(2)}</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-3">
{positions.map((position, index) => (
<div key={index} className="p-4 bg-gray-700 rounded-lg border border-gray-600">
<div className="flex justify-between items-center mb-2">
<span className="text-white font-semibold">{position.symbol}</span>
<span className={`px-2 py-1 rounded text-xs font-semibold ${
position.side === 'LONG' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'
}`}>
{position.side}
</span>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Size:</span>
<span className="text-white">${position.size}</span>
</div>
{position.entryPrice && (
<div className="flex justify-between">
<span className="text-gray-400">Entry:</span>
<span className="text-white">${position.entryPrice}</span>
</div>
)}
{position.markPrice && (
<div className="flex justify-between">
<span className="text-gray-400">Mark:</span>
<span className="text-white">${position.markPrice}</span>
</div>
)}
{position.pnl !== undefined && (
<div className="flex justify-between">
<span className="text-gray-400">PnL:</span>
<span className={`font-semibold ${
position.pnl >= 0 ? 'text-green-400' : 'text-red-400'
}`}>
${position.pnl >= 0 ? '+' : ''}${position.pnl}
</span>
</div>
)}
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
)
}