Возможности Мастера MQL5, которые вам нужно знать (Часть 82): Использование паттернов TRIX и WPR в обучении с подкреплением DQN
Введение
При торговле с помощью роботов или советников поиск четких и воспроизводимых правил обычно начинается со знакомых технических индикаторов. Обычно начинают с осцилляторов, скользящих средних и ценовых паттернов, стремясь построить стратегии, устойчивые к смене рыночных режимов. Среди них классическую пару образуют 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 в зоне сопротивления, то есть перекупленности.

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

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

Сети 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



Паттерн 4


Паттерн 5


Только паттерн 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Анализ CSV-данных (Часть 6): Нормализация результатов по нескольким брокерам и сверка CSV между платформами
Автоматизация торговых стратегий на MQL5 (Часть 35): Разработка торговой системы на основе брейкер-блоков
Автоматизация торговых стратегий на MQL5 (Часть 36): Торговля по зонам спроса и предложения на основе модели ретеста и импульса
Теория графов: Алгоритм Форда—Фалкерсона для оценки ликвидности и пропускной способности рынка
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования