- Fixed internal API calls in Docker environment to use port 3000 instead of 9000 - Added DOCKER_ENV detection to properly route internal fetch requests - Resolves ECONNREFUSED errors when APIs try to call each other within container - Trade validation now works correctly in Docker: 5 USD position validates properly - Successfully tested: amountUSD field properly passed through validation pipeline - Both development and Docker environments now fully functional
199 lines
7.0 KiB
TypeScript
199 lines
7.0 KiB
TypeScript
'use client'
|
|
import React, { useState, useEffect } from 'react'
|
|
import TradingChart from '../../components/TradingChart'
|
|
import CompactTradingPanel from '../../components/CompactTradingPanel'
|
|
import PositionsPanel from '../../components/PositionsPanel'
|
|
|
|
export default function ChartTradingPage() {
|
|
const [currentPrice, setCurrentPrice] = useState(166.21)
|
|
const [positions, setPositions] = useState([])
|
|
const [selectedSymbol, setSelectedSymbol] = useState('SOL')
|
|
|
|
useEffect(() => {
|
|
fetchPositions()
|
|
const interval = setInterval(fetchPositions, 10000) // Update every 10 seconds
|
|
return () => clearInterval(interval)
|
|
}, [])
|
|
|
|
const fetchPositions = async () => {
|
|
try {
|
|
const response = await fetch('/api/trading/positions')
|
|
const data = await response.json()
|
|
|
|
if (data.success) {
|
|
setPositions(data.positions || [])
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch positions:', error)
|
|
}
|
|
}
|
|
|
|
const handleTrade = async (tradeData: any) => {
|
|
try {
|
|
console.log('Executing trade:', tradeData)
|
|
|
|
// For perpetual trades, use the execute-perp endpoint
|
|
const response = await fetch('/api/trading/execute-perp', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(tradeData)
|
|
})
|
|
|
|
const result = await response.json()
|
|
|
|
if (result.success) {
|
|
alert(`Trade executed successfully! ${result.message}`)
|
|
fetchPositions() // Refresh positions
|
|
} else {
|
|
alert(`Trade failed: ${result.error || result.message}`)
|
|
}
|
|
} catch (error) {
|
|
console.error('Trade execution error:', error)
|
|
alert('Trade execution failed. Please try again.')
|
|
}
|
|
}
|
|
|
|
const handlePriceUpdate = (price: number) => {
|
|
setCurrentPrice(price)
|
|
}
|
|
|
|
return (
|
|
<div className="h-screen bg-gray-900 flex flex-col">
|
|
{/* Top Bar */}
|
|
<div className="bg-gray-800 border-b border-gray-700 p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-6">
|
|
<h1 className="text-xl font-bold text-white">Trading Terminal</h1>
|
|
|
|
{/* Symbol Selector */}
|
|
<div className="flex space-x-2">
|
|
{['SOL', 'BTC', 'ETH'].map(symbol => (
|
|
<button
|
|
key={symbol}
|
|
onClick={() => setSelectedSymbol(symbol)}
|
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
|
selectedSymbol === symbol
|
|
? 'bg-blue-600 text-white'
|
|
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
|
|
}`}
|
|
>
|
|
{symbol}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Market Status */}
|
|
<div className="flex items-center space-x-4 text-sm">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
|
<span className="text-gray-300">Market Open</span>
|
|
</div>
|
|
<div className="text-gray-400">
|
|
Server Time: {new Date().toLocaleTimeString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Trading Interface */}
|
|
<div className="flex-1 flex">
|
|
{/* Chart Area (70% width) */}
|
|
<div className="flex-1 p-4">
|
|
<TradingChart
|
|
symbol={selectedSymbol}
|
|
positions={positions}
|
|
onPriceUpdate={handlePriceUpdate}
|
|
/>
|
|
</div>
|
|
|
|
{/* Trading Panel (30% width) */}
|
|
<div className="w-96 border-l border-gray-700 p-4 space-y-4">
|
|
<CompactTradingPanel
|
|
symbol={selectedSymbol}
|
|
currentPrice={currentPrice}
|
|
onTrade={handleTrade}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bottom Panel - Positions */}
|
|
<div className="border-t border-gray-700 bg-gray-800">
|
|
<div className="p-4">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex space-x-6">
|
|
<button className="text-white font-medium border-b-2 border-blue-500 pb-2">
|
|
Positions ({positions.length})
|
|
</button>
|
|
<button className="text-gray-400 hover:text-white pb-2">
|
|
Orders
|
|
</button>
|
|
<button className="text-gray-400 hover:text-white pb-2">
|
|
History
|
|
</button>
|
|
</div>
|
|
|
|
{positions.length > 0 && (
|
|
<div className="text-sm text-gray-400">
|
|
Total P&L: <span className="text-green-400">+$0.00</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Positions Table */}
|
|
<div className="max-h-48 overflow-y-auto">
|
|
{positions.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-400">
|
|
No open positions
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{positions.map((position: any) => (
|
|
<div
|
|
key={position.id}
|
|
className="bg-gray-900 rounded-lg p-4 flex items-center justify-between"
|
|
>
|
|
<div className="flex items-center space-x-4">
|
|
<div className={`w-3 h-3 rounded-full ${
|
|
position.side === 'BUY' ? 'bg-green-400' : 'bg-red-400'
|
|
}`}></div>
|
|
<div>
|
|
<div className="text-white font-medium">
|
|
{position.symbol} • {position.side}
|
|
</div>
|
|
<div className="text-sm text-gray-400">
|
|
Size: {position.amount} • Entry: ${position.entryPrice?.toFixed(2)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<div className="text-white font-medium">
|
|
${position.totalValue?.toFixed(2) || '0.00'}
|
|
</div>
|
|
<div className={`text-sm ${
|
|
(position.unrealizedPnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'
|
|
}`}>
|
|
{(position.unrealizedPnl || 0) >= 0 ? '+' : ''}${(position.unrealizedPnl || 0).toFixed(2)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex space-x-2">
|
|
<button className="px-3 py-1 bg-gray-700 text-gray-300 rounded text-sm hover:bg-gray-600">
|
|
Modify
|
|
</button>
|
|
<button className="px-3 py-1 bg-red-600 text-white rounded text-sm hover:bg-red-700">
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|