Files
trading_bot_v3/components/AdvancedTradingPanel.tsx
mindesbunister b91d35ad60 Fix timeframe selection bug and syntax errors
- Fixed critical timeframe mapping bug where '4h' was interpreted as '4 minutes'
- Now prioritizes minute values: '4h' -> ['240', '240m', '4h', '4H']
- Added fallback mechanism to enter exact minutes (240) in custom interval input
- Fixed multiple syntax errors in tradingview-automation.ts:
  * Missing closing parentheses in console.log statements
  * Missing parentheses in writeFile and JSON.parse calls
  * Fixed import statements for fs and path modules
  * Added missing utility methods (fileExists, markCaptchaDetected, etc.)
- Enhanced timeframe selection with comprehensive hour mappings (1h, 2h, 4h, 6h, 12h)
- Added detailed logging for debugging timeframe selection
- Application now builds successfully without syntax errors
- Interval selection should work correctly for all common timeframes

Key improvements:
 4h chart selection now works correctly (240 minutes, not 4 minutes)
 All TypeScript compilation errors resolved
 Enhanced debugging output for timeframe mapping
 Robust fallback mechanisms for interval selection
 Docker integration and manual CAPTCHA handling maintained
2025-07-13 13:57:35 +02:00

756 lines
27 KiB
TypeScript

