Fix critical balance validation and add comprehensive trading features

- Fixed CoinGecko API rate limiting with fallback SOL price (68.11)
- Corrected internal API calls to use proper Docker container ports
- Fixed balance validation to prevent trades exceeding wallet funds
- Blocked 0.5 SOL trades with only 0.073 SOL available (~2.24)

- Added persistent storage for positions, trades, and pending orders
- Implemented limit order system with auto-fill monitoring
- Created pending orders panel and management API
- Added trades history tracking and display panel
- Enhanced position tracking with P&L calculations
- Added wallet balance validation API endpoint

- Positions stored in data/positions.json
- Trade history stored in data/trades.json
- Pending orders with auto-fill logic
- Real-time balance validation before trades

- All trades now validate against actual wallet balance
- Insufficient balance trades are properly blocked
- Added comprehensive error handling and logging
- Fixed Docker networking for internal API calls

- SPOT and leveraged trading modes
- Limit orders with price monitoring
- Stop loss and take profit support
- DEX integration with Jupiter
- Real-time position updates and P&L tracking

 Tested and verified all balance validation works correctly
This commit is contained in:
mindesbunister
2025-07-14 17:19:58 +02:00
parent 0d7b46fdcf
commit b0b63d5db0
14 changed files with 1445 additions and 61 deletions

View File

@@ -0,0 +1,175 @@
'use client'
import React, { useState, useEffect } from 'react'
export default function TradesHistoryPanel() {
const [trades, setTrades] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchTrades()
// Refresh trades every 15 seconds
const interval = setInterval(fetchTrades, 15000)
return () => clearInterval(interval)
}, [])
const fetchTrades = async () => {
try {
const response = await fetch('/api/trading/history')
const data = await response.json()
if (data.success) {
setTrades(data.trades || [])
}
} catch (error) {
console.error('Failed to fetch trades:', error)
} finally {
setLoading(false)
}
}
const clearHistory = async () => {
try {
const response = await fetch('/api/trading/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'clear' })
})
if (response.ok) {
setTrades([])
}
} catch (error) {
console.error('Failed to clear history:', error)
}
}
const getSideColor = (side) => {
return side === 'BUY' ? 'text-green-400' : 'text-red-400'
}
const getPnlColor = (pnl) => {
if (pnl === null || pnl === 0) return 'text-gray-400'
return pnl > 0 ? 'text-green-400' : 'text-red-400'
}
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 4
}).format(amount)
}
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
if (loading) {
return (
<div className="card card-gradient p-6">
<h2 className="text-xl font-bold text-white mb-4">Recent Trades</h2>
<div className="text-gray-400">Loading trades...</div>
</div>
)
}
return (
<div className="card card-gradient p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-white">Recent Trades</h2>
<div className="flex gap-2">
<button
onClick={fetchTrades}
className="text-blue-400 hover:text-blue-300 text-sm"
>
🔄 Refresh
</button>
{trades.length > 0 && (
<button
onClick={clearHistory}
className="text-red-400 hover:text-red-300 text-sm"
>
🗑 Clear
</button>
)}
</div>
</div>
{trades.length === 0 ? (
<div className="text-center py-8">
<div className="text-gray-400 mb-2">📈 No trades yet</div>
<div className="text-sm text-gray-500">Execute a trade to see history here</div>
</div>
) : (
<div className="space-y-3 max-h-96 overflow-y-auto">
{trades.map((trade) => (
<div
key={trade.id}
className="bg-gray-800 rounded-lg p-4 border border-gray-600"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center space-x-3">
<span className="text-white font-medium">{trade.symbol}</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${
trade.side === 'BUY'
? 'bg-green-600 text-white'
: 'bg-red-600 text-white'
}`}>
{trade.side}
</span>
<span className="px-2 py-1 rounded text-xs bg-blue-600 text-white">
{trade.dex}
</span>
</div>
<div className="text-xs text-gray-400">
{formatTime(trade.timestamp)}
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
<div>
<div className="text-gray-400">Amount</div>
<div className="text-white font-medium">{trade.amount}</div>
</div>
<div>
<div className="text-gray-400">Price</div>
<div className="text-white font-medium">{formatCurrency(trade.price)}</div>
</div>
<div>
<div className="text-gray-400">Status</div>
<div className="text-green-400 font-medium">{trade.status}</div>
</div>
{trade.pnl !== null && trade.pnl !== undefined && (
<div>
<div className="text-gray-400">P&L</div>
<div className={`font-medium ${getPnlColor(trade.pnl)}`}>
{trade.pnl >= 0 ? '+' : ''}{formatCurrency(trade.pnl)}
</div>
</div>
)}
</div>
{/* Additional Info */}
<div className="mt-3 flex justify-between items-center text-xs">
<div className="text-gray-500">
TX: {trade.txId?.substring(0, 12)}...
{trade.notes && (
<span className="ml-2 text-yellow-400"> {trade.notes}</span>
)}
</div>
<div className="text-gray-500">
Fee: ${trade.fee?.toFixed(4) || '0.0000'}
</div>
</div>
</div>
))}
</div>
)}
</div>
)
}