English Deutsch 日本語
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 80): Использование паттернов Ишимоку и ADX Уайлдера в обучении с подкреплением TD3

Возможности Мастера MQL5, которые вам нужно знать (Часть 80): Использование паттернов Ишимоку и ADX Уайлдера в обучении с подкреплением TD3

MetaTrader 5Торговые системы |
40 0
Stephen Njuki
Stephen Njuki

Введение

Алгоритмическая торговля постепенно перешла от статичных индикаторных стратегий к более динамичным методам, адаптирующимся к изменяющимся рыночным условиям. Одним из наиболее перспективных направлений является обучение с подкреплением. Напомним, что ранее мы уже рассматривали реализацию этого подхода, а также подхода Inference Learning в прототипах советников, собранных с помощью Мастера MQL5. В отличие от классических методов машинного обучения, выполняющих прогнозирование или классификацию, обучение с подкреплением (RL) позволяет агенту обучаться, взаимодействуя со средой. В торговле это означает непрерывное отслеживание ценовых характеристик, выбор действий (покупка, продажа или удержание позиции/бездействие) и получение вознаграждения в виде прибыли или убытка.

Среди алгоритмов обучения с подкреплением (RL) Twin Delayed Deep Deterministic Policy Gradient (TD3) в некоторых кругах рассматривается как надежный кандидат для финансовых задач. TD3 разработан для непрерывных пространств действий, поэтому особенно хорошо подходит для торговых задач, где размер позиции и момент входа не сводятся к бинарному выбору, а требуют точной настройки. По сравнению со своим предшественником DDPG алгоритм TD3 включает важные улучшения стабильности: использование нескольких критиков, добавление сглаживающего шума к целевым действиям и отложенные обновления политики для предотвращения переобучения на кратковременных шумовых колебаниях. 

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

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


Обучение с подкреплением в трейдинге

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

  • Агент. В нашем случае это торговый алгоритм, который выбирает покупку, продажу или удержание позиции/бездействие.
  • Среда. Финансовый рынок, представленный историей цен, индикаторами и производными признаками.
  • Состояние. Числовое представление текущих рыночных условий; в нашем случае это показания индикаторов для паттернов 0, 1 и 5. Вместо бинарного формата входных данных, использовавшегося ранее, мы перейдем к более непрерывному представлению.
  • Действие. Торговое решение. Действие может быть дискретным, принимая форму длинной или короткой позиции либо флэта, или непрерывным – например, задавать объем или размер позиции: отрицательные значения соответствуют короткой позиции, а положительные – длинной. В этой статье используется первый, дискретный вариант.
  • Вознаграждение. Числовой показатель, оценивающий качество действия. В трейдинге этот показатель часто рассчитывают на основе прибыли и убытка. Однако этот показатель часто корректируют с учетом риска, например штрафами за просадку или транзакционными издержками.

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

Одна из особенностей финансовых рынков состоит в их нестационарности. Например, в отличие от настольных игр, где правила не меняются, динамика рынка меняется под влиянием новых участников, регулирования и глобальных событий. Поэтому алгоритмы обучения с подкреплением вне политики, такие как TD3, особенно привлекательны: они могут обучаться на буферах воспроизведения с разнообразными состояниями рынка и адаптироваться гибче, чем алгоритмы, использующие политику, такие как SARSA или PPO.

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

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

Таким образом, обучение с подкреплением задает рациональную основу для торговли: повторяющийся процесс наблюдения, выбора действий и получения прибыли или убытка соответствует циклу "агент – среда – вознаграждение". С помощью TD3 эти идеи можно развить в более устойчивую алгоритмическую схему для непрерывных действий, связывающую обучение в Python с практическим исполнением в MQL5.



Почему TD3

