preview
Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров

Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров

MetaTrader 5Трейдинг |
300 0
Yuriy Bykov
Yuriy Bykov

Введение

В первой части серии "Менеджер терминалов своими руками" мы поставили перед собой задачу — создать веб-интерфейс для управления запуском и остановкой экземпляров MetaTrader 5 на локальном компьютере. Мы определили архитектуру системы, начав с разработки минимально жизнеспособного продукта (MVP) — веб-сервера, который может принимать HTTP-запросы и управлять одним экземпляром терминала. Заодно мы рассмотрели такие ключевые понятия как API, REST, HTTP-запросы, декораторы.

Для реализации проекта был выбран язык Python, благодаря его богатой экосистеме библиотек и простоте интеграции с внешними API — в данном случае с библиотекой MetaTrader 5.

Фреймворк FastAPI позволяет создавать веб-приложения с минимальными затратами кода, предоставляя из коробки поддержку типизации, асинхронности и автоматической документации через Swagger UI.

В связке с ним используется Uvicorn, лёгкий и быстрый ASGI-сервер, обеспечивающий высокую производительность даже при большом количестве одновременных запросов.

Для работы с процессами терминалов применяются библиотеки psutil и subprocess, что позволяет удобно получать сведения о запущенных экземплярах и управлять ими.

В дальнейшем планируется добавить использование SQLite или другой базы данных для хранения состояния экземпляров, а также фронтенд-слой на JavaScript или Vue.js для более интерактивного интерфейса.

Теперь пришло время расширять функциональность и переходить к следующим этапам — реализации более сложных возможностей, таких как управление несколькими экземплярами, хранение состояния, интеграция с MetaTrader5 API и веб-интерфейс с полной информацией о терминалах.


Намечаем путь

Попробуем очертить примерные границы следующего кусочка реализации. Во-первых, пока у нас нет реального разделения общей функциональности на два разных веб-сервера, придётся в каком-то минимальном виде добавлять интерфейсные части в тот веб-сервер, который мы уже начали делать. Эта работа не пропадёт зря, так как в дальнейшем мы сможем перенести уже имеющийся код на другой веб-сервер с некоторыми правками. Поэтому начнём с того, что создадим дополнительные файлы для размещения разных частей кода проекта, придав им некоторый налёт организованности.

Во-вторых, сделаем самый напрашивающийся следующий шаг, добавив возможность работать с несколькими экземплярами терминала MetaTrader 5. То есть реализуем создание, запуск, остановку и получение информации о составе запущенных в данный момент экземпляров. Получение детальной информации о торговом счёте оставим на будущее. 

Это потребует предварительного принятия некоторых архитектурных решений, что не так просто, как может показаться на первый взгляд. Сейчас на нас лежит ответственность за будущее развитие проекта. От того, насколько удачными окажутся принятые сейчас решения, зависит легкость дальнейшей реализации и возможные пределы расширения функциональности. Очень не хотелось бы, дойдя до какого-то этапа проекта, выяснить, что для дальнейшего развития нужно вернуться к началу и всё переделать. Так, конечно, тоже бывает, но постараемся этого избежать, за счёт более внимательного отношения к своему выбору.

С другой стороны, важно не утонуть в море возможностей. Поэтому в каких-то вопросах лучше сделать хоть какой-то выбор прямо сейчас, чем заниматься бесконечным загадыванием, как тот или иной выбор отзовётся в будущем. А ещё в некоторых вопросах бывает более эффективно выбрать сейчас что-то простое. То, что потом точно будет переделано на другом, более сложном уровне реализации. Но зато мы не погрязнем в реализации идеальной архитектуры отдельной части проекта и сможем быстрее продвинуть его в целом.


Разделение кода

Добавим несколько новых файлов в наш проект. В файл mt5_control.py вынесем все функции из файла main.py, отвечающие за логику управления терминалами. Таким образом, сейчас в главном файле приложения останутся только функции-обработчики конечных точек (маршрутов) и создание объекта FastAPI с необходимыми настройками.

Для хранения шаблонов HTML-кода страниц, которые должны генерироваться при обработке некоторых конечных точек, создадим папку templates. Пока что разместим в ней один файл index.html для формирования HTML-кода главной страницы (маршрут GET /).

