preview
Нейросети в трейдинге: Агрегация движения по времени (Окончание)

Нейросети в трейдинге: Агрегация движения по времени (Окончание)

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

Введение

Финансовые рынки напоминают живой организм, пульсирующий в ритме ожиданий, страха и надежды. Потоки котировок, как кровь в венах системы, несут в себе информацию, закодированную в каждом ценовом движении, в каждом всплеске ликвидности, в каждом незначительном колебании, которое со стороны может показаться случайным. Однако внимательный наблюдатель знает — случайностей здесь нет. Есть лишь закономерности, скрытые в хаосе. Именно эту тонкую грань между шумом и структурой стремится уловить фреймворк Temporal Motion Aggregation (TMA) — один из наиболее интересных и концептуально свежих подходов к анализу временных рядов, появившихся в последние годы.

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

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

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

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

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

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

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


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

Следует сказать, что архитектура TMA не возникла в вакууме. В её основе лежит идея, предложенная авторами фреймворка RAFT (Recurrent All-Pairs Field Transforms), с которым мы познакомились ранее. RAFT предложил элегантную схему рекуррентной обработки, в которой движение воспринимается как непрерывное поле взаимных соответствий между кадрами оптического потока. Этот принцип оказался удивительно близким к природе финансового рынка, где каждое новое значение цены не существует само по себе, а связано со множеством предыдущих состояний.

Наша интерпретация идей RAFT показала, насколько универсальна сама концепция рекуррентного поля соответствий. Именно она позволяет объединить временную динамику в единую структуру. Здесь каждая новая итерация уточняет восприятие движения, а модель учится видеть причинно-следственную связь между изменениями.

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

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

class CNeuronTMA  :  public CNeuronRAFT
  {
protected:
   CNeuronAddToStack cStatesStack;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronTMA(void) {};
                    ~CNeuronTMA(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &units[], uint &chanels[], uint group_size,
                          uint groups, uint segments, uint segment_size, uint levels,
                          uint window_key, uint heads,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronTMA;   }
   //--- 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 bool      Clear(void) override;
  };

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

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

bool CNeuronTMA::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                      uint &units[], uint &chanels[], uint group_size, uint groups,
                      uint segments, uint segment_size, uint levels, uint window_key,
                      uint heads, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(units.Size() != chanels.Size())
      return false;
   uint layers = units.Size() - 1;
   if(!CNeuronSpikeActivation::Init(numOutputs, myIndex, open_cl, units[layers]*chanels[layers],
                                                                      optimization_type, batch))
      return false;

В самом начале метода проверяется согласованность полученных параметров. Размерность массива числа элементов анализируемой последовательности на разных уровнях (units) должна соответствовать массиву числа каналов (chanels). Количество элементов в указанных массивах указывает на число нейронных блоков первичной обработки анализируемых данных.

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

После завершения подготовительной работы начинается построение основного каркаса — энкодера (cEncoder) и магистрали контекста (cContext). Каждый из них собирается из повторяющихся структур CNeuronSpikeResNeXtBlock. Эти блоки обеспечивают глубокое сверточное извлечение признаков и формируют устойчивое представление временных данных.

   int index = 0;
   CNeuronSpikeResNeXtBlock*              resblock    = NULL;
   CNeuronSpikeConvGRU2D*                 gru         = NULL;
   CNeuronMultScalStackCorrelBySegments*  correlation = NULL;
   CNeuronTMAMFE*                         mfe         = NULL;
   CNeuronTMAMPA*                         mpa         = NULL;
   CNeuronTransposeRCDOCL*                transpose   = NULL;
   CNeuronBaseOCL*                        neuron      = NULL;
   CNeuronBatchNormOCL*                   norm        = NULL;
//---
   cEncoder.Clear();
   cContext.Clear();
   cCorrelation.Clear();
   cG.Clear();
   cEncoder.SetOpenCL(OpenCL);
   cContext.SetOpenCL(OpenCL);
   cCorrelation.SetOpenCL(OpenCL);
   cG.SetOpenCL(OpenCL);