На раннем этапе экспериментов трейдеров с обучением с подкреплением DDPG (Deep Deterministic Policy Gradient) быстро стал основным алгоритмом для задач с непрерывными действиями. DDPG объединил архитектуру "актор – критик" с нейронными сетями, что позволило применять детерминированные политики в задачах распределения портфеля и масштабирования позиции. Однако у DDPG было два существенных недостатка. Смещение в сторону переоценки Q-значений и нестабильность из-за частых обновлений политики. При работе с финансовыми временными рядами, для которых непредсказуемость является нормой, эти недостатки могут приводить к тому, что модель RL хорошо работает в симуляции, но резко деградирует при переходе к реальным условиям.

Эти проблемы стали предпосылкой для появления Twin Delayed Deep Deterministic Policy Gradient (TD3). TD3 разработан для устранения недостатков DDPG при сохранении его сильных сторон. Он включает три нововведения, непосредственно полезных для торговых задач. Рассмотрим их по очереди:

  • Два критика. При оценке значений действий многие алгоритмы RL используют только одну сеть критика. В TD3 используются две такие сети. Для каждой пары "состояние – действие" оба критика рассчитывают оценку значения, а алгоритм выбирает меньшую из двух оценок. Этот консервативный подход значительно снижает склонность к переоценке. С точки зрения торговли это не позволяет агенту чрезмерно оптимистично оценивать рискованные торговые условия, что способствует более осторожному и стабильному принятию решений.
  • Сглаживание целевой политики. Финансовые рынки подвержены шуму в виде случайных скачков цен, ложных пробоев и краткосрочных всплесков волатильности. TD3 противодействует этому, добавляя ограниченный гауссовский шум к целевым действиям при обновлении критиков. Благодаря сглаживанию сети критиков обучаются функциям значений, менее чувствительным к незначительным колебаниям. В результате политика не слишком остро реагирует на аномалии отдельных тиков, а учитывает более устойчивые паттерны.
  • Отложенные обновления политики. В DDPG сети актора и критика обновляются одновременно. Однако в TD3 намеренно вводится задержка. Сеть политики, или актора, обновляется реже, чем сети критиков. Благодаря этому актор опирается на хорошо обученные сети критиков, а не следует нестабильным, частично усвоенным сигналам. На практике эта задержка обеспечивает более стабильное улучшение и сокращает число нетипичных изменений в торговых паттернах.

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

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

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



Обучение моделей TD3

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

В обучении с подкреплением (RL) среда задает правила взаимодействия. Для торговых сценариев мы создаем построчно организованную среду, где каждая строка исторических данных соответствует одному временному шагу. Ценовые признаки, такие как разности цен, спаны Ишимоку и значения ADX, формируют вектор состояния – вход сети актора. Действием становится соответствующее торговое решение, формируемое сетью актора TD3 и обычно масштабируемое в диапазоне [-1, 1]. Вознаграждение, как всегда, имеет большое значение. Это связано с тем, что в долгосрочной перспективе оно направляет агента к прибыльным стратегиям, формируя сигнал для обратного распространения ошибки при обновлении сети актора. В нашем случае подойдет простой, но эффективный вариант:

F1

Где:

  • yt – направленное движение рынка;
  • txn_cost – штраф, пропорциональный изменениям в действии.

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

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

У TD3 также есть ряд гиперпараметров, которые существенно влияют на его эффективность, особенно на финансовых рынках. К ним относятся: размер пакета (большие пакеты сглаживают оценки градиента, но обычно требуют больше памяти); коэффициент дисконтирования gamma, благодаря которому агент оценивает долгосрочную прибыль почти так же высоко, как немедленную (это особенно важно в свинг-трейдинге); скорость мягкого обновления tau, определяющая, насколько медленно целевые сети следуют за рабочими сетями, чтобы предотвратить нестабильность; параметры policy_noise и noise_clip, регулирующие шум, добавляемый к целевым действиям при обновлении сетей критиков (это не позволяет сетям подстраиваться под отдельные выбросы); и, наконец, policy_delay, благодаря которому сеть актора обновляется реже, чем сети критиков, что делает улучшение политики более стабильным.

