П/ВИН

Трейдер статистика MT5: дашборд на Python

·9 мин чтения

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

Что хотели получить

Цель была конкретная: трейдер торгует золотом (XAUUSD) на RoboForex через MetaTrader 5 с айфона. Нужен дашборд, который показывает:

  • График цены со сделками — свечи, стрелки входа и выхода, линии Stop Loss и Take Profit, цветовые зоны прибыли и убытка
  • Кривую эквити — как рос или падал депозит от сделки к сделке
  • Статистику — winrate, профит-фактор, средний R:R, разбивку по дням недели и часам торговли
  • Фильтры — по символу, периоду, направлению (buy/sell)

И всё это в автоматическом режиме — без ручных выгрузок CSV каждый раз.

Проблема с источником данных

Первый вопрос, который встал сразу: откуда брать котировки для графика? Казалось бы, возьми с Yahoo Finance или TwelveData — и готово. Но здесь есть важный нюанс, который многие упускают.

Внешние API дают биржевое золото (спот-цена CME или LBMA), а трейдер торгует у брокера RoboForex, у которого своя котировка с собственным спредом. Если наложить точки входа с MT5 на свечи с Yahoo — они будут «ехать» относительно друг друга. Иногда незначительно, иногда критично — особенно на коротких таймфреймах.

Правильное решение: брать котировки прямо из MT5 через функцию CopyRates. Тогда свечи на дашборде будут ровно те же, что трейдер видит в терминале.

Это определило архитектуру: нам нужен не просто парсер файлов, а живое подключение к MT5.

Три пути автоматизации

Мы рассмотрели три варианта:

Вариант A: Инвесторский пароль + MT5 на сервере. Самый чистый подход. Инвесторский пароль даёт read-only доступ к счёту — можно смотреть историю и открытые позиции, но нельзя торговать. Запускаем MT5 на сервере, подключаемся с инвесторским паролем, забираем данные через Python API.

Вариант B: MQL5-скрипт + WebSocket. Пишем эксперт-советник, который сам отправляет данные на наш сервер. Требует доступа к терминалу на десктопе.

Вариант C: Ручная выгрузка отчёта. Самый простой старт — экспортируем HTML или XLSX из терминала и парсим.

Мы начали с варианта C для быстрого прототипа, параллельно готовя инфраструктуру для варианта A.

Парсер отчётов MT5

Первым делом написали универсальный парсер, который умеет читать оба формата выгрузки — XLSX и HTML — с поддержкой русских и английских заголовков (терминал может быть на любом языке).

В процессе отладки поймали нетривиальный баг: строки депозита в разделе «Сделки» идут без символа инструмента, и парсер обрывал чтение на первой же такой строке. Плюс функция build_columns переименовывала колонку time в open_time даже когда она была одна — это верно для раздела «Позиции» (там два столбца «Время»), но ломало раздел «Сделок».

def build_columns(df, section):
    cols = list(df.columns)
    time_cols = [c for c in cols if 'time' in c.lower()]
    
    # Переименование только при реальном дубле
    if section == 'positions' and len(time_cols) >= 2:
        df = df.rename(columns={time_cols[0]: 'open_time', time_cols[1]: 'close_time'})
    elif section == 'deals' and len(time_cols) == 1:
        df = df.rename(columns={time_cols[0]: 'time'})  # оставляем как есть
    
    return df

Ещё один баг нашли при расчёте MAE/MFE (максимальное неблагоприятное/благоприятное отклонение): время сделок приводилось к UTC, а время свечей оставалось в серверном времени брокера. Окно для расчёта съезжало на несколько часов и брало не те бары. Решение — привести обе шкалы к единому формату перед любыми сравнениями.

def normalize_timestamps(deals_df, candles_df, server_offset_hours=3):
    """Приводим всё к UTC перед сравнением"""
    deals_df['time_utc'] = pd.to_datetime(deals_df['time'], utc=True)
    
    # Свечи MT5 в серверном времени (обычно UTC+2 или UTC+3)
    candles_df['time_utc'] = (
        pd.to_datetime(candles_df['time'])
        .dt.tz_localize(f'Etc/GMT-{server_offset_hours}')
        .dt.tz_convert('UTC')
    )
    return deals_df, candles_df

Wine в Docker: эпопея с установкой MT5

