mt5-manager/mt5_control.py

249 lines
9.7 KiB
Python
Raw Permalink Normal View History

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-30 09:59:38 +03:00
MT5_FOLDER = 'C:/VDS'
2025-10-14 14:13:43 +03:00
# Имя запускаемого файла терминала
MT5_EXE = 'terminal64.exe'
2025-10-14 16:04:37 +03:00
class MT5_Control:
2025-10-30 09:59:38 +03:00
def __init__(self, terminals: dict, mt5_folder: str, mt5_exe: str = 'terminal64.exe'):
self.mt5_folder = mt5_folder
self.mt5_exe = mt5_exe
# Список имён экземпляров (пока фиксированный)
2025-10-30 09:59:38 +03:00
self.instances_folders = list(terminals.keys())
# Словарь соответствия имя -> путь
2025-10-30 09:59:38 +03:00
self.instanсes_paths = {folder: self.instance_path(
folder) for folder in self.instances_folders}
# Словарь соответствия путь -> имя
self.paths_instanсes = {self.instance_path(
2025-10-30 09:59:38 +03:00
folder): folder for folder in self.instances_folders}
self.instances = terminals.copy()
2025-10-30 09:59:38 +03:00
def instance_path(self, folder: str) -> str:
'''
Формирует полный путь по имени экземпляра терминала
Args:
2025-10-30 09:59:38 +03:00
folder (str): Имя экземпляра терминала.
Returns:
path (str): Полый путь к запускаемому файлу экземпляра терминала.
'''
2025-10-30 09:59:38 +03:00
return f'{self.mt5_folder}/{folder}/{self.mt5_exe}'
2025-10-30 09:59:38 +03:00
def instance_info(self, folder: str) -> dict:
info = {}
if folder in self.instances:
info = self.instances[folder]
path = self.instance_path(folder)
2025-10-30 09:59:38 +03:00
if 'pid' in self.instances[folder] and self.instances[folder]['pid']:
print(f'Start mt5.initialize[{folder}]')
2025-10-31 21:45:09 +03:00
if mt5.initialize(path, timeout=200, portable=True):
# выведем информацию о настройках и состоянии терминала
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-30 09:59:38 +03:00
2025-10-31 21:45:09 +03:00
code, description = mt5.last_error()
status = 'running'
else:
2025-10-30 09:59:38 +03:00
print("initialize() failed, error code =", mt5.last_error())
2025-10-31 21:45:09 +03:00
if info['status'] == 'starting':
code, description = 0, 'Starting'
status = 'starting'
else:
code, description = mt5.last_error()
status = 'running'
2025-10-30 09:59:38 +03:00
mt5.shutdown()
2025-10-30 09:59:38 +03:00
print(f'End mt5.initialize[{folder}]')
else:
code, description = 0, 'Stopped'
status = 'stopped'
else:
code, description = -1, 'Manager: Terminal not found'
status = 'not found'
info['last_error'] = {'code': code, 'description': description}
info['status'] = status
return info
def find_instances(self) -> None:
'''Получение информации о статусе экземпляров
терминала MetaTrader 5 из заданных папок.
Returns:
instances (dict): Информация об экземплярах терминала.
'''
# Формируем словарь для всех экземпляров по списку имён
2025-10-30 09:59:38 +03:00
for folder in self.instances_folders:
self.instances[folder]['pid'] = 0
self.instances[folder]['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
# Если он есть среди путей экземпляров терминала
if exe_path and exe_path in self.paths_instanсes:
# Определяем имя экземпляра для данного пути
2025-10-30 09:59:38 +03:00
folder = self.paths_instanсes[exe_path]
2025-10-14 14:13:43 +03:00
# Берём нужный экземпляр
2025-10-30 09:59:38 +03:00
instance = self.instances[folder]
2025-10-16 20:41:52 +03:00
# Запоминаем идентификатор и статус процесса
instance['pid'] = proc.info['pid']
2025-10-31 21:45:09 +03:00
instance['status'] = 'starting'
2025-10-30 09:59:38 +03:00
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
2025-10-16 20:41:52 +03:00
def load_instances(self) -> dict:
self.find_instances()
print(self.instances)
2025-10-30 09:59:38 +03:00
return self.instances
2025-10-16 20:41:52 +03:00
2025-10-30 09:59:38 +03:00
def start_mt5(self, folder: str) -> dict:
'''
Запуск экземпляра терминала
2025-10-16 20:41:52 +03:00
Args:
2025-10-30 09:59:38 +03:00
folder (str): Имя экземпляра терминала.
2025-10-14 16:04:37 +03:00
Returns:
instance (dict): Информация о запущенном терминале.
'''
2025-10-14 14:13:43 +03:00
# Получаем информацию об экземплярах терминала
instances = self.load_instances()
2025-10-14 14:13:43 +03:00
# Если имя экземпляра есть среди имеющихся
2025-10-30 09:59:38 +03:00
if folder in instances:
# Берём нужный экземпляр
2025-10-30 09:59:38 +03:00
instance = instances[folder]
2025-10-14 14:13:43 +03:00
# Если для него нет запущенного процесса, то
if not instance['pid']:
# Запускаем новый процесс
process = subprocess.Popen(
2025-10-30 09:59:38 +03:00
[self.instanсes_paths[folder], '/portable'])
2025-10-14 16:04:37 +03:00
# Идентификатор запущенного процесса
pid = process.pid
2025-10-14 14:13:43 +03:00
# Запоминаем идентификатор и статус процесса
instance['pid'] = pid
2025-10-31 21:45:09 +03:00
instance['status'] = 'starting'
2025-10-10 10:16:59 +03:00
# Возвращаем результат - успешный запуск
2025-10-30 09:59:38 +03:00
return {folder: instance}
2025-10-10 10:16:59 +03:00
# Возвращаем результат - имя не найдено
2025-10-30 09:59:38 +03:00
return {folder: {'status': 'not found'}}
2025-10-14 14:13:43 +03:00
2025-10-30 09:59:38 +03:00
def stop_mt5(self, folder: str) -> dict:
'''
Остановка терминала
2025-10-14 14:13:43 +03:00
Args:
2025-10-30 09:59:38 +03:00
folder (str): Имя экземпляра терминала.
2025-10-14 14:13:43 +03:00
Returns:
instance (dict): Информация о запущенном терминале.
'''
2025-10-14 14:13:43 +03:00
# Получаем информацию об экземплярах терминала
instances = self.load_instances()
2025-10-14 14:13:43 +03:00
# Если имя экземпляра есть среди имеющихся
2025-10-30 09:59:38 +03:00
if folder in instances:
# Берём нужный экземпляр
2025-10-30 09:59:38 +03:00
instance = instances[folder]
2025-10-14 14:13:43 +03:00
# Берём идентификатор его процесса
pid = instance['pid']
2025-10-10 10:16:59 +03:00
# Если ранее терминал был запущен
if pid:
try:
# Получаем объект процесса терминала
p = psutil.Process(pid)
2025-10-10 10:16:59 +03:00
# Останавливаем процесс
p.terminate()
p.wait(10)
except psutil.NoSuchProcess:
pass
2025-10-14 14:13:43 +03:00
# Удаляем информацию о запущенном ранее процессе
instance['pid'] = 0
instance['status'] = 'stopped'
2025-10-10 10:16:59 +03:00
# Возвращаем результат - успешная остановка
2025-10-30 09:59:38 +03:00
return {folder: instance}
2025-10-16 20:41:52 +03:00
# Возвращаем результат - процесс не найден
2025-10-30 09:59:38 +03:00
return {folder: {'status': 'not found'}}
2025-10-16 20:41:52 +03:00
2025-10-30 09:59:38 +03:00
def create_mt5(self, folder: str) -> dict:
'''
Запуск экземпляра терминала
2025-10-16 20:41:52 +03:00
Args:
2025-10-30 09:59:38 +03:00
folder (str): Имя экземпляра терминала.
2025-10-16 20:41:52 +03:00
Returns:
instance (dict): Информация о запущенном терминале.
'''
2025-10-16 20:41:52 +03:00
# Получаем информацию об экземплярах терминала
instances = self.load_instances()
2025-10-16 20:41:52 +03:00
# Если имя экземпляра есть среди имеющихся
2025-10-30 09:59:38 +03:00
if folder 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
account_info = mt5.account_info()
if account_info != None:
info['account'] = account_info._asdict()
2025-10-16 20:41:52 +03:00
mt5.shutdown()
# Возвращаем результат - успешный запуск
2025-10-30 09:59:38 +03:00
return {folder: folder}
2025-10-16 20:41:52 +03:00
# Возвращаем результат - имя не найдено
2025-10-30 09:59:38 +03:00
return {folder: {'status': 'already exists'}}