English
preview
RiskGate: Централизованное управление рисками для нескольких советников

RiskGate: Централизованное управление рисками для нескольких советников

MetaTrader 5 — Примеры |
32 2
Douglas Nascimento Rechia
Douglas Nascimento Rechia

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

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

В этой статье предлагается иная архитектура. Вместо самостоятельного управления своими рисками советники становятся генераторами сигналов: они определяют, когда и чем торговать, но перед отправкой ордера запрашивают разрешение и размер позиции у центрального "мозга" управления рисками. Этот центральный слой управления рисками реализован в MetaTrader 5 в виде сервиса RiskGate. Советники отправляют в RiskGate простое "торговое намерение" (сигнал); RiskGate проверяет соблюдение правил для всего счета (дневной убыток, экспозиция, корреляции, лимиты стратегий) и возвращает решение — разрешить или отклонить сделку — с указанием размера лота и причины. В этой статье концепция проиллюстрирована реализацией, собранной из пакетов KnitPkg. Однако эта архитектура не привязана к конкретным инструментам и может быть реализована с использованием других технологий.



Проблема — почему счета с несколькими советниками разоряются

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

Случай 1 — наложенная USD-экспозиция. Вы используете три разных советника: один торгует на пробоях по паре EURUSD, второй — на откатах по паре GBPUSD, а третий — на возврате к среднему по паре USDJPY. В один из дней все три системы независимо друг от друга обнаруживают сетапы "покупка USD". Каждый советник, исходя из того, что он работает на счете один, открывает позицию полного размера. В результате формируется крупная концентрированная длинная позиция по доллару США. Если доллар резко двинется против ваших позиций, совокупный убыток окажется гораздо больше суммы, которой любая из этих стратегий рискнула бы по отдельности. Ни один советник не учитывал совокупный риск по доллару США; каждый учитывал лишь "одну сделку в рамках моих правил".

Случай 2 — синхронное движение коррелирующих пар. Вы применяете две стратегии на коррелирующих парах, например EURUSD и GBPUSD. Система для EURUSD получает сигнал на покупку при пробое, а система для GBPUSD — при откате. При тестировании на исторических данных каждая из них показывает хорошие результаты. В реальной торговле, когда на фоне ухода инвесторов от риска европейские активы начинают снижаться, обе пары падают одновременно. Счет получает двойной удар, поскольку нет механизма, который сообщал бы: "У вас уже есть крупная длинная позиция по высококоррелированной паре; объем второй сделки следует уменьшить либо заблокировать ее открытие".

Случай 3 — одна стратегия достигает лимита дневного убытка, остальные продолжают торговать. Многие трейдеры устанавливают лимит дневного убытка на уровне советника: "Если этот советник сегодня потеряет более 2%, он прекращает торговлю". Теперь представьте, что на одном счете у вас работают пять советников, у каждого из которых свой дневной лимит убытка в 2%. Один советник достигает лимита убытка в 2% и прекращает торговлю. Остальные четыре, не зная об этом, продолжают торговать и легко могут потерять еще по 2-3% каждый. В итоге счет завершает день с убытком 6-8%, хотя "каждый советник соблюдал свой дневной лимит". Проблема в том, что не было общего лимита дневного убытка для всего счета, а действовали только локальные правила каждого советника.

Все эти сценарии объединяет одна закономерность: рисками управляют локально, а последствия затрагивают весь счет. Схема с несколькими советниками на одном счете дает сбой, потому что маржа и просадка общие для всех. Дальнейшее изложение строится на этом наблюдении. Если счет общий, решения по рискам тоже должны быть общими. Именно это и призван обеспечить RiskGate.



Основная идея — отделить сигналы от управления рисками

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

В обычной схеме советник делает все сам. В модели RiskGate каждый советник по-прежнему решает: "Я хочу открыть длинную позицию по EURUSD прямо сейчас с таким стоп-лоссом". Но прежде чем выйти на рынок, он отправляет это торговое намерение в центральный сервис и спрашивает: "С учетом текущего состояния счета разрешена ли эта сделка и, если да, какого она должна быть объема?"

На практике это означает, что мы разделяем два вопроса, которые часто смешивают:

  • Сигнал (советник): подходит ли эта торговая идея для этой стратегии прямо сейчас?
  • Риск (RiskGate): с учетом всех уже открытых на счете позиций, какой суммой можно рискнуть ради этой идеи или стоит от нее отказаться?

Советник по-прежнему отвечает на первый вопрос. На второй вопрос отвечает центральный сервис RiskGate. Советник описывает планируемую сделку в простой структуре данных (символ, направление, стоп-лосс, возможно, идентификатор стратегии и несколько тегов) и отправляет ее в RiskGate. RiskGate проверяет торговое намерение на соответствие правилам для всего счета — лимитам дневного убытка, текущей экспозиции по каждому символу или валюте, количеству открытых позиций, бюджетам стратегий, лимитам корреляции — и возвращает решение. Это решение всегда состоит из трех основных элементов:

  • approved — true/false (сделка разрешена или заблокирована);
  • lot — размер позиции, который следует использовать, если сделка разрешена;
  • reason — краткое пояснение (полезно для логов и отладки).

Затем советник действует в соответствии с решением. Если approved равно false, он записывает причину в лог и ничего не делает. Если approved равно true, при отправке ордера брокеру он использует значение lot из RiskGate. Для советника RiskGate — всего лишь "оракул риска", к которому нужно обращаться перед торговлей. На уровне счета RiskGate — единственный центр, где рисками действительно управляют.