Для варианта A с инвесторским паролем нужно запустить MetaTrader 5 на Linux-сервере. MT5 — это Windows-приложение, поэтому без Wine не обойтись.

Сервер на Hetzner с Ubuntu 24.04, ресурсов достаточно. Принципиально важно: собираем свой Docker-образ, а не берём чужой готовый. Когда в дело идут учётные данные торгового счёта, доверять стороннему контейнеру нельзя.

Первая попытка с обычным Wine провалилась сразу: установщик MT5 показывал диалог «A debugger has been found running in your system» и отказывался работать. Это защита MetaQuotes от реверс-инжиниринга — она сканирует окружение на признаки отладчика.

Перебрали все гипотезы по очереди:

  1. Запуск от root — Wine не любит root, но смена пользователя не помогла
  2. Версия Windows в Wine — переключили на Windows 10 (winecfg), установщик стал запускаться, но всё равно падал
  3. Ограничения ptrace в Docker — добавили --cap-add SYS_PTRACE, не помогло
  4. Регистрация winedbg — Wine регистрирует свой отладчик как обработчик аварий, защита MT5 его видит. Убрали регистрацию — не помогло

Решение нашлось на пятой итерации: wine-staging. Это экспериментальная ветка Wine с патчами, которые ещё не вошли в стабильную версию. Именно она обходит защиту установщика MT5.

FROM ubuntu:24.04
 
# Добавляем репозиторий wine-staging
RUN dpkg --add-architecture i386 && \
    wget -qO- https://dl.winehq.org/wine-builds/winehq.key | apt-key add - && \
    add-apt-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ noble main' && \
    apt-get update && \
    apt-get install -y --install-recommends winehq-staging
 
# Подавляем диалоги Mono/Gecko при инициализации
ENV WINEDLLOVERRIDES="mscoree,mshtml="
ENV WINEDEBUG="-all"
 
# Инициализируем Wine-префикс без интерактивных диалогов
RUN WINEPREFIX=/wine WINEARCH=win64 wineboot --init 2>/dev/null; \
    wineserver --wait

После переключения на wine-staging установщик прошёл без единого диалога, папка MetaTrader 5 создалась, terminal64.exe оказался на месте.

Дизайн дашборда

Пока шла эпопея с Wine, параллельно разрабатывали визуальную часть. Принципиальное решение по дизайну: не делать «золотой» дашборд.

