| | 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()) |