English Deutsch 日本語
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 82): Использование паттернов TRIX и WPR в обучении с подкреплением DQN

Возможности Мастера MQL5, которые вам нужно знать (Часть 82): Использование паттернов TRIX и WPR в обучении с подкреплением DQN

MetaTrader 5Интеграция |
41 0
Stephen Njuki
Stephen Njuki

Введение

При торговле с помощью роботов или советников поиск четких и воспроизводимых правил обычно начинается со знакомых технических индикаторов. Обычно начинают с осцилляторов, скользящих средних и ценовых паттернов, стремясь построить стратегии, устойчивые к смене рыночных режимов. Среди них классическую пару образуют TRIX (тройная сглаженная экспоненциальная скользящая средняя) и WPR, то есть Williams Percent Range (процентный диапазон Уильямса). TRIX обычно отражает импульс, отфильтровывая краткосрочный ценовой шум, тогда как WPR выделяет состояния перекупленности и перепроданности. В сочетании сигналы этих индикаторов дополняют друг друга и помогают выявлять развороты или продолжение ценового движения.

Стратегии на основе этих индикаторов обычно опираются на фиксированные правила. Например: "покупать, когда TRIX пересекает нулевой уровень снизу вверх, а WPR ниже -80" или "продавать, когда TRIX достигает пика, а WPR выше -20". Эти правила детерминированы и легко проверяются на исторических данных, но имеют общий недостаток: предполагают неизменность взаимосвязей на высокодинамичном рынке. Поэтому при изменении рыночных условий точность таких правил может снижаться, а "абсолютные пороги" требуют корректировки. 

Эта проблема подводит нас к обучению с подкреплением (RL). Обучение с подкреплением – один из методов машинного обучения, в котором агент на основе опыта взаимодействия со средой обучается выбирать подходящие действия. Вместо того чтобы полагаться только на заданные пороги индикаторов, агент RL исследует разные уровни порогов и корректирует торговые решения, стремясь максимизировать долгосрочное вознаграждение. Для большинства трейдеров это означало бы систему, которая не просто следует правилам, а адаптируется – по крайней мере, в теории.

Один из многообещающих для торговли методов RL – Deep Q Network (DQN). В отличие от моделей обучения с учителем, которые напрямую сопоставляют входные и выходные данные, DQN оценивают ценность определенных действий в заданных состояниях. В торговом контексте состояниями могут служить признаки, полученные из индикаторов и представленные в преобразованном виде, в виде двоичных значений или даже исходных сырых данных. В этой статье используются индикаторы TRIX и WPR. Действия могут соответствовать покупке, продаже или сохранению нейтральной позиции. RL-алгоритм DQN позволяет оценивать и уточнять эти действия на основе опыта, а не фиксированных произвольных правил.

Эта часть 82 серии "Возможности Мастера MQL5, которые вам нужно знать" посвящена использованию паттернов TRIX и WPR с алгоритмом обучения с подкреплением DQN. Сначала рассмотрим, что представляет собой DQN и какое место он занимает среди алгоритмов обучения с подкреплением. Затем рассмотрим реализацию сети Quantile Regression DQN на Python в классе QuantileNetwork. Кроме того, обсудим сложности развертывания моделей обучения с подкреплением в MetaTrader. Наконец, рассмотрим результаты Тестера стратегий для трех паттернов TRIX и WPR: 1, 4 и 5. Основная тема статьи – переход от теории к практической реализации с помощью Мастера MQL5 и демонстрация возможностей и ограничений такого гибридного подхода.

Паттерны 1, 4 и 5 показаны на графике ниже:

Паттерн 1

Медвежий сигнал возникает при отрицательной дивергенции TRIX и WPR в зоне сопротивления, то есть перекупленности.

p1

Паттерн 4

Бычий сигнал возникает, когда TRIX пробивает уровень сопротивления, а WPR находится в диапазоне от -50 до -20.

p4

Паттерн 5

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

p5


Сети Deep Q – обучение с подкреплением на основе ценности

Чтобы понять, почему DQN хорошо подходят для использования в торговле, полезно начать с основ обучения с подкреплением. В основе обучения с подкреплением лежит систематическое взаимодействие агента со средой. На каждом временном шаге агент наблюдает текущее состояние, выполняет действие и получает вознаграждение, пропорциональное полезности этого действия. Цель агента – максимизировать суммарное вознаграждение с течением времени. Этого можно достичь, улучшая его политику, то есть правило сопоставления состояний и действий. 

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

