preview
Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (STSSM-блок)

Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (STSSM-блок)

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

Введение

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

Авторы создали архитектуру, которая работает с потоками событий очень естественно. Данные здесь неравномерны и хаотичны. Это сильно напоминает поведение рынка. Цена на графике не идёт ровной линией. Объёмы появляются порывами, тики приходят с разной скоростью. Чтобы извлечь смысл из таких потоков, события сначала преобразуются в аккуратный тензор — трёхмерную сетку, где фиксируются координаты и время каждого изменения. Представьте, что это как структурированная тиковая лента, где каждое движение и каждая пауза имеют значение. Так авторы подготавливают поток, чтобы модель могла работать с ним без потерь информации.

Полученный тензор передается в Spatio-Temporal Encoder — ключевой модуль архитектуры. Он превращает поток событий в набор признаков, из которых формируется картина движения. Encoder улавливает локальные колебания и связывает их с общей динамикой. Модель реагирует на ритм потока, а не на отдельные значения. Для задач, где важна скорость реакции на изменения, такой подход даёт значительное преимущество.

Сердцем энкодера является Spatio-Temporal State Space Model (STSSM). Этот модуль обрабатывает объём событий слоями, сохраняя пространственные закономерности и учитывая их последовательность во времени. В результате модель видит поток как единое целое: она понимает импульсы, паузы и их сочетание. В контексте финансовых рынков это похоже на способность уловить начало разворота тренда или момент нарастающей волатильности раньше, чем это заметят классические индикаторы. STSSM формирует устойчивое представление данных, позволяя модели различать значимые события и обычный шум.

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

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

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

Важно, что авторское решение остаётся простым по духу. Всё построено на аккуратной дискретизации событий, обработке STSSM и компактных свёртках. Такое решение показывает, что мощная аналитика возможна без лишней сложности, и именно это делает E-STMFlow привлекательным для практических задач.

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

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

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

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


Модуль Spike-SSM

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

Мы уже не раз говорили о важности учёта как временных, так и пространственных зависимостей при анализе потоков событий. В рамках данной работы для выполнения данного функционала используется 2D-SSM модуль, способный фиксировать изменения не только во времени, но и в различных измерениях данных. В рыночной аналогии это похоже на трейдера, который одновременно следит за колебаниями цены и объёмами торгов, связывая каждое движение с общим контекстом рынка. Для спайковой версии алгоритма мы реализуем новый класс CNeuronSpike2DSSMOCL, который наследует базовые возможности нейронного элемента и адаптирует их для работы с потоками спайковых событий.

class CNeuronSpike2DSSMOCL  :  public CNeuronBaseOCL
  {
protected:
   uint                    iWindowOut;
   uint                    iUnitsOut;
   //---
   CNeuronBaseOCL          cHiddenStates;
   CLayer                  cProjectionX_Time;
   CLayer                  cProjectionX_Variable;
   CNeuronSpikeConvBlock   cA;
   CNeuronSpikeConvBlock   cBC_Time;
   CNeuronSpikeConvBlock   cBC_Variable;
   CNeuronBaseOCL          cB_Time;
   CNeuronBaseOCL          cB_Variable;
   CNeuronBaseOCL          cC_Time;
   CNeuronBaseOCL          cC_Variable;
   CNeuronSpikeConvBlock   cDelta_Time;
   CNeuronSpikeConvBlock   cDelta_Variable;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      feedForwardSSM2D(void);
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradientsSSM2D(void);
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSpike2DSSMOCL(void)  {};
                    ~CNeuronSpike2DSSMOCL(void)  {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window_in, uint window_out, uint units_in, uint units_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronSpike2DSSMOCL; }
   //---
   virtual bool      Save(int const file_handle);
   virtual bool      Load(int const file_handle);
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   //---
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual void      TrainMode(bool flag) override;
   //---
   virtual bool      Clear(void) override;
  };

Архитектура класса построена, как аккуратно настроенная система фильтров и преобразователей. Скрытые состояния фиксируют текущую динамику потока, словно трейдер держит в уме последние импульсы на рынке. Проекции по временной и пространственной осям позволяют увязать локальные изменения с глобальной картиной, как если бы мы сопоставляли мгновенные колебания цены с общим трендом за день. Блоки свёртки (cA, cBC_Time, cBC_Variable и другие) отвечают за выявление значимых изменений в потоке, отфильтровывая шум, точно так же, как опытный аналитик отделяет случайные всплески объёмов от настоящего рыночного импульса.

Метод инициализации задаёт внутреннюю архитектуру 2D-SSM модуля и фиксирует все ключевые параметры, необходимые для его стабильной работы. Если проводить аналогию с трейдингом, то этот процесс похож на настройку рабочего места. Определяются окна наблюдения, количество инструментов для анализа, активируются индикаторы и готовятся ресурсы для обработки потока тиков. Без такой подготовки любая модель теряет чувствительность к сигналам и рискует пропустить важные импульсы.

bool CNeuronSpike2DSSMOCL::Init(uint numOutputs, uint myIndex, COpenCLMy * open_cl,
                                uint window_in, uint window_out, uint units_in, uint units_out,
                                ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window_out * units_out, optimization_type, batch))
      return false;
   SetActivationFunction(None);

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

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

   iWindowOut = window_out;
   iUnitsOut = units_out;

Далее создаются блоки проекции по времени (cProjectionX_Time) и переменным (cProjectionX_Variable). Для временной проекции данные проходят через цепочку из транспозиции, свёртки и повторной транспозиции.

   int index = 0;
   CNeuronSpikeConvBlock *conv = NULL;
   CNeuronTransposeOCL *transp = NULL;
//--- Projection Time
   cProjectionX_Time.Clear();
   cProjectionX_Time.SetOpenCL(OpenCL);
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, units_in, window_in, optimization, iBatch) ||
      !cProjectionX_Time.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, units_in, units_in, iUnitsOut, window_in, 1, optimization, iBatch) ||
      !cProjectionX_Time.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, window_in, iUnitsOut, optimization, iBatch) ||
      !cProjectionX_Time.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, window_in, window_in, iWindowOut, iUnitsOut, 1, optimization, iBatch) ||
      !cProjectionX_Time.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }

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

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

//--- Projection Variables
   cProjectionX_Variable.Clear();
   cProjectionX_Variable.SetOpenCL(OpenCL);
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, window_in, window_in, iUnitsOut, units_in, 1, optimization, iBatch) ||
      !cProjectionX_Variable.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, units_in, iUnitsOut, optimization, iBatch) ||
      !cProjectionX_Variable.Add(transp))
     {
      DeleteObj(transp);
      return false;
     }
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, units_in, units_in, iWindowOut, iUnitsOut, 1, optimization, iBatch) ||
      !cProjectionX_Variable.Add(conv))
     {
      DeleteObj(conv);
      return false;
     }

Скрытые состояния (cHiddenStates) и блок A формируют ядро анализа, аккуратно связывая локальные события с глобальной картиной рынка.

//--- HiddenState
   index++;
   if(!cHiddenStates.Init(0, index, OpenCL, 2 * iUnitsOut * iWindowOut, optimization, iBatch))
      return false;
//--- A*H
   index++;
   if(!cA.Init(0, index, OpenCL, iWindowOut, iWindowOut, 2 * iWindowOut, iUnitsOut, 2, optimization, iBatch))
      return false;

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

Блок BC выступает связующим звеном между ядром модуля и матрицами B и C. Он одновременно генерирует эти матрицы, которые затем разделяются в специально созданные буферы для последующей обработки.