Это разделение имеет несколько важных последствий. Во-первых, становится возможным обеспечивать соблюдение правил, которые ни один советник не смог бы надежно реализовать в одиночку: "не открывать новые сделки, если убыток по счету за сегодня достиг 3%", "никогда не рисковать более чем 1% в сумме на кроссах с EUR", "не открывать новые сделки по символу, если по нему уже есть две открытые позиции", "уменьшать размер позиции, если экспозиция по коррелирующим инструментам уже высока" и т.д. Во-вторых, вы можете добавлять, удалять или изменять советники, не переписывая каждый раз логику управления рисками. Если все они используют один и тот же простой протокол "торговое намерение/решение", на них распространяются единые правила управления рисками. В-третьих, изменение политики управления рисками сводится к обновлению центрального сервиса, а не к редактированию десяти разных советников.

Далее мы перейдем к реализации этой идеи: сначала покажем минимальную схему взаимодействия клиента и сервера (советник запрашивает разрешение, сервис отвечает полями approved/lot/reason), затем реализуем практические правила управления рисками (дневной убыток, лимиты экспозиции, расчет размера позиции) и, наконец, обсудим более сложные ограничения, такие как корреляция, бюджеты стратегий и принадлежность сделок.



Архитектура Советник → RiskGate → брокер

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

Схема ниже показывает полный цикл обмена запросом и ответом:

Sequence diagram

Ниже приведено краткое описание каждого пронумерованного действия:

Советник (OnTick)
    1. Стратегия обнаруживает сигнал.
    2. Создается JSON-сигнал с помощью класса CJAVal: поля symbol, side, stop_loss, ...
    3. Вызывается client.RequestPositionSize(signal, response)

↓ JSON через TCP-сокет (localhost)

RiskGateServer
    4. Получает необработанные данные JSON от советника.
    5. Преобразует их в объект сигнала и вызывает handler.OnSignal(signal, response).

RiskGateHandler
    6. Применяет правила управления рисками и заполняет ответ: поля approved, lot, reason.

↑ JSON через TCP-сокет (localhost)

RiskGateClient
    7. Получает и разбирает ответ, передает его обратно в советник.

Советник (OnTick)
   8. Считывает response["approved"]:
       - true  → размещает ордер с помощью response["lot"]
       - false → логируется причина response["reason"], сделка отклоняется

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

RiskGateClient — точка входа на стороне советника. Это единственный класс, с которым разработчику советника нужно взаимодействовать напрямую. Внутри он инкапсулирует TCP-клиент ClientSocket, сериализует сигнал CJAVal в JSON, отправляет его с разделителем сообщений (\r\n\r\n) и ожидает ответа сервера в пределах настраиваемого таймаута (по умолчанию — 500 мс). Если ответ не поступает вовремя или соединение прервано, клиент пытается восстановить соединение. Затем клиент применяет настроенную резервную политику: отклонять все сделки (RISKGATE_FALLBACK_REJECT — рекомендуемый вариант по умолчанию для счетов в проп-фирмах) или одобрять их с фиксированным размером лота (RISKGATE_FALLBACK_FIXED_LOT). Код советника не работает с сокетами, таймаутами или повторным подключением — он лишь вызывает методы RiskGateClient всякий раз, когда требуется решение по рискам.

RiskGateServer — соответствующий компонент на стороне сервиса. Он выполняет блокирующий цикл приема подключений внутри сервиса MQL5, поддерживая до 32 одновременных подключений советников. Для каждого подключенного клиента он считывает входящие сообщения, удаляет разделитель, десериализует JSON в объект сигнала CJAVal, создает пустой объект ответа CJAVal и передает оба объекта обработчику через OnSignal(). После возврата из OnSignal() сервер сериализует ответ в JSON и отправляет его советнику по тому же TCP-соединению. Сервер не предполагается изменять: вы создаете его экземпляр, передаете ему логгер и обработчик, затем вызываете Run().

RiskGateHandler — абстрактный базовый класс с единственным методом, который нужно реализовать: OnSignal(CJAVal& signal, CJAVal& response). Именно здесь реализуется вся логика управления рисками. Сервер вызывает этот метод для каждого полученного сигнала, передавая ему результат разбора JSON-сигнала и пустой объект ответа. Вы заполняете в ответе как минимум поля approved, lot и reason, затем завершаете выполнение метода. Остальное сервер возьмет на себя. В обработчике также предусмотрены необязательные обратные вызовы жизненного цикла — OnClientConnected() и OnClientDisconnected(), — полезные для ведения состояния каждого клиента или записи событий подключения в лог.

Позже мы реализуем OnSignal() с практическими правилами управления рисками. Пока важно то, что единственный конкретный класс, который вы пишете на стороне сервера, наследуется от RiskGateHandler. Все остальное — цикл приема TCP-соединений, выделение границ сообщений, разбор JSON, управление пулом клиентов и запись логов — выполняется внутри RiskGateServer.

Пакеты и транзитивные зависимости. Архитектура RiskGate реализована в виде небольшого набора компонентов MQL5, каждый из которых размещен в отдельном Git-репозитории и оформлен как пакет для использования с KnitPkg. Два компонента играют основную роль:

  • riskgate — серверная инфраструктура (RiskGateServer и RiskGateHandler);
  • riskgate-ea — клиент на стороне советника (RiskGateClient).

