preview
TradeMux как Quant Backbone: Подключение институциональных Python-пайплайнов к разным терминалам и брокерам

TradeMux как Quant Backbone: Подключение институциональных Python-пайплайнов к разным терминалам и брокерам

MetaTrader 5Торговые системы |
77 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Введение

Профессиональный quant-разработчик пишет на MQL5 и Python — и каждый язык решает свою задачу. Python — среда исследования: данные, модели, бэктесты, факторные пайплайны, риск-расчёты. MQL5 и MetaTrader 5 — среда исполнения: брокерский коннект, торговый сервер, нативная скорость обработки ордеров. Задача одна: соединить их чисто и надёжно, не дублируя логику на двух сторонах.

TradeMux — инфраструктурный мост между Python-пайплайном и терминалом MetaTrader 5. Он не подменяет ни исследовательскую экосистему Python, ни MQL5-движок исполнения MetaTrader 5 — он устраняет операционный разрыв между ними. Python генерирует сигнал, TradeMux доставляет его в терминал, MetaTrader 5 исполняет ордер у брокера. Помимо этого, TradeMux берёт на себя кросс-брокерную нормализацию: один и тот же Python-код работает через RoboForex, IC Markets, Alpari и OANDA без единого изменения в логике.

После прочтения статьи в вашем распоряжении окажется полноценный Python-сервис исполнения (execution service). Он включает:

  • подключение к MetaTrader 5 через TradeMux Python SDK с единственным API-ключом;
  • чтение состояния счёта и открытых позиций через нормализованный интерфейс;
  • генерацию сигналов из Python-пайплайна с интеграцией CatBoost-модели на L1-признаках;
  • исполнение рыночных ордеров с предторговым риск-контролем в Python;
  • мониторинг открытых позиций и аварийное закрытие через kill_switch();
  • мультисчётный broadcast одного сигнала на несколько терминалов MetaTrader 5;
  • supervisor-паттерн с автоматическим перезапуском после аварийных событий.


Две среды — два назначения

Python и MetaTrader 5 решают разные задачи, и это разделение не случайно — оно отражает реальную структуру quant-разработки.

Python — среда исследования и расчётов. pandas и numpy обеспечивают векторизованную обработку временных рядов: загрузку 45 000 баров H1 по 29 инструментам, каузальную (без look-ahead) L1-фильтрацию через ADMM и построение пространства признаков. Всё это можно реализовать в одном пайплайне. scikit-learn, CatBoost, LightGBM и PyTorch дополняются Optuna (байесовская оптимизация) и SHAP (интерпретируемость). Экосистема ML-инструментов постоянно обновляется. Backtesting-фреймворки, walk-forward validation с purge+embargo по López de Prado, параметрическая VaR-аллокация на мультиинструментальный портфель — всё это Python-нативные задачи, решаемые в рамках единого исследовательского стека.

MetaTrader 5 — среда исполнения сделок. MetaTrader 5 обеспечивает прямой коннект к торговому серверу брокера, нативную скорость обработки ордеров, встроенное управление позициями и полный доступ к котировочному потоку. Это зрелая, проверенная платформа с широчайшим брокерским покрытием, которую используют миллионы трейдеров по всему миру.

TradeMux — мост между этими двумя средами. Он позволяет Python-системе управлять терминалом MetaTrader 5 через нормализованный SDK, не требуя дублирования исследовательской логики на стороне терминала. Python считает, TradeMux доставляет, MetaTrader 5 исполняет.


Практическая архитектура

Производственная архитектура для Python-нативной quant-системы состоит из четырёх слоёв с чёткими зонами ответственности.

Слой 1: Research & Signal Engine (Python). Jupyter-ноутбуки и Python-скрипты, где живут данные, модели и бэктесты. Этот слой генерирует торговые решения: {symbol: direction, lot, confidence}. Он работает с историческими данными, строит признаки, обучает модели и производит все расчёты — от L1-фильтрации до VaR-аллокации лотов.

Слой 2: Execution Service (Python). Лёгкий Python-процесс, который принимает сигналы от signal engine и управляет жизненным циклом позиций. Он инкапсулирует предторговые риск-проверки, логику входа и выхода, мониторинг открытых позиций и аварийные защиты. Весь этот код — Python, под полным инженерным контролем разработчика.