Подбор значений каждого из этих гиперпараметров – отчасти наука, отчасти искусство. На практике можно начать с рекомендованных в работах по TD3 значений по умолчанию, а затем корректировать их с учетом характеристик торговых данных и результатов тестирования. Обучающий цикл выполняется по эпохам, в каждой из которых агент последовательно проходит строки исторических данных. На каждом шаге мы наблюдаем состояние и извлекаем из набора данных признаки – сигналы Ишимоку и ADX, объединенные в вектор; затем выбираем действие, формируемое сетью актора с добавлением исследовательского шума на этапе обучения; далее применяем действие к среде, вычисляем вознаграждение и переходим к следующему состоянию; затем сохраняем переход, помещая показанный выше кортеж в буфер воспроизведения; наконец, периодически выбираем небольшой пакет из буфера воспроизведения и обновляем сети критиков, а затем с меньшей частотой – сеть актора.

Реализация этого обучения в Python приведена в следующем фрагменте исходного кода:

if __name__ == "__main__":
    # Example dummy data; replace with your real x_data, y_data arrays
    T = 5000
    state_dim = 6
    action_dim = 1

#----------------------------MT5 Connections-------------------------
    if not mt5.initialize(login = 5040189685, server = "MetaQuotes-Demo", password = "JmYp-u4v"):
        print("initialize() failed, error code =",mt5.last_error())
        quit()

    # set start and end dates for history data
    from datetime import timedelta, datetime
    #end_date = datetime.now()
    end_date = datetime(2024, 7, 1, 0)
    start_date = datetime(2023, 7, 1, 0)

    # print start and end dates
    print("data start date =",start_date)
    print("data end date =",end_date)

    # get rates
    eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H4, start_date, end_date)

    # create dataframe
    df = pd.DataFrame(eurusd_rates)

    df = Ichimoku(df)
    df = ADX_Wilder(df)

    index = 5
    scaler = 1    
    
    x_data, drops = GetFeatures(index, scaler, df)
    y_data = GetStates(df, drops)
    
    ##    min_rows = min(len(x_data), len(y_data))
    min_rows = min(x_data.shape[0], y_data.shape[0])
    x_data = x_data[:min_rows]
    y_data =  y_data[:min_rows]

    print(" x rows =",x_data.shape[0])
    print(" y rows =",y_data.shape[0])
    
    # Example usage (your style)
    state_dim = x_data.shape[1]           # Example state dimension
    action_dim = y_data.shape[1]          # Example action dimension
    agent = TD3Agent(state_dim, action_dim)  # alias to TD3Agent

    # Load the environment
    env = CustomDataEnv(x_data, y_data, txn_cost=TXN_COST)

    epochs = 10

    # Training loop
    for epoch in range(epochs):
        s_np = env.reset()
        for row in range(x_data.shape[0]):
            # Build torch state
            state = torch.tensor(s_np, dtype=torch.float32)
            # Policy action (with exploration noise)
            action = agent.act(state, noise_scale=0.1)
            next_state, reward, done, info = env.step(action)
            agent.replay_buffer.add(state, action, reward, next_state, done)

            # Periodic learning
            if row % BATCH_SIZE == 0:
                agent.learn(batch_size=BATCH_SIZE)

            # Advance
            s_np = next_state
            if done:
                env.render()
                s_np = env.reset()

        print(f"Epoch {epoch+1}/{epochs} done. Buffer size={agent.replay_buffer.size}")

    # Export actor ONNX post-training
    os.makedirs("exports", exist_ok=True)
    out_path = agent.export_actor_onnx(os.path.join("exports",str(index)+"_"+str(scaler)+"_actor.onnx"))
    print(f"Exported trained actor to: {out_path}")

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

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

F2


Экспорт TD3 в ONNX

После обучения агента TD3 в Python следующим шагом становится экспорт сети актора для развертывания в MetaTrader. Наиболее практичный способ сделать это – использовать формат Open Neural Network Exchange (ONNX). Это независимый от фреймворка формат модели. ONNX позволяет обучать модели с помощью PyTorch, а затем эффективно выполнять инференс в MetaTrader с помощью встроенной поддержки ONNX Runtime.