//--- BC
   index++;
   if(!cBC_Time.Init(0, index, OpenCL, iWindowOut, iWindowOut, iUnitsOut + 1, iUnitsOut,
                                                                  1, optimization, iBatch))
      return false;
   index++;
   if(!cBC_Variable.Init(0, index, OpenCL, iWindowOut, iWindowOut, iUnitsOut + 1, iUnitsOut,
                                                                   1, optimization, iBatch))
      return false;
//--- B
   index++;
   if(!cB_Time.Init(0, index, OpenCL, iUnitsOut, optimization, iBatch))
      return false;
   cB_Time.SetActivationFunction(None);
   index++;
   if(!cB_Variable.Init(0, index, OpenCL, iUnitsOut, optimization, iBatch))
      return false;
   cB_Variable.SetActivationFunction(None);
//--- C
   index++;
   if(!cC_Time.Init(0, index, OpenCL, iUnitsOut * iUnitsOut, optimization, iBatch))
      return false;
   cC_Time.SetActivationFunction(None);
   index++;
   if(!cC_Variable.Init(0, index, OpenCL, iUnitsOut * iUnitsOut, optimization, iBatch))
      return false;
   cC_Variable.SetActivationFunction(None);

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

Delta-блоки (cDelta_Time и *cDelta_Variable) отслеживают изменения состояния по времени и переменным, корректируя прогнозы в зависимости от рыночной динамики. Например, резкий всплеск объёма или краткосрочный скачок цены сразу фиксируется и влияет на будущие прогнозы, как если бы опытный трейдер учитывал моментальную волатильность.

//--- Delta
   index++;
   if(!cDelta_Time.Init(0, index, OpenCL, iWindowOut, iWindowOut, iUnitsOut, iUnitsOut, 1, optimization, iBatch))
      return false;
   index++;
   if(!cDelta_Variable.Init(0, index, OpenCL, iWindowOut, iWindowOut, iUnitsOut, iUnitsOut, 1, optimization, iBatch))
      return false;
//---
   if(!Clear())
      return false;
//---
   return true;
  }

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

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

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

Алгоритм прямого прохода данных в 2D-SSM реализован в методе feedForward. Его можно представить как виртуальную торговую сессию. Поток тиков проходит через ряд аналитических фильтров, каждый из которых выполняет свою задачу. Сначала данные поступают в цепочку Projection Time, где каждый блок обрабатывает события по временной оси. Это как если бы трейдер внимательно следил за каждой свечой на графике: фиксировал краткосрочные колебания, импульсы и резкие движения цены. Каждый элемент проекции аккуратно фильтрует сигнал, выделяя важные моменты и передавая их дальше.

bool CNeuronSpike2DSSMOCL::feedForward(CNeuronBaseOCL * NeuronOCL)
  {
   CNeuronBaseOCL *inp = NeuronOCL;
   CNeuronBaseOCL *x_time = NULL;
   CNeuronBaseOCL *x_var = NULL;
//--- Projection Time
   int total = cProjectionX_Time.Total();
   for(int i = 0; i < total; i++)
     {
      x_time = cProjectionX_Time.At(i);
      if(!x_time ||
         !x_time.FeedForward(inp))
         return false;
      inp = x_time;
     }

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

//--- Projection Variable
   inp = NeuronOCL;
   total = cProjectionX_Variable.Total();
   for(int i = 0; i < total; i++)
     {
      x_var = cProjectionX_Variable.At(i);
      if(!x_var ||
         !x_var.FeedForward(inp))
         return false;
      inp = x_var;
     }

Следующий этап — ядро модели. Блок A аккумулирует информацию скрытых состояний, формируя основу для прогнозирования.

   if(!cA.FeedForward(cHiddenStates.AsObject()))
      return false;

Блоки BC одновременно генерируют матрицы B и C, которые затем разделяются в специальные буферы.

   if(!cBC_Time.FeedForward(x_time) ||
      !cBC_Variable.FeedForward(x_var))
      return false;
   if(!DeConcat(cB_Time.getOutput(), cC_Time.getOutput(), cBC_Time.getOutput(), 1, iUnitsOut, iUnitsOut) ||
      !DeConcat(cB_Variable.getOutput(), cC_Variable.getOutput(), cBC_Variable.getOutput(), 1, iUnitsOut, iUnitsOut))
      return false;

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

Delta-блоки отслеживают изменения состояния по времени и переменным, корректируя прогноз.

   if(!cDelta_Time.FeedForward(x_time) ||
      !cDelta_Variable.FeedForward(x_var))
      return false;

И сохраняем последнее скрытое состояние, которое нам потребуется при организации процессов обратного прохода.

   if(!cHiddenStates.SwapOutputs())
      return false;
//---
   return feedForwardSSM2D();
  }

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

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