Слой 3: TradeMux SDK/API (мост). Нормализованный интерфейс между execution service и терминалом MetaTrader 5. Python вызывает методы SDK — TradeMux транслирует их в команды для EA. Этот слой абстрагирует брокерскую специфику: символьные суффиксы, минимальные лоты, шаг лота, режимы исполнения. Один и тот же Python-код работает с любым подключённым брокером.

Слой 4: MetaTrader 5 Terminal + TradeMux EA (исполнение). MetaTrader 5 с прикреплённым TradeMux EA на любом графике. EA принимает команды через цикл WebRequest и исполняет их нативными торговыми функциями MetaTrader 5. Zero-DLL архитектура: никаких сторонних библиотек, никаких проблем с безопасностью на VPS.

# Схема архитектуры
#
#  ┌─────────────────────────────────────────────────────┐
#  │     PYTHON — Research / Signal Engine               │
#  │     pandas · CatBoost · L1 Filter · VaR Alloc       │
#  └──────────────────────┬──────────────────────────────┘
#                         │  {symbol, direction, lots}
#  ┌──────────────────────▼──────────────────────────────┐
#  │     PYTHON — Execution Service                      │
#  │     pre-trade risk · monitor · kill_switch()        │
#  └──────────────────────┬──────────────────────────────┘
#                         │  SDK calls
#  ┌──────────────────────▼──────────────────────────────┐
#  │     TRADEMUX — SDK / API Bridge                     │
#  │     normalized schemas · broker abstraction         │
#  └──────────────────────┬──────────────────────────────┘
#                         │  WebRequest loop (zero DLL)
#  ┌──────────────────────▼──────────────────────────────┐
#  │     MetaTrader 5 — Terminal + TradeMux EA           │
#  │     native execution · full broker connectivity     │
#  └──────────────────────┬──────────────────────────────┘
#                         │
#  ┌──────────────────────▼──────────────────────────────┐
#  │     Broker A / Broker B / Broker C                  │
#  │     RoboForex · IC Markets · Alpari · OANDA · …     │
#  └─────────────────────────────────────────────────────┘


Настройка на стороне MetaTrader 5: один EA, ноль торговой логики

На стороне MetaTrader 5 настройка состоит из трёх шагов. Первый: скачать и установить TradeMux EA из маркетплейса или с официального сайта. Второй: в настройках MetaTrader 5 разрешить WebRequest для домена TradeMux API — Tools → Options → Expert Advisors → Allow WebRequest for listed URL. Третий: прикрепить EA к любому графику и ввести API-ключ в параметрах входа.

TradeMux EA выполняет ровно одну функцию: принимает команды от Python через цикл WebRequest и передаёт их нативным торговым функциям MetaTrader 5. Мост работает полностью без DLL-зависимостей — это ключевое архитектурное решение, которое обеспечивает безопасность на VPS и совместимость с любыми брокерскими конфигурациями. Вся стратегическая логика — расчёты, модели, риск-контроль — остаётся в Python.


Подключение и чтение состояния счёта

Инициализация клиента — единственная точка входа в TradeMux SDK. Метод get_account_info() возвращает нормализованную структуру: поля одинаковы для MetaTrader 4/MetaTrader 5 и не зависят от брокера (в т.ч. OANDA). Это принципиально важно для мультисчётных операций — один и тот же Python-код читает счёт в RoboForex и IC Markets без каких-либо изменений.

import time
import logging
import numpy as np
import pandas as pd
from trademux import TradeMuxClient

# ─── Конфигурация ────────────────────────────────────────────────
API_KEY          = "tm_live_xxxxxxxxxxxxxxxxxxxx"
ACCOUNT_ID       = "RBF-123456"
BASE_URL         = "https://api.trademux.io/v1"

MAX_DRAWDOWN_USD = 300.0   # абсолютный стоп на просадку счёта
MAX_OPEN_LOTS    = 2.0    # суммарный лот по всем позициям
EQUITY_HIGH_MARK = 0.0    # обновляется после первого чтения счёта