DQN основана на принципе Q-обучения. Q-обучение – один из наиболее известных алгоритмов обучения с подкреплением на основе ценности, вводящий понятие Q-функции. Ее обозначают как Q(s, a): функция оценивает ожидаемое будущее вознаграждение за действие a в состоянии s при дальнейшем следовании оптимальной политике. Если Q-функция точна, в каждый момент выбирается действие с наибольшим Q-значением. В классических реализациях Q-обучения эти Q-значения хранятся в таблице, что осуществимо только при ограниченном пространстве действий и ограниченном пространстве состояний.

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

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

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

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

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


Обучение с подкреплением на основе ценности в сравнении с методами на основе политики и актора-критика

Также сравним эти подходы. Сети Deep Q относятся к семейству алгоритмов обучения с подкреплением на основе ценности. Их особенность – оценка ценности действий, а не условное прямое сопоставление состояний и действий. Иными словами, агент, работающий на основе ценности, отвечает на вопрос: "Если я выполню это действие сейчас, какого долгосрочного вознаграждения я могу ожидать?" Напротив, альтернативные методы обучения с подкреплением сосредоточены не на оценке действий, а на прямом формировании политики либо на сочетании оценки и обучения политики. Понимание этих различий особенно важно при оценке того, насколько каждый метод соответствует уникальным требованиям для успешной торговли. 

Методы на основе политики, такие как REINFORCE и Proximal Policy Optimization (PPO), вообще не оценивают Q-значения. Вместо этого в них параметризуется сама политика, которая становится распределением вероятностей по возможным действиям. Поэтому обучение корректирует политику так, чтобы действия с более высоким суммарным вознаграждением выбирались чаще. Такой подход особенно полезен в средах с непрерывным пространством действий, где дискретизация каждого возможного выбора неосуществима. Методы на основе политики могут формировать непрерывный диапазон размеров позиций, причем знак + или - может обозначать длинную или короткую позицию, вместо ограничения тремя вариантами: длинная позиция, короткая позиция или нейтральное состояние. Однако методы градиента политики обычно характеризуются высокой дисперсией при обновлении и требуют больше данных для стабилизации, чем методы на основе ценности.

Подходы актора-критика объединяют сильные стороны обоих подходов. В этой гибридной модели актор соответствует политике и определяет, какие действия выполнять. Критик оценивает выбранные действия, определяя их ценность. Совместная работа этих компонентов повышает стабильность обучения и эффективность обновления политики в алгоритмах актора-критика. На финансовых рынках подходы актора-критика позволяют модели формировать торговые действия разного масштаба, а критик уточняет выбор этих действий. К известным примерам таких подходов относятся Advantage Actor-Critic (A2C), Deep Deterministic Policy Gradient (DDPG) и Twin Delayed DDPG (TD3), рассмотренный в недавней статье.

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


Реализация квантильной сети на Python

Код, лежащий в основе советника из этой статьи, показывает, как адаптировать Deep Q Network к торговому контексту с помощью квантильной регрессии. Ниже приведен соответствующий код:

# ---------------------------- QR-DQN Core ---------------------------

class QuantileNetwork(nn.Module):
    def __init__(self, state_dim: int, action_dim: int, hidden: int = 256, num_quantiles: int = NUM_QUANTILES):
        super().__init__()
        self.num_quantiles = num_quantiles
        self.action_dim = action_dim
        self.fc1 = nn.Linear(state_dim, hidden)
        self.fc2 = nn.Linear(hidden, hidden)
        self.fc3 = nn.Linear(hidden, hidden)
        self.fc4 = nn.Linear(hidden, action_dim * num_quantiles)
        fanin_init(self.fc1.weight); nn.init.zeros_(self.fc1.bias)
        fanin_init(self.fc2.weight); nn.init.zeros_(self.fc2.bias)
        fanin_init(self.fc3.weight); nn.init.zeros_(self.fc3.bias)
        nn.init.uniform_(self.fc4.weight, -3e-3, 3e-3); nn.init.zeros_(self.fc4.bias)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        q = self.fc4(x)  # [batch, action_dim * num_quantiles]
        # FIX: only one inferred dimension allowed — reshape explicitly
        return q.view(x.size(0), self.num_quantiles, self.action_dim)  # [batch, N, A]

    def q_values(self, x):
        q = self.forward(x)            # [batch, N, A]
        return q.mean(dim=1)           # [batch, A]