bool CNeuronSpike2DSSMOCL::calcInputGradients(CNeuronBaseOCL * NeuronOCL)
  {
   if(!NeuronOCL)
      return false;
//---
   if(!calcInputGradientsSSM2D())
      return false;

Сначала метод вызывает calcInputGradientsSSM2D, где ошибки, полученные на выходе объекта, распределяются по структуре 2D-SSM, фиксируя расхождения как по времени, так и по переменным.

Далее градиенты блоков B и C, формируемые модулем BC, аккуратно объединяются в единый тензор.

   if(!Concat(cB_Time.getGradient(), cC_Time.getGradient(), cBC_Time.getGradient(),
                                                                     1, iUnitsOut, iUnitsOut) ||
      !Concat(cB_Variable.getGradient(), cC_Variable.getGradient(), cBC_Variable.getGradient(),
                                                                       1, iUnitsOut, iUnitsOut))
      return false;

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

//--- Deactivation
   CNeuronBaseOCL *x_time = cProjectionX_Time[-1];
   CNeuronBaseOCL *x_var = cProjectionX_Variable[-1];
   if(!x_time || !x_var)
      return false;
   Deactivation(x_time)
   Deactivation(x_var)
   Deactivation(cBC_Time)
   Deactivation(cBC_Variable)
   Deactivation(cDelta_Time)
   Deactivation(cDelta_Variable)
   Deactivation(cA)

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

//--- Gradient to projections X
   CBufferFloat *grad_x_time = x_time.getGradient();
   CBufferFloat *grad_x_var = x_var.getGradient();
   if(!x_time.SetGradient(x_time.getPrevOutput(), false) ||
      !x_var.SetGradient(x_var.getPrevOutput(), false))
      return false;
//--- BC -> X
   if(!x_time.CalcHiddenGradients(cBC_Time.AsObject()) ||
      !SumAndNormilize(grad_x_time, x_time.getGradient(), grad_x_time, iWindowOut, false, 0, 0, 0, 1))
      return false;
   if(!x_var.CalcHiddenGradients(cBC_Variable.AsObject()) ||
      !SumAndNormilize(grad_x_var, x_var.getGradient(), grad_x_var, iWindowOut, false, 0, 0, 0, 1))
      return false;

Следом передаем градиенты от Delta-блоков.

//--- Delta -> X
   if(!x_time.CalcHiddenGradients(cDelta_Time.AsObject()) ||
      !SumAndNormilize(grad_x_time, x_time.getGradient(), grad_x_time, iWindowOut, false, 0, 0, 0, 1))
      return false;
   if(!x_var.CalcHiddenGradients(cDelta_Variable.AsObject()) ||
      !SumAndNormilize(grad_x_var, x_var.getGradient(), grad_x_var, iWindowOut, false, 0, 0, 0, 1))
      return false;
   if(!x_time.SetGradient(grad_x_time, false) ||
      !x_var.SetGradient(grad_x_var, false))
      return false;

Затем градиенты распространяются по цепочкам Projection Variable и Projection Time от конца к началу. Каждое отклонение, будь то резкий всплеск объёма, импульсная свеча или кратковременная пауза в движении цены, аккуратно учитывается и интегрируется в глобальную картину рынка.

//--- Projection Variable
   int total = cProjectionX_Variable.Total() - 2;
   for(int i = total; i >= 0; i--)
     {
      x_var = cProjectionX_Variable[i];
      if(!x_var ||
         !x_var.CalcHiddenGradients(cProjectionX_Variable[i + 1]))
         return false;
     }
//--- Projection Time
   total = cProjectionX_Time.Total() - 2;
   for(int i = total; i >= 0; i--)
     {
      x_time = cProjectionX_Time[i];
      if(!x_time ||
         !x_time.CalcHiddenGradients(cProjectionX_Time[i + 1]))
         return false;
     }

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

//--- Projections -> inputs
   if(!NeuronOCL.CalcHiddenGradients(x_var.AsObject()))
      return false;
   grad_x_time = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(x_time.getPrevOutput(), false) ||
      !NeuronOCL.CalcHiddenGradients(x_time.AsObject()) ||
      !SumAndNormilize(grad_x_time, NeuronOCL.getGradient(), grad_x_time, 1, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(grad_x_time, false))
      return false;
//---
   return true;
  }