SYMBOLS = [
    "EURUSD", "GBPUSD", "USDJPY", "USDCHF",
    "AUDUSD", "USDCAD", "NZDUSD", "EURGBP",
]

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)-8s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("trademux_service")


def init_client() -> TradeMuxClient:
    """Создаёт клиент TradeMux и выводит начальное состояние счёта."""
    client = TradeMuxClient(
        api_key=API_KEY,
        account_id=ACCOUNT_ID,
        base_url=BASE_URL,
    )
    info = client.get_account_info()
    log.info(
        "Connected | Balance=%.2f %s | Equity=%.2f | Margin=%.1f%%",
        info["balance"], info["currency"],
        info["equity"],
        info["margin_level"],
    )
    return client


def snapshot_account(client: TradeMuxClient) -> dict:
    """Возвращает текущий снимок счёта и открытых позиций."""
    info      = client.get_account_info()
    positions = client.get_positions()
    open_lots = sum(p["lots"] for p in positions)
    float_pnl = sum(p["profit"] for p in positions)
    return {
        "balance"     : info["balance"],
        "equity"      : info["equity"],
        "margin_level": info["margin_level"],
        "open_lots"   : open_lots,
        "float_pnl"   : float_pnl,
        "positions"   : positions,
        "n_positions" : len(positions),
    }

Метод get_positions() возвращает список словарей с полями ticket, symbol, type, lots, open_price, profit — нормализованный формат, одинаковый для всех брокеров. Это позволяет строить мониторинг и риск-логику в Python один раз и применять её к любому подключённому терминалу без адаптации.


Загрузка рыночных данных через SDK

Метод get_ohlc() возвращает исторические бары из MetaTrader 5 напрямую в Python-процесс. TradeMux транслирует запрос через EA и возвращает нормализованный массив OHLCV. Таймфрейм, количество баров и символ — параметры SDK. Полученный DataFrame передаётся непосредственно в функцию генерации признаков: Python получает данные из MetaTrader 5 и тут же применяет к ним полный исследовательский пайплайн.

def fetch_ohlc_dataframe(
    client: TradeMuxClient,
    symbol: str,
    timeframe: str = "H1",
    bars: int = 200,
) -> pd.DataFrame:
    """Загружает OHLC из MetaTrader 5 через TradeMux SDK и возвращает DataFrame."""
    raw = client.get_ohlc(symbol=symbol, timeframe=timeframe, bars=bars)
    df  = pd.DataFrame(raw, columns=["time", "open", "high", "low", "close", "volume"])
    df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
    df.set_index("time", inplace=True)
    return df

Если сигнальная логика построена на L1 Trend Filter, следующий шаг — каузальная (без look-ahead) фильтрация скользящим окном на полученных ценах закрытия, затем построение признаков и предсказание CatBoost-модели. Вся эта цепочка остаётся в Python. MetaTrader 5 предоставляет данные котировок через TradeMux и принимает команды на исполнение — это его роль в архитектуре.


Сигнальная логика: Python считает, MetaTrader 5 исполняет

Генерация торгового сигнала происходит полностью внутри Python. Расчётная часть — ML-модель, L1-фильтр, факторный пайплайн — не покидает Python-процесс. TradeMux принимает уже готовое торговое решение и доставляет его в MetaTrader 5. Ниже показан пример momentum-сигнала на основе OHLC-данных; в production-системе здесь стоит CatBoost с L1-фильтрованными признаками — интерфейс функции остаётся тем же.

def compute_signal(df: pd.DataFrame) -> str:
    """
    Momentum-сигнал на основе MACD + наклона тренда.
    В production: заменить на ML-предсказание CatBoost.
    Возвращает: 'BUY' | 'SELL' | 'HOLD'
    """
    close    = df["close"]
    ema_fast = close.ewm(span=12, adjust=False).mean()
    ema_slow = close.ewm(span=26, adjust=False).mean()
    macd     = ema_fast - ema_slow
    signal   = macd.ewm(span=9,  adjust=False).mean()

    # Фильтр направления тренда за последние 20 баров
    slope_20 = (close.iloc[-1] - close.iloc[-21]) / close.iloc[-21]
    macd_val = macd.iloc[-1]
    sig_val  = signal.iloc[-1]

    cross_up = macd_val > sig_val and macd.iloc[-2] <= signal.iloc[-2]
    cross_dn = macd_val < sig_val and macd.iloc[-2] >= signal.iloc[-2]

    if cross_up and slope_20 > 0:
        return "BUY"
    elif cross_dn and slope_20 < 0:
        return "SELL"
    return "HOLD"