Почему экспортируется только сеть актора? Дело в том, что в TD3 и большинстве других алгоритмов RL актор формирует необходимые действия для текущего состояния. Сеть критика необходима только при обучении, но не при инференсе в реальном времени. Экспортируя только сеть актора, мы минимизируем потребление ресурсов во время выполнения и избегаем ненужных сложностей, связанных с обучением в MQL5. Код экспорта из Python приведен ниже:

    # -------------------- ONNX Export --------------------
    def export_actor_onnx(self, path: str):
        self.actor.eval()
        dummy = torch.zeros(1, self.state_dim, dtype=torch.float32, device=DEVICE)
        torch.onnx.export(
            self.actor, dummy, path,
            export_params=True, opset_version=17, do_constant_folding=True,
            input_names=["state"], output_names=["action"], dynamic_axes=None
        )
        return path

В приведенной выше функции экспорта вход обозначен как "state", а выход – как "action"; размерность выхода [1, action_dim] соответствует размеру выходного слоя. Входной и выходной слои представлены тензорами типа float32. Мы также используем opset_version=17 для совместимости с актуальными версиями ONNX Runtime, включая реализацию MQL5. Перед переносом файла в MQL5 необходимо протестировать экспорт в Python. Помимо проверки корректности файла необходимо определить и проверить размерности входного и выходного слоев – это ключевое условие для инициализации ресурсов ONNX в MQL5.

После проверки файла ONNX и импорта обученной политики TD3 в советник MQL5 модель начинает формировать торговые сигналы в среде MetaTrader. 



ONNX в MQL5

После экспорта сети актора TD3 следующим шагом становится ее импорт в MetaTrader 5. Как мы видели в предыдущих статьях, MQL5 имеет встроенную поддержку ONNX Runtime, позволяющую нативно выполнять во время торговли нейронные сети, обученные в Python. Благодаря этому агенты RL могут работать в советнике как традиционные источники сигналов. В MQL5 ONNX инициализируется как ресурс в функции OnInit() пользовательского класса сигнала следующим образом:

#resource "Python/0_1_actor.onnx" as uchar __81_0[]
#resource "Python/1_1_actor.onnx" as uchar __81_1[]
#resource "Python/5_1_actor.onnx" as uchar __81_5[]
#include <SRI\PipeLine.mqh>
#define __PATTERNS 3
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSignalRL_Ichimoku_ADXWilder::CSignalRL_Ichimoku_ADXWilder(void) : m_pattern_0(50),
   m_pattern_1(50),
   m_pattern_5(50)
//m_patterns_usage(255)
{
//--- initialization of protected data
   // 
   //-----omitted source
   //
//--- create model from static buffer
   m_handles[0] = OnnxCreateFromBuffer(__81_0, ONNX_DEFAULT);
   m_handles[1] = OnnxCreateFromBuffer(__81_1, ONNX_DEFAULT);
   m_handles[2] = OnnxCreateFromBuffer(__81_5, ONNX_DEFAULT);
   // 
   //-----omitted source
   //
}

Так через хэндлы загружаются три экспортированные модели для паттернов сигналов 0, 1 и 5, готовые к вызовам инференса во время торговли. Сети акторов TD3 для каждого из трех паттернов сигналов ожидают входные данные той же размерности, с которой они обучались. Это тензор float32 размерности [1, state_dim]. Для завершения инициализации выполняется проверка в функции ValidationSettings(). Код выглядит следующим образом:

//+------------------------------------------------------------------+
//| Validation settings protected data.                              |
//+------------------------------------------------------------------+
bool CSignalRL_Ichimoku_ADXWilder::ValidationSettings(void)
{
//--- validation settings of additional filters
   if(!CExpertSignal::ValidationSettings())
      return(false);
//--- initial data checks
   const ulong _out_shape[] = {1, 1};
   for(int i = 0; i < __PATTERNS; i++)
   {  // Set input shapes
      int _in = 3;
      if(i == __PATTERNS - 1)
      {  _in = 7;
      }
      const ulong _in_shape[] = {1, _in};
      if(!OnnxSetInputShape(m_handles[i], ONNX_DEFAULT, _in_shape))
      {  Print("OnnxSetInputShape error ", GetLastError(), " for feature: ", i);
         return(false);
      }
      // Set output shapes
      if(!OnnxSetOutputShape(m_handles[i], 0, _out_shape))
      {  Print("OnnxSetOutputShape error ", GetLastError(), " for feature: ", i);
         return(false);
      }
   }
//--- ok
   return(true);
}

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

//+------------------------------------------------------------------+
//| Forward Feed Network, to Get Forecast State.                     |
//+------------------------------------------------------------------+
double CSignalRL_Ichimoku_ADXWilder::RunModel(int Index, ENUM_POSITION_TYPE T, vectorf &X)
{  vectorf _y(1);
   _y.Fill(0.0);
   //Print(" x: ", __FUNCTION__, X);
   ResetLastError();
   if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, X, _y))
   {  printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError());
      return(double(_y[0]));
   }
   //printf(__FUNCSIG__ + " pre y is: " + DoubleToString(_y[0], 5));
   if(T == POSITION_TYPE_BUY && _y[0] > 0.5f)
   {  _y[0] = 2.0f * (_y[0] - 0.5f);
   }
   else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f)
   {  _y[0] = 2.0f * (0.5f - _y[0]);
   }
   //printf(__FUNCSIG__ + " post y is: ", DoubleToString(_y[0], 5));
   return(double(_y[0]));
}

Выходные данные таких моделей RL легко настраивать. Например, если модель выдает несколько действий, например размер позиции и прогноз направления, то вектор выходных данных также будет многомерным – в данном случае двумерным. В данном случае модель выдает одно значение, при этом значение типа float должно находиться в диапазоне [0,1]. Это означает, что нам нужно преобразовать это единственное выходное значение в торговые операции. Для этого используем следующую упрощенную интерпретацию:

  • Если Action больше или равен 0.5f, мы открываем или удерживаем позицию на покупку.
  • Если Action меньше 0.5f, мы открываем или удерживаем позицию на продажу.

Обратите внимание: между покупкой и продажей здесь нет нейтральной зоны, но читатели могут изучить этот вариант, используя симметричный буфер между покупкой и продажей. Например, если этот буфер равен 0.2, зона бездействия будет простираться от -0.2f до 0.2f и т.д. Такой порог может помочь советнику избежать чрезмерного числа сделок из-за чувствительности к небольшим колебаниям около нуля. Если советник оптимизируется для непрерывного, а не фиксированного размера позиции, величину действия можно преобразовать в объем лота в пределах, установленных брокером. 

Предварительная обработка данных приобретает все большее значение. Модуль Python библиотеки SCIKIT-LEARN помог утвердить нормализацию и стандартизацию данных как важный этап перед их подачей в сеть или модель. Если масштабирование выполняется в Python во время обучения, перед инференсом ко входным данным крайне важно применять в точности то же масштабирование. Если при обучении использовались масштабирование по минимуму и максимуму, стандартное или робастное масштабирование, перед вызовом RunModel() необходимо применить в точности те же преобразования. Любое несоответствие в масштабировании неизбежно приведет к непредсказуемому поведению сети актора.

Внутри функции Direction(), которая вызывает LongCondition() и ShortCondition(), выполняется следующая последовательность: из последних баров формируется вектор признаков; признаки масштабируются в соответствии с преобразованием, использованным при обучении, – в нашем случае со стандартным масштабированием; затем они передаются в модель ONNX; выходное действие преобразуется в решение о покупке или продаже; наконец, сигналы на открытие и закрытие позиций формируются с учетом порогов пользовательского сигнала MQL5.