Центральный класс реализации на Python – QuantileNetwork – аппроксимирует Q-функцию. Это лежит в основе обучения с подкреплением на основе ценности. Обычная DQN выдает по одному Q-значению для каждого действия, тогда как квантильный вариант прогнозирует распределение возможных исходов. Это особенно важно для торговли, где неопределенность рынка не менее значима, чем математическое ожидание. Моделируя полное распределение с помощью нескольких квантилей, сеть учитывает для каждого действия не только ожидаемое вознаграждение, но и сопутствующий риск.

Класс квантильной сети представляет собой полносвязную нейронную сеть из четырех линейных слоев. В первых трех слоях линейные преобразования сопровождаются функцией активации ReLU. Это позволяет сети выявлять нелинейные зависимости между входными состояниями и действиями на выходе. Четвертый, выходной слой имеет размерность, соответствующую числу действий и количеству квантилей.

При обучении и инференсе этот плоский выход преобразуется в тензор размерности [размер пакета, число квантилей, число действий]. На практике это означает, что для каждого входного состояния сеть формирует набор квантильных Q-значений для всех возможных действий. Усреднение квантилей позволяет модели получить обычное Q-значение для принятия решений, сохраняя при этом гибкость, необходимую для анализа рисков.

В начальных слоях веса инициализируются с учетом fan-in (масштабирования по числу входов). Для последнего выходного слоя используется равномерное распределение с малым диапазоном. Такая инициализация важна для обучения с подкреплением, поскольку нестабильное распределение весов может усиливать шум в данных. Иначе обучение стало бы нестабильным. Сбалансированная инициализация весов помогает квантильной сети избежать преждевременного расхождения и улучшает сходимость в ходе оптимизации и обратного распространения ошибки.

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

class QRDQNAgent:
    def __init__(self, state_dim: int, action_dim: int, hidden: int = 256):
        self.state_dim = state_dim
        self.action_dim = action_dim
        self.num_quantiles = NUM_QUANTILES

        self.net        = QuantileNetwork(state_dim, action_dim, hidden).to(DEVICE)
        self.target_net = QuantileNetwork(state_dim, action_dim, hidden).to(DEVICE)
        self.target_net.load_state_dict(self.net.state_dict())

        self.optimizer = optim.Adam(self.net.parameters(), lr=LR)
        self.replay_buffer = ReplayBuffer(capacity=100_000, state_dim=state_dim)

        # quantile fractions τ_i (midpoints)
        self.taus = torch.linspace(0.0 + 1.0/(2*self.num_quantiles),
                                   1.0 - 1.0/(2*self.num_quantiles),
                                   self.num_quantiles, device=DEVICE).view(1, -1)  # [1, N]

    @torch.no_grad()
    def act(self, state: torch.Tensor, epsilon: float = 0.1) -> int:
        if np.random.rand() < epsilon:
            return np.random.randint(self.action_dim)
        q_vals = self.net.q_values(state.unsqueeze(0).to(DEVICE))  # [1, A]
        return int(torch.argmax(q_vals, dim=1).item())

    def _quantile_huber_loss(self, preds: torch.Tensor, target: torch.Tensor, taus: torch.Tensor) -> torch.Tensor:
        """
        preds:  [B, N]   (predicted quantiles for chosen action)
        target: [B, N]   (target quantiles)
        taus:   [1, N]
        """
        diff = target.unsqueeze(1) - preds.unsqueeze(2)  # [B, N, N]
        huber = torch.where(diff.abs() <= KAPPA,
                            0.5 * diff.pow(2),
                            KAPPA * (diff.abs() - 0.5 * KAPPA))
        tau = taus.view(1, -1, 1)  # [1, N, 1]
        loss = (torch.abs(tau - (diff.detach() < 0).float()) * huber).mean()
        return loss

    def learn(self, batch_size: int = BATCH_SIZE):
        if self.replay_buffer.size < batch_size:
            return

        s, a, r, ns, d = self.replay_buffer.sample(batch_size)

        with torch.no_grad():
            # Double DQN selection w/ target evaluation (simple variant)
            q_next_online = self.net.q_values(ns)                  # [B, A]
            a_next = q_next_online.argmax(dim=1, keepdim=True)     # [B,1]
            q_next_all = self.target_net(ns)                       # [B, N, A]
            q_next_sel = q_next_all.gather(2, a_next.unsqueeze(1).expand(-1, self.num_quantiles, 1)).squeeze(-1)  # [B, N]
            target = r + (1.0 - d) * GAMMA * q_next_sel            # broadcast [B,1] + [B,N] -> [B,N]

        q_preds_all = self.net(s)                                  # [B, N, A]
        a_expanded  = a.view(-1, 1, 1).expand(-1, self.num_quantiles, 1)  # [B, N, 1]
        q_preds     = q_preds_all.gather(2, a_expanded).squeeze(-1)       # [B, N]

        loss = self._quantile_huber_loss(q_preds, target, self.taus)

        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        # Soft update target
        with torch.no_grad():
            for tp, sp in zip(self.target_net.parameters(), self.net.parameters()):
                tp.data.mul_(1.0 - TARGET_TAU)
                tp.data.add_(TARGET_TAU * sp.data)

    # -------------------- ONNX Export --------------------
    def export_qnet_onnx(self, path: str):
        """
        Export a wrapper that outputs mean Q-values: [1, action_dim]
        """
        class QValuesHead(nn.Module):
            def __init__(self, net: QuantileNetwork):
                super().__init__()
                self.net = net
            def forward(self, x):
                return self.net.q_values(x)

        wrapper = QValuesHead(self.net).to(DEVICE).eval()
        dummy = torch.zeros(1, self.state_dim, dtype=torch.float32, device=DEVICE)
        torch.onnx.export(
            wrapper, dummy, path,
            export_params=True, opset_version=17, do_constant_folding=True,
            input_names=["state"], output_names=["q_values"], dynamic_axes=None
        )
        return path

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

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

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

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

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