Для хранения статического кода, который будет всегда отправляться браузеру без какой-либо обработки веб-сервером, сделаем папку static. К таким файлам можно отнести, например, файлы таблиц CSS-стилей или JavaScript-кода, на которые есть ссылки из HTML-кода страниц. Добавим сразу парочку таких пустых файлов для главной страницы.

После этого дерево файлов проекта будет выглядеть так:

mt5_manager/

├── main.py                 # Основной файл FastAPI
├── mt5_control.py          # Логика управления запуском и остановкой терминалов
├── templates/
│   └── index.html          # Шаблон основной страницы
└── static/
    ├── styles.css          # CSS-стили
    └── script.js           # JavaScript-код

Будем в дальнейшем придерживаться принятых соглашений по размещению файлов проекта. При необходимости можно будет создавать новые вложенные папки внутри уже имеющихся, если это будет помогать структурированию кода.

Чтобы в нашем приложении можно было воспользоваться механизмом шаблонов и статическими файлами, нужно подключить соответствующие библиотеки в файле main.py:

from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

Затем, после создания объекта приложения (app), мы подключаем новую конечную точку GET /static, при обработке которой любые имена, идущие в URL после static/, будут восприниматься как файл из папки static нашего проекта:

# Создаём объект приложения
app = FastAPI()

# Подключаем статические файлы
app.mount("/static", StaticFiles(directory="static"), name="static")

Заметьте, что в данном случае мы не создаём отдельную функцию-обработчик маршрута GET /static, а вызываем метод объекта приложения, который сам создаст необходимую функцию с нужным содержимым.

Теперь создадим объект, который будет использоваться при необходимости передать в качестве ответа HTML-код, сформированный по определённому шаблону из папки templates:

# Создаём объект для работы с шаблонами HTML-кода
templates = Jinja2Templates(directory="templates")

Обработчик маршрута GET / перепишем таким образом:

@app.get('/', response_class=HTMLResponse)
async def index(request: Request):
    '''Обработчик GET / (корневой каталог)'''
    instances = load_instances()
    return templates.TemplateResponse("index.html", {"request": request, "instances": instances})

В нём мы получаем словарь instances с информацией о зарегистрированных экземплярах терминалов и их текущем статусе (запущен/остановлен). Далее мы возвращаем шаблонный ответ, получаемый из файла index.html с учётом информации из словаря instances. Какая именно информация будет присутствовать в словаре instances, и как она будет трансформироваться в HTML-код, мы посмотрим чуть позже.

В два оставшихся обработчика маршрутов мы тоже внесем небольшие изменения. Они будут однотипны, то есть для обработчика запуска экземпляра мы сделаем то же самое, что и для обработчика остановки. Необходимость изменений вызвана тем, что теперь нужно в обработчик передавать информацию о том, какой экземпляр мы хотим сейчас запустить или остановить. Когда экземпляр был единственный, такой проблемы не возникало.

Добавим в URL этих маршрутов изменяемую часть, обозначенную как {name}. Это обязывает нас добавить в список аргументов функции-обработчика новый аргумент с именем name и типом str (так как имя экземпляра — это строка):

@app.post('/start/{name}')
async def start_instance(name: str):
    '''Обработчик POST /start - запуск терминала'''
    result = start_mt5(name)
    return JSONResponse(result)


@app.post('/stop/{name}')
async def stop_instance(name: str):
    '''Обработчик POST /stop - остановка терминала'''
    result = stop_mt5(name)
    return JSONResponse(result)

Это приведёт к тому, что при запуске обработчика ему в качестве аргумента будет передаваться строка, вырезанная из той части URL, которая соответствует положению {name}. А далее мы уже можем с ней работать внутри функций-обработчиков.

Обновим нашу таблицу маршрутов в связи со сделанными изменениями:

#МетодURLОписание, параметрыФормат
ответа 
1GET/Отображает главную страницу с веб-интерфейсом для управления экземплярами MetaTrader 5
Пока что пусть отображается только заголовок с названием проекта.
HTML
POST/start/{name} Запускает экземпляр терминала MetaTrader 5 с именем {name}JSON
3POST
/stop/{name}Останавливает запущенный процесс MetaTrader 5 с именем {name}JSON

Даже несмотря на то, что проект пока находится на стадии активной разработки, важно уже сейчас формировать устойчивую архитектуру. В идеале код приложения должен быть легко расширяемым — чтобы добавление новых функций, маршрутов или логики управления не требовало переписывания существующих частей.

