preview
Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Окончание)

Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Окончание)

MetaTrader 5Торговые системы |
206 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Введение

Финансовые рынки живут по законам перемен. Сегодня они могут демонстрировать устойчивый рост, а завтра реагировать бурей на малейший внешний импульс. Новые инструменты, регуляторные инициативы, макроэкономические потрясения и даже социальные настроения — всё это меняет рыночный ландшафт столь же резко, как строительство новой автомагистрали перестраивает транспортные потоки в городе. Именно поэтому классические алгоритмы прогнозирования часто теряют эффективность, едва только сталкиваются с новыми условиями, отсутствующими в обучающей выборке.

Фреймворк ST-Expert был предложен в качестве одного из вариантов решения этой проблемы. Его основная идея проста и в то же время глубока. Вместо того чтобы полагаться на единую универсальную стратегию, он формирует ансамбль экспертов, каждый из которых специализируется на своём участке рыночной динамики. Такой подход можно сравнить с управлением инвестиционным портфелем. У опытного управляющего нет одной стратегии на все случаи жизни, но есть целый набор инструментов. Одни лучше работают в периоды устойчивого тренда, другие приносят прибыль в условиях высокой волатильности, третьи помогают сохранить капитал во время затяжного боковика. Подобно такому портфелю, ST-Expert распределяет внимание между своими экспертами и формирует сбалансированное решение для новых рыночных условий.

Главное преимущество этого подхода заключается в устойчивости. В отличие от моделей, которые фиксируют жёсткие связи между активами и становятся беспомощными при их изменении, ST-Expert умеет адаптироваться. Его архитектура построена вокруг слоя экспертных графонов, выступающих в роли генераторов связей между активами. Каждый графон формирует собственное представление о том, как могут взаимодействовать рынки или отдельные инструменты, а модуль смешивания динамически подбирает комбинацию экспертов в зависимости от текущей ситуации.

Архитектурно фреймворк основан на принципе смеси экспертов (Mixture of Experts). В основе лежит не один сверхэксперт, а целый ансамбль моделей, обученных на различных временных интервалах и сценариях. Важнейшей частью процесса становится эпизодическое обучение, при котором модель намеренно подвергается множеству стресс-тестов. Данные разделяются на разные рыночные режимы, например, фазы тренда, коррекции или флэта. А затем формируется задача, в которой каждый эксперт обучается в определенных условиях. В то же время модуль смешивания обучается комбинировать экспертов для достижения максимальной эффективности. Это сродни тренировке трейдера на исторических данных. Он учится принимать решения в условиях неполной информации и непредсказуемых изменений. И именно это делает его опытным.

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

Такой подход сочетает три ключевых качества. Во-первых, это гибкость. Слой экспертов можно встроить практически в любую современную архитектуру без ломки базовой структуры. Во-вторых, это устойчивость. В отличие от классических моделей, склонных залипать на одном паттерне, ST-Expert сохраняет точность даже при смене рыночного фона. И наконец, это эффективность. Расширение возможностей достигается без чрезмерного роста числа параметров. Это делает фреймворк практичным для реальных финансовых систем, где важны точность и скорость работы.

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

Авторская визуализация фреймворка ST-Expert представлена ниже.

Авторская визуализация фреймворка ST-Expert

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

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

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

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

Пройденный путь стал фундаментом для дальнейшей работы.



Глобально-локальное внимание

В предыдущей статье мы подробно обсудили и обосновали решения по внедрению подходов фреймворка ST-Expert в архитектуру Extralonger. Основная цель заключалась в интеграции модулей графонов в архитектуру Transformer, чтобы усилить возможности модели в анализе сложных зависимостей на финансовых рынках. На практике мы реализовали объекты полного и разреженного внимания, что позволило нам гибко работать с информацией на разных уровнях иерархии данных.

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

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

Класс CNeuronGlobLocGraphAtt можно представить как опытного трейдера, одновременно отслеживающего долгосрочные тренды и ловящего краткосрочные сигналы. Он строится на базе многоголового пулинга, наследуя возможности объекта CNeuronMHAttentionPooling, что даёт прочную основу для гибкой обработки данных.

