algorembrant's picture
Upload 16 files
5cbffcd verified
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { TrendingUp, TrendingDown, X } from 'lucide-react';
const TradingTerminal = () => {
const [ws, setWs] = useState(null);
const [connected, setConnected] = useState(false);
const [symbol, setSymbol] = useState('XAUUSDc');
const [timeframe, setTimeframe] = useState('M15');
const [chartData, setChartData] = useState([]);
const [tick, setTick] = useState(null);
const [positions, setPositions] = useState([]);
const [volume, setVolume] = useState(0.01);
const [sl, setSl] = useState('');
const [tp, setTp] = useState('');
useEffect(() => {
const socket = new WebSocket('ws://localhost:8765');
socket.onopen = () => {
setConnected(true);
console.log('Connected to MT5 server');
socket.send(JSON.stringify({
action: 'get_rates',
symbol: symbol,
timeframe: timeframe,
count: 500
}));
socket.send(JSON.stringify({ action: 'get_positions' }));
};
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'rates') {
setChartData(message.data || []);
} else if (message.type === 'tick_update' || message.type === 'tick') {
setTick(message.data);
} else if (message.type === 'positions') {
setPositions(message.data || []);
} else if (message.type === 'order_result') {
if (message.data.success) {
alert('Order placed successfully!');
socket.send(JSON.stringify({ action: 'get_positions' }));
} else {
alert('Order failed: ' + message.data.error);
}
} else if (message.type === 'close_result') {
if (message.data.success) {
alert('Position closed!');
socket.send(JSON.stringify({ action: 'get_positions' }));
} else {
alert('Close failed: ' + message.data.error);
}
}
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
setConnected(false);
};
socket.onclose = () => {
setConnected(false);
console.log('Disconnected from MT5 server');
};
setWs(socket);
return () => {
socket.close();
};
}, []);
const placeOrder = (type) => {
if (!ws || !connected) return;
ws.send(JSON.stringify({
action: 'place_order',
symbol: symbol,
order_type: type,
volume: parseFloat(volume),
sl: sl ? parseFloat(sl) : null,
tp: tp ? parseFloat(tp) : null
}));
};
const closePosition = (ticket) => {
if (!ws || !connected) return;
ws.send(JSON.stringify({
action: 'close_position',
ticket: ticket
}));
};
const changeTimeframe = (tf) => {
setTimeframe(tf);
if (ws && connected) {
ws.send(JSON.stringify({
action: 'get_rates',
symbol: symbol,
timeframe: tf,
count: 500
}));
}
};
const formatPrice = (price) => {
return price ? price.toFixed(2) : '-';
};
const totalPnL = positions.reduce((sum, pos) => sum + pos.profit, 0);
return (
<div className="w-full h-screen bg-neutral-950 text-neutral-200 flex flex-col">
<div className="h-12 bg-neutral-900 border-b border-yellow-600/20 flex items-center justify-between px-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${connected ? 'bg-yellow-500' : 'bg-neutral-600'}`}></div>
<span className="text-sm font-medium text-yellow-500">{symbol}</span>
</div>
{tick && (
<div className="flex items-center gap-4 text-sm">
<span className="text-neutral-400">BID: <span className="text-yellow-500 font-mono">{formatPrice(tick.bid)}</span></span>
<span className="text-neutral-400">ASK: <span className="text-yellow-500 font-mono">{formatPrice(tick.ask)}</span></span>
</div>
)}
</div>
<div className="flex items-center gap-2">
<span className={`text-sm font-mono ${totalPnL >= 0 ? 'text-green-500' : 'text-red-500'}`}>
P&L: ${totalPnL.toFixed(2)}
</span>
</div>
</div>
<div className="flex-1 flex overflow-hidden">
<div className="flex-1 flex flex-col">
<div className="h-10 bg-neutral-900 border-b border-yellow-600/20 flex items-center px-4 gap-2">
{['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'].map(tf => (
<button
key={tf}
onClick={() => changeTimeframe(tf)}
className={`px-3 py-1 text-xs font-medium transition-colors ${
timeframe === tf
? 'bg-yellow-600 text-neutral-950'
: 'text-neutral-400 hover:text-yellow-500'
}`}
>
{tf}
</button>
))}
</div>
<div className="flex-1 bg-neutral-950 p-4">
{chartData.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartData}>
<XAxis
dataKey="time"
tickFormatter={(time) => new Date(time).toLocaleTimeString()}
stroke="#525252"
style={{ fontSize: 10 }}
/>
<YAxis
domain={['auto', 'auto']}
stroke="#525252"
style={{ fontSize: 10 }}
/>
<Tooltip
contentStyle={{
backgroundColor: '#171717',
border: '1px solid #ca8a04',
borderRadius: 0,
fontSize: 12
}}
labelFormatter={(time) => new Date(time).toLocaleString()}
/>
<Line
type="monotone"
dataKey="close"
stroke="#ca8a04"
dot={false}
strokeWidth={1}
/>
</LineChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-full text-neutral-600">
Loading chart data...
</div>
)}
</div>
</div>
<div className="w-80 bg-neutral-900 border-l border-yellow-600/20 flex flex-col">
<div className="p-4 border-b border-yellow-600/20">
<div className="text-xs font-medium text-yellow-500 mb-3">NEW ORDER</div>
<div className="space-y-2">
<div>
<label className="text-xs text-neutral-400">Volume</label>
<input
type="number"
step="0.01"
value={volume}
onChange={(e) => setVolume(e.target.value)}
className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600"
/>
</div>
<div>
<label className="text-xs text-neutral-400">Stop Loss</label>
<input
type="number"
step="0.01"
value={sl}
onChange={(e) => setSl(e.target.value)}
placeholder="Optional"
className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
/>
</div>
<div>
<label className="text-xs text-neutral-400">Take Profit</label>
<input
type="number"
step="0.01"
value={tp}
onChange={(e) => setTp(e.target.value)}
placeholder="Optional"
className="w-full bg-neutral-950 border border-yellow-600/20 px-2 py-1 text-sm text-neutral-200 focus:outline-none focus:border-yellow-600 placeholder:text-neutral-700"
/>
</div>
<div className="flex gap-2 pt-2">
<button
onClick={() => placeOrder('BUY')}
className="flex-1 bg-green-700 hover:bg-green-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
disabled={!connected}
>
<TrendingUp size={14} />
BUY
</button>
<button
onClick={() => placeOrder('SELL')}
className="flex-1 bg-red-700 hover:bg-red-600 text-white py-2 text-sm font-medium transition-colors flex items-center justify-center gap-1"
disabled={!connected}
>
<TrendingDown size={14} />
SELL
</button>
</div>
</div>
</div>
<div className="flex-1 overflow-auto">
<div className="p-4">
<div className="text-xs font-medium text-yellow-500 mb-3">POSITIONS ({positions.length})</div>
{positions.length === 0 ? (
<div className="text-xs text-neutral-600 text-center py-8">No open positions</div>
) : (
<div className="space-y-2">
{positions.map((pos) => (
<div key={pos.ticket} className="bg-neutral-950 border border-yellow-600/20 p-3">
<div className="flex items-start justify-between mb-2">
<div>
<div className="text-xs font-medium text-neutral-200">{pos.symbol}</div>
<div className={`text-xs ${pos.type === 'BUY' ? 'text-green-500' : 'text-red-500'}`}>
{pos.type} {pos.volume}
</div>
</div>
<button
onClick={() => closePosition(pos.ticket)}
className="text-neutral-400 hover:text-red-500 transition-colors"
>
<X size={14} />
</button>
</div>
<div className="space-y-1 text-xs">
<div className="flex justify-between text-neutral-400">
<span>Entry:</span>
<span className="font-mono">{formatPrice(pos.price_open)}</span>
</div>
<div className="flex justify-between text-neutral-400">
<span>Current:</span>
<span className="font-mono">{formatPrice(pos.price_current)}</span>
</div>
<div className="flex justify-between">
<span className="text-neutral-400">P&L:</span>
<span className={`font-mono font-medium ${pos.profit >= 0 ? 'text-green-500' : 'text-red-500'}`}>
${pos.profit.toFixed(2)}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default TradingTerminal;