Именно поэтому имеет смысл придерживаться принципов разделения ответственности и чистой архитектуры, когда:

  • Модуль mt5_control.py отвечает только за работу с процессами терминалов.
  • Файл main.py концентрируется на маршрутах и веб-интерфейсе.
  • Папки templates и static изолируют визуальную часть приложения.

Такое разделение делает проект более устойчивым к изменениям и облегчает тестирование. В будущем, при необходимости, эти части можно будет вынести в отдельные микросервисы.


Экземпляры терминалов

Чтобы можно было запускать несколько экземпляров терминалов MetaTrader 5 на сервере, он там должны физически присутствовать. То есть на сервере должны быть несколько папок, в каждой из располагается запускаемый файл терминала. Существуют разные способы, как можно этого добиться, но сейчас для упрощения предположим, что эту часть работы пользователь будет пока выполнять вручную. 

Проделаем эту операцию. Если на компьютере ещё нет установленного терминала, то необходимо скачать его с сайта брокера или www.metatrader5.com и установить. Создадим папку MT5 с тремя вложенными папками с именами вида MetaTrader5.x и скопируем в них запускаемые файлы терминала (terminal64.exe) и редактора (metaeditor64.exe) из любой имеющийся установленной копии терминала MetaTrader 5:

Выбор конкретных названий для папок терминалов и их корневой папки не играет никакой роли. Мы взяли именно такие, потому что они достаточно короткие и понятные.

В папке каждого терминала запустим файл terminal64.exe в режиме portable и подключимся к какому-то торговому счёту. Здесь мы будем использовать демо-счета от компании MetaQuotes. При необходимости установки обновлений, дождемся завершения этого процесса. Итак, теперь у нас есть три экземпляра терминала, установленные в папки с такими полными путями:

C:/MT5/MetaTrader5.1/terminal64.exe
C:/MT5/MetaTrader5.2/terminal64.exe
C:/MT5/MetaTrader5.3/terminal64.exe

Именно их мы будем запускать и останавливать через создаваемый веб-сервер. Также мы пока не будем касаться вопросов смены торговых счетов, к которым подключены терминалы, то есть сфокусируемся только на запуске и остановке.

В дальнейшем то, что мы сейчас проделали вручную, будет каким-то образом автоматизировано. Но сейчас мы можем не отвлекаться на реализацию: проделав такую подготовительную работу один раз, мы можем теперь долго к ней не возвращаться, сосредоточив усилия на содержательной части проекта.


Формирование списка терминалов

В прошлый раз мы реализовали запуск и остановку одного единственного экземпляра терминала, находившегося по фиксированному пути на нашем сервере. Теперь приступим к реализации выборочного запуска и остановки каждого экземпляра из фиксированного списка. Как уже было сказано, этот код мы перенесли и продолжим писать в новом файле mt5_control.py.

Новых библиотек, кроме уже добавленных subprocess и psutil, нам пока не понадобится. Мы создали три папки с экземплярами терминалов, которые отличаются только названием последней папки в пути к файлу terminal64.exe. Поэтому давайте объявим две константы, содержащие те части пути, которые одинаковы для всех экземпляров:

# Путь к папке с экземплярами терминалов
MT5_FOLDER = 'C:/MT5'

# Имя запускаемого файла терминала
MT5_EXE = 'terminal64.exe'

Теперь можно сделать функцию, которая по имени будет формировать полный путь к запускаемому файлу нужного экземпляра терминала. Сейчас мы просто будем соединять вместе три части полного пути: общую папку, имя экземпляра и имя запускаемого файла:

def instance_path(name: str) -> str:
    '''
    Формирует полный путь по имени экземпляра терминала

    Args:
        name (str): Имя экземпляра терминала.

    Returns:
        path (str): Полый путь к запускаемому файлу экземпляра терминала.
    '''
    return f'{MT5_FOLDER}/{name}/{MT5_EXE}'

Для хранения списка имён экземпляров будем использовать пока обычный список, в котором будут находиться выбранные нами названия папок:

# Список имён экземпляров (пока фиксированный)
instances_names = ['MetaTrader5.1', 'MetaTrader5.2', 'MetaTrader5.3']