Эта последовательность превращает сеть актора TD3 в основной механизм принятия решений советника. Платформа MQL5 позволяет собирать советников с помощью Мастера MQL5, а трейдерам – создавать прототипы торговых роботов из модульных блоков кода. Новым читателям помогут руководства здесь и здесь. Как мы уже рассматривали в этой серии, такие блоки обычно содержат пользовательский класс сигнала, использующий индикаторы, например RSI или скользящие средние. Этот класс объединяется с блоками кода управления капиталом и трейлинг-стопа, оформленными как независимые классы. Мы интегрируем RL в эту экосистему через пользовательский класс сигнала, распознаваемый Мастером MQL5.

Реализация условий открытия длинных и коротких позиций в этом классе выглядит следующим образом:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalRL_Ichimoku_ADXWilder::LongCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
//--- if the model 0 is used
   if(((m_patterns_usage & 0x01) != 0))
   {  matrixf _raw = IsPattern_0();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         //Print(" buy x: ", __FUNCTION__, _x);
         double _y = RunModel(0, POSITION_TYPE_BUY, _x);
         if(_y > 0.0)
         {  result += m_pattern_0;
            results++;
         }
      }
   }
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0))
   {  matrixf _raw = IsPattern_1();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(1, POSITION_TYPE_BUY, _x);
         if(_y > 0.0)
         {  result += m_pattern_1;
            results++;
         }
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0))
   {  matrixf _raw = IsPattern_5();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(2, 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_Ichimoku_ADXWilder::ShortCondition(void)
{  int result  = 0, results = 0;
   vectorf _x;
//--- if the model 0 is used
   if(((m_patterns_usage & 0x01) != 0))
   {  matrixf _raw = IsPattern_0();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         //Print(" sell x: ", __FUNCTION__, _x);
         double _y = RunModel(0, POSITION_TYPE_SELL, _x);
         if(_y > 0.0)
         {  result += m_pattern_0;
            results++;
         }
      }
   }
//--- if the model 1 is used
   if(((m_patterns_usage & 0x02) != 0))
   {  matrixf _raw = IsPattern_1();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(1, POSITION_TYPE_SELL, _x);
         if(_y > 0.0)
         {  result += m_pattern_1;
            results++;
         }
      }
   }
//--- if the model 5 is used
   if(((m_patterns_usage & 0x20) != 0))
   {  matrixf _raw = IsPattern_5();
      matrixf _fitted(_raw.Rows(), _raw.Cols());
      if(m_pipeline.FitTransformPipeline(_raw, _fitted))
      {  _x = _fitted.Row(0);
         double _y = RunModel(2, 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);
}



Результаты тестирования

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

Для этой статьи мы провели три отдельных теста – по одному для каждого из сигнальных паттернов 0, 1 и 5 из части 74. Каждому из этих паттернов соответствовала отдельная сеть актора, обученная в Python и затем экспортированная в формат ONNX. Все три сети обучались на валютной паре EURUSD на четырехчасовом таймфрейме. Период обучения – с 2023.07.01 по 2024.07.01. Последующий период форвардного тестирования – с 2024.07.01 по 2025.07.01. По сравнению с предыдущей статьей мы изменили входной вектор сетей, сохранив использование пайплайнов. Мы не используем двоичный формат входных данных сети актора. Напомним: ранее, когда мы рассматривали добавление обучения с учителем к сигналу как способ лучше систематизировать сигнал советника, входной вектор состоял исключительно из нулей и единиц. Наличие единицы в любой размерности означало, что для выбранного сигнального паттерна выполнялись все важные условия для сигнала на покупку либо для медвежьего сигнала.

В этой статье мы снова экспериментируем со входными данными с плавающей запятой, то есть с векторами из нескольких значений типа double, чтобы исследовать возможности пайплайнов. Таким образом, входные векторы отражают величину учитываемых показаний индикаторов. Поэтому для них нет фиксированной размерности, например 2, как в предыдущих статьях. Результаты приведены ниже. Они существенно отличаются от результатов прошлого раза, когда использовались бинарные входные векторы: лишь сигнальный паттерн 5 показал некоторый потенциал при форвардном тестировании.

Паттерн 0:

r0r

c0r

Паттерн 1:

r1r

c1r

Паттерн 5:

r5r

c5r

Из приведенных выше отчетов видно, что переход от бинарных 0 и 1 во входных признаках к более непрерывному представлению отрицательно сказался на результатах. Однако эти изменения были внесены, чтобы использовать недавно введенную предварительную обработку данных, важность которой трудно отрицать. Можно предположить, что выбранная в этой статье реализация предварительной обработки не оптимальна; в следующих статьях я рассмотрю возможности ее тонкой настройки.

Заключение

В заключение мы показали, что TD3 можно обучить в Python, экспортировать в ONNX и интегрировать в MQL5 в виде пользовательского класса сигнала. Однако наш подход несколько нетрадиционен: мы используем обучение с подкреплением подобно обучению с учителем, поскольку не продолжаем обучение и обратное распространение ошибки во время инференса или торговли в реальном времени. Хотя это выполняется при моделировании и обучении в Python, обучение с подкреплением часто предполагает продолжение этого процесса и при развертывании в реальных условиях.

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

имяописание
WZ-81.mq5Советник, собранный Мастером, в заголовке которого перечислены включенные файлы
SignalWZ-81.mqhФайл класса сигнала, используемый Мастером для создания советника
0-1-actor.onnxЭкспортированная сеть для паттерна 0
1-1-actor.onnxЭкспортированная сеть для паттерна 1
5-1-actor.onnxЭкспортированная сеть для паттерна 5
PipeLine.mqhКлассы для различных типов преобразований

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

Прикрепленные файлы |
WZ-81.mq5 (7.53 KB)
0_1_actor.onnx (519.96 KB)
1_1_actor.onnx (519.96 KB)
5_1_actor.onnx (523.96 KB)
PipeLine.mqh (15.01 KB)
SignalWZ_81.mqh (16.99 KB)
Династический алгоритм — Dynastic Optimization Algorithm (DOA) Династический алгоритм — Dynastic Optimization Algorithm (DOA)
В статье реализован и разобран Dynastic Optimization Algorithm в рамках фреймворка C_AO на MQL5. Мы воспроизводим каноническую модель, закрываем неописанные в первоисточниках места и тестируем алгоритм на стенде с фиксированным бюджетом и ростом размерности. Показана причина деградации каноники и предложена модификация оператора с разреженной мутацией. Читатель получит рабочий класс и рекомендации по настройке для задач с дискретными параметрами.
Разработка самовосстанавливающегося советника в MQL5 (Часть 2): Виртуальная защита сделок, устойчивая к перезапуску Разработка самовосстанавливающегося советника в MQL5 (Часть 2): Виртуальная защита сделок, устойчивая к перезапуску
Постройте уровень виртуальной защиты, устойчивый к перезапускам, поверх механизма персистентности SQLite из Части 1. Советник восстанавливает скрытые стоп-лосс и тейк-профит после перезапуска, сверяет текущую цену с восстановленными уровнями выхода и в зависимости от результата либо закрывает позиции, либо продолжает ими управлять. В результате формируется согласованный сценарий восстановления, который обнаруживает управляемые позиции и обеспечивает безопасное управление в рабочем режиме.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Анализ CSV-данных (Часть 3): Построение аналитического пайплайна на Python для CSV-экспорта из MetaTrader 5 Анализ CSV-данных (Часть 3): Построение аналитического пайплайна на Python для CSV-экспорта из MetaTrader 5
MetaTrader 5 предоставляет обширные данные о результатах, но ограниченные возможности структурного анализа. В статье показано, как экспортировать результаты из MQL5 в CSV и построить пять визуализаций на Python, раскрывающих согласованность параметров между активами, компромисс между задержкой и шумом, деградацию результатов при walk-forward-валидации, распределение глубины и длительности просадок, а также внутридневные кластеры по часам и дням недели. Единый модуль автоматизации запускает полный пайплайн для каждого нового файла экспорта и обеспечивает воспроизводимую диагностику.