def generate_signals(
    client: TradeMuxClient,
    symbols: list[str],
) -> dict[str, str]:
    """Генерирует сигналы по всему списку символов."""
    signals = {}
    for sym in symbols:
        try:
            df = fetch_ohlc_dataframe(client, sym)
            signals[sym] = compute_signal(df)
        except Exception as e:
            log.warning("Signal error %s: %s", sym, e)
            signals[sym] = "HOLD"
    return signals


Интеграция CatBoost-модели: drop-in замена сигнальной функции

В production-системе функция compute_signal заменяется на вызов предобученной модели. Класс-обёртка ниже имеет идентичный интерфейс — DataFrame на входе, строка на выходе. Модель загружается один раз при старте сервиса; пересчёт признаков происходит на каждом сигнальном цикле на свежих данных из get_ohlc(). Веса модели, инженерия признаков, пороги уверенности — всё это остаётся в Python-коде под полным контролем разработчика.

from catboost import CatBoostClassifier, Pool
import joblib


class CatBoostSignalEngine:
    """
    Production-wrapper для CatBoost-модели на L1-признаках.
    Drop-in замена для compute_signal() — тот же интерфейс.
    """

    FEATURE_COLS = [
        "ret_1", "ret_3", "ret_5", "ret_10", "ret_20",
        "l1_slope", "l1_accel", "l1_trend_up", "l1_trend_chg",
        "deviation", "dev_pct",
        "vol_5", "vol_10", "vol_20",
        "rsi_l1_14", "rsi_close_14",
        "bb_pos_10", "bb_pos_20",
    ]
    LONG_THR  = 0.58   # порог уверенности для BUY
    SHORT_THR = 0.58   # порог уверенности для SELL

    def __init__(self, model_path: str):
        self.model: CatBoostClassifier = joblib.load(model_path)
        log.info("CatBoost model loaded from %s", model_path)

    def __call__(self, df: pd.DataFrame) -> str:
        """Возвращает 'BUY' | 'SELL' | 'HOLD' по последней строке DataFrame."""
        row   = df[self.FEATURE_COLS].iloc[[-1]].fillna(0)
        proba = self.model.predict_proba(Pool(row))[0]
        p_up, p_dn = proba[1], proba[0]

        if   p_up >= self.LONG_THR:  return "BUY"
        elif p_dn >= self.SHORT_THR: return "SELL"
        else:                        return "HOLD"


# Использование — одна строка вместо compute_signal:
# signal_fn = CatBoostSignalEngine("models/eurusd_l1_catboost.cbm")


Исполнение ордеров и предторговый риск-контроль

Предторговые проверки выполняются в Python до вызова SDK. Логика валидации — margin level, суммарный лот, drawdown от high-water mark — написана один раз и работает для любого брокера, подключённого через TradeMux. Методы buy_market() и sell_market() принимают нормализованный лот; TradeMux на своей стороне валидирует его против актуальных спецификаций брокера и округляет до допустимого шага. MetaTrader 5 получает уже выверенную команду и исполняет её нативными средствами.

def pre_trade_checks(snap: dict, lots: float) -> tuple[bool, str]:
    """
    Набор предторговых проверок в Python.
    Возвращает (ok: bool, reason: str).
    """
    # 1. Уровень маржи
    if snap["margin_level"] < 200.0:
        return False, f"Margin level {snap['margin_level']:.1f}% < 200%"

    # 2. Суммарный открытый лот
    if snap["open_lots"] + lots > MAX_OPEN_LOTS:
        return False, f"Lots limit: {snap['open_lots']:.2f} + {lots:.2f} > {MAX_OPEN_LOTS}"

    # 3. Просадка equity от high-water mark
    global EQUITY_HIGH_MARK
    EQUITY_HIGH_MARK = max(EQUITY_HIGH_MARK, snap["equity"])
    drawdown = EQUITY_HIGH_MARK - snap["equity"]
    if drawdown > MAX_DRAWDOWN_USD:
        return False, f"Drawdown ${drawdown:.2f} > limit ${MAX_DRAWDOWN_USD:.2f}"

    return True, "ok"