И riskgate, и riskgate-ea зависят от трех общих компонентов:

  • logger — структурированная запись логов;
  • sockets — клиентские и серверные TCP-сокеты;
  • JAson — JSON для MQL5 (CJAVal и вспомогательные средства, разработанные Артемом Мальцевым и другими участниками; форк исходного репозитория).

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



Минимальная реализация — взаимодействие клиента и сервера

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

Вариант 1 — использование KnitPkg (рекомендуется). KnitPkg берет на себя разрешение зависимостей, установку заголовочных файлов и компиляцию. Если вы хотите создать собственный проект с нуля и у вас есть KnitPkg, воспользуйтесь полными пошаговыми руководствами: "Минимальный сервер" (RiskGateServer) и "Минимальный советник" (RiskGateClient).

В каждом руководстве пошагово описаны создание проекта, добавление и установка необходимых пакетов, а также запуск полученного приложения. Главное преимущество этого подхода — связь проекта с экосистемой пакетов: команда kp install (kp — интерфейс командной строки KnitPkg) всегда устанавливает нужные версии всех зависимостей (riskgate, riskgate-ea, sockets, logger и JAson), а команда kp compile заново генерирует BuildInfo.mqh, чтобы версия и метаданные в скомпилированном бинарном файле всегда соответствовали манифесту. В дальнейшем для обновления зависимости достаточно одной операции в командной строке.

Примечание: подробнее о KnitPkg и его экосистеме зависимостей пакетов см. в документации и в этой статье.

Вариант 2 — прямое использование плоских файлов. Если вы предпочитаете не использовать KnitPkg, можно скачать приложенные заранее сгенерированные плоские файлы и поместить их непосредственно в каталог данных MetaTrader 5. KnitPkg может генерировать "плоские" версии заголовочных файлов — отдельные самодостаточные файлы, в которые встроены все транзитивные зависимости. Это означает, что вам не нужно устанавливать какой-либо набор инструментов разработки. Файлы организованы следующим образом:

На стороне сервера:

  • RiskGateHandlerDep_flat.mqh — единый плоский заголовочный файл, содержащий все серверные зависимости (riskgate, logger, sockets, JAson). Этот файл сгенерирован KnitPkg, и его нельзя редактировать.
  • RiskgateHandler.mqh — подкласс RiskGateHandler, в котором реализуются ваши правила управления рисками. Именно этот файл вы пишете и дорабатываете. Он подключает файл RiskGateHandlerDep_flat.mqh и определяет класс MyHandler с реализацией OnSignal().
  • RiskgateServiceExample.mq5 — точка входа сервиса. Он подключает RiskgateHandler.mqh и через него — плоский заголовочный файл зависимостей, создает экземпляры Logger, RiskGateServer и MyHandler, затем вызывает server.Run(). Вы можете переименовать этот файл и настроить его свойства, но тело OnStart() требует лишь минимальных изменений.
На стороне советника:
  • RiskgateEaExample_flat.mqh — единый плоский заголовочный файл, содержащий все зависимости советника (riskgate-ea, logger, sockets, JAson). Этот файл сгенерирован KnitPkg, и его нельзя редактировать.
  • RiskgateEaExample.mq5 — точка входа советника. Он подключает RiskgateEaExample_flat.mqh, объявляет глобальный экземпляр RiskGateClient, настроенный на localhost и порт 5555, устанавливает соединение в OnInit(), отключается в OnDeinit() и отправляет минимальный сигнал каждые 30 тиков в OnTick(). Вы можете переименовать этот файл и дополнить его своей торговой логикой.

Поместите файлы сервера в MQL5/Services/, а файлы советника — в MQL5/Experts/ в каталоге данных MetaTrader 5, затем откройте их в MetaEditor и скомпилируйте обычным способом. Внешние инструменты не требуются.

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

Сервис запускается, привязывается к TCP-порту 5555 и входит в блокирующий цикл ожидания подключений. Когда советник подключается и отправляет сигнал, RiskGateServer десериализует JSON, вызывает метод MyHandler.OnSignal(), сериализует ответ и отправляет его обратно. В минимальном обработчике OnSignal() всегда возвращает approved=true и lot=0.01 без фактической оценки риска. Это сделано намеренно: на этом этапе нужно лишь убедиться, что канал связи работает от отправки запроса до получения ответа. На скриншоте показан MetaTrader с запущенным сервисом; см. записи в логах на вкладке "Эксперты":

RiskGate server running

Советник подключается к сервису в функции OnInit(). Каждые 30 тиков он формирует сигнал CJAVal с полями symbol, side и stop_loss, вызывает client.RequestPositionSize(signal, response) и выводит три поля ответа — approved, lot и reason — на вкладку "Эксперты". Если сервис недоступен, RiskGateClient применяет резервную политику (по умолчанию отклоняет запрос) и возвращает false, поэтому советник всегда работает предсказуемо независимо от состояния сервиса. Ниже показана одновременная работа минимального советника на EURUSD и GBPUSD с отправкой запросов серверу:

EA client running

Если в логах появились записи, подтверждающие полный цикл обмена запросом и ответом — signal sent, response received, approved=true, lot=0.01, — инфраструктура работает. С этого момента дальнейшая разработка ведется только в двух местах: в OnSignal() на стороне сервера — логика управления рисками, в OnTick() на стороне советника — торговая логика. Слой взаимодействия между ними менять не нужно.



Практический пример управления рисками