Таким образом, calcInputGradients обеспечивает полный цикл обратной связи. Каждая ошибка фиксируется, структурируется и аккуратно распределяется по всей архитектуре 2D-SSM. Для финансовых потоков это значит, что модель учится на каждом рыночном импульсе, учитывает динамику цены и объёмов, фиксирует краткосрочные и долгосрочные тенденции и становится всё более точной и адаптивной с каждой новой итерацией.

Каждый компонент CNeuronSpike2DSSMOCL играет свою роль, но вместе они формируют единое целое. Полный код объекта и всех его методов представлен во вложении.



Spike-Mamba

После того как мы завершили построение 2D-SSM модуля, логично двигаться выше по иерархии STSSM-блока. Авторы фреймворка оставили пространство для экспериментов с различными SSM-решениями. Однако представленные в авторской работе результаты тестов демонстрируют, что Mamba работает лучше всего. Поэтому следующим шагом становится создание нового класса CNeuronSpikeMamba2D, который объединяет проверенную эффективность Mamba с уже реализованным спайковым 2D-SSM блоком.

class CNeuronSpikeMamba2D   :  public CNeuronSpikeConvBlock
  {
protected:
   CNeuronSpikeConvBlock   cXZProject;
   CNeuronBaseOCL          cX;
   CNeuronBaseOCL          cZ;
   CNeuronSpike2DSSMOCL    cSSM;
   CNeuronBaseOCL          cZSSM;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   //---

public:
                     CNeuronSpikeMamba2D(void) {};
                    ~CNeuronSpikeMamba2D(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint inside_dimension, uint units_count,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   const   {  return defNeuronSpikeMamba2D; }
   //---
   virtual bool      Save(int const file_handle);
   virtual bool      Load(int const file_handle);
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj);
   //---
   virtual bool      Clear(void) override;
  };

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

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

bool CNeuronSpikeMamba2D::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint window, uint inside_dimension, uint units_count,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, inside_dimension,
                inside_dimension, window, units_count, 1, optimization_type, batch))
      return false;

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

Компоненты cX и cZ используются для разделения потоков информации и фиксируют промежуточные состояния модели, отражая краткосрочные колебания и локальные тенденции рынка.

   if(!cXZProject.Init(0, 0, OpenCL, window, window, 2 * inside_dimension, units_count,
                       1, optimization, iBatch))
      return false;
   if(!cX.Init(0, 1, OpenCL, inside_dimension * units_count, optimization, iBatch))
      return false;
   cX.SetActivationFunction(None);
   if(!cZ.Init(0, 2, OpenCL, inside_dimension * units_count, optimization, iBatch))
      return false;
   cZ.SetActivationFunction(None);

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