def execute_signal(
    client: TradeMuxClient,
    snap: dict,
    symbol: str,
    direction: str,
    lots: float,
) -> None:
    """
    Передаёт торговое решение из Python в MetaTrader 5 через TradeMux SDK.
    Перед исполнением: предторговые проверки в Python.
    """
    if direction == "HOLD":
        return

    # Позиция в том же направлении уже открыта — пропускаем
    existing = [
        p for p in snap["positions"]
        if p["symbol"] == symbol and p["type"] == direction
    ]
    if existing:
        log.debug("Skip %s %s — position already open", direction, symbol)
        return

    ok, reason = pre_trade_checks(snap, lots)
    if not ok:
        log.warning("Pre-trade BLOCK %s %s: %s", direction, symbol, reason)
        return

    try:
        # Python → TradeMux SDK → TradeMux EA → MetaTrader 5 → Broker
        if direction == "BUY":
            result = client.buy_market(symbol=symbol, lots=lots)
        else:
            result = client.sell_market(symbol=symbol, lots=lots)

        log.info(
            "ORDER | %s %s %.2f lots | ticket=%s | fill=%.5f",
            direction, symbol, lots,
            result.get("ticket", "?"),
            result.get("fill_price", 0.0),
        )
    except Exception as e:
        log.error("Order FAILED %s %s: %s", direction, symbol, e)


Мониторинг позиций и kill switch

Execution service непрерывно отслеживает состояние открытых позиций через get_positions(). Самый важный защитный механизм — kill switch: аварийное закрытие всех позиций при нарушении критических условий. kill_switch() отправляет одну атомарную команду через TradeMux API — EA передаёт её нативным торговым функциям MetaTrader 5, которые закрывают все позиции немедленно. Логика принятия решения о срабатывании kill switch полностью остаётся в Python и может быть изменена без касания терминала.

def monitor_and_protect(client: TradeMuxClient, snap: dict) -> bool:
    """
    Риск-мониторинг в Python.
    При срабатывании — kill_switch() закрывает позиции через MetaTrader 5.
    Возвращает True если kill_switch() сработал.
    """
    global EQUITY_HIGH_MARK

    equity  = snap["equity"]
    balance = snap["balance"]
    margin  = snap["margin_level"]

    EQUITY_HIGH_MARK = max(EQUITY_HIGH_MARK, equity)
    drawdown = EQUITY_HIGH_MARK - equity

    reasons = []

    # Критическая просадка equity
    if drawdown > MAX_DRAWDOWN_USD:
        reasons.append(f"equity drawdown ${drawdown:.2f}")

    # Опасно низкий уровень маржи
    if margin < 120.0:
        reasons.append(f"margin level {margin:.1f}%")

    # Убыток превысил 5% баланса за сессию
    session_loss = balance - equity
    if session_loss > balance * 0.05:
        reasons.append(f"session loss ${session_loss:.2f}")

    if reasons:
        log.critical("KILL SWITCH triggered: %s", ", ".join(reasons))
        try:
            # Атомарная команда: Python → TradeMux → MetaTrader 5
            result = client.kill_switch()
            log.critical(
                "kill_switch() confirmed: closed %d positions, PnL=%.2f",
                result.get("closed_positions", 0),
                result.get("realized_pnl", 0.0),
            )
        except Exception as e:
            log.critical("kill_switch() FAILED: %s — manual action required", e)
        return True

    return False


def log_portfolio_status(snap: dict) -> None:
    """Выводит текущее состояние портфеля в лог."""
    log.info(
        "Portfolio | Equity=%.2f | Float=%.2f | Pos=%d | Lots=%.2f | Margin=%.1f%%",
        snap["equity"], snap["float_pnl"],
        snap["n_positions"], snap["open_lots"], snap["margin_level"],
    )
    for p in snap["positions"]:
        log.info(
            "  → %s %s %.2f lots | open=%.5f | profit=%.2f",
            p["type"], p["symbol"], p["lots"],
            p["open_price"], p["profit"],
        )