//---
   for(uint i = 0; i < layers; i++)
     {
      resblock = new CNeuronSpikeResNeXtBlock();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, chanels[i], chanels[i + 1], units[i], units[i + 1],
                                                 group_size, groups, optimization, iBatch) ||
         !cEncoder.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      index++;
      resblock = new CNeuronSpikeResNeXtBlock();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, chanels[i], chanels[i + 1], units[i], units[i + 1], 
                                                 group_size, groups, optimization, iBatch) ||
         !cContext.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      index++;
     }

Энкодер отвечает за компрессию информации, а контекстная ветвь — за её рекуррентное уточнение, что создаёт основу для памяти модуля.

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

   gru = new CNeuronSpikeConvGRU2D();
   if(!gru ||
      !gru.Init(0, index, OpenCL, units[layers], chanels[layers], chanels[layers], optimization, iBatch) ||
      !cContext.Add(gru))
     {
      DeleteObj(gru)
      return false;
     }
   index++;

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

   if(!cStatesStack.Init(0, index, OpenCL, segments, chanels[layers], units[layers], optimization, iBatch))
      return false;
   index++;

За ним следует модуль CNeuronMultScalStackCorrelBySegments, вычисляющий многоуровневые корреляции между сегментами. Именно здесь рождается центральная идея TMA — выявление паттернов движения через сопоставление множества временных окон.

   correlation = new CNeuronMultScalStackCorrelBySegments();
   if(!correlation ||
      !correlation.Init(0, index, OpenCL, segments, segment_size, chanels[layers],
                                   units[layers], levels, optimization, iBatch) ||
      !cCorrelation.Add(correlation))
     {
      DeleteObj(correlation)
      return false;
     }
   index++;

Рядом с ним располагается модуль CNeuronTMAMFE (Multi-Feature Extractor), который играет роль фильтра-синтезатора. Он соединяет два типа информации (пространственно-временные признаки движения цены и матрицу корреляций между сегментами) в единое представление. Его основная задача — извлечь и усилить ключевые признаки из этих потоков, подчёркивая взаимосвязи между ними.

На вход подаются два массива — результаты временной агрегации (stack states) и данные многомасштабной корреляции (correlation map). Первый массив содержит компактные векторы динамики — своего рода кадры временного движения цены, отражающие последовательные состояния рынка. Второй массив хранит коэффициенты взаимосвязи между этими состояниями, показывая, какие сегменты истории движутся синхронно, а какие — в противофазе.

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

   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, correlation.Neurons() + cStatesStack.Neurons(), optimization, iBatch) ||
      !cCorrelation.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   index++;
     {
      uint temp[] = { cStatesStack.GetDimension(), correlation.GetDimension() };
      mfe = new CNeuronTMAMFE();
      if(!mfe ||
         !mfe.Init(0, index, OpenCL, segments * units[layers], temp, chanels[layers], optimization, iBatch) ||
         !cCorrelation.Add(mfe))
        {
         DeleteObj(mfe)
         return false;
        }
     }
   index++;

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

   transpose = new CNeuronTransposeRCDOCL();
   if(!transpose ||
      !transpose.Init(0, index, OpenCL, units[layers], segments, chanels[layers], optimization, iBatch) ||
      !cCorrelation.Add(transpose))
     {
      DeleteObj(transpose)
      return false;
     }
   index++;

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

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

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

   for(int i = 0; i < 2; i++)
     {
      mpa = new CNeuronTMAMPA();
      if(!mpa ||
         !mpa.Init(0, index, OpenCL, chanels[layers], window_key, segments, units[layers],
                                                           heads, optimization, iBatch) ||
         !cCorrelation.Add(mpa))
        {
         DeleteObj(mpa)
         return false;
        }
      index++;
     }

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

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

Далее выполняется объединение всех ветвей в единый массив признаков с помощью cConcatenated.

   transpose = new CNeuronTransposeRCDOCL();
   if(!transpose ||
      !transpose.Init(0, index, OpenCL, segments, units[layers], chanels[layers], optimization, iBatch) ||
      !cCorrelation.Add(transpose))
     {
      DeleteObj(transpose)
      return false;
     }
   index++;
//---
   if(!cConcatenated.Init(0, index, OpenCL, units[layers] * ((segments + 2) * chanels[layers]),
                                                                        optimization, iBatch))
      return false;
   index++;

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

   if(!cGRU.Init(0, index, OpenCL, units[layers], (segments + 2) * chanels[layers], chanels[layers],
                                                                              optimization, iBatch))
      return false;
   index++;