На следующем уровне работает cSSM. Это наш 2D-SSM блок, который объединяет локальные события в целостную картину: всплески объёмов, резкие скачки цен, краткосрочные тренды.

   if(!cSSM.Init(0, 3, OpenCL, inside_dimension, inside_dimension, units_count, units_count,
                 optimization, iBatch))
      return false;

Если рынок — это живой организм, то cSSM отслеживает, как локальные колебания разгуливаются внутри глобального движения, создавая сложные взаимодействия между событиями.

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

   if(!cZSSM.Init(0, 4, OpenCL, inside_dimension * units_count, optimization, iBatch))
      return false;
   cZSSM.SetActivationFunction(None);
//---
   return true;
  }

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

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

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

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

Далее с помощью операции DeConcat сигнал разделяется на компоненты cX и cZ.

   if(!DeConcat(cX.getOutput(), cZ.getOutput(), cXZProject.getOutput(), cXZProject.GetFilters() / 2,
                cXZProject.GetFilters() / 2, cXZProject.GetUnits()))
      return false;

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

На следующем этапе включается наш 2D-SSM блок, который аккумулирует сложные пространственно-временные зависимости. Он видит комбинации событий, как всплески объёмов или резкие скачки цен, и формирует прогнозные сигналы на основе этих взаимодействий.

   if(!cSSM.FeedForward(cX.AsObject()))
      return false;

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

   if(!ElementMult(cSSM.getOutput(), cZ.getOutput(), cZSSM.getOutput()))
      return false;

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

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

Объект CNeuronSpikeMamba2D выступает как мозг STSSM-блока: он объединяет сильные стороны Mamba и 2D-SSM, позволяя модели видеть рынок целиком, фиксировать детали и строить прогнозы с высокой точностью. Этот подход обеспечивает готовность модели реагировать на любые рыночные сигналы, одновременно анализируя их локальную интенсивность и глобальный контекст.



STSSM-блок

Следующим шагом мы поднимаемся на новую ступеньку и приступаем к построению STSSM-блока, который станет центральным элементом нашей событийной модели. В рамках нового объекта CNeuronSpikeSTSSM аккумулируется всё, что было реализовано ранее: стек эмбедингов, SSM-модули, а теперь они объединяются в единую структуру, способную работать с полными пространственно-временными паттернами рынка.

class CNeuronSpikeSTSSM   :  public CNeuronSpikeConvBlock
  {
protected:
   CNeuronSpikePatchStak   cEmbedings;
   CLayer                  cProjection;
   CLayer                  cSSM;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSpikeSTSSM(void) {};
                    ~CNeuronSpikeSTSSM(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint stack_size, uint dimension, uint patch_dimension,
                          uint ssm_dimension, uint variables,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronSpikeSTSSM;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj)   override;
   virtual void      TrainMode(bool flag) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

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

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

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

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

bool CNeuronSpikeSTSSM::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                             uint stack_size, uint dimension, uint patch_dimension,
                             uint ssm_dimension, uint variables,
                             ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, ssm_dimension * (stack_size - 2),
             ssm_dimension * (stack_size - 2), dimension, variables, 1, optimization_type, batch))
      return false;

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

   uint index = 0;
   if(!cEmbedings.Init(0, index, OpenCL, stack_size, dimension, patch_dimension, variables, optimization, iBatch))
      return false;

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

   cProjection.Clear();
   cProjection.SetOpenCL(OpenCL);
   CNeuronSpikeConvBlock* conv = NULL;
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, 2 * patch_dimension, patch_dimension, patch_dimension,
                                      stack_size - 1, variables, optimization, iBatch) ||
      !cProjection.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, 2 * patch_dimension, patch_dimension, ssm_dimension, 
                                      stack_size - 2, variables, optimization, iBatch) ||
      !cProjection.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }

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

   cSSM.Clear();
   cSSM.SetOpenCL(OpenCL);
   CNeuronSpikeMamba2D* ssm = NULL;
   for(int i = 0; i < 2; i++)
     {
      index++;
      ssm = new CNeuronSpikeMamba2D();
      if(!ssm ||
         !ssm.Init(0, index, OpenCL, ssm_dimension, ssm_dimension, (stack_size - 2)*variables,
                                                                      optimization, iBatch) ||
         !cSSM.Add(ssm))
        {
         DeleteObj(ssm)
         return false;
        }
     }