Проблемы интеграции RL и MQL5

Хотя Python и интеграция с MQL5 позволяют обучать RL-модели на исторических данных, их перенос в MetaTrader связан с рядом трудностей. Эти трудности уже упоминались в предыдущих статьях, особенно в заключении прошлой статьи, где использовалась модель, обученная с подкреплением. Основное ограничение состоит в том, что MQL5, как, впрочем, и другие подобные платформы, не поддерживает обратное распространение ошибки для ONNX-файлов. По крайней мере в последних версиях ONNX-файлы предназначены только для прямого прохода. 

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

Это ограничение вынуждает выбрать единственный доступный вариант – использовать экспортированную обученную модель. Этим же объясняется, почему при использовании обучения с подкреплением в MQL5 мы пока экспортируем в ONNX только одну сеть. В классическом цикле обучения с подкреплением агент непрерывно взаимодействует со средой, сохраняет опыт и корректирует Q-значения в реальном времени. Однако в MQL5 обучение должно полностью выполняться офлайн в Python. Модель однократно обучается на исторических данных. Затем экспортируются ее веса, параметры слоев и смещения, после чего она используется как статический актор.

Поэтому так называемая система обучения с подкреплением в MetaTrader фактически используется как модель, обученная с учителем. Она сопоставляет входные признаки индикаторов TRIX и WPR с выходными значениями, отражающими относительную привлекательность торговых действий. Эта политика, то есть правило сопоставления, не адаптируется к новым или развивающимся состояниям ни в реальной торговле, ни при форвардном тестировании.

Еще одна решаемая проблема – согласование требований среды выполнения ONNX с интерфейсом MQL5. Модели ONNX фактически неизменяемы: для соответствующих входных и выходных слоев они требуют заданных размерностей данных. Эти размерности задаются в Python с учетом размера пакета и точности чисел с плавающей точкой – 32 или 64 бита. Если размерности контейнеров, заданные в MQL5 при инициализации ONNX-дескрипторов, не совпадают с заданными в Python размерностями модели, инициализация среды выполнения завершится ошибкой. Эта проблема обычно решаема, но требует внимания, поскольку при экспорте моделей часто не выводят фактические размерности входных и выходных слоев.

Хорошая новость заключается в том, что помимо вывода фактических размерностей слоев в Python, MetaEditor теперь предлагает в меню "Сервис" приложение для просмотра нейронных сетей. После запуска приложения в отдельном окне MetaEditor можно открыть экспортированный ONNX-файл и просмотреть его подробную структуру, включая размерности входных и выходных слоев.


Компромисс обучения с учителем

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