По готовому списку имён мы можем однозначно получить полные пути к исполняемым файлам каждого экземпляра терминала. Поскольку они пока тоже будут постоянными, то можно заранее сформировать два вспомогательных словаря, которые будут использованы для получения пути по заданному имени и имени по заданному пути:

# Словарь соответствия имя -> путь
instances_paths = {name: instance_path(name) for name in instances_names}

# Словарь соответствия путь -> имя
paths_instances = {instance_path(name): name for name in instances_names}

После длительных размышлений мы решили отказаться от повторного использования информации о состоянии экземпляров, которая была получена при запуске сервера. Вместо этого, мы при каждой операции будем актуализировать состав запущенных экземпляров. Для этого напишем функцию load_instances(), которая будет формировать словарь instances. В этом словаре ключом будет имя экземпляра, а значением — ещё один словарь с двумя ключами pid и status. В них мы будем хранить уникальный идентификатор запущенного процесса (PID) и статус экземпляра с заданным именем.

Возможно, что хранение этих двух видов информации является избыточным, так как в принципе, по значению PID мы можем судить о том, запущен ли данный экземпляр или нет. Если PID > 0, то процесс выполняется. Но сейчас не совсем понятно, как сделать лучше, поэтому пусть будут храниться оба этих поля. Далее мы, скорее всего, будем расширять словарь с информацией об одном экземпляре.

Вначале мы инициализируем словарь instances для экземпляров одинаковыми значениями:

{'pid': 0, 'status': 'stopped'}

что соответствует незапущенному экземпляру. Затем, используя библиотеку psutil, организуем перебор всех запущенных на компьютере процессов. Для каждого из них мы будем смотреть, не является ли данный процесс одним из экземпляров нашего терминала. Если да, то определяем его имя, PID, и сохраняем их в соответствующем элементе словаря instances.

def load_instances() -> dict:
    '''Получение информации о статусе экземпляров 
    терминала MetaTrader 5 из заданных папок.

    Returns:
        instances (dict): Информация об экземплярах терминала.
    '''

    # Формируем словарь для всех экземпляров по списку имён
    instances = {name: {'pid': 0, 'status': 'stopped'}
                 for name in instances_names}

    # Перебираем все запущенные процессы
    for proc in psutil.process_iter(['pid', 'name', 'exe']):
        try:
            # Получаем путь к запускаемому файлу процесса
            exe_path = str(proc.info['exe']).replace('\\', '/')

            # Если он есть среди путей экземпляров терминала
            if exe_path and exe_path in paths_instanses:
                # Определяем имя экземпляра для данного пути
                name = paths_instanses[exe_path]

                # Берём нужный экземпляр
                instance = instances[name]

                # Запоминаем идентификатор и статус процесса
                instance['pid'] = proc.info['pid']
                instance['status'] = 'running'
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue

    return instances

Эта функция будет использована в обработчике маршрута GET / для формирования актуального состояния всех зарегистрированных экземпляров. Но также она будет использована и при каждой операции запуска и остановки. С её помощью мы сможем не запускать уже запущенный ранее экземпляр и не останавливать уже остановленный.


Запуск терминалов

Посмотрим теперь на реализацию функции запуска экземпляра терминала с определённым именем, передаваемым в качестве аргумента. Сначала мы получаем актуальную информацию о состоянии всех зарегистрированных терминалов. Если переданное имя нашлось среди существующих имён, то проверим, существует ли запущенный процесс для этого экземпляра.

Если нет, то тогда запускаем новый процесс, используя функцию Popen() из библиотеки subprocess. В качестве аргумента ей надо передать список аргументов командной строки запуска нужного исполняемого файла. Первым в этом списке идёт полный путь к запускаемому файлу, а дальше — ключ /portable для использования текущей папки терминала в качестве рабочей.

После успешного запуска, мы получаем значение уникального идентификатора процесса в операционной системе (PID) и вместе со статусом "running" сохраняем его в элементе словаря экземпляров для возврата из функции:

