backtest-terminal / backend /mt5_server.py
algorembrant's picture
Upload 16 files
5cbffcd verified
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, # Convert to milliseconds
'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'] # Add more symbols as needed
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) # Update every 100ms
async def start(self):
"""Start the WebSocket server"""
if not await self.init_mt5():
return
self.running = True
# Start WebSocket server
server = await websockets.serve(self.handle_client, "localhost", 8765)
print("WebSocket server started on ws://localhost:8765")
# Start broadcasting ticks
await self.broadcast_ticks()
if __name__ == "__main__":
server = MT5Server()
asyncio.run(server.start())