Наша компромиссная реализация начинается с встраивания ONNX-файлов актора непосредственно в MQL5 в виде ресурсов. Эти ONNX-файлы по сути содержат веса, смещения и сведения о размерности слоев обученной квантильной сети. Модель преобразуется в формат, который среда выполнения MQL5 может эффективно обрабатывать. В пользовательском классе сигнала используются три модели для паттернов 1, 4 и 5. Каждая модель соответствует уникальному набору паттернов индикаторов TRIX и WPR. При инициализации советник создает ONNX-дескрипторы из этих ресурсов, чтобы использовать все сети при оценке условий входа в сделку. На этом этапе модель не переобучается и не изменяется, а просто загружается в память как готовый к использованию инструмент.


Логика сигнала

После экспорта ONNX и его встраивания в MQL5 необходимо сформировать код сигнала и условия открытия позиций. Этот код преобразует выходные данные квантильной сети в осмысленные условия для торговли. Эта логика реализована в функциях LongCondition и ShortCondition пользовательского класса сигнала:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalRL_TRX_WPR::LongCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
   _x.Init(2);
   _x.Fill(0.0);
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_1;
         results++;
      }
   }
//--- if the model 4 is used
   if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_4;
         results++;
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_BUY))
   {  _x[0] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_BUY, _x);
      if(_y > 0.0)
      {  result += m_pattern_5;
         results++;
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int CSignalRL_TRX_WPR::ShortCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
   _x.Init(2);
   _x.Fill(0.0);
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0) && IsPattern_1(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_1;
         results++;
      }
   }
//--- if the model 4 is used
   if(((m_patterns_usage & 0x10) != 0) && IsPattern_4(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_4;
         results++;
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0) && IsPattern_5(POSITION_TYPE_SELL))
   {  _x[1] = 1.0f;
      double _y = RunModel(0, POSITION_TYPE_SELL, _x);
      if(_y < 0.0)
      {  result += m_pattern_5;
         results++;
      }
   }
//--- return the result
//if(result > 0)printf(__FUNCSIG__+" result is: %i",result);
   if(results > 0 && result > 0)
   {  return(int(round(result / results)));
   }
   return(0);
}

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

Функция LongCondition начинается с объявления вектора признаков, представляющего входное состояние. Вектор x инициализируется и заполняется нулями. Затем в зависимости от используемых советником паттернов одному из двух индексов вектора присваивается значение 1. Напомним, так используется входной параметр Patterns-Used.

Например, если паттерн имеет индекс 1, параметру Patterns-Used нужно присвоить значение 2 в первой степени, то есть 2. Если индекс равен 2, получаем 2 в степени 2, то есть 4, и т.д. Максимальное значение этого параметра – 1023, поскольку предусмотрено только 10 паттернов. Любое число от 0 до 1023, не являющееся точной степенью двойки, задает комбинацию из этих паттернов. Читатель может поэкспериментировать с настройкой советника на использование нескольких паттернов. Однако на основании аргументов и результатов тестирования из предыдущих статей мы пока не рассматриваем этот вариант в рамках серии.

Используются только паттерны 1, 4 и 5, поэтому проверяются только они. Если выполнены условия паттерна 1, первому индексу вектора присваивается значение 1. То же выполняется для паттерна 4 и паттерна 5. Хотя данный советник теоретически допускает использование нескольких паттернов, как пояснялось выше курсивом, в этой реализации одновременно используется только один паттерн. В действительности использование нескольких паттернов с высокой вероятностью приведет к очень ненадежным результатам тестирования из-за частого взаимного подавления сигналов.

При срабатывании активного паттерна итог условия определяется сочетанием его оптимизированного веса и результата прямого прохода через ONNX-модель. Функция ShortCondition повторяет эту логику, но применяется к паттернам для открытия коротких позиций. Объявляется вектор признаков x и выполняются аналогичные проверки паттернов. Главное отличие состоит в том, что при совпадении паттерна, подтвержденном квантильной сетью, значение 1 присваивается второму, а не первому индексу. Все остальные взвешенные расчеты и определение итогового значения выполняются так же, как в LongCondition.


Результаты тестов

При обучении и оптимизации использовался символ EURUSD на 4-часовом таймфрейме за период с 01.07.2023 по 01.07.2024. Период форвардного тестирования охватывал время с 01.07.2024 по 01.07.2025. Для паттернов 1, 4 и 5 получены следующие результаты.