def start_mt5(name: str) -> dict:
    '''
    Запуск экземпляра терминала

    Args:
        name (str): Имя экземпляра терминала.

    Returns:
        instance (dict): Информация о запущенном терминале.
    '''

    # Получаем информацию об экземплярах терминала
    instances = load_instances()

    # Если имя экземпляра есть среди имеющихся
    if name in instances:
        # Берём нужный экземпляр
        instance = instances[name]

        # Если для него нет запущенного процесса, то
        if not instance['pid']:
            # Запускаем новый процесс
            process = subprocess.Popen([instanсes_paths[name], '/portable'])

            # Идентификатор запущенного процесса
            pid = process.pid

            # Запоминаем идентификатор и статус процесса
            instance['pid'] = pid
            instance['status'] = 'running'

        # Возвращаем результат - успешный запуск
        return {name: instance}

    # Возвращаем результат - имя не найдено
    return {name: {'status': 'not found'}}

Если же в запросе пришло имя экземпляра, которого нет среди зарегистрированных имён, то ничего не делаем и возвращаем информацию о том, что такой экземпляр не найден.


Остановка терминалов

Функция остановки экземпляра терминалов работает похожим образом. Ей также передаётся имя экземпляра, полученное из запроса. Также формируется актуальный состав всех зарегистрированных экземпляров с их текущим статусом. Если имя экземпляра корректное, то берём его PID, и создаём объект, позволяющий управлять процессом с заданным идентификатором. Посылаем процессу команду остановки и ждем не более 10 секунд завершения его работы:
def stop_mt5(name: str) -> dict:
    '''
    Остановка терминала

    Args:
        name (str): Имя экземпляра терминала.

    Returns:
        instance (dict): Информация о запущенном терминале.
    '''

    # Получаем информацию об экземплярах терминала
    instances = load_instances()

    # Если имя экземпляра есть среди имеющихся
    if name in instances:
        # Берём нужный экземпляр
        instance = instances[name]

        # Берём идентификатор его процесса
        pid = instance['pid']

        # Если ранее терминал был запущен
        if pid:
            try:
                # Получаем объект процесса терминала
                p = psutil.Process(pid)

                # Останавливаем процесс
                p.terminate()
                p.wait(10)
            except psutil.NoSuchProcess:
                pass

        # Удаляем информацию о запущенном ранее процессе
        instance['pid'] = 0
        instance['status'] = 'stopped'

        # Возвращаем результат - успешная остановка
        return {name: instance}

    # Возвращаем результат - процесс не найден
    return {name: {'status': 'not found'}}

Если процесс благополучно завершился, то возвращаем информацию об этом. Если нет, то всё равно считаем его завершённым. В текущих условиях мы пока не столкнулись с какими-либо проблемами при завершении процессов, поэтому оставим эту функцию именно в таком виде. Когда же проблемы возникнут, тогда и подумаем о дальнейшей доработке.

Ну и если так оказалось, что мы пытаемся остановить уже завершённый процесс (например, мы могли вручную закрыть окно запущенного терминала), то просто вернётся сообщение о том, что процесс не найден.


Главная страница

Теперь настала очередь заняться HTML-шаблоном формирования главной страницы. В нём нам надо сделать несколько вещей. Во-первых, подключим внешние статические файлы с CSS и JS-кодом. Во-вторых, добавим на страницу ненумерованный список, каждый элемент которого будет представлять собой информацию об одном экземпляре терминала.

Для его формирования воспользуемся движком шаблонов Jinja2. В обработчике маршрута GET / мы передали объекту шаблона наш сформированный словарь с информацией о зарегистрированных экземплярах. Он выглядит примерно так:

{
    "MetaTrader5.1": {
        "pid": 23820,
        "status": "running"
    },
    "MetaTrader5.2": {
        "pid": 24888,
        "status": "running"
    },
    "MetaTrader5.3": {
        "pid": 0,
        "status": "stopped"
    }
}

Используя конструкцию {% for name, data in instances.items() %} ... {% endfor %}, мы можем организовать цикл, который будет генерировать однотипные куски HTML-кода, подставляя в нужные места значения переменных цикла. В данном случае в переменную name будут попадать по очереди названия наших экземпляров терминалов: 'MetaTrader5.1', 'MetaTrader5.2', 'MetaTrader5.3'. А в переменную data будет попадать словарь с ключами pid и status для каждого экземпляра из словаря instances.

Также при необходимости мы можем использовать конструкцию условного оператора {% if data.pid > 0 %} ... {% else %} ... {% endif %}. Благодаря ей мы отображаем для каждого экземпляра только одну кнопку для выполнения возможного действия: запуска для остановленного экземпляра и остановки для запущенного.

<!DOCTYPE html>
<html>