Главный цикл execution service

Все компоненты собираются в единый event-loop. Python управляет расписанием: пересчитывает сигналы каждые SIGNAL_INTERVAL секунд, проверяет портфель каждую минуту и обрабатывает сигналы разворота (reverse signals). Каждый раз, когда принимается решение, оно передаётся в MetaTrader 5 через TradeMux SDK одним вызовом. MetaTrader 5 исполняет, Python наблюдает через нормализованный feedback.

SIGNAL_INTERVAL  = 3600   # пересчёт сигналов раз в час
MONITOR_INTERVAL = 60    # проверка портфеля каждую минуту
DEFAULT_LOTS     = 0.01   # базовый лот (переопределяется VaR-аллокацией)


def run_execution_service() -> None:
    """Главный цикл: Python считает, TradeMux доставляет, MetaTrader 5 исполняет."""
    client = init_client()
    snap   = snapshot_account(client)
    global EQUITY_HIGH_MARK
    EQUITY_HIGH_MARK = snap["equity"]

    last_signal_ts  = 0.0
    last_monitor_ts = 0.0
    current_signals: dict[str, str] = {}

    log.info("Execution service started. HWM: %.2f", EQUITY_HIGH_MARK)

    while True:
        now = time.time()

        # ── Python: пересчёт сигналов ────────────────────────
        if now - last_signal_ts >= SIGNAL_INTERVAL:
            log.info("--- Signal update cycle ---")
            current_signals = generate_signals(client, SYMBOLS)
            last_signal_ts  = now

            snap = snapshot_account(client)
            if monitor_and_protect(client, snap):
                log.critical("Kill switch fired — service stopped")
                break

            # Передаём решения из Python в MetaTrader 5 через TradeMux
            for symbol, direction in current_signals.items():
                execute_signal(client, snap, symbol, direction, DEFAULT_LOTS)
                snap = snapshot_account(client)  # читаем актуальное состояние из MetaTrader 5

        # ── Python: мониторинг портфеля ──────────────────────
        if now - last_monitor_ts >= MONITOR_INTERVAL:
            snap = snapshot_account(client)
            log_portfolio_status(snap)
            if monitor_and_protect(client, snap):
                log.critical("Kill switch fired during monitor — service stopped")
                break
            last_monitor_ts = now

        # ── Python: обработка сигналов разворота ─────────────
        if current_signals:
            snap = snapshot_account(client)
            for pos in snap["positions"]:
                new_dir = current_signals.get(pos["symbol"], "HOLD")
                if new_dir not in ("HOLD", pos["type"]):
                    log.info(
                        "Reverse signal: closing %s %s → sending to MetaTrader 5",
                        pos["type"], pos["symbol"],
                    )
                    try:
                        client.close_position(ticket=pos["ticket"])
                    except Exception as e:
                        log.error("Close failed %s: %s", pos["ticket"], e)

        time.sleep(10)   # базовый тик цикла — 10 секунд


TradeMux как кросс-брокерный мост

Важное практическое преимущество TradeMux — нормализация брокерной специфики. Разные брокеры используют разные символьные суффиксы: EURUSD.raw, EURUSD.pro, EURUSDi — одна и та же пара, разные обозначения. Шаг лота у одного брокера 0.01, у другого 0.1. Режим исполнения — instant у одного, market у другого. TradeMux абстрагирует все эти различия: Python-код вызывает buy_market("EURUSD", 0.10), а TradeMux на своей стороне транслирует это в корректный запрос для конкретного брокера.

ACCOUNTS = {
    "RBF-123456": TradeMuxClient(api_key=API_KEY, account_id="RBF-123456", base_url=BASE_URL),
    "ICM-789012": TradeMuxClient(api_key=API_KEY, account_id="ICM-789012", base_url=BASE_URL),
    "ALP-DEMO01": TradeMuxClient(api_key=API_KEY, account_id="ALP-DEMO01", base_url=BASE_URL),
}