class CNeuronGlobLocGraphAtt  :  public CNeuronMHAttentionPooling
  {
protected:
   CNeuronGraphAttention         cGlobal;
   CNeuronSparseGraphAttention   cLocal;
   CNeuronBaseOCL                cConcat;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronGlobLocGraphAtt(void) {};
                    ~CNeuronGlobLocGraphAtt(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint experts, float dropout,
                          uint emb_dimension, uint sparse_dimension,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const   {  return defNeuronGlobLocGraphAtt;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      TrainMode(bool flag) override;
  };

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

Все внутренние компоненты модуля объявлены статично, что позволяет нам оставить конструктор и деструктор класса пустыми. Инициализация происходит в методе Init. Этот подход напоминает опытного трейдера, который приходит на рынок уже с готовой командой аналитиков. Каждый специалист знает свою роль. А задача руководителя — правильно расставить их и организовать взаимодействие.

bool CNeuronGlobLocGraphAtt::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                  uint units, uint window, uint experts, float dropout,
                                  uint emb_dimension, uint sparse_dimension,
                                  ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, window, units,
                                       2, optimization_type, batch))
      return false;

Метод Init отвечает за настройку архитектуры нашего модуля глобально-локального внимания. Сначала вызывается метод инициализации родительского класса CNeuronMHAttentionPooling. Если на этом этапе возникают ошибки, процесс останавливается. Это похоже на проверку фундаментальных показателей актива перед открытием позиции — без надёжной основы дальнейшая работа теряет смысл.

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

   int index = 0;
   if(!cGlobal.Init(0, index, OpenCL, iUnits, iWindow, emb_dimension, experts, dropout,
                    optimization, iBatch))
      return false;

Затем создаётся cLocal, объект локального внимания. Он фокусируется на ближайших событиях и разреженных паттернах, позволяя модели ловить важные сигналы и аномалии, которые могут быть упущены глобальным анализом. Инициализация cLocal также проверяется на корректность — в случае ошибки, модуль не запускается, чтобы не подставить систему под неверные решения. Как трейдер, который не станет открывать позицию без надёжных данных.

   index++;
   if(!cLocal.Init(0, index, OpenCL, iUnits, iWindow, experts, dropout, emb_dimension,
                   sparse_dimension, optimization, iBatch))
      return false;

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

   index++;
   if(!cConcat.Init(0, index, OpenCL, 2 * iWindow * iUnits, optimization, iBatch))
      return false;
   cConcat.SetActivationFunction(None);
//---
   return true;
  }

Если все шаги прошли успешно, метод Init возвращает true, сигнализируя о готовности модуля к работе. В противном случае, инициализация прекращается, что гарантирует стабильность и надёжность работы модели.

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

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

bool CNeuronGlobLocGraphAtt::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cGlobal.FeedForward(NeuronOCL))
      return false;

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

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

   if(!cLocal.FeedForward(NeuronOCL))
      return false;

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

   if(!Concat(cGlobal.getOutput(), cLocal.getOutput(), cConcat.getOutput(),
              iWindow, iWindow, iUnits))
      return false;
//---
   return CNeuronMHAttentionPooling::feedForward(cConcat.AsObject());
  }

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

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

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

bool CNeuronGlobLocGraphAtt::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

Сначала метод проверяет корректность полученного указателя на объект исходных данных NeuronOCL, ведь без него вычисления невозможно провести. Далее вызывается базовый механизм обратного распространения ошибки родительского класса CNeuronMHAttentionPooling, который распределяет градиенты на уровне объединённого сигнала.

   if(!CNeuronMHAttentionPooling::calcInputGradients(cConcat.AsObject()))
      return false;
   if(!DeConcat(cGlobal.getGradient(), cLocal.getGradient(), cConcat.getGradient(),
                iWindow, iWindow, iUnits))
      return false;

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

Далее нам предстоит передать градиенты на уровень исходных данных по двум информационным потокам: глобального и локального внимания. Вначале осуществим обработку глобального модуля.

   if(!NeuronOCL.CalcHiddenGradients(cGlobal.AsObject()))
      return false;
   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(PrevOutput, false))
      return false;
   if(!NeuronOCL.CalcHiddenGradients(cLocal.AsObject()))
      return false;

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

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

   if(!SumAndNormilize(temp, PrevOutput, temp, iWindow, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

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

После вычисления градиентов наступает этап корректировки параметров модели, который реализуется в методе updateInputWeights. Здесь мы не создаём новые алгоритмы, а лишь последовательно передаём управление внутренним компонентам модуля, позволяя каждому из них самостоятельно скорректировать свои веса на основе полученных градиентов.

bool CNeuronGlobLocGraphAtt::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cGlobal.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cLocal.UpdateInputWeights(NeuronOCL))
      return false;
//---
   return CNeuronMHAttentionPooling::updateInputWeights(cConcat.AsObject());
  }

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