<head>
  <title>MT5 Manager</title>
  <link rel="stylesheet" href="/static/styles.css">
</head>

<body>
  <h1>MT5 Manager</h1>
  <div class="instances">
    <h2>Instances</h2>
    <ul id="instanceList">
      {% for name, data in instances.items() %}
      <li class="{{ data.status }}">
        <strong>{{ name }}</strong>
        {% if data.pid > 0 %}
        (PID: {{ data.pid }})
        <button onclick="stopInstance('{{ name }}')">Stop</button>
        {% else %}
        <button onclick="startInstance('{{ name }}')">Start</button>
        {% endif %}
      </li>
      {% endfor %}
    </ul>
  </div>
  
  <script src="/static/script.js"></script>
</body>

</html>

При нажатии на кнопки на главной странице, будет выполняться JS-код в функциях startInstance() и stopInstance().


Обработка нажатий

Функции-обработчики событий нажатия на кнопки Start и Stop будут располагаться в отдельном файле script.js, который мы разместили в папке /static. Их код почти одинаков и отличается только тем, на какую конечную точку функция будет посылать POST-запрос. Переданное в аргументе имя экземпляра терминала подставляется в URL запроса и дальше ожидается получение ответа от нашего же веб-сервера. После получения ответа, он выводится в консоль JavaScript браузера, и текущая страница перезагружается:

/**
 * Запуск терминала
 * @param {string} name - Имя экземпляра
 */
async function startInstance(name) {
  // Посылаем запрос по нужному маршруту
  const res = await fetch(`/start/${name}`, { method: "POST" });

  // Получаем ответ
  const data = await res.json();

  // Здесь можно добавить дополнительные действия 
  console.log(data);

  // Перезагружаем страницу
  location.reload();
}
/**
 * Остановка терминала
 * @param {string} name - Имя экземпляра
 */
async function stopInstance(name) {
  // Посылаем запрос по нужному маршруту
  const res = await fetch(`/stop/${name}`, { method: "POST" });

  // Получаем ответ
  const data = await res.json();

  // Здесь можно добавить дополнительные действия
  console.log(data);

  // Перезагружаем страницу
  location.reload();
}

В дальнейшем информацию, получаемую в переменной data, можно использовать для показа каких-либо дополнительных сообщений или обновления кода страницы без её полной перезагрузки. Но пока у нас на главной странице выводится очень мало информации, можно её перезагружать полностью. 

Для улучшения внешнего вида главной страницы, добавим немного стилей в файл styles.css, располагающийся в папке /static. Не будем пока подтягивать мощные фреймворки стилизации HTML вроде Bootstrap, мы всегда сможем ими воспользоваться после того, как проект приобретёт более внятные очертания. Пока же мы только нащупываем, как мог бы выглядеть интерфейс взаимодействия с пользователем, и поэтому не стоит сразу уделять большого внимания внешнему виду.

Но немного — можно! Например, мы добавили к каждому элементу списка дополнительный CSS-класс, совпадающий со статусом экземпляра. А в файле styles.css добавили правила, задающие разный цвет фона для элемента в зависимости от наличия класса running:

.instances li.running {
    background: #d1ffd1;
    border: 1px solid #89fd85;
}

Теперь запущенные и незапущенные экземпляры будут хорошо отличимы друг от друга.


Тестирование

Запустим веб-сервер командой из рабочей папки проекта:

uvicornmain:app--reload--no-use-colors

и откроем в браузере страницу по адресу http://127.0.0.1:8000. 

Пока ни один экземпляр терминала не запущен, мы будем видеть примерно следующее:

Нажмём на кнопку Start для первого и второго экземпляра. Спустя мгновение мы увидим, что они перешли в состояние выполняющихся, что подтверждается Диспетчером задач Windows, где мы видим два процесса с такими же идентификаторами, как и на главной странице нашего веб-интерфейса:

Также можно проверить, что система автоматической документации подхватила сделанные нами комментарии и уточнения про маршруты и их параметры:

Как видно, для маршрута POST /start/{name} есть описание как самого маршрута, так и параметра {name} с примером возможного значения. 


Заключение

На данном этапе проект MetaTrader 5 Manager превратился из идеи и простого прототипа в полноценное приложение, которое уже способно управлять несколькими экземплярами торговых терминалов MetaTrader 5 через удобный веб-интерфейс. Мы шаг за шагом отделили логику работы с процессами от серверной части, добавили маршруты для управления экземплярами, реализовали простую визуализацию состояния терминалов и сделали первые шаги к структурированию кода проекта.