Классическая ошибка — взять тему «золото» и залить всё жёлтым с блестящими градиентами. Это выглядит дёшево и отвлекает от данных. Вместо этого выбрали палитру «пробирной лаборатории»:

  • Основа: графит с зеленовато-синим отливом (#1a1f2e, #242938)
  • Акцент: приглушённая латунь #B8935A — единственный «золотой» элемент, используется точечно
  • Прибыль: сдержанный нефрит #4CAF7D
  • Убыток: оксид железа #E05252
  • Без неона — никаких кислотных цветов

Для шрифтов нашли отличное решение: Nimbus Sans Narrow — узкий гротеск с полной кириллицей, который есть на сервере локально. Для плотных торговых таблиц узкий шрифт критичен — в одну строку влезает больше данных. И он не похож на дефолтные системные шрифты, что сразу даёт характер.

Графики рисуем на Canvas через JavaScript — это даёт плавность при зуме и скролле, чего не добиться с SVG на большом количестве свечей.

В процессе отладки нашли несколько UX-багов:

  • Числа смешивали запятую и точку («−181,50» рядом с «1.71%») — привели к единому русскому формату
  • «2 сделок» вместо «2 сделки» — добавили правильное склонение
  • Маркеры сделок терялись на фоне свечей — добавили обводку контрастным цветом
  • Сессии считались по UTC, а подпись говорила «время сервера» — исправили подписи

Деплой на mt5.pashavin.ru

Дашборд разворачиваем на собственном сервере. Домен pashavin.ru на Vercel DNS — добавляем A-запись для поддомена mt5.

В качестве reverse proxy используем Caddy — он уже стоит на сервере и автоматически выпускает TLS-сертификаты через Let's Encrypt.

Обязательно ставим базовую авторизацию: данные торгового счёта не должны лежать в открытом доступе.

mt5.pashavin.ru {
    basicauth {
        # Хеш генерируется: caddy hash-password
        trader $2a$14$...
    }
    
    reverse_proxy localhost:8050
    
    log {
        output file /var/log/caddy/mt5.log
    }
}

При перезагрузке конфига Caddy столкнулись с интересным багом: systemctl reload caddy висел бесконечно из-за особенности unit-файла (ждёт notify, которого Caddy не отправляет). Плюс файл лога создался от root с правами 600, и Caddy под своим пользователем не мог в него писать. Решение — исправить владельца лога и перезагружать конфиг напрямую через caddy reload, а не через systemctl.

# Исправляем права на лог
chown caddy:caddy /var/log/caddy/mt5.log
 
# Перезагружаем конфиг напрямую
caddy reload --config /etc/caddy/Caddyfile

Проверка: без пароля получаем 401, с паролем — 200. Остальные сайты не затронуты.

Конвейер обновления данных

Архитектура конвейера:

  1. Сборщик данных (Python + MetaTrader5 под Wine) — запускается по расписанию, подключается к счёту с инвесторским паролем, забирает историю сделок и свечи
  2. Аналитический модуль — собирает позиции из сделок, считает метрики
  3. Генератор дашборда — рендерит HTML с графиками
  4. Caddy — отдаёт статику с авторизацией

Сборка позиций из сделок — самая хитрая часть. MT5 хранит не позиции, а сделки (deals): открытие и закрытие — это две отдельные записи, связанные через position_id. Нужно правильно их сматчить:

def build_positions(deals_df):
    """Собираем позиции из пар сделок MT5"""
    positions = []
    
    # Группируем по position_id
    for pos_id, group in deals_df.groupby('position_id'):
        group = group.sort_values('time')
        
        entry = group[group['entry'] == 'in'].iloc[0]
        exits = group[group['entry'] == 'out']
        
        if exits.empty:
            # Позиция ещё открыта
            continue
            
        exit_deal = exits.iloc[-1]
        
        positions.append({
            'position_id': pos_id,
            'symbol': entry['symbol'],
            'type': 'buy' if entry['type'] == 0 else 'sell',
            'volume': entry['volume'],
            'open_time': entry['time'],
            'open_price': entry['price'],
            'close_time': exit_deal['time'],
            'close_price': exit_deal['price'],
            'profit': group['profit'].sum(),
            'commission': group['commission'].sum(),
            'swap': group['swap'].sum(),
        })
    
    return pd.DataFrame(positions)

Результат

Что получили в итоге:

  • Парсер читает оба формата выгрузки MT5 (XLSX и HTML) на русском и английском
  • Wine-staging в Docker успешно запускает MetaTrader 5 на Linux-сервере
  • Подключение с инвесторским паролем даёт автоматический сбор данных без ручных выгрузок
  • Дашборд доступен по адресу с авторизацией, TLS-сертификат выпущен автоматически
  • Дизайн с палитрой «пробирной лаборатории» и узким гротеском выглядит профессионально, не как шаблонный трейдерский дашборд

Выводы и уроки

Главный урок этого проекта — источник данных определяет всю архитектуру. Казалось бы, можно взять котировки с любого публичного API. Но как только понимаешь, что точки входа должны совпадать со свечами брокера, а не с биржевой ценой — весь стек меняется. Вместо простого HTTP-запроса к Yahoo Finance нужен Wine, Docker, инвесторский пароль и конвейер обновления.

Второй урок: Wine — это не «просто запустить .exe». Разные версии Wine ведут себя принципиально по-разному с защищёнными приложениями. Стабильная версия может не работать там, где staging работает отлично. Всегда стоит проверять обе ветки, прежде чем искать более сложные решения.

Третий урок: параллельная разработка спасает время. Пока Wine компилировался и устанавливался (это занимало десятки минут), мы писали парсер, отлаживали аналитический модуль, делали дизайн, настраивали Caddy. В итоге к моменту, когда MT5 заработал под Wine, всё остальное уже было готово.

Четвёртый урок касается безопасности: инвесторский пароль — правильный инструмент для мониторинга. Он даёт read-only доступ к счёту, и даже если сервер скомпрометирован, торговые операции провести нельзя. Это принцип минимальных привилегий в действии. Плюс базовая авторизация на дашборде — данные счёта не должны быть публичными.

Проект продолжается: следующий шаг — live-обновление открытых позиций и алерты в Telegram при закрытии сделки.

Полезные ссылки

Паша Вин
Паша Вин

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.