def broadcast_signal(
    clients: dict[str, TradeMuxClient],
    symbol: str,
    direction: str,
    lot_map: dict[str, float],
) -> None:
    """
    Один сигнал из Python → несколько брокеров через TradeMux.
    TradeMux нормализует брокерную специфику для каждого счёта.
    lot_map: {account_id: lots}
    """
    for acc_id, client in clients.items():
        lots = lot_map.get(acc_id, 0.01)
        log.info("[%s] %s %s %.2f lots → MetaTrader 5 via TradeMux", acc_id, direction, symbol, lots)
        try:
            if direction == "BUY":
                client.buy_market(symbol=symbol, lots=lots)
            elif direction == "SELL":
                client.sell_market(symbol=symbol, lots=lots)
        except Exception as e:
            log.error("[%s] Broadcast failed: %s", acc_id, e)

Это открывает архитектуру мультисчётных операций: один Python-сервис управляет счётами у нескольких брокеров одновременно. Каждый терминал MetaTrader 5 запускает один и тот же TradeMux EA. Python-логика единая. TradeMux нормализует коммуникацию с каждым брокером независимо.


Supervisor-паттерн: автоматический рестарт

Производственная система должна корректно обрабатывать сетевые сбои и нештатные ситуации. После срабатывания kill switch сервис выдерживает recovery-паузу и перезапускается с теми же параметрами. Ограничение числа перезапусков защищает от бесконечного цикла при системной проблеме.

import sys

RECOVERY_WAIT_SECONDS = 3600   # пауза после kill switch — 1 час
MAX_KILL_EVENTS       = 3      # после 3 событий — полная остановка


def supervised_run() -> None:
    """
    Supervisor-обёртка над run_execution_service().
    Перезапускает сервис с ограничением числа попыток.
    """
    kill_count = 0

    while kill_count < MAX_KILL_EVENTS:
        try:
            run_execution_service()
            log.info("Service completed normally.")
            sys.exit(0)

        except KeyboardInterrupt:
            log.info("Manual shutdown.")
            sys.exit(0)

        except Exception as e:
            kill_count += 1
            log.critical(
                "Service crashed (%d/%d): %s — waiting %ds",
                kill_count, MAX_KILL_EVENTS, e, RECOVERY_WAIT_SECONDS,
            )
            if kill_count >= MAX_KILL_EVENTS:
                log.critical("Max kill events reached — full stop.")
                sys.exit(1)
            time.sleep(RECOVERY_WAIT_SECONDS)


if __name__ == "__main__":
    supervised_run()


Путь масштабирования: от PoC к fund-style операциям

Описанный execution service запускается на одном счёте и одном терминале MetaTrader 5. Та же Python-архитектура масштабируется без переписывания торговой логики — изменяется только конфигурация подключения.

Первый этап — proof of concept на демо-счёте. Прикрепить TradeMux EA, создать API-ключ, запустить execution service с DEFAULT_LOTS = 0.01. За 1–2 дня вся цепочка проверяется в реальных условиях: Python генерирует сигнал → TradeMux доставляет команду → MetaTrader 5 исполняет ордер → Python читает feedback через get_positions(). Бесплатный developer workflow позволяет пройти этот этап полностью до перехода на live-счёт.

Второй этап — VPS-деплой на live-счёт. Python-сервис разворачивается на VPS рядом с терминалом MetaTrader 5. Латентность к TradeMux API минимальна. Тот же код, та же логика — меняется одна строка: ACCOUNT_ID и размер лотов.

Третий этап — несколько счётов MetaTrader 5. Словарь clients в broadcast_signal() расширяется до нужного числа счётов. Python-логика единая. Каждый терминал MetaTrader 5 запускает один и тот же TradeMux EA — только с другим account_id.

Четвёртый этап — кросс-платформенный охват. Тот же Python-код работает через TradeMux с MetaTrader 4-терминалами и OANDA. Диверсификация по брокерам снижает инфраструктурный риск: если один брокер временно недоступен, остальные продолжают работу. На каждом из этих этапов Python-логика не меняется. TradeMux обеспечивает единообразие интерфейса для всех брокеров и платформ.


Дальнейшие шаги: от статьи к live-торговле

