Files
trading_bot_v3/app/automation-v2/page.js
mindesbunister abc94c06e2 Fix position sizing and improve automation UI
Major fixes:
- Fixed position size calculation: converts USD amount to SOL tokens properly
- Fixed insufficient collateral error by using correct position sizing
- Added proper TP/SL parameter passing through automation chain
- Enhanced position sizing UI with balance percentage slider

Position Sizing Fixes:
- Convert 2 USD to SOL tokens using current price (2 ÷ 97.87 = ~0.162 SOL)
- Remove incorrect 32 SOL token calculation (was 32,000,000,000 base units)
- Use USD position value for perpetual futures trading correctly

Take Profit & Stop Loss Improvements:
- Pass TP/SL percentages from config through automation → trade → drift chain
- Use actual config percentages instead of hardcoded 2:1 ratio
- Enable proper risk management with user-defined TP/SL levels

UI/UX Enhancements:
- Remove redundant 'Risk Per Trade (%)' field that caused confusion
- Remove conflicting 'Auto-Size (%)' dropdown
- Keep clean balance percentage slider (10% - 100% of available balance)
- Simplify position sizing to: Balance % → Position Size → Leverage → TP/SL

Technical Changes:
- Update Drift API position calculation from SOL tokens to USD conversion
- Fix automation trade route parameter passing
- Clean up AutomationConfig interface
- Improve position size validation and safety margins

These changes enable proper leveraged perpetual futures trading with correct
position sizing, collateral usage, and automated TP/SL order placement.
2025-07-23 14:16:55 +02:00

536 lines
22 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',
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: 2,
takeProfitPercent: 6
})
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 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>
{/* Risk Management */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Stop Loss (%)</label>
<input
type="number"
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:border-blue-500"
min="0.5"
max="20"
step="0.5"
value={config.stopLossPercent}
onChange={(e) => setConfig({...config, stopLossPercent: parseFloat(e.target.value)})}
disabled={status?.isActive}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Take Profit (%)</label>
<input
type="number"
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:border-blue-500"
min="1"
max="50"
step="1"
value={config.takeProfitPercent}
onChange={(e) => setConfig({...config, takeProfitPercent: parseFloat(e.target.value)})}
disabled={status?.isActive}
/>
</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>
{/* 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>
)
}