Паттерн 1

r1

c1

i1

Паттерн 4

r4

c4

Паттерн 5

r5

c5

Только паттерн 1 оказался прибыльным при форвардном тестировании, тогда как паттерны 4 и 5 дали слабые результаты. Это не финансовая рекомендация. Однако слабые или неадаптированные сети акторов, используемые как модели обучения с учителем, могли повлиять на результат. Наша цель, как всегда, не предложить готовые решения, а изучить различные возможности и показать, что можно реализовать. Если читатель решит использовать представленные идеи, дальнейшая работа и необходимые проверки остаются за ним.


Заключение

Статья о сигнальных паттернах TRIX и WPR в рамках обучения с подкреплением показала как возможности, так и текущие ограничения MQL5. Модули Python постоянно развиваются, поэтому в будущем экспортированные в ONNX сети, возможно, удастся обновлять с помощью обратного распространения ошибки. Пока это невозможно, и в этом состоит ограничение. Модели необходимо обучать офлайн, а затем фиксировать в неизменяемом виде, чтобы они работали как механизмы для инференса.

имя описание
WZ_82.mq5 Основной файл советника, собранный Мастером, в заголовке которого перечислены включенные файлы
SignalWZ_82.mqh Пользовательский класс сигнала, который импортирует DQN-модели
82_5_0.onnx Модель для паттерна 5, экспортированная в формат ONNX.
82_4_0.onnx Модель для паттерна 4, экспортированная в формат ONNX.
82_1_0.onnx Модель для паттерна 1, экспортированная в формат ONNX.

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

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

Прикрепленные файлы |
WZ_82.mq5 (7.28 KB)
SignalWZ_82.mqh (14.9 KB)
82_5_0.onnx (671.85 KB)
82_4_0.onnx (669.9 KB)
82_1_0.onnx (671.85 KB)
Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами
В статье представлен фреймворк нормализации CSV-данных от нескольких брокеров. Подключаемый файл MQL5 дополняет экспорт брокерскими метаданными. Модуль Python устраняет расхождения схем — различия в разрядности котирования и соглашениях о пунктах, вариантах названий символов, временных смещениях, моделях комиссий и валютах счёта и валютах, в которых выражена прибыль — и формирует единый канонический набор данных. Сравнительные визуализации распределений проскальзывания и результатов после учёта издержек позволяют надёжно сопоставлять результаты стратегии между разными брокерскими средами без незаметного искажения данных.
Автоматизация торговых стратегий на MQL5 (Часть 35): Разработка торговой системы на основе брейкер-блоков Автоматизация торговых стратегий на MQL5 (Часть 35): Разработка торговой системы на основе брейкер-блоков
В этой статье мы создадим на MQL5 торговую систему на основе брейкер-блоков. Она определяет диапазоны консолидации, обнаруживает пробои и подтверждает брейкер-блоки по свинговым точкам, чтобы торговать их ретесты с заданными параметрами риска. Система динамически отображает ордер-блоки и брейкер-блоки, а также связанные с ними подписи и стрелки; кроме того, система поддерживает автоматическую торговлю и использование трейлинг-стопа.
Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса
В этой статье мы создадим на MQL5 торговую систему на основе зон спроса и предложения. Она определяет такие зоны по диапазонам консолидации, подтверждает их импульсными движениями и открывает позиции на ретестах с подтверждением тренда и настраиваемыми параметрами риска. Система отображает зоны с помощью динамических подписей и цветов, а также поддерживает трейлинг-стопы для управления рисками.
Теория графов: Алгоритм Форда—Фалкерсона для оценки ликвидности и пропускной способности рынка Теория графов: Алгоритм Форда—Фалкерсона для оценки ликвидности и пропускной способности рынка
В статье представлен советник на MQL5, который адаптирует метод максимального потока Форда-Фалкерсона в фильтр ликвидности и пропускной способности. Рыночные структуры — максимумы/минимумы свингов, ценовой дисбаланс (FVG), ордер-блоки и пулы ликвидности — образуют ориентированный граф с пропускной способностью ребер, определяемой объемом, ценовой реакцией, расстоянием и качеством структуры. Максимальный поток позволяет оценить возможности ICT-сделок, отфильтровать слабые пути и использовать динамическое управление размером позиции для последовательного двухэтапного процесса принятия решений.