Минимальная реализация из предыдущего раздела подтверждает, что канал связи работает от отправки запроса до получения ответа, однако обработчик всегда возвращает approved=true и lot=0.01 независимо от состояния счета. Это не управление рисками, а лишь налаженное взаимодействие компонентов. В этом разделе мы заменяем заглушку обработчиком, который применяет практические правила для всего счета, и связываем его с советником, отправляющим содержательные сигналы на основе фактических рыночных данных.

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

  • Максимальный дневной убыток — 2% от эквити счета. Если реализованный убыток по счету за сегодня составляет 2% или более, до следующего торгового дня не одобряется ни одна новая сделка ни по одному символу и ни по одной стратегии.
  • Максимальный риск на сделку — 0,5% от эквити. Убыток по каждой одобренной сделке при срабатывании стоп-лосса не должен превышать 0,5% эквити.
  • Максимальное количество открытых позиций по одному символу — 2. Если по запрошенному символу уже открыты две или более позиции, сделка отклоняется.
  • Максимальное количество сделок в день — 6. Обработчик отслеживает количество сделок, открытых сегодня всеми советниками. Когда счетчик достигает 6, дальнейшие сигналы блокируются до следующего торгового дня.
  • Уменьшение размера лота на 50% при экспозиции по коррелирующим инструментам — если входящий сигнал относится к корреляционной группе, определяемой по его магическому числу, и на счете уже есть открытая позиция из той же группы, одобренный размер лота автоматически уменьшается вдвое перед передачей советнику.
  • Резервная политика при недоступности сервиса — отклонять все сделки. Если сервис недоступен, сделки не открываются.

Эти шесть правил приведены для примера. Лимит дневного убытка не позволяет одному или нескольким советникам продолжать торговлю после того, как счет уже понес убытки за день. Лимит по одному символу предотвращает накопление позиций по одному и тому же инструменту. Дневной лимит количества сделок (не более 6) жестко ограничивает число повторных попыток на счете за одну сессию. Уменьшение размера лота на 50% при экспозиции по коррелирующим инструментам гарантирует, что новый сигнал из уже активной группы (с тем же магическим числом) добавляет лишь дополнительный риск, а не удваивает ставку.

Расширение минимального сервера. В репозитории riskgate-service-realcase показано, как расширить минимальный каркас из предыдущего раздела для реализации этих правил. Структура проекта остается прежней: RiskgateServiceRealCase.mq5 — точка входа сервиса, которая остается без изменений; вся логика управления рисками находится в RiskgateHandler.mqh, в подклассе MyHandler.

Примечание: если вы используете KnitPkg, выполните `kp get mql5 @douglasrechia/riskgate-service-realcase`, чтобы одной командой загрузить и собрать сервис. Дополнительные инструкции по установке см. в файле README проекта.

Метод MyHandler.OnSignal() в практическом примере имеет четкую последовательную структуру. Правила проверяются по порядку; при нарушении первого же правила сделка немедленно отклоняется с указанием конкретной причины:

   //+------------------------------------------------------------------+
   //| Entry point called by RiskGateServer for every incoming signal.  |
   //| Runs the full risk pipeline and writes approved/lot/reason into  |
   //| response.                                                        |
   //+------------------------------------------------------------------+
   void              OnSignal(CJAVal& signal, CJAVal& response) override
     {
      // 1. Signal validation
      if(!signal.HasKey("symbol") || !signal.HasKey("sl_points") || !signal.HasKey("magic"))
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "invalid_signal";
         return;
        }

      string symbol   = signal["symbol"].ToStr();
      double slPoints = signal["sl_points"].ToDbl();
      long   magic    = signal["magic"].ToInt();

      if(symbol == "" || slPoints <= 0)
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "invalid_signal";
         return;
        }

      // 2. Compute stats for today
      this.ComputeDayStats();

      // 3. Max daily positions limit
      if(IsDailyMaxPositionsReached())
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "daily_max_positions_limit";
         return;
        }

      // 4. Daily loss limit
      if(IsDailyLossBreached())
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "daily_loss_limit";
         return;
        }

      // 5. Max positions per symbol + correlated exposure
      int  countBySymbol;
      bool correlatedExposure;
      ScanPositions(symbol, magic, countBySymbol, correlatedExposure);

      if(countBySymbol >= 2)
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "max_positions_reached";
         return;
        }

      // 6. Lot from SL + % risk, with correlated exposure reduction
      double lot = CalcLot(symbol, slPoints, correlatedExposure);
      if(lot <= 0)
        {
         response["approved"] = false;
         response["lot"]      = 0.0;
         response["reason"]   = "lot_too_small";
         return;
        }

      // 5. Approved
      response["approved"] = true;
      response["lot"]      = lot;
      response["reason"]   = "";
     }

Каждая проверка получает данные от вспомогательного объекта, переданного обработчику: m_account инкапсулирует доступ к балансу счета, m_positions — к перебору позиций, m_history работает с историей сделок, а m_symbolInfo предоставляет необходимую информацию по каждому инструменту. Эти вспомогательные средства описаны через интерфейсы, а не реализованы как прямые вызовы нативных функций MQL5. Это архитектурное решение не случайно.

Модульное тестирование обработчика. Тестер стратегий MetaTrader не поддерживает запуск сервисов MQL5. В среде тестирования на исторических данных нет встроенного способа запустить RiskGateServer, подавать ему сигналы и наблюдать за его решениями в контролируемых условиях. Если обработчик напрямую вызывает AccountInfoDouble() и PositionSelect(), его становится трудно тестировать. Без реального счета в требуемом состоянии сложно смоделировать конкретные условия (например, дневной убыток 1,9%).