Полный код объекта и всех его методов представлен во вложении.



Объект верхнего уровня

После построения всех необходимых компонентов, следующим шагом становится непосредственное внедрение новых объектов в ранее созданную архитектуру фреймворка Extralonger. Напомню, что при работе над этим фреймворком мы изначально создали объект верхнего уровня, внутри которого были объявлены четыре динамических массива для хранения указателей на последовательности внутренних компонентов. Это решение дало нам гибкость. Теперь для добавления новых элементов нет необходимости строить объект с нуля, достаточно унаследовать функционал существующего объекта и переопределить лишь метод инициализации, в котором задаётся архитектура нового модуля. Именно таким образом создаётся класс CNeuronExtralongerGraph.

class CNeuronExtralongerGraph   :  public CNeuronExtralonger
  {
public:
                     CNeuronExtralongerGraph(void) {};
                    ~CNeuronExtralongerGraph(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint time_steps_in, uint time_steps_out, uint variables,
                          uint dimension, uint emb_dimension, uint period1,
                          uint frame1, uint period2, uint frame2, uint layers,
                          uint experts, uint m_units, float sparse,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronExtralongerGraph;   }
  };

Конструктор и деструктор оставлены пустыми, поскольку все внутренние структуры инициализируются в методе Init. Его задача — собрать разрозненные элементы в единую структуру, которая способна одновременно отслеживать долгосрочные тренды, краткосрочные колебания и пространственные взаимосвязи между переменными.

bool CNeuronExtralongerGraph::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint time_steps_in, uint time_steps_out, uint variables,
                                   uint dimension, uint emb_dimension, uint period1,
                                   uint frame1, uint period2, uint frame2, uint layers,
                                   uint experts, uint m_units, float sparse,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, variables, time_steps_out,
                                       3, optimization_type, batch))
      return false;

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

   CNeuronBatchNormOCL           *norm    = NULL;
   CNeuronConvOCL                *conv    = NULL;
   CNeuronTransposeOCL           *transp  = NULL;
   CNeuronLearnabledPE           *lnoise  = NULL;
   CNeuronSpatialEmbedding       *semb    = NULL;
   CNeuronTempEmbedding          *temb    = NULL;
   CNeuronGraphAttention         *att     = NULL;
   CNeuronGlobLocGraphAtt        *glatt   = NULL;
//--- Time projection
   cProjectionT.Clear();
   cProjectionT.SetOpenCL(OpenCL);
   int index = 0;
   lnoise = new CNeuronLearnabledPE();
   if(!lnoise ||
      !lnoise.Init(0, index, OpenCL, time_steps_in * variables, optimization, iBatch) ||
      !cProjectionT.Add(lnoise))
     {
      DeleteObj(lnoise);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, variables, variables, dimension, time_steps_in, 1,
                 optimization, iBatch) ||
      !cProjectionT.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   temb = new CNeuronTempEmbedding();
   uint half_emb = (emb_dimension + 1) / 2;
   if(!temb ||
      !temb.Init(0, index, OpenCL, time_steps_in, dimension, half_emb, period1,
                 frame1, emb_dimension - half_emb, period2, frame2, optimization, iBatch) ||
      !cProjectionT.Add(temb))
     {
      DeleteObj(temb);
      return false;
     }
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, temb.Neurons(), iBatch, optimization) ||
      !cProjectionT.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

Данный этап полностью перенесен из родительского класса без каких либо изменений. Кратко напомню, что проекция начинается с объекта Learnable Positional Encoding (LPE). В данном случае он добавляет обучаемый шум, который помогает модели стать более устойчивой к рыночной волатильности и шуму данных. Далее данные проходят через свёрточные слои, которые выделяют локальные паттерны и структурные особенности временного ряда, выявляя короткие тренды и аномалии. После этого к данным добавляются временные эмбеддинги (TempEmbedding). Заключительным шагом является Batch Normalization, которая стабилизирует сигналы и устраняет дисбаланс между разными компонентами эмбеддингов. Благодаря этому модель получает чистый, согласованный вход для дальнейших модулей внимания, минимизируя риск накопления ошибок на раннем этапе.

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

