| import asyncio
|
| import json
|
| import MetaTrader5 as mt5
|
| from datetime import datetime
|
| import websockets
|
| import numpy as np
|
|
|
| class MT5Server:
|
| def __init__(self):
|
| self.clients = set()
|
| self.running = False
|
|
|
| async def init_mt5(self):
|
| """Initialize MT5 connection"""
|
| if not mt5.initialize():
|
| print(f"MT5 initialization failed: {mt5.last_error()}")
|
| return False
|
| print(f"MT5 initialized. Version: {mt5.version()}")
|
| return True
|
|
|
| def get_rates(self, symbol, timeframe, count=500):
|
| """Fetch historical rates from MT5"""
|
| rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
| if rates is None:
|
| return None
|
|
|
| return [{
|
| 'time': int(rate[0]) * 1000,
|
| 'open': float(rate[1]),
|
| 'high': float(rate[2]),
|
| 'low': float(rate[3]),
|
| 'close': float(rate[4]),
|
| 'volume': int(rate[5])
|
| } for rate in rates]
|
|
|
| def get_tick(self, symbol):
|
| """Get latest tick data"""
|
| tick = mt5.symbol_info_tick(symbol)
|
| if tick is None:
|
| return None
|
|
|
| return {
|
| 'time': tick.time * 1000,
|
| 'bid': tick.bid,
|
| 'ask': tick.ask,
|
| 'last': tick.last,
|
| 'volume': tick.volume
|
| }
|
|
|
| def get_positions(self):
|
| """Get open positions"""
|
| positions = mt5.positions_get()
|
| if positions is None:
|
| return []
|
|
|
| return [{
|
| 'ticket': pos.ticket,
|
| 'symbol': pos.symbol,
|
| 'type': 'BUY' if pos.type == 0 else 'SELL',
|
| 'volume': pos.volume,
|
| 'price_open': pos.price_open,
|
| 'price_current': pos.price_current,
|
| 'profit': pos.profit,
|
| 'sl': pos.sl,
|
| 'tp': pos.tp
|
| } for pos in positions]
|
|
|
| def place_order(self, symbol, order_type, volume, price=None, sl=None, tp=None):
|
| """Place market or pending order"""
|
| symbol_info = mt5.symbol_info(symbol)
|
| if symbol_info is None:
|
| return {'success': False, 'error': 'Symbol not found'}
|
|
|
| if not symbol_info.visible:
|
| if not mt5.symbol_select(symbol, True):
|
| return {'success': False, 'error': 'Failed to select symbol'}
|
|
|
| point = symbol_info.point
|
|
|
| request = {
|
| "action": mt5.TRADE_ACTION_DEAL,
|
| "symbol": symbol,
|
| "volume": volume,
|
| "type": mt5.ORDER_TYPE_BUY if order_type == 'BUY' else mt5.ORDER_TYPE_SELL,
|
| "deviation": 20,
|
| "magic": 234000,
|
| "comment": "Trading Terminal",
|
| "type_time": mt5.ORDER_TIME_GTC,
|
| "type_filling": mt5.ORDER_FILLING_IOC,
|
| }
|
|
|
| if price:
|
| request["price"] = price
|
|
|
| if sl:
|
| request["sl"] = sl
|
|
|
| if tp:
|
| request["tp"] = tp
|
|
|
| result = mt5.order_send(request)
|
|
|
| if result.retcode != mt5.TRADE_RETCODE_DONE:
|
| return {'success': False, 'error': f'Order failed: {result.comment}'}
|
|
|
| return {
|
| 'success': True,
|
| 'ticket': result.order,
|
| 'volume': result.volume,
|
| 'price': result.price
|
| }
|
|
|
| def close_position(self, ticket):
|
| """Close position by ticket"""
|
| positions = mt5.positions_get(ticket=ticket)
|
| if not positions:
|
| return {'success': False, 'error': 'Position not found'}
|
|
|
| position = positions[0]
|
|
|
| request = {
|
| "action": mt5.TRADE_ACTION_DEAL,
|
| "symbol": position.symbol,
|
| "volume": position.volume,
|
| "type": mt5.ORDER_TYPE_SELL if position.type == 0 else mt5.ORDER_TYPE_BUY,
|
| "position": ticket,
|
| "deviation": 20,
|
| "magic": 234000,
|
| "comment": "Close position",
|
| "type_time": mt5.ORDER_TIME_GTC,
|
| "type_filling": mt5.ORDER_FILLING_IOC,
|
| }
|
|
|
| result = mt5.order_send(request)
|
|
|
| if result.retcode != mt5.TRADE_RETCODE_DONE:
|
| return {'success': False, 'error': f'Close failed: {result.comment}'}
|
|
|
| return {'success': True, 'ticket': ticket}
|
|
|
| async def handle_client(self, websocket, path):
|
| """Handle WebSocket client connections"""
|
| self.clients.add(websocket)
|
| print(f"Client connected. Total clients: {len(self.clients)}")
|
|
|
| try:
|
| async for message in websocket:
|
| data = json.loads(message)
|
| action = data.get('action')
|
|
|
| if action == 'get_rates':
|
| symbol = data.get('symbol', 'XAUUSDc')
|
| timeframe_map = {
|
| 'M1': mt5.TIMEFRAME_M1,
|
| 'M5': mt5.TIMEFRAME_M5,
|
| 'M15': mt5.TIMEFRAME_M15,
|
| 'M30': mt5.TIMEFRAME_M30,
|
| 'H1': mt5.TIMEFRAME_H1,
|
| 'H4': mt5.TIMEFRAME_H4,
|
| 'D1': mt5.TIMEFRAME_D1
|
| }
|
| timeframe = timeframe_map.get(data.get('timeframe', 'M15'), mt5.TIMEFRAME_M15)
|
| count = data.get('count', 500)
|
|
|
| rates = self.get_rates(symbol, timeframe, count)
|
| await websocket.send(json.dumps({
|
| 'type': 'rates',
|
| 'data': rates
|
| }))
|
|
|
| elif action == 'get_tick':
|
| symbol = data.get('symbol', 'XAUUSDc')
|
| tick = self.get_tick(symbol)
|
| await websocket.send(json.dumps({
|
| 'type': 'tick',
|
| 'data': tick
|
| }))
|
|
|
| elif action == 'get_positions':
|
| positions = self.get_positions()
|
| await websocket.send(json.dumps({
|
| 'type': 'positions',
|
| 'data': positions
|
| }))
|
|
|
| elif action == 'place_order':
|
| result = self.place_order(
|
| data.get('symbol'),
|
| data.get('order_type'),
|
| data.get('volume'),
|
| data.get('price'),
|
| data.get('sl'),
|
| data.get('tp')
|
| )
|
| await websocket.send(json.dumps({
|
| 'type': 'order_result',
|
| 'data': result
|
| }))
|
|
|
| elif action == 'close_position':
|
| result = self.close_position(data.get('ticket'))
|
| await websocket.send(json.dumps({
|
| 'type': 'close_result',
|
| 'data': result
|
| }))
|
|
|
| except websockets.exceptions.ConnectionClosed:
|
| pass
|
| finally:
|
| self.clients.remove(websocket)
|
| print(f"Client disconnected. Total clients: {len(self.clients)}")
|
|
|
| async def broadcast_ticks(self):
|
| """Broadcast real-time ticks to all connected clients"""
|
| symbols = ['XAUUSDc']
|
|
|
| while self.running:
|
| for symbol in symbols:
|
| tick = self.get_tick(symbol)
|
| if tick and self.clients:
|
| message = json.dumps({
|
| 'type': 'tick_update',
|
| 'symbol': symbol,
|
| 'data': tick
|
| })
|
| await asyncio.gather(
|
| *[client.send(message) for client in self.clients],
|
| return_exceptions=True
|
| )
|
| await asyncio.sleep(0.1)
|
|
|
| async def start(self):
|
| """Start the WebSocket server"""
|
| if not await self.init_mt5():
|
| return
|
|
|
| self.running = True
|
|
|
|
|
| server = await websockets.serve(self.handle_client, "localhost", 8765)
|
| print("WebSocket server started on ws://localhost:8765")
|
|
|
|
|
| await self.broadcast_ticks()
|
|
|
| if __name__ == "__main__":
|
| server = MT5Server()
|
| asyncio.run(server.start()) |