Несмотря на то, что текущее решение ещё далеко от окончательного, именно в таком виде оно уже выполняет свою основную задачу — позволяет централизованно запускать и останавливать терминалы, а также получать информацию об их текущем состоянии. Таким образом, у нас появился прочный фундамент, на котором можно строить более сложные уровни функциональности.

В дальнейшем мы будем развивать проект в нескольких направлениях:

  • автоматизация регистрации новых экземпляров терминалов и хранение информации о них в базе данных;
  • расширение взаимодействия с Python MetaTrader 5 API для получения статистики, ордеров и торговых историй;
  • улучшение интерфейса — добавление динамических обновлений без перезагрузки страницы и визуальных элементов управления;
  • возможное выделение интерфейсной части в отдельный фронтенд-сервис.

Также необходимо продумать систему ограничения доступа. Для этого можно добавить базовую авторизацию или работу через защищённый канал HTTPS.

Кроме того, стоит предусмотреть ведение журнала операций: когда и кто запускал или останавливал терминалы. Эти данные могут быть полезны для аудита и анализа использования системы.

Но главное, что уже сейчас можно видеть: выбранная архитектура и стек технологий (Python + FastAPI + Jinja2 + psutil) позволяют гибко развивать проект, не теряя управляемости и прозрачности. Это отличный пример того, как из простого REST API можно шаг за шагом вырастить полноценную систему управления реальными торговыми процессами.

Спасибо за внимание и до следующей встречи!


Содержание архива

#
ИмяВерсияОписаниеПоследние изменения
 mt5-manager/ Рабочая папка проекта веб-сервера терминалов 
1├─ main.py0.1.0
Веб-приложение для веб-сервера терминалов
Часть 2
2├─ mt5_control.py0.1.0Логика управления запуском и остановкой терминалов  Часть 2

├─ templates/   
3│   └─ index.html
0.1.0Шаблон основной страницыЧасть 2
 └─ static/   
4     ├─ styles.css
0.1.0CSS-стили
Часть 2
5     └─ script.js  0.1.0JavaScript-код
Часть 2

Также исходный код проекта доступен в репозитории mt5-manager.

Прикрепленные файлы |
mt5-manager.zip (4.38 KB)
Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами
В настоящей статье мы представляем стратегию лейеринга, которая сочетает индикаторы MACD и RSI со статистическими методами для автоматизации динамической торговли на MQL5. Мы исследуем архитектуру этого каскадного подхода, подробно описываем его реализацию с помощью ключевых сегментов кода и даем рекомендации читателям по тестированию на истории для оптимизации эффективности. Наконец, в заключение мы подчеркиваем потенциал стратегии и закладываем основу для дальнейших усовершенствований в автоматической торговле.
От начального до среднего уровня: Struct (IV) От начального до среднего уровня: Struct (IV)
В данной статье мы рассмотрим, как создавать так называемый структурный код, в котором весь контекст и способы манипулирования переменными и информацией помещаются в структуру, чтобы создать подходящий контекст для реализации любого кода. Итак, мы рассмотрим необходимость использования приватной (private) части кода, чтобы отделить то, что является общедоступным, от того, что не является таковым, соблюдая тем самым правило инкапсуляции и сохраняя контекст, для которого была создана структура данных.
Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS) Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS)
Метаэвристический алгоритм, имитирующий рост коронарных артерий в сердце человека для задач оптимизации. Использует принципы ангиогенеза (роста новых сосудов), бифуркации (разветвления) и обрезки слабых ветвей для поиска оптимальных решений в многомерном пространстве. Проверка его эффективности на широком спектре задач принесла неожиданные результаты.
Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Основные компоненты) Нейросети в трейдинге: Спайково-семантический подход к пространственно-временной идентификации (Основные компоненты)
В статье мы подробно рассмотрели интеграцию модуля SSAM в блок SEW‑ResNeXt, демонстрируя, как фреймворк S3CE‑Net позволяет эффективно объединять спайковое внимание с остаточными блоками. Такая архитектура обеспечивает точную обработку временных и пространственных потоков данных и высокую стабильность обучения. Модульность и гибкость компонентов упрощают расширение модели и повторное использование проверенных методов.