"use client"
import React, { useState, useEffect } from 'react'
interface TradeParams {
symbol: string
side: 'LONG' | 'SHORT'
amount: number
leverage: number
orderType: 'MARKET' | 'LIMIT'
price?: number
stopLoss?: number
takeProfit?: number
stopLossType?: 'PRICE' | 'PERCENTAGE'
takeProfitType?: 'PRICE' | 'PERCENTAGE'
}
interface MarketData {
symbol: string
price: number
change24h: number
}
interface AccountData {
totalCollateral: number
freeCollateral: number
leverage: number
maintenanceMargin: number
}
interface BalanceApiResponse {
totalCollateral?: number
freeCollateral?: number
leverage?: number
marginRequirement?: number
}
interface TradingInfoApiResponse {
totalCollateral?: number
availableCollateral?: number
accountLeverage?: number
maintenanceMargin?: number
maxPositionSize?: number
requiredMargin?: number
}
export default function AdvancedTradingPanel() {
// Trading form state
const [symbol, setSymbol] = useState('SOLUSD')
const [side, setSide] = useState<'LONG' | 'SHORT'>('LONG')
const [orderType, setOrderType] = useState<'MARKET' | 'LIMIT'>('MARKET')
const [leverage, setLeverage] = useState(1)
const [positionSize, setPositionSize] = useState('')
const [limitPrice, setLimitPrice] = useState('')
const [loading, setLoading] = useState(false)
const [result, setResult] = useState<any>(null)
// Risk Management
const [enableStopLoss, setEnableStopLoss] = useState(false)
const [enableTakeProfit, setEnableTakeProfit] = useState(false)
const [stopLossType, setStopLossType] = useState<'PRICE' | 'PERCENTAGE'>('PERCENTAGE')
const [takeProfitType, setTakeProfitType] = useState<'PRICE' | 'PERCENTAGE'>('PERCENTAGE')
const [stopLossValue, setStopLossValue] = useState('')
const [takeProfitValue, setTakeProfitValue] = useState('')
// Market and account data
const [marketData, setMarketData] = useState<MarketData>({ symbol: 'SOLUSD', price: 160, change24h: -2.1 })
const [accountData, setAccountData] = useState<AccountData>({
totalCollateral: 0,
freeCollateral: 0,
leverage: 0,
maintenanceMargin: 0
})
const [positions, setPositions] = useState<any[]>([])
const [closingPosition, setClosingPosition] = useState<string | null>(null)
// Calculated values
const [liquidationPrice, setLiquidationPrice] = useState(0)
const [requiredMargin, setRequiredMargin] = useState(0)
const [maxPositionSize, setMaxPositionSize] = useState(0)
const [stopLossPrice, setStopLossPrice] = useState(0)
const [takeProfitPrice, setTakeProfitPrice] = useState(0)
const [riskRewardRatio, setRiskRewardRatio] = useState(0)
const [potentialLoss, setPotentialLoss] = useState(0)
const [potentialProfit, setPotentialProfit] = useState(0)
const availableSymbols = [
'SOLUSD', 'BTCUSD', 'ETHUSD', 'DOTUSD', 'AVAXUSD', 'ADAUSD',
'MATICUSD', 'LINKUSD', 'ATOMUSD', 'NEARUSD', 'APTUSD', 'ORBSUSD',
'RNDUSD', 'WIFUSD', 'JUPUSD', 'TNSUSD', 'DOGEUSD', 'PEPE1KUSD',
'POPCATUSD', 'BOMERUSD'
]
// Fetch account data on component mount
useEffect(() => {
fetchAccountData()
fetchPositions()
}, [])
// Recalculate when inputs change
useEffect(() => {
calculateTradingMetrics()
}, [positionSize, leverage, side, marketData.price, stopLossValue, takeProfitValue, stopLossType, takeProfitType, enableStopLoss, enableTakeProfit])
const fetchAccountData = async () => {
try {
// Fetch both balance and trading info
const [balanceResponse, tradingInfoResponse] = await Promise.all([
fetch('/api/drift/balance'),
fetch('/api/drift/trading-info')
])
let balanceData: BalanceApiResponse = {}
let tradingInfoData: TradingInfoApiResponse = {}
if (balanceResponse.ok) {
balanceData = await balanceResponse.json()
}
if (tradingInfoResponse.ok) {
tradingInfoData = await tradingInfoResponse.json()
}
// Combine data with fallbacks
setAccountData({
totalCollateral: balanceData.totalCollateral || tradingInfoData.totalCollateral || 0,
freeCollateral: balanceData.freeCollateral || tradingInfoData.availableCollateral || 0,
leverage: balanceData.leverage || tradingInfoData.accountLeverage || 0,
maintenanceMargin: balanceData.marginRequirement || tradingInfoData.maintenanceMargin || 0
})
// Update max position size from trading info if available
if (tradingInfoData.maxPositionSize) {
setMaxPositionSize(tradingInfoData.maxPositionSize)
}
} catch (error) {
console.error('Failed to fetch account data:', error)
}
}
const fetchPositions = async () => {
try {
const response = await fetch('/api/drift/positions')
if (response.ok) {
const data = await response.json()
setPositions(data.positions || [])
}
} catch (error) {
console.error('Failed to fetch positions:', error)
}
}
const calculateTradingMetrics = async () => {
if (!positionSize || !marketData.price) return
const size = parseFloat(positionSize)
const entryPrice = marketData.price
const notionalValue = size * entryPrice
// Try to get accurate calculations from the API first
try {
const response = await fetch(`/api/drift/trading-info?symbol=${symbol}&side=${side}&amount=${size}&leverage=${leverage}`)
if (response.ok) {
const apiData = await response.json()
// Use API calculations if available
if (apiData.requiredMargin !== undefined) setRequiredMargin(apiData.requiredMargin)
if (apiData.maxPositionSize !== undefined) setMaxPositionSize(apiData.maxPositionSize)
if (apiData.liquidationPrice !== undefined) setLiquidationPrice(apiData.liquidationPrice)
}
} catch (error) {
console.error('Failed to fetch trading calculations from API:', error)
}
// Fallback to local calculations
const margin = notionalValue / leverage
if (requiredMargin === 0) setRequiredMargin(margin)
// Calculate max position size based on available collateral
if (maxPositionSize === 0) {
const maxNotional = accountData.freeCollateral * leverage
const maxSize = maxNotional / entryPrice
setMaxPositionSize(maxSize)
}
// Calculate liquidation price
if (liquidationPrice === 0) {
// Simplified liquidation calculation (actual Drift uses more complex formula)
const maintenanceMarginRate = 0.05 // 5% maintenance margin
const liquidationBuffer = notionalValue * maintenanceMarginRate
let liqPrice = 0
if (side === 'LONG') {
// For long: liquidation when position value + margin = liquidation buffer
liqPrice = entryPrice * (1 - (margin - liquidationBuffer) / notionalValue)
} else {
// For short: liquidation when position value - margin = liquidation buffer
liqPrice = entryPrice * (1 + (margin - liquidationBuffer) / notionalValue)
}
setLiquidationPrice(Math.max(0, liqPrice))
}
// Calculate Stop Loss and Take Profit prices
let slPrice = 0
let tpPrice = 0
let potLoss = 0
let potProfit = 0
if (enableStopLoss && stopLossValue) {
if (stopLossType === 'PERCENTAGE') {
const slPercentage = parseFloat(stopLossValue) / 100
if (side === 'LONG') {
slPrice = entryPrice * (1 - slPercentage)
} else {
slPrice = entryPrice * (1 + slPercentage)
}
} else {
slPrice = parseFloat(stopLossValue)
}
// Calculate potential loss
if (slPrice > 0) {
potLoss = Math.abs(entryPrice - slPrice) * size
}
}
if (enableTakeProfit && takeProfitValue) {
if (takeProfitType === 'PERCENTAGE') {
const tpPercentage = parseFloat(takeProfitValue) / 100
if (side === 'LONG') {
tpPrice = entryPrice * (1 + tpPercentage)
} else {
tpPrice = entryPrice * (1 - tpPercentage)
}
} else {
tpPrice = parseFloat(takeProfitValue)
}
// Calculate potential profit
if (tpPrice > 0) {
potProfit = Math.abs(tpPrice - entryPrice) * size
}
}
setStopLossPrice(slPrice)
setTakeProfitPrice(tpPrice)
setPotentialLoss(potLoss)
setPotentialProfit(potProfit)
// Calculate Risk/Reward Ratio
if (potLoss > 0 && potProfit > 0) {
setRiskRewardRatio(potProfit / potLoss)
} else {
setRiskRewardRatio(0)
}
}
const handleTrade = async () => {
if (!positionSize || parseFloat(positionSize) <= 0) {
setResult({ success: false, error: 'Please enter a valid position size' })
return
}
if (requiredMargin > accountData.freeCollateral) {
setResult({ success: false, error: 'Insufficient collateral for this trade' })
return
}
if (orderType === 'LIMIT' && (!limitPrice || parseFloat(limitPrice) <= 0)) {
setResult({ success: false, error: 'Please enter a valid limit price' })
return
}
setLoading(true)
setResult(null)
try {
const tradeParams: TradeParams = {
symbol,
side,
amount: parseFloat(positionSize),
leverage,
orderType,
price: orderType === 'LIMIT' ? parseFloat(limitPrice) : undefined,
stopLoss: enableStopLoss && stopLossPrice > 0 ? stopLossPrice : undefined,
takeProfit: enableTakeProfit && takeProfitPrice > 0 ? takeProfitPrice : undefined,
stopLossType: enableStopLoss ? stopLossType : undefined,
takeProfitType: enableTakeProfit ? takeProfitType : undefined
}
const response = await fetch('/api/drift/trade', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(tradeParams)
})
const data = await response.json()
setResult(data)
if (data.success) {
// Refresh account data and positions after successful trade
await fetchAccountData()
await fetchPositions()
// Reset form
setPositionSize('')
setLimitPrice('')
}
} catch (error) {
setResult({ success: false, error: 'Trade execution failed' })
}
setLoading(false)
}
const closePosition = async (symbol: string, amount?: number) => {
setClosingPosition(symbol)
try {
const response = await fetch('/api/drift/close-position', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ symbol, amount })
})
const data = await response.json()
if (data.success) {
setResult({ success: true, message: `Position in ${symbol} closed successfully`, txId: data.txId })
// Refresh positions and account data
await fetchPositions()
await fetchAccountData()
} else {
setResult({ success: false, error: data.error })
}
} catch (error) {
setResult({ success: false, error: 'Failed to close position' })
}
setClosingPosition(null)
}
const setMaxSize = () => {
setPositionSize(maxPositionSize.toFixed(4))
}
const setPercentageSize = (percentage: number) => {
const size = (maxPositionSize * percentage / 100).toFixed(4)
setPositionSize(size)
}
return (
<div className="card card-gradient">
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-bold text-white flex items-center">
<span className="w-8 h-8 bg-gradient-to-br from-blue-400 to-purple-600 rounded-lg flex items-center justify-center mr-3">
</span>
Advanced Trading
</h2>
<span className="text-xs text-gray-400">Drift Protocol</span>
</div>
{/* Market Info */}
<div className="mb-6 p-4 bg-gray-800/50 rounded-lg">
<div className="flex items-center justify-between">
<select
value={symbol}
onChange={(e) => setSymbol(e.target.value)}
className="bg-gray-700 text-white px-3 py-2 rounded border border-gray-600 focus:border-blue-400"
>
{availableSymbols.map(sym => (
<option key={sym} value={sym}>{sym}</option>
))}
</select>
<div className="text-right">
<div className="text-lg font-bold text-white">${marketData.price.toFixed(2)}</div>
<div className={`text-sm ${marketData.change24h >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{marketData.change24h >= 0 ? '+' : ''}{marketData.change24h.toFixed(2)}%
</div>
</div>
</div>
</div>
{/* Side Selection */}
<div className="mb-4">
<label className="block text-gray-400 text-sm font-medium mb-2">Position Side</label>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setSide('LONG')}
className={`py-2 px-4 rounded font-medium transition-colors ${
side === 'LONG'
? 'bg-green-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
LONG
</button>
<button
onClick={() => setSide('SHORT')}
className={`py-2 px-4 rounded font-medium transition-colors ${
side === 'SHORT'
? 'bg-red-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
SHORT
</button>
</div>
</div>
{/* Order Type */}
<div className="mb-4">
<label className="block text-gray-400 text-sm font-medium mb-2">Order Type</label>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setOrderType('MARKET')}
className={`py-2 px-4 rounded font-medium transition-colors ${
orderType === 'MARKET'
? 'bg-blue-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
MARKET
</button>
<button
onClick={() => setOrderType('LIMIT')}
className={`py-2 px-4 rounded font-medium transition-colors ${
orderType === 'LIMIT'
? 'bg-blue-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
LIMIT
</button>
</div>
</div>
{/* Limit Price (if LIMIT order) */}
{orderType === 'LIMIT' && (
<div className="mb-4">
<label className="block text-gray-400 text-sm font-medium mb-2">Limit Price</label>
<input
type="number"
value={limitPrice}
onChange={(e) => setLimitPrice(e.target.value)}
placeholder="Enter limit price"
className="w-full bg-gray-700 text-white px-3 py-2 rounded border border-gray-600 focus:border-blue-400"
/>
</div>
)}
{/* Leverage Slider */}
<div className="mb-4">
<label className="block text-gray-400 text-sm font-medium mb-2">
Leverage: {leverage}x
</label>
<input
type="range"
min="1"
max="20"
value={leverage}
onChange={(e) => setLeverage(parseInt(e.target.value))}
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>1x</span>
<span>10x</span>
<span>20x</span>
</div>
</div>
{/* Position Size */}
<div className="mb-4">
<label className="block text-gray-400 text-sm font-medium mb-2">Position Size</label>
<div className="relative">
<input
type="number"
value={positionSize}
onChange={(e) => setPositionSize(e.target.value)}
placeholder="Enter position size"
className="w-full bg-gray-700 text-white px-3 py-2 pr-16 rounded border border-gray-600 focus:border-blue-400"
/>
<button
onClick={setMaxSize}
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-blue-400 hover:text-blue-300"
>
MAX
</button>
</div>
{/* Quick Size Buttons */}
<div className="grid grid-cols-4 gap-1 mt-2">
{[25, 50, 75, 100].map(pct => (
<button
key={pct}
onClick={() => setPercentageSize(pct)}
className="py-1 px-2 text-xs bg-gray-700 text-gray-300 rounded hover:bg-gray-600"
>
{pct}%
</button>
))}
</div>
</div>
{/* Risk Management - Stop Loss */}
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<label className="text-gray-400 text-sm font-medium">Stop Loss</label>
<button
onClick={() => setEnableStopLoss(!enableStopLoss)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
enableStopLoss
? 'bg-red-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
{enableStopLoss ? 'ON' : 'OFF'}
</button>
</div>
{enableStopLoss && (
<div className="space-y-2">
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setStopLossType('PERCENTAGE')}
className={`py-2 px-3 rounded text-sm font-medium transition-colors ${
stopLossType === 'PERCENTAGE'
? 'bg-red-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
%
</button>
<button
onClick={() => setStopLossType('PRICE')}
className={`py-2 px-3 rounded text-sm font-medium transition-colors ${
stopLossType === 'PRICE'
? 'bg-red-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
$
</button>
</div>
<input
type="number"
value={stopLossValue}
onChange={(e) => setStopLossValue(e.target.value)}
placeholder={stopLossType === 'PERCENTAGE' ? 'e.g., 5' : 'e.g., 150.00'}
className="w-full bg-gray-700 text-white px-3 py-2 rounded border border-gray-600 focus:border-red-400"
/>
{stopLossType === 'PERCENTAGE' && (
<div className="grid grid-cols-3 gap-1">
{[2, 5, 10].map(pct => (
<button
key={pct}
onClick={() => setStopLossValue(pct.toString())}
className="py-1 px-2 text-xs bg-red-600/20 text-red-400 rounded hover:bg-red-600/30"
>
{pct}%
</button>
))}
</div>
)}
{stopLossPrice > 0 && (
<div className="text-xs text-red-400">
Stop Loss Price: ${stopLossPrice.toFixed(2)}
</div>
)}
</div>
)}
</div>
{/* Risk Management - Take Profit */}
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<label className="text-gray-400 text-sm font-medium">Take Profit</label>
<button
onClick={() => setEnableTakeProfit(!enableTakeProfit)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
enableTakeProfit
? 'bg-green-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
{enableTakeProfit ? 'ON' : 'OFF'}
</button>
</div>
{enableTakeProfit && (
<div className="space-y-2">
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setTakeProfitType('PERCENTAGE')}
className={`py-2 px-3 rounded text-sm font-medium transition-colors ${
takeProfitType === 'PERCENTAGE'
? 'bg-green-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
%
</button>
<button
onClick={() => setTakeProfitType('PRICE')}
className={`py-2 px-3 rounded text-sm font-medium transition-colors ${
takeProfitType === 'PRICE'
? 'bg-green-500 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
$
</button>
</div>
<input
type="number"
value={takeProfitValue}
onChange={(e) => setTakeProfitValue(e.target.value)}
placeholder={takeProfitType === 'PERCENTAGE' ? 'e.g., 10' : 'e.g., 180.00'}
className="w-full bg-gray-700 text-white px-3 py-2 rounded border border-gray-600 focus:border-green-400"
/>
{takeProfitPrice > 0 && (
<div className="text-xs text-green-400">
Take Profit Price: ${takeProfitPrice.toFixed(2)}
</div>
)}
</div>
)}
</div>
{/* Trading Metrics */}
<div className="mb-6 space-y-3 p-4 bg-gray-800/30 rounded-lg">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Required Margin:</span>
<span className="text-white font-mono">${requiredMargin.toFixed(2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Available Collateral:</span>
<span className="text-white font-mono">${accountData.freeCollateral.toFixed(2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Max Position Size:</span>
<span className="text-white font-mono">{maxPositionSize.toFixed(4)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Est. Liquidation Price:</span>
<span className={`font-mono ${liquidationPrice > 0 ? 'text-red-400' : 'text-gray-500'}`}>
{liquidationPrice > 0 ? `$${liquidationPrice.toFixed(2)}` : '--'}
</span>
</div>
{/* Risk Management Metrics */}
{(enableStopLoss || enableTakeProfit) && (
<>
<div className="border-t border-gray-700 pt-3">
<div className="text-xs text-gray-500 mb-2 font-medium">RISK MANAGEMENT</div>
</div>
{enableStopLoss && potentialLoss > 0 && (
<div className="flex justify-between text-sm">
<span className="text-gray-400">Max Loss:</span>
<span className="font-mono text-red-400">-${potentialLoss.toFixed(2)}</span>
</div>
)}
{enableTakeProfit && potentialProfit > 0 && (
<div className="flex justify-between text-sm">
<span className="text-gray-400">Max Profit:</span>
<span className="font-mono text-green-400">+${potentialProfit.toFixed(2)}</span>
</div>
)}
{riskRewardRatio > 0 && (
<div className="flex justify-between text-sm">
<span className="text-gray-400">Risk/Reward:</span>
<span className={`font-mono font-bold ${
riskRewardRatio >= 2 ? 'text-green-400' :
riskRewardRatio >= 1 ? 'text-yellow-400' : 'text-red-400'
}`}>
1:{riskRewardRatio.toFixed(2)}
</span>
</div>
)}
</>
)}
</div>
{/* Open Positions */}
{positions.length > 0 && (
<div className="mb-6 p-4 bg-gray-800/30 rounded-lg">
<div className="flex items-center justify-between mb-3">
<div className="text-sm font-medium text-gray-400">Open Positions</div>
<div className="text-xs text-gray-500">{positions.length} position{positions.length !== 1 ? 's' : ''}</div>
</div>
<div className="space-y-2">
{positions.slice(0, 3).map((position, index) => (
<div key={index} className="flex items-center justify-between p-2 bg-gray-700/50 rounded">
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-white">{position.symbol}</span>
<span className={`text-xs px-2 py-0.5 rounded ${
position.side === 'LONG' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
}`}>
{position.side}
</span>
</div>
<div className="text-xs text-gray-400">
Size: {position.size.toFixed(4)} | PnL: <span className={position.pnl >= 0 ? 'text-green-400' : 'text-red-400'}>
${position.pnl.toFixed(2)}
</span>
</div>
</div>
<button
onClick={() => closePosition(position.symbol)}
disabled={closingPosition === position.symbol}
className="px-2 py-1 text-xs bg-red-600 hover:bg-red-700 disabled:bg-gray-600 text-white rounded transition-colors"
>
{closingPosition === position.symbol ? '...' : 'Close'}
</button>
</div>
))}
</div>
</div>
)}
{/* Trade Button */}
<button
onClick={handleTrade}
disabled={loading || !positionSize || requiredMargin > accountData.freeCollateral}
className={`w-full py-3 px-4 rounded font-bold transition-colors ${
side === 'LONG'
? 'bg-green-500 hover:bg-green-600 disabled:bg-gray-600'
: 'bg-red-500 hover:bg-red-600 disabled:bg-gray-600'
} text-white disabled:cursor-not-allowed`}
>
{loading ? 'Executing...' : `${side} ${symbol}`}
</button>
{/* Result Display */}
{result && (
<div className={`mt-4 p-3 rounded text-sm ${
result.success ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
}`}>
{result.success ? (
<div>
<div className="font-medium">Trade Executed Successfully!</div>
{result.txId && (
<div className="text-xs mt-1 opacity-75">TX: {result.txId}</div>
)}
</div>
) : (
<div className="font-medium">{result.error}</div>
)}
</div>
)}
</div>
)
}