//---
   return true;
  }

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

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

bool CNeuronSpikeSTSSM::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//---
   if(!cEmbedings.FeedForward(NeuronOCL))
      return false;

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

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

   CNeuronBaseOCL* prev = cEmbedings.AsObject();
   CNeuronBaseOCL* curr = NULL;
   for(int i = 0; i < cProjection.Total(); i++)
     {
      curr = cProjection[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

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

   uint units = GetUnits() * GetVariables();
   for(int i = 0; i < cSSM.Total(); i++)
     {
      curr = cSSM[i];
      if(!curr ||
         !curr.FeedForward(prev) ||
         !SumAndNormilize(curr.getOutput(), prev.getOutput(), curr.getOutput(),
                                     curr.Neurons() / units, true, 0, 0, 0, 1))
         return false;
      prev = curr;
     }

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

   if(!CNeuronSpikeConvBlock::feedForward(prev))
      return false;
//---
   return true;
  }

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

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

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



Заключение

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

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

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3371.39 KB)
Знакомство с языком MQL5 (Часть 16): Создание советников с использованием паттернов технического анализа Знакомство с языком MQL5 (Часть 16): Создание советников с использованием паттернов технического анализа
Эта статья знакомит новичков с созданием советника на языке MQL5, который выявляет классический паттерн технического анализа – "голову и плечи" – и торгует по нему. В статье рассматривается, как обнаружить паттерн, используя ценовое действие, нарисовать его на графике, установить уровни входа, стоп-лосса и тейк-профита, а также автоматизировать выполнение сделок на основе паттерна.
Знакомство с языком MQL5 (Часть 15): Руководство для начинающих по созданию пользовательских индикаторов (IV) Знакомство с языком MQL5 (Часть 15): Руководство для начинающих по созданию пользовательских индикаторов (IV)
В этой статье вы узнаете, как создать индикатор ценового действия на языке MQL5, сосредоточив внимание на ключевых точках, таких как минимум (L), максимум (H), более высокий минимум (HL), более высокий максимум (HH), более низкий минимум (LL) и более низкий максимум (LH) для анализа трендов. Вы также изучите, как выявлять зоны премии и дисконта, отмечать уровень коррекции 50% и использовать соотношение риска и вознаграждения для расчета целевых уровней прибыли. В статье также рассмотрено определение точек входа, уровней стоп-лосса (SL) и тейк-профита (TP) на основе структуры тренда.
Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (Окончание) Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (Окончание)
Представляем адаптацию фреймворк E-STMFlow — современное решение для построения автономных торговых систем. В статье завершаем реализацию подходов, предложенных авторами фреймворка. Результаты тестирования демонстрируют стабильный рост капитала, минимальные просадки и предсказуемое распределение рисков, подтверждая практическую эффективность подхода и открывая перспективы дальнейшей оптимизации стратегии.
Алгоритм дифференциального поиска — Differential Search Algorithm (DSA) Алгоритм дифференциального поиска — Differential Search Algorithm (DSA)
В статье рассматривается алгоритм дифференциального поиска DSA, имитирующий миграцию суперорганизма в поисках оптимальных условий обитания. Алгоритм использует гамма-распределение для генерации псевдо-стабильного блуждания и предлагает четыре стратегии выбора направления движения с тремя механизмами мутации координат. Какова будет производительность метода?