В проекте riskgate-service-realcase эта проблема решается набором модульных тестов, поставляемым вместе с кодом обработчика. Это возможно благодаря уже упомянутому промежуточному слою: каждый вызов нативной функции MQL5 скрыт за простым интерфейсом. Например: IAccountInfo предоставляет Balance(); IOpenPositions — Total(), GetSymbol() и GetMagic() для каждой открытой позиции. Обработчик зависит только от этих интерфейсов, а не от скрытых за ними нативных вызовов.

В модульных тестах реальные реализации этих интерфейсов заменяются mock-реализациями. Mock-объект MockAccountInfo может возвращать любое значение эквити. Mock-объект MockPosition может имитировать наличие ровно двух открытых позиций по EURUSD. После передачи этих mock-объектов обработчику тесты напрямую проверяют OnSignal() в контролируемых условиях. Вот пример:

//+------------------------------------------------------------------+
//| Verifies that MyHandler approves a trade when closed deals sum   |
//| to a daily loss below the 2% threshold (-1.77% in this case).    |
//+------------------------------------------------------------------+
bool Test_ApprovedTrade_DailyLossUnder2Percent()
  {
// 3 x -60 = -180; m_dayPnl = -180/10180*100 ≈ -1.77% → under threshold
   MockTradeHistory* history = new MockTradeHistory();
   history.AddDeal(1, true, -60.0);
   history.AddDeal(2, true, -60.0);
   history.AddDeal(3, true, -60.0);

   MyHandler* handler = BuildHandler(history, new MockOpenPositions());
   CJAVal signal, response;
   BuildSignal(signal);
   handler.OnSignal(signal, response);
   delete handler;

   return response["approved"].ToBool() &&
          MathAbs(response["lot"].ToDbl() - CalcExpectedLot(TEST_BALANCE, TEST_SL)) < SymbolInfoDouble(TEST_SYMBOL, SYMBOL_VOLUME_STEP);
  }


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

На стороне советника — сигналы на основе ATR с RiskGateClient. Сопутствующий проект советника показывает, как сформировать содержательный сигнал и отправить его сервису. Советник отслеживает четыре валютные пары (EURUSD, GBPUSD, AUDUSD, NZDUSD), объединенные в две корреляционные группы: EURUSD и GBPUSD имеют общее магическое число 1000, а AUDUSD и NZDUSD — общее магическое число 1001. Это магическое число включается в сигнал, чтобы обработчик мог применять групповые правила для коррелированных активов.

Примечание: если вы используете KnitPkg, выполните `kp get mql5 @douglasrechia/riskgate-ea-realcase`, чтобы одной командой загрузить и собрать советник. Дополнительные инструкции по установке см. в файле README проекта.

За слой взаимодействия полностью отвечает RiskGateClient, как и в минимальном примере. Меняется лишь содержимое сигнала в RiskgateEaRealCase.mq5. Вместо фиксированной цены stop_loss советник рассчитывает динамическое расстояние до стоп-лосса в пунктах, используя ATR(14), умноженный на коэффициент 4. Этот расчет выполняется на каждом новом баре с помощью пакета @douglasrechia/calc, предоставляющего удобные вспомогательные средства и утилиты ATR, поэтому разработчику советника не нужно вручную реализовывать арифметические вычисления. За обнаружение новых баров отвечает класс BarWatcher из пакета @douglasrechia/bar. Он генерирует событие при появлении каждого нового бара независимо от символа и таймфрейма, к которым привязан советник, избавляя тело OnTick() от типового кода обнаружения баров.

Сигнал, отправляемый сервису при появлении каждого нового бара, выглядит следующим образом:

{
  "symbol":    "EURUSD",
  "sl_points": 120,
  "magic":     1000
}
Значение sl_points сервер использует для расчета размера лота: располагая символом, эквити, процентом риска и расстоянием до стоп-лосса в пунктах, он может рассчитать точный объем в лотах, при котором риск по сделке составит ровно 0,5% эквити. Советнику не нужна формула расчета размера лота; ему достаточно описать планируемую сделку. Если сервер дает разрешение, советник считывает response["lot"] и размещает ордер, используя это значение как размер позиции, а sl_points * _Point — как расстояние до стоп-лосса.

В этом примере логика входа намеренно проста: советник запрашивает разрешение при появлении каждого нового бара. В реальной системе вы замените это своими условиями входа (паттерном, сигналом индикатора, временным фильтром или любой их комбинацией). Инфраструктура — BarWatcher для обнаружения баров, calc для расчета ATR, RiskGateClient для взаимодействия с сервером — остается неизменной независимо от подключаемой логики входа. В этом и заключается суть: наладив взаимодействие компонентов, можно развивать логику генерации сигналов независимо от логики управления рисками, причем ни одной стороне не нужно знать, как работает другая.


Расширение RiskGate для более сложных правил управления рисками

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

Управление корреляцией. В практическом примере уже применяется простое правило корреляции: советники одной корреляционной группы используют общее магическое число, а обработчик уменьшает размер лота вдвое, если для этого магического числа уже есть открытая экспозиция. Уже одно это существенно помогает при работе с сочетаниями EURUSD/GBPUSD или AUDUSD/NZDUSD, когда часто нежелательно одновременно держать две позиции полного размера в одном направлении.