Архитектуру можно проверить за четыре шага:

  1. прикрепить TradeMux EA к любому графику в MetaTrader 5, разрешить WebRequest для домена API;
  2. создать API-ключ — бесплатный developer workflow позволяет проверить полную цепочку на демо-счёте без ограничений по функциональности;
  3. запустить execution service и убедиться, что get_account_info(), get_positions(), buy_market() и kill_switch() работают корректно;
  4. подключить собственную signal-логику — CatBoost-модель с L1-фильтрованными признаками, мультипарный пайплайн, VaR-аллокацию лотов — и перейти на live.

Переход от демо к live — смена одного параметра ACCOUNT_ID. Логика, код, архитектура — не меняются.

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




Заключение

Python и MetaTrader 5 — две профессиональные среды, каждая из которых сильна в своей области. Python — богатейшая экосистема для исследований, моделирования и расчётов. MetaTrader 5 — надёжная execution-платформа с прямым брокерским коннектом и нативной скоростью обработки ордеров. TradeMux соединяет их в единую production-архитектуру: Python считает и принимает решения, TradeMux доставляет их через нормализованный интерфейс, MetaTrader 5 исполняет у брокера.

Полный торговый цикл описан через шесть методов SDK: get_account_info(), get_positions(), get_ohlc(), buy_market(), sell_market(), kill_switch(). Один и тот же Python-код работает с MetaTrader 4, MetaTrader 5 и OANDA через TradeMux без рефакторинга. При масштабировании на несколько счётов и брокеров меняется конфигурация подключения — торговая и исследовательская логика остаётся неизменной. TradeMux обеспечивает нормализованный кросс-брокерный интерфейс, MetaTrader 5 обеспечивает исполнение, Python обеспечивает интеллект системы.

Прикрепленные файлы |
Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса
Узнайте, как создать аккуратную и профессиональную панель управления на графике в MQL5 для советника, размещающего сделки на основе риска. В этом пошаговом руководстве объясняется, как спроектировать функциональный графический интерфейс, позволяющий трейдерам вводить параметры сделки, рассчитывать размер лота и готовиться к автоматическому размещению ордеров.
Знакомство с языком MQL5 (Часть 42): Руководство для начинающих по работе с файлами в MQL5 (IV) Знакомство с языком MQL5 (Часть 42): Руководство для начинающих по работе с файлами в MQL5 (IV)
В этой статье показано, как создать индикатор на языке MQL5, который считывает торговую историю из CSV, извлекает значения из столбца Profit($) и общее число сделок, а затем рассчитывает накопительную кривую баланса. Мы строим кривую в отдельном окне индикатора, автоматически масштабируем ось Y и рисуем горизонтальную и вертикальную оси для выравнивания. Индикатор обновляется по таймеру и перерисовывается только при появлении новых сделок. Необязательные метки показывают прибыль или убыток по каждой сделке, помогая прямо на графике оценивать результаты торговли и просадки.
Разрабатываем пользовательский индикатор рыночных настроений Разрабатываем пользовательский индикатор рыночных настроений
В этой статье мы разрабатываем пользовательский индикатор рыночных настроений, который классифицирует рыночные условия как бычьи, медвежьи, в режиме risk-on, в режиме risk-off или нейтральные. Благодаря анализу нескольких таймфреймов индикатор дает трейдерам более ясное представление об общей направленности рынка и краткосрочных подтверждениях.
Разработка инструментария для анализа Price Action (Часть 62): Создание адаптивной системы обнаружения параллельных каналов и пробоев на языке MQL5 Разработка инструментария для анализа Price Action (Часть 62): Создание адаптивной системы обнаружения параллельных каналов и пробоев на языке MQL5
В этой статье представлена адаптивная система обнаружения параллельных каналов и пробоев на языке MQL5. В ней показано, как определяются точки свинга, как строятся и динамически пересчитываются каналы, а также как пробои подтверждаются и визуализируются с помощью сигналов, сохраняющихся на графике. Этот подход объединяет геометрию трендовых линий, фильтрацию на основе ATR и проверку ретеста, обеспечивая надежный анализ Price Action в реальном времени для профессиональной работы с графиками и принятия торговых решений.