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

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

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

Введение

Рынок живёт совершенно особой динамикой, и каждое его движение — это вспышка события, мгновенный отпечаток изменения спроса и предложения. И чем глубже мы внедрялись в идею событийных представлений, тем яснее становилось — архитектура BAT будто создана именно для таких данных. Её корни — в мире event-vision, но сама логика удивительно близка к рынку. Здесь нет кадров. Есть лишь асинхронные потоки, нерегулярные, непредсказуемые и бесконечно разнообразные. Эти потоки нужно не просто ловить, а понимать, структурировать и превращать в ориентиры для принятия решений.

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

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

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

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

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

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

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

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

Авторская визуализация фреймворка BATSpatially Adaptive Temporal Motion Aggregation (SATMA)



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

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

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

class CNeuronBAT :  public CNeuronSpikeConvBlock
  {
protected:
   CLayer                              cPrepare;
   CLayer                              cEncoder;
   CLayer                              cContext;
   CNeuronBaseOCL                      cConcatenated;
   CNeuronSpikeConvGRU2D               cGRU;
   CNeuronTransposeRCDOCL              cTranspose;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

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

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

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

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

bool CNeuronBAT::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                      uint dimension, uint embed_size,
                      uint stack_size, uint variables,
                      ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, dimension, dimension,
                                   dimension, stack_size * variables, 1, optimization_type, batch))
      return false;

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

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

   int index = 0;
   CNeuronBatchNormOCL*                norm = NULL;
   CNeuronCreateFlow*                  stack = NULL;
   CNeuronSpikeConvBlock*              conv = NULL;
   CNeuronMultiWindowsConvWPadOCL*     mwconv = NULL;
   CNeuronMSRes*                       resblock = NULL;
   CNeuronBiDirectCorrelation*         correlation = NULL;
   CNeuronSATMA*                       satma = NULL;
//---
   cPrepare.Clear();
   cPrepare.SetOpenCL(OpenCL);
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, dimension * variables, iBatch, optimization) ||
      !cPrepare.Add(norm))
     {
      DeleteObj(norm)
      return false;
     }

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

   index++;
   stack = new CNeuronCreateFlow();
   if(!stack ||
      !stack.Init(0, index, OpenCL, stack_size, dimension, embed_size,
                                   variables, optimization, iBatch) ||
      !cPrepare.Add(stack))
     {
      DeleteObj(stack)
      return false;
     }

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

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

   cEncoder.Clear();
   cContext.Clear();
   cEncoder.SetOpenCL(OpenCL);
   cContext.SetOpenCL(OpenCL);
//---
   uint windows[] = {embed_size, 3 * embed_size, 5 * embed_size, 7 * embed_size};
   mwconv = new CNeuronMultiWindowsConvWPadOCL();
   index++;
   if(!mwconv ||
      !mwconv.Init(0, index, OpenCL, windows, embed_size, embed_size, stack_size,
                                              variables, optimization, iBatch) ||
      !cEncoder.Add(mwconv))
     {
      DeleteObj(mwconv)
      return false;
     }
   mwconv.SetActivationFunction(None);
   mwconv = new CNeuronMultiWindowsConvWPadOCL();
   index++;
   if(!mwconv ||
      !mwconv.Init(0, index, OpenCL, windows, embed_size, embed_size, stack_size,
                                              variables, optimization, iBatch) ||
      !cContext.Add(mwconv))
     {
      DeleteObj(mwconv)
      return false;
     }
   mwconv.SetActivationFunction(None);

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

   uint mult = windows.Size();
   while(mult > 1)
     {
      index++;
      resblock = new CNeuronMSRes();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, mult * stack_size, embed_size, variables,
                                                           optimization, iBatch) ||
         !cEncoder.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      index++;
      resblock = new CNeuronMSRes();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, mult * stack_size, embed_size, variables,
                                                           optimization, iBatch) ||
         !cContext.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      mult = (mult + 1) / 2;
      index++;
      conv = new CNeuronSpikeConvBlock();
      if(!conv ||
         !conv.Init(0, index, OpenCL, 2 * embed_size, 2 * embed_size, embed_size,
                           mult * stack_size, variables, optimization, iBatch) ||
         !cEncoder.Add(conv))
        {
         DeleteObj(conv)
         return false;
        }
      index++;
      conv = new CNeuronSpikeConvBlock();
      if(!conv ||
         !conv.Init(0, index, OpenCL, 2 * embed_size, 2 * embed_size, embed_size,
                           mult * stack_size, variables, optimization, iBatch) ||
         !cContext.Add(conv))
        {
         DeleteObj(conv)
         return false;
        }
     }

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

   index++;
   correlation = new CNeuronBiDirectCorrelation();
   if(!correlation ||
      !correlation.Init(0, index, OpenCL, embed_size, stack_size, variables,
                                        embed_size, optimization, iBatch) ||
      !cEncoder.Add(correlation))
     {
      DeleteObj(correlation)
      return false;
     }

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

   index++;
   satma = new CNeuronSATMA();
   if(!satma ||
      !satma.Init(0, index, OpenCL, embed_size, stack_size, variables,
                     (embed_size + 3) / 4, 4, optimization, iBatch) ||
      !cEncoder.Add(satma))
     {
      DeleteObj(satma)
      return false;
     }

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

   index++;
   if(!cConcatenated.Init(0, index, OpenCL, 2 * embed_size * stack_size * variables,
                                                             optimization, iBatch))
      return false;
   index++;
   if(!cGRU.Init(0, index, OpenCL, stack_size * variables, 2 * embed_size, dimension,
                                                               optimization, iBatch))
      return false;

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

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

   index++;
   if(!cTranspose.Init(0, index, OpenCL, variables, stack_size, dimension, optimization, iBatch))
      return false;