Есть два естественных способа расширить эту схему. Первый — перейти от фиксированного правила "делить на 2" к бюджету группы, заданному как "максимальный риск группы = X % от эквити". Обработчик рассчитывал бы текущий риск группы с этим магическим числом, затем либо отклонял бы новую сделку, либо корректировал ее размер лота, чтобы группа оставалась в пределах бюджета. Второй — добавить небольшую статическую таблицу корреляций между символами (или группами) и использовать ее для расчета совокупной экспозиции с учетом корреляции, а не только количества позиций в каждой группе. Оба усовершенствования сохраняют формат сигнала (symbol + magic уже отправляются советником) и полностью реализуются внутри обработчика.

Бюджеты стратегий. Обработчик из практического примера уже обеспечивает соблюдение бюджетов всего счета; распространить этот подход на стратегии — естественный следующий шаг. Схема выглядит следующим образом:

  • добавьте в сигнал strategy_id (или группу по магическому числу);
  • храните в обработчике счетчики и данные состояния каждой стратегии: реализованный убыток за сегодня, текущий риск по открытым позициям, количество открытых позиций;
  • применяйте те же типы правил, но для данного идентификатора: максимальный дневной убыток, максимальный риск по открытым позициям и максимальное количество открытых сделок для стратегии.

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

Принадлежность сделок и реестр позиций. Для более сложного управления может понадобиться четкое представление о том, "кому что принадлежит" на счете. Для этого достаточно небольшого реестра в обработчике с ключом в виде тикета или сочетания (символ, направление, магическое число), в котором хранятся:

  • strategy_id или идентификатор группы;
  • начальное R (риск в денежном выражении или процентах);
  • исходные значения SL/TP и время открытия;
  • необязательные теги (например, "news", "trend", "mean_reversion").

Вы можете заполнять этот реестр, просматривая текущие позиции при каждом сигнале и определяя принадлежность по magic/strategy_id, либо расширить протокол, чтобы советники отправляли события жизненного цикла ("open", "update", "close") наряду с запросами по позициям. Имея такой реестр, можно реализовать правила "максимальное количество открытых позиций по стратегии", "запрет хеджирования между стратегиями по одному символу" или "ужесточение лимитов после серии убыточных сделок стратегии" — в одном месте и с учетом полного контекста.

От простых проверок к пайплайну правил. По мере реализации этих идей OnSignal естественным образом превращается в небольшой пайплайн правил: проверить сигнал; применить жесткие блокировки для всего счета; применить правила стратегии; применить правила портфеля (группы, корреляция); затем, если все проверки пройдены, рассчитать размер позиции. Каждое правило можно реализовать в отдельной вспомогательной функции, сохраняя читаемость кода и, что еще важнее, возможность его тестирования. Ваши советники по-прежнему лишь отправляют symbol/side/SL/strategy_id и следуют approved/lot/reason; вся сложная логика остается инкапсулированной в одном компоненте, который обрабатывает каждую сделку до ее отправки брокеру.



Эксплуатационные аспекты

Задержка. RiskGate целиком работает в том же терминале MetaTrader 5, что и ваши советники, используя TCP-соединения через localhost и небольшие сообщения JSON. На типичном VPS или настольном компьютере полный цикл обмена запросом и ответом — сериализация, отправка, обработка в сервисе, отправка ответа, десериализация — обычно занимает менее нескольких миллисекунд. Для стратегий, работающих на таймфреймах M1, M5 или выше, такая дополнительная задержка пренебрежимо мала. Главное — считать RequestPositionSize частью процесса принятия решения: запросить размер позиции, дождаться ответа и только затем разместить ордер. Для стратегий с ультранизкой задержкой или тикового скальпинга вблизи источника котировок эти накладные расходы могут иметь значение, но такие стратегии изначально редко используют MQL5 для исполнения.

Надежность и виды отказов. Любой централизованный сервис становится единой точкой отказа, поэтому важно понимать, как RiskGate работает в неблагоприятных условиях. Если сервис остановлен, аварийно завершился или TCP-соединение прервано, RiskGateClient обнаруживает разрыв соединения, пытается восстановить его с заданным интервалом и максимальным количеством попыток и возвращает false из RequestPositionSize. В этом случае он также заполняет объект ответа по настроенной резервной политике: либо отклоняет сделки по умолчанию (RISKGATE_FALLBACK_REJECT), либо одобряет их с фиксированным размером лота (RISKGATE_FALLBACK_FIXED_LOT). Для счетов с существенным капиталом и торговли в проп-фирмах рекомендуется отклонять сделки при недоступности сервиса, приняв базовое правило безопасности: "нет централизованного управления рисками — нет сделок".

Вопросы безопасности. Представленная архитектура предполагает локальное развертывание: Советники и сервис RiskGate работают в одном терминале, а сервер прослушивает порт на localhost. В такой конфигурации поверхность атаки минимальна, а вопросы безопасности в основном сводятся к стандартному усилению защиты ОС и соблюдению базовых мер безопасности VPS. Если вы решите сделать RiskGateServer доступным за пределами localhost, к нему следует относиться как к любому другому внутреннему TCP-сервису: ограничить доступ правилами межсетевого экрана, избегать привязки к публичным интерфейсам и, если взаимодействие между машинами действительно необходимо, разместить перед ним прокси или шлюз, способный обеспечивать аутентификацию, шифрование и ограничение частоты запросов. В текущих пакетах RiskGate сетевое взаимодействие намеренно упрощено и ориентировано на локальное использование; удаленный доступ следует организовывать с дополнительной инфраструктурой и должной осторожностью.