//--- Time Module
   cTimeModule.Clear();
   cTimeModule.SetOpenCL(OpenCL);
   for(uint i = 0; i < layers; i++)
     {
      index++;
      att = new CNeuronGraphAttention();
      if(!att ||
         !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension,
                   emb_dimension, experts, sparse, optimization, iBatch) ||
         !cTimeModule.Add(att))
        {
         DeleteObj(att);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension,
                 variables, time_steps_in, 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(TANH);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) ||
      !cTimeModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, variables,
                 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables,
                 1, optimization, iBatch) ||
      !cTimeModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cTimeModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) ||
      !cTimeModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }

Конструктивно, архитектура блока остается прежней. Мы только заменяем модули  классического Transformer нашими новыми модулями графового внимания. Эти компоненты позволяют модели выявлять сложные зависимости во временных рядах, словно аналитическая команда, которая одновременно отслеживает тренды и реагирует на краткосрочные аномалии.

После временного анализа строится Mix Module. Этот блок аккуратно добавляет новые слои графового внимания и глобально-локальных модулей с графонами.

//--- Mix Module
   cMixModule.Clear();
   cMixModule.SetOpenCL(OpenCL);
   uint att_layers = (layers + 1) / 2;
   for(uint i = 0; i < att_layers; i++)
     {
      index++;
      att = new CNeuronGraphAttention();
      if(!att ||
         !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension,
                   emb_dimension, experts, sparse, optimization, iBatch) ||
         !cMixModule.Add(att))
        {
         DeleteObj(att);
         return false;
        }
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   for(uint i = (att_layers == layers ? 0 : att_layers - 1); i < layers; i++)
     {
      index++;
      glatt = new CNeuronGlobLocGraphAtt();
      if(!glatt ||
         !glatt.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_in, experts, sparse,
                     emb_dimension, uint(sparse * (dimension + emb_dimension)), optimization, iBatch) ||
         !cMixModule.Add(glatt))
        {
         DeleteObj(glatt);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, dimension + emb_dimension,
                                                                                1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out,
                 dimension + emb_dimension, 1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(TANH);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_out, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables,
                 time_steps_out, 1, optimization, iBatch) ||
      !cMixModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cMixModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

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

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

//--- Spatial Module
   cSpatialModule.Clear();
   cSpatialModule.SetOpenCL(OpenCL);
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) ||
      !cSpatialModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, dimension, variables,
                 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   semb = new CNeuronSpatialEmbedding();
   if(!semb ||
      !semb.Init(0, index, OpenCL, variables, dimension, emb_dimension, optimization, iBatch) ||
      !cSpatialModule.Add(semb))
     {
      DeleteObj(semb);
      return false;
     }
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, semb.Neurons(), iBatch, optimization) ||
      !cSpatialModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }

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

   for(uint i = 0; i < layers; i++)
     {
      index++;
      glatt = new CNeuronGlobLocGraphAtt();
      if(!glatt ||
         !glatt.Init(0, index, OpenCL, variables, dimension + emb_dimension, experts, sparse,
                     emb_dimension, m_units, optimization, iBatch) ||
         !cSpatialModule.Add(glatt))
        {
         DeleteObj(glatt);
         return false;
        }
     }
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension,
                 time_steps_out, variables, 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out,
                 variables, 1, optimization, iBatch) ||
      !cSpatialModule.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   conv.SetActivationFunction(None);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cMixModule.Add(norm))
     {
      DeleteObj(norm);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) ||
      !cMixModule.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }

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

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

   index++;
   if(!cConcatResults.Init(0, index, OpenCL, 3 * variables * time_steps_out, optimization, iBatch))
      return false;
   cConcatResults.SetActivationFunction(None);
//---
   return true;
  }

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

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

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



Архитектура модели