На завершающем этапе формируется контур остаточных связей cResidual.

   if(!cResidual.Init(0, index, OpenCL, chanels[0], chanels[layers], units[0], units[layers], optimization, iBatch))
      return false;
   index++;

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

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

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

   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, Neurons(), optimization, iBatch) ||
      !cG.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   if(!neuron.SetGradient(cGRU.getGradient(), true) ||
      !cResidual.SetGradient(cGRU.getGradient(), true))
      return false;
//---
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, Neurons(), iBatch, optimization) ||
      !cG.Add(norm))
     {
      DeleteObj(norm)
      return false;
     }
//---
   return true;
  }

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

Алгоритм прямого прохода реализован в метод feedForward, где организован основной цикл распространения данных через все компоненты фреймворка TMA. Его можно рассматривать как жизненный путь одного пакета информации — от исходных признаков рынка до финального прогноза изменения состояния.

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

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

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

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

   CNeuronBaseOCL* current = NULL;
   CNeuronBaseOCL* prev = NeuronOCL;
   for(int i = 0; i < cEncoder.Total(); i++)
     {
      current = cEncoder[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

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

   prev = NeuronOCL;
   for(int i = 0; i < cContext.Total(); i++)
     {
      current = cContext[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

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

   if(!cStatesStack.FeedForward(cEncoder[-1]))
      return false;

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

   CNeuronMultScalStackCorrelBySegments* correlation = cCorrelation[0];
   if(!correlation ||
      !correlation.FeedForward(cEncoder[-1]))
      return false;

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

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

   uint variables = cStatesStack.GetVariables();
   uint segments = cStatesStack.GetStackSize();
   uint dimension = cStatesStack.GetDimension();
   uint corr_dim = correlation.GetDimension();
//---
   prev = cCorrelation[1];
   if(!Concat(cStatesStack.getOutput(), correlation.getOutput(), prev.getOutput(),
              dimension, corr_dim, variables * segments))
      return false;

Далее начинается работа блока анализа динамики, внутри которого последовательно активируются модули MFE и MPA.

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

   for(int i = 2; i < cCorrelation.Total(); i++)
     {
      current = cCorrelation[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

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

Далее происходит новое объединение потоков (cEncoder, cContext и cCorrelation) в единую структуру. Этот шаг можно рассматривать как момент интеграции знаний: локальные признаки, контекст и корреляционные взаимосвязи объединяются в согласованную форму.

   if(!Concat(cEncoder[-1].getOutput(), cContext[-1].getOutput(), cCorrelation[-1].getOutput(),
              cConcatenated.getOutput(), dimension, dimension, segments * dimension, variables))
      return false;

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

   if(!cGRU.FeedForward(cConcatenated.AsObject()))
      return false;

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

   current = cG[0];
   if(!current ||
      !SumAndNormilize(cGRU.getOutput(), cResidual.getOutput(), current.getOutput(), cGRU.GetChanels(),
                                                                                   false, 0, 0, 0, 1))
      return false;

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

   prev = current;
   for(int i = 1; i < cG.Total(); i++)
     {
      current = cG[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }
//---
   return CNeuronSpikeActivation::feedForward(prev);
  }

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

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

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

bool CNeuronTMA::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;
//---
   if(!CNeuronSpikeActivation::calcInputGradients(cG[-1]))
      return false;

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

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

   CNeuronBaseOCL* curr = NULL;
//---
   for(int i = cG.Total() - 2; i >= 0; i--)
     {
      curr = cG[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cG[i + 1]))
         return false;
     }

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

Далее градиент спускается по магистрали остаточной ветви cResidual. Её роль можно сравнить с системой компенсации: она корректирует накопленные перекосы и балансирует влияние соседних слоёв. Благодаря этому модель не перенастраивает себя слишком резко, а делает тонкие, выверенные корректировки.

   if(!NeuronOCL.CalcHiddenGradients(cResidual.AsObject()))
      return false;

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

   if(!cConcatenated.CalcHiddenGradients(cGRU.AsObject()))
      return false;
//---
   uint variables = cStatesStack.GetVariables();
   uint segments = cStatesStack.GetStackSize();
   uint dimension = cStatesStack.GetDimension();
//---
   if(!cEncoder[-1] || !cContext[-1] || !cCorrelation[-1] ||
      !DeConcat(cEncoder[-1].getGradient(), cContext[-1].getGradient(), cCorrelation[-1].getGradient(),
                cConcatenated.getGradient(), dimension, dimension, segments * dimension, variables))
      return false;
   Deactivation(cContext[-1])
   Deactivation(cCorrelation[-1])
   if(cEncoder[-1].Activation() != None)
      if(!DeActivation(cEncoder[-1].getOutput(), cEncoder[-1].getPrevOutput(),
                       cEncoder[-1].getGradient(), cEncoder[-1].Activation()))
         return false;

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

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

  for(int i = cCorrelation.Total() - 2; i >= 1; i--)
     {
      curr = cCorrelation[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cCorrelation[i + 1]))
         return false;
     }

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

   CNeuronMultScalStackCorrelBySegments* correlation = cCorrelation[0];
   uint corr_dim = correlation.GetDimension();
   if(!DeConcat(cStatesStack.getGradient(), correlation.getGradient(), curr.getGradient(),
                dimension, corr_dim, variables * segments))
      return false;
   curr = cEncoder[-1];
   if(!curr.CalcHiddenGradients(cStatesStack.AsObject()) ||
      !SumAndNormilize(curr.getGradient(), curr.getPrevOutput(),
                       curr.getPrevOutput(), dimension, false, 0, 0, 0, 1))
      return false;
   if(!curr.CalcHiddenGradients(correlation) ||
      !SumAndNormilize(curr.getGradient(), curr.getPrevOutput(),
                       curr.getGradient(), dimension, false, 0, 0, 0, 1))
      return false;

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

   for(int i = cEncoder.Total() - 2; i >= 0; i--)
     {
      curr = cEncoder[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cEncoder[i + 1]))
         return false;
     }
   if(!NeuronOCL.CalcHiddenGradients(curr))
      return false;

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

   for(int i = cContext.Total() - 2; i >= 0; i--)
     {
      curr = cContext[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cContext[i + 1]))
         return false;
     }

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

   CBufferFloat* temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(NeuronOCL.getPrevOutput(), false) ||
      !NeuronOCL.CalcHiddenGradients(curr) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

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

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

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

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


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

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

На этом этапе мы снова следуем принципу преемственности. Как и прежде, фреймворк развивается эволюционно: без излишнего разрушения старого, но с постоянным совершенствованием уже созданного. Именно поэтому в качестве основы мы используем проверенную временем архитектуру, разработанную в рамках реализации фреймворка RAFT. Там, где раньше в Энкодере состояния окружающей среды использовался модуль CNeuronRAFT, теперь его место занимает новый компонент — CNeuronTMA. Эта замена кажется незначительной лишь на первый взгляд, но на деле именно она открывает модели путь к более глубокому и многослойному восприятию рыночных процессов.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTMA;
     {
      uint temp[] = {prev_out,                  // Chanels In
                     32,
                     64,
                     128                        // Chanels Out
                    };
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
     {
      uint temp[] = {prev_count,                // Units In
                     128,
                     64,
                     32                         // Units Out
                    };
      if(ArrayCopy(descr.units, temp) < (int)temp.Size())
         return false;
     }
     {
      uint temp[] = {StackSize,                 // Segments
                     4                          // Segments Size
                    };
      if(ArrayCopy(descr.heads, temp) < (int)temp.Size())
         return false;
     }
   descr.window = EmbeddingSize / NHeads;       // Group size
   descr.count = NHeads;                        // Groups
   descr.layers = 3;                            // Correlation's Levels
   descr.window_out = EmbeddingSize / NHeads;   // Dimension K
   descr.step = NHeads;                         // Attention Heads
   descr.optimization = ADAM;
   descr.batch = BatchSize;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   iLatentLayer++;

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


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

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

Первый этап обучения можно сравнить с пробным выступлением на исторической сцене рынка. Мы используем данные валютной пары EURUSD с таймфреймом M15 за период с Января 2024 по Июнь 2025 года. На этом отрезке модель получает богатую историческую среду и учится распознавать внутренние закономерности: волатильность, реакцию на новости, взаимосвязи между ключевыми признаками и скрытую структуру трендов. Здесь фреймворк выступает как системный аналитик, формирующий устойчивое понимание рыночного состояния. Постепенно модель вырабатывает способность прогнозировать развитие событий и оценивать степень риска для каждой потенциальной сделки.

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

Финальный шаг — тестирование на новых данных за Июль–Сентябрь 2025 года. Здесь модель выходит на чистое поле, без предварительных подсказок. Все параметры, выработанные во время обучения, загружаются без изменений. Этот этап показывает, насколько TMA способен к обобщению, к работе в новых рыночных условиях, где прошлый опыт помогает, но не гарантирует успеха.

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

Финансовый результат оказался скромным: чистая прибыль 1.44% при стартовом депозите в 100USD. При этом валовая прибыль и убыток практически уравновешены (40.52USD против –39.08USD), что даёт Profit Factor на уровне 1.04 — границу безубыточности. Такой результат типичен для модели, находящейся в процессе адаптации: она ещё не оптимизирована по входным условиям и стратегии управления капиталом, но уже демонстрирует стабильность без катастрофических потерь.

Просадка в 16–17% указывает на умеренный риск, вполне допустимый для исследовательского этапа. Баланс и equity движутся синхронно, что подтверждает — модель закрывает позиции своевременно, без опасного накопления плавающего убытка. Это важный показатель зрелости внутренней логики принятия решений: система не залипает в убыточных сделках и корректно отрабатывает сигналы выхода.

Анализ серий сделок также подтверждает сбалансированность поведения: максимум подряд идущих выигрышей — 4 сделки, убытков — 6. Доля прибыльных операций (52.5%) лишь немного превышает порог вероятности, но при этом прослеживается тенденция к симметрии между короткими и длинными, что говорит о нейтральности стратегии — она не подвержена направленным перекосам.

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

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


Заключение

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

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

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3307.17 KB)
От новичка до эксперта: Индикатор Market Periods Synchronizer От новичка до эксперта: Индикатор Market Periods Synchronizer
В настоящем обсуждении мы представляем инструмент синхронизации таймфреймов от старших к младшим, предназначенный для решения проблемы анализа рыночных паттернов, охватывающих периоды старших таймфреймов. Встроенные маркеры периодов в MetaTrader 5 часто ограничены, жестки и их нелегко настроить для нестандартных таймфреймов. Наше решение использует язык MQL5 для разработки индикатора, обеспечивающего динамичный и наглядный способ выравнивания структур старших таймфреймов на графиках младших таймфреймов. Этот инструмент может быть очень полезен для детального анализа рынка. Чтобы узнать больше о его функциях и реализации, приглашаю вас присоединиться к обсуждению.
Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (II): Модуляризация Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (II): Модуляризация
В этом обсуждении мы сделаем шаг вперед в разбиении нашей программы MQL5 на более мелкие и более управляемые модули. Эти модульные компоненты затем будут интегрированы в основную программу, что улучшит ее организацию и удобство обслуживания. Такой подход упрощает структуру нашей основной программы и делает отдельные компоненты пригодными для повторного использования в других советниках и индикаторах. Приняв эту модульную конструкцию, мы создаем прочную основу для будущих улучшений, что принесет пользу как нашему проекту, так и широкому сообществу разработчиков.
Разработка инструментария для анализа движения цен (Часть 13): RSI Sentinel Разработка инструментария для анализа движения цен (Часть 13): RSI Sentinel
Ценовую динамику можно эффективно анализировать, выявляя расхождения, при этом технические индикаторы, такие как RSI, подают важные подтверждающие сигналы. В статье ниже мы объясняем, как автоматизированный анализ дивергенции RSI может определять продолжение и разворот тренда, тем самым предоставляя ценную информацию о настроениях рынка.
Разработка динамического советника на нескольких парах (Часть 2): Диверсификация и оптимизация портфеля Разработка динамического советника на нескольких парах (Часть 2): Диверсификация и оптимизация портфеля
Диверсификация и оптимизация портфеля позволяют стратегически распределять инвестиции по нескольким активам, чтобы минимизировать риски, и при этом выбирать идеальную комбинацию активов для максимизации доходности на основе показателей эффективности с учетом риска.