Логирование и мониторинг. RiskGate изначально спроектирован так, чтобы его работу можно было отслеживать с помощью logger. На стороне сервиса можно записывать в лог каждое подключение, отключение, входящий сигнал и решение по рискам, при необходимости сохраняя все в файл для последующего анализа. Это позволяет легко ответить на вопросы "Почему эта сделка была отклонена?" или "Сколько сделок сегодня отправила стратегия A?". На стороне советника RiskGateClient может записывать в лог случаи применения резервной политики при недоступности сервиса, частоту повторных подключений и решения, получаемые от сервиса. Вместе эти логи образуют историю работы механизма управления рисками для последующего аудита. В рабочей среде стоит установить единый формат логов и политику их хранения, чтобы разбирать инциденты и оценивать, работают ли правила управления рисками так, как задумано, в реальных рыночных условиях.



Когда эту архитектуру стоит использовать — и когда она избыточна

Оптимальная область применения — диверсификация с централизованной защитой. RiskGate особенно эффективен при активной диверсификации портфеля — запуске нескольких независимых стратегий на одном счете для сглаживания кривых эквити и снижения волатильности. Эта архитектура становится необходимой, когда:
  • на одном счете работают три или более стратегии, особенно если они торгуют коррелирующими инструментами (например, системы для EURUSD и GBPUSD);
  • ограничения по рискам обязательны к соблюдению (например, лимиты просадки проп-фирм, лимиты дневного убытка или регуляторные лимиты экспозиции);
  • стратегии имеют разные профили риска (например, скальпер с риском 0,3% на сделку и свинг-трейдер с риском 1,5%).

В таком случае RiskGate превращается из "полезного дополнения" в критически важную инфраструктуру. Это единственный способ:

  • предотвратить скрытую корреляцию (например, когда три "некоррелирующие" стратегии одновременно открывают позиции на покупку USD);
  • обеспечить соблюдение единых ограничений по убыткам для всего счета (а не иллюзорных ограничений отдельных советников);
  • динамически изменять размер позиций по мере накопления экспозиции (например, "Уменьшить объем сделки по EURUSD на 50% из-за существующей экспозиции по GBPUSD").

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

  • На одном счете запущена только одна стратегия. Достаточно локального управления рисками.
  • Все стратегии предполагают минимальный риск (например, фиксированный объем 0,01 лота с риском 0,1% эквити). Эффект "накопления" пренебрежимо мал.
  • Ручной мониторинг вполне возможен (например, в случае одной активной стратегии с редкими сделками).
  • Вы в основном сосредоточены на тестировании на исторических данных. Ограничения RiskGate, связанные с работой в реальном времени, усложняют работу в Тестере стратегий.

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

  • единая точка отказа (если сервис выходит из строя, торговля останавливается);
  • сложность тестирования (модульные и интеграционные тесты правил управления рисками обязательны);
  • чувствительность к задержкам (в высокочастотных стратегиях возможно истечение таймаута).

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


Заключение

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

Представленная в статье архитектура предлагает иной подход. Вместо самостоятельных попыток каждого советника управлять рисками используется единый сервис RiskGate, централизующий решения по рискам для всех стратегий счета. Советники становятся генераторами сигналов: описывают планируемую сделку в JSON и спрашивают сервис: "Разрешена ли эта сделка и с каким размером лота?". Сервис возвращает структурированное решение, и только после этого советник обращается к брокеру.

Теперь у вас есть набор компонентов для простой реализации этой архитектуры: пакет sockets для обмена по TCP, logger для структурированных логов, riskgate для сервера и базового класса обработчика, riskgate-ea для клиента на стороне советника. Больше не нужно разрабатывать собственный протокол обмена через сокеты, создавать логику повторного подключения или разносить разовые проверки рисков по коду каждого советника. Вы можете создать собственный обработчик RiskGate и советник, используя приложенные файлы или интерфейс командной строки KnitPkg.

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

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



Приложения

Прилагаемые исходные файлы образуют минимальный рабочий каркас сервера RiskGate (сервис) и клиента (советник). Их можно использовать как отправную точку для собственных проектов даже без KnitPkg: отредактируйте обработчик и файлы точек входа сервиса и советника, оставив плоские заголовочные файлы зависимостей без изменений. Если вы предпочитаете полноценные проекты KnitPkg плоским файлам, в реестре также доступны оба компонента: сервер и советник.

Имя файла Сторона Роль Возможность редактирования
RiskGateHandlerDep_flat.mqh Сервер Единый плоский заголовочный файл со всеми серверными зависимостями (riskgate, logger, sockets, JAson). Используется обработчиком. Нет (сгенерирован KnitPkg)
RiskGateHandler.mqh Сервер
Ваш подкласс RiskGateHandler со всеми правилами управления рисками, включая OnSignal() и вспомогательные средства. Да (основной файл серверной логики управления рисками)
RiskGateServiceExample.mq5 Сервер
Точка входа сервиса: подключает обработчик, создает Logger и RiskGateServer, подключает MyHandler, вызывает Run(). Да (обычно нужны лишь минимальные изменения в OnStart())
RiskGateEaExample_flat.mqh Советник Единый плоский заголовочный файл со всеми зависимостями советника (riskgate-ea, logger, sockets, JAson). Используется советником. Нет (сгенерирован KnitPkg)
RiskGateEaExample.mq5 Советник Точка входа советника: подключает плоский заголовочный файл, создает экземпляр RiskGateClient, устанавливает и разрывает соединение, отправляет минимальные сигналы. Да (можно переименовать файл и дополнить его логикой своей стратегии)
MQL5.zip ZIP Архив со всеми перечисленными файлами; распакуйте его непосредственно в каталог данных MetaTrader 5. Нет (ZIP-архив)

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21720