Мы постепенно приближаемся к логическому финалу нашей работы и, после реализации всех необходимых компонентов, переходим к описанию архитектуры самой модели. Здесь важно подчеркнуть ключевой момент. Наша цель выходит за рамки простого прогнозирования ценовых рядов. Как и ранее, мы строим полноценного торгового робота — умного, автономного агента, способного анализировать рынок и принимать решения, открывать и закрывать позиции, адаптируясь к постоянной изменчивости рыночной ситуации. Прогнозирование цен становится лишь инструментом — Энкодером состояния рынка, сенсором, превращающим хаотичные финансовые данные в структурированное представление, понятное модели.

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

Архитектура моделей создаётся в методе CreateDescriptions, который аккуратно формирует массивы описаний слоёв для каждой из трёх частей модели.

bool CreateDescriptions(CArrayObj *&encoder,
                        CArrayObj *&actor,
                        CArrayObj *&critic
                       )
  {
//---
   CLayerDescription *descr;
//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
         return false;
     }
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }
   if(!critic)
     {
      critic = new CArrayObj();
      if(!critic)
         return false;
     }

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

//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   uint prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormWithNoise;
   descr.count = prev_count;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Далее используется слой Batch Normalization с добавлением случайного шума для дополнительной аугментации. Он стабилизирует сигнал и одновременно добавляет контролируемую флуктуацию, которая имитирует рыночную неопределённость.

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

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConcatDiff;
   prev_count = descr.count = HistoryBars;
   descr.layers = BarDescr;
   descr.step = 1;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   uint prev_out = descr.layers*2 ;

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

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

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronExtralongerGraph;
     {
      uint temp[] = {HistoryBars,               // Time steps history
                     NForecast,                 // Time steps forecast
                     ShortPeriod,               // Period 2
                     LongPeriod,                // Period 2
                     BarDescr/2                 // M units
                    };
      if(ArrayCopy(descr.units, temp) < (int)temp.Size())
         return false;
     }
   prev_count = descr.units[1];
   descr.window = prev_out;                     // Variables
   descr.window_out = EmbeddingSize;            // Inside Dimension
     {
      uint temp[] = {EmbeddingSize,             // Embedding Dimension
                     PeriodSeconds(PERIOD_H1),  // Frame 1
                     PeriodSeconds(PERIOD_D1)   // Frame 2
                    };
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
   descr.layers=2;
   descr.step=NExperts;                         //Experts
   descr.probability=0.3f;
   descr.optimization=ADAM;
   descr.batch=BatchSize;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   uint window=descr.window;
   uint count=prev_count;

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

Сверточный слой понижает размерность признаков, упрощая обработку и фокусируя внимание модели на ключевых паттернах. Функция активации TANH ограничивает амплитуду сигналов, сглаживая резкие колебания и предотвращая чрезмерную чувствительность к шуму, что особенно важно при высокой волатильности. При этом на выходе объекта мы получаем сигнал в диапазоне от -1 до 1, что свойственно нормализованным данным. А объект RevInDenorm возвращает полученные значения в масштаб исходных данных и одновременно добавляет статистические параметры анализируемых рядов, извлечённые при первичной нормализации. Таким образом, на выходе модели получаем прогнозные значения в привычном контексте исходного уровня рынка — среднее, дисперсию и другие характеристики, которые позволяют сопоставить прогнозные и фактические значения.

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

Архитектура моделей Актёра и Критика полностью перенесена из предыдущих работ без каких-либо изменений. И мы не будем останавливаться на детальном разборе их структуры, так как основной фокус сосредоточен на работе Энкодера и подготовке признаков. Полный код описания архитектуры всех моделей представлен во вложении, что позволяет ознакомиться с их внутренним устройством при необходимости.


Тестирование

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

Первый этап — офлайн-обучение — проводился на исторических данных по валютной паре EURUSD с таймфреймом H1 за период с Января 2024 по Июнь 2025 года. Этот период оказался настоящей тренировочной площадкой для модели. Разнохарактерная среда позволила модели отработать навык распознавания ключевых сигналов, выработки устойчивых торговых решений и сохранения ориентира даже в самых хаотичных ситуациях. На этом этапе модель училась не просто повторять исторические паттерны, а понимать рыночные закономерности, адаптируясь к динамике цен и объёму сделок. Словно опытный трейдер, анализирующий рынок на уровне интуиции и стратегии одновременно.

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

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

Результаты тестирования модели демонстрируют эффективность обучения и способность адаптироваться к реальным рыночным условиям. На графике баланса и эквити видно, что модель удерживает стабильный рост капитала, несмотря на волатильность рынка. Линии Balance и Equity практически совпадают, что свидетельствует о низкой просадке капитала и точном управлении рисками.

Анализ статистики по сделкам подтверждает эффективность стратегии. Модель показала общий чистый профит в размере 79.72USD при начальном депозите 100.0USD. Общая прибыль (Gross Profit) составила 315.47USD, а общие убытки (Gross Loss) — 235.75USD, что обеспечивает коэффициент профита (Profit Factor) на уровне 1.34. Это указывает на то, что каждый заработанный доллар приносил примерно 34 цента чистой прибыли сверх убытков, что является достойным результатом для стратегии реального времени.

Важно отметить, что стратегия демонстрирует умеренные просадки: максимальная просадка баланса составила 30.05%, а эквити — 43.01%. При этом Recovery Factor равен 1.07, а Sharpe Ratio достигает 2.59. Все это говорит о хорошем соотношении доходности к риску.

Модель показала сбалансированную работу с короткими и длинными позициями: 17 коротких сделок с выигрышей 64.71% и 18 длинных сделок с выигрышем 55.56%. Всего было совершено 69 сделок, из которых 21 оказалась прибыльной (60%), а 14убыточной (40%).

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



Заключение

Мы завершили работу по реализации подходов, предложенных авторами фреймворка ST-Expert, и успешно интегрировали их в структуру фреймворка Extralonger. Получившаяся архитектура позволяет одновременно анализировать временные и пространственные представления данных, сочетая глобальные тренды с локальными аномалиями, что существенно повышает точность прогнозирования и устойчивость стратегий.

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

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


Ссылки


Программы, используемые в статье

#ИмяТипОписание
1Study.mq5СоветникСоветник офлайн обучения моделей
2StudyOnline.mq5 Советник Советник онлайн обучения моделей
3Test.mq5СоветникСоветник для тестирования модели
4Trajectory.mqhБиблиотека классаСтруктура описания состояния системы и архитектуры моделей
5NeuroNet.mqhБиблиотека классаБиблиотека классов для создания нейронной сети
6NeuroNet.clБиблиотекаБиблиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3133.22 KB)
Автоматизация торговых стратегий на MQL5 (Часть 3): система Zone Recovery RSI для динамического управления торговлей Автоматизация торговых стратегий на MQL5 (Часть 3): система Zone Recovery RSI для динамического управления торговлей
В этой статье мы создадим систему Zone Recovery RSI EA на языке MQL5, используя сигналы RSI для запуска сделок и стратегию восстановления для управления убытками. Мы реализуем класс ZoneRecovery для автоматизации входа в сделку, логики восстановления и управления позициями. В заключение статьи приводятся результаты бэктестинга для оптимизации производительности и повышения эффективности советника.
Как упростить ручное тестирование стратегий с помощью MQL5: строим свой набор инструментов Как упростить ручное тестирование стратегий с помощью MQL5: строим свой набор инструментов
В этой статье разрабатываем пользовательский набор инструментов MQL5 для удобного ручного тестирования на исторических данных в Тестере стратегий. Объясним его конструкцию и реализацию, уделив особое внимание интерактивным средствам управления сделками. Затем покажем, как использовать его для эффективного тестирования стратегий
Модификация Алгоритма оптимизации динго — Dingo Optimization Algorithm M (DOAm) Модификация Алгоритма оптимизации динго — Dingo Optimization Algorithm M (DOAm)
Представленная в статье авторская модификация алгоритма динго высоко подняла планку для поиска лучшего из лучших алгоритма оптимизации. Возможны ли еще более высокие результаты?
Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Модули внимания) Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Модули внимания)
В данной статье мы продолжаем реализацию подходов фреймворка ST-Expert, сосредотачиваясь на практических аспектах его применения средствами MQL5. Ранее мы рассмотрели теоретические основы и ключевые компоненты модели, а теперь переходим к непосредственной работе с алгоритмами графового внимания, локального и глобального распределения внимания. Основная цель текущей работы — показать, как концептуальные идеи ST-Expert превращаются в работоспособные решения для анализа и прогнозирования финансовых рядов.