2025-10-14 16:04:37 +03:00
|
|
|
'''
|
|
|
|
|
File: mt5_control.py
|
|
|
|
|
Description: Логика управления запуском и остановкой терминалов MetaTrader 5
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
__version__ = '0.1.0'
|
|
|
|
|
|
2025-10-10 10:16:59 +03:00
|
|
|
import subprocess
|
|
|
|
|
import psutil
|
2025-10-21 21:24:09 +03:00
|
|
|
from datetime import datetime
|
2025-10-16 20:41:52 +03:00
|
|
|
import MetaTrader5 as mt5
|
2025-10-10 10:16:59 +03:00
|
|
|
|
|
|
|
|
|
2025-10-14 16:04:37 +03:00
|
|
|
# Путь к папке с экземплярами терминалов
|
2025-10-14 14:13:43 +03:00
|
|
|
MT5_FOLDER = 'C:/MT5'
|
|
|
|
|
|
|
|
|
|
# Имя запускаемого файла терминала
|
|
|
|
|
MT5_EXE = 'terminal64.exe'
|
|
|
|
|
|
2025-10-14 16:04:37 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
class MT5_Control:
|
|
|
|
|
def __init__(self, terminals: dict):
|
|
|
|
|
# Список имён экземпляров (пока фиксированный)
|
|
|
|
|
self.instances_names = list(terminals.keys())
|
|
|
|
|
|
|
|
|
|
# Словарь соответствия имя -> путь
|
|
|
|
|
self.instanсes_paths = {name: self.instance_path(
|
|
|
|
|
name) for name in self.instances_names}
|
|
|
|
|
|
|
|
|
|
# Словарь соответствия путь -> имя
|
|
|
|
|
self.paths_instanсes = {self.instance_path(
|
|
|
|
|
name): name for name in self.instances_names}
|
|
|
|
|
|
|
|
|
|
self.instances = terminals.copy()
|
|
|
|
|
|
|
|
|
|
def instance_path(self, name: str) -> str:
|
|
|
|
|
'''
|
|
|
|
|
Формирует полный путь по имени экземпляра терминала
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Имя экземпляра терминала.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
path (str): Полый путь к запускаемому файлу экземпляра терминала.
|
|
|
|
|
'''
|
|
|
|
|
return f'{MT5_FOLDER}/{name}/{MT5_EXE}'
|
|
|
|
|
|
|
|
|
|
def instance_info(self, name: str) -> dict:
|
|
|
|
|
if name in self.instances:
|
|
|
|
|
info = self.instances[name]
|
|
|
|
|
path = self.instance_path(name)
|
|
|
|
|
|
2025-10-21 21:24:09 +03:00
|
|
|
if 'pid' in self.instances[name] and self.instances[name]['pid']:
|
2025-10-24 02:13:35 +03:00
|
|
|
print(f'Start mt5.initialize[{name}]')
|
2025-10-21 21:24:09 +03:00
|
|
|
if mt5.initialize(path, timeout=10000, portable=True):
|
2025-10-21 16:08:01 +03:00
|
|
|
# выведем информацию о настройках и состоянии терминала
|
|
|
|
|
terminal_info = mt5.terminal_info()
|
|
|
|
|
if terminal_info != None:
|
|
|
|
|
info['terminal'] = terminal_info._asdict()
|
|
|
|
|
|
|
|
|
|
account_info = mt5.account_info()
|
|
|
|
|
if account_info != None:
|
|
|
|
|
info['account'] = account_info._asdict()
|
2025-10-21 21:24:09 +03:00
|
|
|
|
|
|
|
|
info['last_update'] = str(datetime.now())
|
2025-10-24 02:13:35 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
else:
|
2025-10-24 02:13:35 +03:00
|
|
|
print("initialize() failed, error code =",mt5.last_error())
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
code, description = mt5.last_error()
|
|
|
|
|
status = 'running'
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
mt5.shutdown()
|
|
|
|
|
print(f'End mt5.initialize[{name}]')
|
|
|
|
|
else:
|
|
|
|
|
code, description = 0, 'Stopped'
|
|
|
|
|
status = 'stopped'
|
2025-10-21 16:08:01 +03:00
|
|
|
else:
|
2025-10-24 02:13:35 +03:00
|
|
|
code, description = -1, 'Manager: Terminal not found'
|
|
|
|
|
status = 'not found'
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
info['last_error'] = {'code': code, 'description': description}
|
|
|
|
|
info['status'] = status
|
2025-10-21 16:08:01 +03:00
|
|
|
return info
|
|
|
|
|
|
|
|
|
|
def find_instances(self) -> None:
|
|
|
|
|
'''Получение информации о статусе экземпляров
|
|
|
|
|
терминала MetaTrader 5 из заданных папок.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
instances (dict): Информация об экземплярах терминала.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
# Формируем словарь для всех экземпляров по списку имён
|
|
|
|
|
for name in self.instances_names:
|
|
|
|
|
self.instances[name]['pid'] = 0
|
|
|
|
|
self.instances[name]['status'] = 'stopped'
|
|
|
|
|
|
|
|
|
|
# Перебираем все запущенные процессы
|
|
|
|
|
for proc in psutil.process_iter(['pid', 'name', 'exe']):
|
|
|
|
|
try:
|
|
|
|
|
# Получаем путь к запускаемому файлу процесса
|
|
|
|
|
exe_path = str(proc.info['exe']).replace('\\', '/')
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если он есть среди путей экземпляров терминала
|
|
|
|
|
if exe_path and exe_path in self.paths_instanсes:
|
|
|
|
|
# Определяем имя экземпляра для данного пути
|
|
|
|
|
name = self.paths_instanсes[exe_path]
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Берём нужный экземпляр
|
|
|
|
|
instance = self.instances[name]
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Запоминаем идентификатор и статус процесса
|
|
|
|
|
instance['pid'] = proc.info['pid']
|
|
|
|
|
instance['status'] = 'running'
|
|
|
|
|
|
|
|
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
|
|
|
continue
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
def load_instances(self) -> dict:
|
|
|
|
|
self.find_instances()
|
|
|
|
|
print(self.instances)
|
|
|
|
|
return self.instances
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
def start_mt5(self, name: str) -> dict:
|
|
|
|
|
'''
|
|
|
|
|
Запуск экземпляра терминала
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Args:
|
|
|
|
|
name (str): Имя экземпляра терминала.
|
2025-10-14 16:04:37 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Returns:
|
|
|
|
|
instance (dict): Информация о запущенном терминале.
|
|
|
|
|
'''
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Получаем информацию об экземплярах терминала
|
|
|
|
|
instances = self.load_instances()
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если имя экземпляра есть среди имеющихся
|
|
|
|
|
if name in instances:
|
|
|
|
|
# Берём нужный экземпляр
|
|
|
|
|
instance = instances[name]
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если для него нет запущенного процесса, то
|
|
|
|
|
if not instance['pid']:
|
|
|
|
|
# Запускаем новый процесс
|
|
|
|
|
process = subprocess.Popen(
|
|
|
|
|
[self.instanсes_paths[name], '/portable'])
|
2025-10-14 16:04:37 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Идентификатор запущенного процесса
|
|
|
|
|
pid = process.pid
|
2025-10-14 14:13:43 +03:00
|
|
|
|
|
|
|
|
# Запоминаем идентификатор и статус процесса
|
2025-10-21 16:08:01 +03:00
|
|
|
instance['pid'] = pid
|
2025-10-14 14:13:43 +03:00
|
|
|
instance['status'] = 'running'
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Возвращаем результат - успешный запуск
|
|
|
|
|
return {name: instance}
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Возвращаем результат - имя не найдено
|
|
|
|
|
return {name: {'status': 'not found'}}
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
def stop_mt5(self, name: str) -> dict:
|
|
|
|
|
'''
|
|
|
|
|
Остановка терминала
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Args:
|
|
|
|
|
name (str): Имя экземпляра терминала.
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Returns:
|
|
|
|
|
instance (dict): Информация о запущенном терминале.
|
|
|
|
|
'''
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Получаем информацию об экземплярах терминала
|
|
|
|
|
instances = self.load_instances()
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если имя экземпляра есть среди имеющихся
|
|
|
|
|
if name in instances:
|
|
|
|
|
# Берём нужный экземпляр
|
|
|
|
|
instance = instances[name]
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Берём идентификатор его процесса
|
|
|
|
|
pid = instance['pid']
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если ранее терминал был запущен
|
|
|
|
|
if pid:
|
|
|
|
|
try:
|
|
|
|
|
# Получаем объект процесса терминала
|
|
|
|
|
p = psutil.Process(pid)
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Останавливаем процесс
|
|
|
|
|
p.terminate()
|
|
|
|
|
p.wait(10)
|
|
|
|
|
except psutil.NoSuchProcess:
|
|
|
|
|
pass
|
2025-10-14 14:13:43 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Удаляем информацию о запущенном ранее процессе
|
|
|
|
|
instance['pid'] = 0
|
|
|
|
|
instance['status'] = 'stopped'
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Возвращаем результат - успешная остановка
|
|
|
|
|
return {name: instance}
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Возвращаем результат - процесс не найден
|
|
|
|
|
return {name: {'status': 'not found'}}
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
def create_mt5(self, name: str) -> dict:
|
|
|
|
|
'''
|
|
|
|
|
Запуск экземпляра терминала
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Args:
|
|
|
|
|
name (str): Имя экземпляра терминала.
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
Returns:
|
|
|
|
|
instance (dict): Информация о запущенном терминале.
|
|
|
|
|
'''
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Получаем информацию об экземплярах терминала
|
|
|
|
|
instances = self.load_instances()
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Если имя экземпляра есть среди имеющихся
|
|
|
|
|
if name not in instances:
|
|
|
|
|
info = dict()
|
|
|
|
|
if mt5.initialize():
|
|
|
|
|
# выведем информацию о настройках и состоянии терминала
|
|
|
|
|
terminal_info = mt5.terminal_info()
|
|
|
|
|
if terminal_info != None:
|
|
|
|
|
info['terminal'] = terminal_info._asdict()
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
account_info = mt5.account_info()
|
|
|
|
|
if account_info != None:
|
|
|
|
|
info['account'] = account_info._asdict()
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
mt5.shutdown()
|
|
|
|
|
# Возвращаем результат - успешный запуск
|
|
|
|
|
return {name: name}
|
2025-10-16 20:41:52 +03:00
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
# Возвращаем результат - имя не найдено
|
|
|
|
|
return {name: {'status': 'already exists'}}
|