//---
   return true;
  }

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

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

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

bool CNeuronBAT::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   CNeuronBaseOCL* current = NULL;
   CNeuronBaseOCL* prev = NeuronOCL;
//---
   for(int i = 0; i < cPrepare.Total(); i++)
     {
      current = cPrepare[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

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

После подготовки поток поступает в ветку Encoder. Здесь он проходит через все компоненты cEncoder — многомасштабные свёртки, residual-блоки, корректирующие SpikeConv-модули, корреляционные и адаптивные слои. Именно в этой точке формируется структурная характеристика рыночного состояния: модель улавливает геометрию движения, многократные резонансы паттернов и несущую форму свечных последовательностей.

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

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

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

Когда обе ветви завершают обработку, начинается этап объединения. Выходы Encoder и Context сводятся в единое пространство признаков через операцию Concat.

   uint units = cGRU.GetUnits();
   if(!Concat(cEncoder[-1].getOutput(), cContext[-1].getOutput(), cConcatenated.getOutput(),
              cEncoder[-1].Neurons() / units, cContext[-1].Neurons() / units, units))
      return false;
   if(!cGRU.FeedForward(cConcatenated.AsObject()))
      return false;

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

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

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

Особое внимание заслуживает следующая операция SumVecMatrix.

   if(!SumVecMatrix(cPrepare[0].getOutput(), cTranspose.getOutput(), cTranspose.getOutput(),
                    cPrepare[0].Neurons(), 1, 0, 0, 0, 1))
      return false;

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

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

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

И только после всех этих шагов управляемый поток направляется в родительский SpikeConvBlock, завершая прямой проход.

   return CNeuronSpikeConvBlock::feedForward(cTranspose.AsObject());
  }

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

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

bool CNeuronBAT::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;
//---
   if(!CNeuronSpikeConvBlock::calcInputGradients(cTranspose.AsObject()))
      return false;

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

   if(!cGRU.CalcHiddenGradients(cTranspose.AsObject()))
      return false;

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

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

   if(!cConcatenated.CalcHiddenGradients(cGRU.AsObject()))
      return false;
   uint units = cGRU.GetUnits();
   if(!cEncoder[-1] || !cContext[-1] ||
      !DeConcat(cEncoder[-1].getGradient(), cContext[-1].getGradient(), cConcatenated.getGradient(),
                cEncoder[-1].Neurons() / units, cContext[-1].Neurons() / units, units))
      return false;
   Deactivation(cEncoder[-1])
   Deactivation(cContext[-1])

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

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

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

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

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

   current = cPrepare[-1];
   if(!current ||
      !current.CalcHiddenGradients(cEncoder[0]))
      return false;
   CBufferFloat* temp = current.getGradient();
   if(!current.SetGradient(current.getPrevOutput(), false))
      return false;
   if(!current.CalcHiddenGradients(cContext[0]) ||
      !SumAndNormilize(temp, current.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
      !current.SetGradient(temp, false))
      return false;
//---
   for(int i = cPrepare.Total() - 2; i >= 0; i--)
     {
      current = cPrepare[i];
      if(!current ||
         !current.CalcHiddenGradients(cPrepare[i + 1]))
         return false;
     }

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

   if(!SumVecMatrixGrad(current.getPrevOutput(), cTranspose.getPrevOutput(), cTranspose.getGradient(),
                        current.Neurons(), 1, 0, 0, 0, 1))
      return false;
   if(current.Activation() != None)
      if(!DeActivation(current.getOutput(), current.getPrevOutput(), current.getPrevOutput(),
                                                                       current.Activation()))
         return false;
   if(!SumAndNormilize(current.getPrevOutput(), current.getGradient(), current.getGradient(),
                                                                       1, false, 0, 0, 0, 1))
      return false;
//---
   if(!NeuronOCL.CalcHiddenGradients(current))
      return false;
//---
   return true;
  }

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

По сути, CNeuronBAT является точкой сборки, в которой событийный подход становится полноценным инструментом анализа финансовых данных. Мы объединяем все идеи оригинального BAT — от voxel-представления до временной корреляции и адаптивного моделирования. И воплощаем их в архитектуре, пригодной для практического применения внутри торговых алгоритмов. Это и есть тот момент, когда теоретический фреймворк начинает работать как прикладной инструмент.



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

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

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

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

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

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

  • Энкодер состояния окружающей среды;
  • блок выделения ключевых признаков STFS, заимствованный из S3CE-Net (Spike-guided Spatiotemporal Semantic Coupling and Expansion Network);
  • пара моделей Актёр и Критик.
По сути, здесь мы описываем тот каркас, который впоследствии наполняется параметрами и превращается в полноценный механизм принятия торговых решений.

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

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

После подготовки контейнеров начинается построение Энкодера — модели, отвечающей за интерпретацию рыночного состояния. На первом шаге мы добавляем входной слой. Он получает на вход развёрнутую историю ценовых и объёмных признаков, представленную в виде одномерного массива длиной HistoryBars * BarDescr.

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

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

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

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBAT;
   descr.count = NForecast;
   descr.window = BarDescr;
   descr.window_out = EmbeddingSize;
   descr.variables = HistoryBars;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   iLatentLayer++;
//---
   uint window = descr.window;
   uint count = descr.count*descr.variables;

Мы задаём длину прогнозируемой последовательности NForecast, размер окна признаков BarDescr, длину вектора эмбеддингов EmbeddingSize, а также количество анализируемых состояний HistoryBars. Эта конфигурация определяет, какой объём рыночной истории будет преобразован в voxel-тензор и как он передаётся вглубь фреймворка. Для задачи автономного трейдинга такая связка особенно показательна: модель получает достаточно информации, чтобы выделять рыночные паттерны, формировать временные сигнатуры и передавать их далее уже в структурированном виде.

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

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

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

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



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

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

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

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

За четыре месяца периода тестирования начальный депозит в $100.0 вырос до $209.25, обеспечив рост более 109.0%. Профит-фактор оказался на уровне 1.59, а общее соотношение прибыли к убыткам составило $295.18 к $185.93. Всего было выполнено 90 сделок — поровну прибыльных и убыточных. Средняя прибыльная сделка дала $6.56, а средняя убыточная — $4.13. Это в среднем приносило около $1.21 за сделку. Максимальная просадка по equity составила 20.2%, по балансу — 14.95%.

Эти цифры отражают не просто сухую статистику, а динамику того, как модель чувствует рынок. Каждая сделка строилась на осознанной оценке текущего состояния рынка и прогнозе ключевых признаков. Линейная регрессия equity показывает корреляцию 0.95, подтверждая уверенный рост капитала. Хотя стандартная ошибка указывает на естественную волатильность и рыночные шумы, с которыми сталкивается любая торговая система, профит-фактор и Sharpe говорят о том, что стратегия способна генерировать прибыль с учётом риска. Однако короткий период тестирования и небольшое число сделок требуют внимательности и осторожного подхода.

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

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


Заключение

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

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

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

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

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


Ссылки


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

#ИмяТипОписание
1Study.mq5СоветникСоветник офлайн обучения моделей
2StudyOnline.mq5 Советник Советник онлайн обучения моделей
3Test.mq5СоветникСоветник для тестирования модели
4Trajectory.mqhБиблиотека классаСтруктура описания состояния системы и архитектуры моделей
5NeuroNet.mqhБиблиотека классаБиблиотека классов для создания нейронной сети
6NeuroNet.clБиблиотекаБиблиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3424.88 KB)
Алгоритм дендритных клеток — Dendritic Cell Algorithm (DCA) Алгоритм дендритных клеток — Dendritic Cell Algorithm (DCA)
Алгоритм дендритных клеток (DCA) — метаэвристика, вдохновлённая механизмами врождённого иммунитета. Дендритные клетки патрулируют пространство поиска, накапливают сигналы о качестве позиций и выносят коллективный вердикт: эксплуатировать найденное или продолжать исследование. Разберём, как биологическая модель обнаружения патогенов превращается в алгоритм оптимизации.
Знакомство с MQL5 (Часть 19): Автоматизация обнаружения волн Вульфа Знакомство с MQL5 (Часть 19): Автоматизация обнаружения волн Вульфа
Эта статья описывает, как программно выявлять бычьи и медвежьи паттерны волн Вульфа и торговать на их основе с помощью языка MQL5. Мы рассмотрим, как выявлять структуры волн Вульфа программным образом и исполнять сделки на их основе с помощью языка MQL5. Это включает в себя обнаружение ключевых точек свинга, проверку правил паттерна и подготовку советника к действию на основе найденных сигналов.
Знакомство с языком MQL5 (Часть 20): Введение в гармонические паттерны Знакомство с языком MQL5 (Часть 20): Введение в гармонические паттерны
В этой статье мы исследуем основы гармонических паттернов, их структуру и то, как они применяются в торговле. Вы узнаете о коррекциях и расширениях Фибоначчи, а также о том, как реализовать обнаружение гармонических паттернов на языке MQL5, тем самым закладывая основу для создания продвинутых торговых инструментов и советников.
Детерминированный алгоритм дендритных клеток — Deterministic Dendritic Cell Algorithm (dDCA) Детерминированный алгоритм дендритных клеток — Deterministic Dendritic Cell Algorithm (dDCA)
Представлена адаптация детерминированного алгоритма дендритных клеток (dDCA) для задач непрерывной оптимизации. Алгоритм, вдохновлённый Теорией Опасности иммунной системы, использует механизм накопления сигналов для автоматического баланса между исследованием и эксплуатацией пространства поиска.