Прикрепленные файлы |
MQL5.zip (30.13 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Syamsurizal Dimjati
Syamsurizal Dimjati | 20 мая 2026 в 20:37

Это очень интересная инициатива и перспектива для будущего развития советников и алгоритмической торговли.
Я считаю, что подобные идеи могут в будущем существенно изменить подход к разработке, оптимизации и адаптации советников к рыночным условиям.

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

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

Есть один вопрос, который меня по-прежнему интересует:
Как вы оцениваете долгосрочный баланс между адаптивными/самообучающимися системами и необходимостью обеспечения стабильности, последовательности и предотвращения переобучения в условиях реальной торговли?

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


С уважением
Douglas Nascimento Rechia
Douglas Nascimento Rechia | 20 мая 2026 в 21:49
Syamsurizal Dimjati алгоритмической торговли.
Я считаю, что подобные идеи могут в будущем существенно изменить подход к разработке, оптимизации и адаптации советников к рыночным условиям.

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

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

Есть один вопрос, который меня по-прежнему интересует:
Как вы оцениваете долгосрочный баланс между адаптивными/самообучающимися системами и необходимостью обеспечения стабильности, последовательности и предотвращения переобучения в условиях реальной торговли?

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


С уважением

Здравствуйте, Syamsurizal,

Большое спасибо за вдумчивый отзыв и ваш вопрос.

У меня нет однозначного ответа на вопрос о долгосрочном балансе между адаптивными/самообучающимися системами и стабильностью, но я могу поделиться тем, что оказалось полезным в моём опыте.

Некоторые формы адаптации к текущим рыночным условиям кажутся действительно полезными — например, ATR для волатильности, коэффициент эффективности Перри Кауфмана для шума и адаптивные скользящие средние. Вы также можете изучить метод продвижения вперед (walk-forward analysis), хотя лично я не добился с его помощью реального успеха в реальной торговле.

Сегодня много шума вокруг машинного обучения (с контролем и без), моделей классификации и даже подходов на основе LLM. Все это, старое или новое, может легко превратиться в очень сложную переобученность, если не использовать с большой осторожностью. Для меня ключевые моменты следующие:
- Начинайте с чёткой гипотезы, основанной на фундаментальных рыночных предпосылках, а не только на статистике.
- Разрабатывайте систему для проверки этой предпосылки; не проводите беспорядочный поиск методом «грубой силы».
- Не ждите «святого Грааля», который сам по себе выдержит любые рыночные условия.

В конечном итоге лучшим «методом» по-прежнему остаются ваше собственное мышление и суждение, согласованные с фундаментальными рыночными принципами, а также способность управлять системами в условиях меняющихся рыночных режимов. Адаптивные инструменты и интеллектуальные архитектуры, такие как RiskGate, призваны поддерживать это, а не заменять.
Разработка классического индикатора «Крестики-нолики» на языке MQL5 Разработка классического индикатора «Крестики-нолики» на языке MQL5
В данной статье на языке MQL5 реализован пользовательский индикатор «Крестики-нолики» (Point and Figure, PF), который отображает движение цены в столбцы X/O, используя фиксированный размер клетки и логику разворота через три клетки. Мы определяем базовую цену, преобразуем цены в интервалы клеток, управляем трендами и разворотами, автоматически масштабируем окно индикатора и отображаем символы с помощью объектов, обеспечивая четкое, независимое от времени представление трендов, прорывов и уровней поддержки и сопротивления.
Создание индикатора для моделирования будущих свечей после пересечения EMA в MQL5 Создание индикатора для моделирования будущих свечей после пересечения EMA в MQL5
Пользовательский модуль моделирования будущих свечей обнаруживает пересечения быстрой и медленной EMA и сразу же строит проекцию синтетических свечей после сигнального бара. Он формирует тела и тени свечей по заданным правилам, отрисовывает их с помощью графических объектов и обновляется при каждом новом сигнале или изменении точки привязки. Вы получаете наглядное представление о возможном дальнейшем движении, позволяющее проверять выбранный момент для совершения сделки, визуализировать сценарии и отслеживать отмену сценария прямо на графике.
Построение моделей волатильности в MQL5 (Часть III): Реализация алгоритма SLSQP для оценки моделей Построение моделей волатильности в MQL5 (Часть III): Реализация алгоритма SLSQP для оценки моделей
На языке MQL5 реализован оптимизатор SLSQP для устранения расхождений в параметрах между библиотекой волатильности и модулем ARCH для Python. В статье подробно описаны обработка ограничений, варианты вычисления градиента, настройка и средства управления сходимостью, а также показано, как интегрировать решатель в существующий код. Практические примеры и сравнения демонстрируют совпадение логарифмов правдоподобия и параметров на общих наборах данных.
Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление) Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление)
Рассматриваем реализацию вероятностных компонентов Habi на MQL5 и OpenCL: обработку рыночных наблюдений, преобразование параметров нормального распределения и пакетное семплирование латентных векторов. На примере CNeuronHabiObservationOCL, CNeuronHabiGaussianOCL и CNeuronHabiVAEOCL разбираем архитектуру, инициализацию, прямой и обратный проходы. Показываем согласование Prior и Posterior через условную KL-дивергенцию и накопление градиентов от нескольких выборок. Полученные компоненты создают основу для последующей генерации торговых действий.