preview
Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Окончание)

Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Окончание)

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

Введение

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

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

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

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

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

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

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

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

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

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


Энкодер корреляций

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

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

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

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

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

class CNeuronCorrelationEncoder  :  public CNeuronSpikeConvBlock
  {
protected:
   uint                    iMaxDisplacement;
   int                     ibShifts;
   CNeuronBaseOCL          cCorrelation;
   CNeuronSpikeConvBlock   cWarpCorrs;
   CNeuronSpikeConvBlock   cWarpInps;
   CLayer                  cProjection;
   //---
   virtual int       CreateShifts(const uint max_displacement, int &count);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronCorrelationEncoder(void) {};
                    ~CNeuronCorrelationEncoder(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint dimension, uint units,
                          uint bottleneck, uint max_displacement,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronCorrelationEncoder;   }
   //--- 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 bool      Clear(void) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj)   override;
   virtual void      TrainMode(bool flag) override;
  };

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

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

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

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

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

bool CNeuronCorrelationEncoder::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                     uint dimension, uint units,
                                     uint bottleneck, uint max_displacement,
                                     ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, 2 * bottleneck, 2 * bottleneck,
                                   dimension, units, 1, optimization_type, batch))
      return false;

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

Далее формируется набор допустимых смещений через CreateShifts.

   int count = 0;
   ibShifts = CreateShifts(max_displacement, count);
   if(ibShifts == INVALID_HANDLE)
      return false;
   iMaxDisplacement = max_displacement;

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

Следующим шагом инициализируется объект cCorrelation. Он отвечает за хранение корреляционного объёма.

   uint index = 0;
   if(!cCorrelation.Init(0, index, OpenCL, units * count, optimization, iBatch))
      return false;
   cCorrelation.SetActivationFunction(None);

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

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

   index++;
   if(!cWarpCorrs.Init(0, index, OpenCL, count, count, bottleneck, units, 1, optimization, iBatch))
      return false;
   index++;
   if(!cWarpInps.Init(0, index, OpenCL, dimension, dimension, bottleneck, units, 1, optimization, iBatch))
      return false;
   index++;

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

Завершающая часть инициализации посвящена формированию проекционного блока cProjection. Он собирается вручную, что даёт полный контроль над структурой. Сначала добавляется базовый нейрон CNeuronBaseOCL, который принимает на вход объединённые выходы cWarpCorrs и cWarpInps. Этот слой выполняет роль точки слияния корреляционного и входного представлений, не внося нелинейностей и сохраняя линейную интерпретацию признаков.

   cProjection.Clear();
   cProjection.SetOpenCL(OpenCL);
   CNeuronBaseOCL*   neuron = NULL;
   CNeuronMSRes*     res_block = NULL;
//---
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, cWarpCorrs.Neurons() + cWarpInps.Neurons(), optimization, iBatch) ||
      !cProjection.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   index++;

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

   res_block = new CNeuronMSRes();
   if(!res_block ||
      !res_block.Init(0, index, OpenCL, units, 2 * bottleneck, 1, optimization, iBatch) ||
      !cProjection.Add(res_block))
     {
      DeleteObj(res_block)
      return false;
     }
//---
   return true;
  }

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

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

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

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

Следующим шагом выполняется вычисление корреляционного объёма с помощью функции DilatedCorrelation.

   if(!DilatedCorrelation(NeuronOCL, cCorrelation.AsObject(), ibShifts,
                          cWarpInps.GetWindow(), cWarpInps.GetUnits()))
      return false;

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

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

   if(!cWarpCorrs.FeedForward(cCorrelation.AsObject()))
      return false;

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

Далее обработка переходит к проекционному блоку cProjection. Сначала выходы cWarpInps и cWarpCorrs конкатенируются в единый тензор признаков.

   CNeuronBaseOCL* curr = cProjection[0];
   CNeuronBaseOCL* prev = NULL;
//---
   if(!curr ||
      !Concat(cWarpInps.getOutput(), cWarpCorrs.getOutput(), curr.getOutput(),
              cWarpInps.GetFilters(), cWarpCorrs.GetFilters(), cWarpInps.GetUnits()))
      return false;
   prev = curr;

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

   for(int i = 1; i < cProjection.Total(); i++)
     {
      curr = cProjection[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

   if(!CNeuronSpikeConvBlock::feedForward(prev))
      return false;
   if(!SumAndNormilize(NeuronOCL.getOutput(), Output, Output, cConv.GetFilters(), true, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

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

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

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


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

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

class CNeuronEDCFlow :  public CNeuronSpikeConvBlock
  {
protected:
   CLayer                        cPrepare;
   CLayer                        cFeatureEncoder[2];
   CLayer                        cConextEncoder;
   CNeuronMultiScaleDifference   cDiffEncoder;
   CNeuronCorrelationEncoder     cCorrelation;
   CLayer                        cCorrelationUpsampling;
   CLayer                        cFlow;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronEDCFlow(void) {};
                    ~CNeuronEDCFlow(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint dimension_in, uint units_in, uint bottleneck,
                          uint dimension_out, uint units_out,
                          uint &shifts[], uint max_displacement,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronEDCFlow;   }
   //--- 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 bool      Clear(void) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj)   override;
   virtual void      TrainMode(bool flag) override;
//---
   virtual CBufferFloat   *getWeights(void) override;
  };

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

  • cPrepare выполняет начальную обработку входного потока событий, подготавливая его к многомасштабной обработке.
  • cFeatureEncoder[0] и cFeatureEncoder[1] формируют ветви кодирования признаков, где каждая ветвь отвечает за выделение специфической пространственно-временной информации с разным разрешением. Этот шаг напрямую повторяет идею авторской работы: многомасштабное извлечение признаков позволяет одновременно фиксировать локальные импульсы и глобальные закономерности.
  • cConextEncoder агрегирует глобальный контекст, сохраняя устойчивость оценки движения и обеспечивая согласованность пространственно-временного представления.

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

  • <cCorrelation реализует корреляционный путь, о котором мы говорили ранее: построение корреляционного объёма, выравнивание признаков и их проекция.

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

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

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

bool CNeuronEDCFlow::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint dimension_in, uint units_in, uint bottleneck,
                          uint dimension_out, uint units_out,
                          uint &shifts[], uint max_displacement,
                          ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, bottleneck, bottleneck,
                                   dimension_out, units_out, 1, optimization_type, batch))
      return false;
//---
   for(uint i = 0; i < cFeatureEncoder.Size(); i++)
     {
      cFeatureEncoder[i].Clear();
      cFeatureEncoder[i].SetOpenCL(OpenCL);
     }
   cPrepare.Clear();
   cConextEncoder.Clear();
   cCorrelationUpsampling.Clear();
   cFlow.Clear();
   cPrepare.SetOpenCL(OpenCL);
   cConextEncoder.SetOpenCL(OpenCL);
   cCorrelationUpsampling.SetOpenCL(OpenCL);
   cFlow.SetOpenCL(OpenCL);
//---
   CNeuronBaseOCL*               neuron = NULL;
   CNeuronBatchNormOCL*          norm=NULL;
   CNeuronCreateFlow*            stack = NULL;
   CNeuronSpikeADM*              adm = NULL;
   CNeuronSpikeDepthWiseConv*    dw_conv = NULL;
   CNeuronSpikeConvBlock*        conv = NULL;
   CNeuronTransposeOCL*          transp = NULL;
   CNeuronMultiScaleExcitation*  excitation = NULL;
   CNeuronSpikeConvGRU2D*        gru = NULL;
   uint index = 0;
//--- Prepare
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, dimension_in, iBatch, optimization) ||
      !cPrepare.Add(norm))
     {
      DeleteObj(norm)
      return false;
     }
   index++;
   stack = new CNeuronCreateFlow();
   if(!stack ||
      !stack.Init(0, index, OpenCL, units_in, dimension_in, dimension_in, 1, optimization, iBatch) ||
      !cPrepare.Add(stack))
     {
      DeleteObj(stack)
      return false;
     }
   index++;
   adm = new CNeuronSpikeADM();
   if(!adm ||
      !adm.Init(0, index, OpenCL, dimension_in, units_in, 1, optimization, iBatch) ||
      !cPrepare.Add(adm))
     {
      DeleteObj(adm)
      return false;
     }
   index++;

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

//--- Feature Encoder HD
   uint units = (units_in + 1) / 2;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, dimension_in, dimension_in, 5, 2, units, 1, optimization, iBatch) ||
      !cFeatureEncoder[0].Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;
   units = (units + 1) / 2;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, dimension_in, bottleneck, 5, 2, units, 1, optimization, iBatch) ||
      !cFeatureEncoder[0].Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;
   if(!cDiffEncoder.Init(0, index, OpenCL, bottleneck, units, bottleneck, shifts, optimization, iBatch))
      return false;
   index++;

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

//--- Feature Encoder LD
   units = (units + 1) / 2;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, bottleneck, 2 * bottleneck, 5, 2, units, 1, optimization, iBatch) ||
      !cFeatureEncoder[1].Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;
   if(!cCorrelation.Init(0, index, OpenCL, 2 * bottleneck, units, bottleneck, max_displacement,
                                                                         optimization, iBatch))
      return false;
   index++;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, 2 * bottleneck, 2 * bottleneck, 5, 1, units, 1, optimization, iBatch) ||
      !cCorrelationUpsampling.Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;

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

//--- Conext Encoder
   units = (units_in + 1) / 2;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, dimension_in, dimension_in, 10, 2, units, 1, optimization, iBatch) ||
      !cConextEncoder.Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;
   units = (units + 1) / 2;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, dimension_in, bottleneck, 10, 2, units, 1, optimization, iBatch) ||
      !cConextEncoder.Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;

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

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

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

//--- Flow
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, 4 * bottleneck, optimization, iBatch) ||
      !cFlow.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   index++;
   excitation = new CNeuronMultiScaleExcitation();
   if(!excitation ||
      !excitation.Init(0, index, OpenCL, bottleneck, units, 4, (bottleneck + 1) / 2,
                                                            optimization, iBatch) ||
      !cFlow.Add(excitation))
     {
      DeleteObj(excitation)
      return false;
     }
   index++;
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, 4 * bottleneck, 4 * bottleneck, bottleneck, units,
                                                           1, optimization, iBatch) ||
      !cFlow.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;
   dw_conv = new CNeuronSpikeDepthWiseConv();
   if(!dw_conv ||
      !dw_conv.Init(0, index, OpenCL, bottleneck, bottleneck, 3, 1, units, 1, optimization, iBatch) ||
      !cFlow.Add(dw_conv))
     {
      DeleteObj(dw_conv)
      return false;
     }
   index++;
   gru = new CNeuronSpikeConvGRU2D();
   if(!gru ||
      !gru.Init(0, index, OpenCL, units, bottleneck, bottleneck, optimization, iBatch) ||
      !cFlow.Add(gru))
     {
      DeleteObj(gru)
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, units, bottleneck, optimization, iBatch) ||
      !cFlow.Add(transp))
     {
      DeleteObj(transp)
      return false;
     }
   index++;
   conv = CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, units, units, units_out, bottleneck, 1, optimization, iBatch) ||
      !cFlow.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, bottleneck, units_out, optimization, iBatch) ||
      !cFlow.Add(transp))
     {
      DeleteObj(transp)
      return false;
     }
//---
   return true;
  }

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

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

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

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

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

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

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

//--- Feature Encoder
   for(uint fe = 0; fe < cFeatureEncoder.Size(); fe++)
     {
      for(int i = 0; i < cFeatureEncoder[fe].Total(); i++)
        {
         curr = cFeatureEncoder[fe][i];
         if(!curr ||
            !curr.FeedForward(prev))
            return false;
         prev = curr;
        }
     }

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

//--- Context Encoder
   prev = cPrepare[-1];
   for(int i = 0; i < cConextEncoder.Total(); i++)
     {
      curr = cConextEncoder[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

//--- Moution Encoder
   if(!cDiffEncoder.FeedForward(cFeatureEncoder[0][-1].AsObject()))
      return false;
   if(!cCorrelation.FeedForward(cFeatureEncoder[1][-1].AsObject()))
      return false;

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

   prev = cCorrelation.AsObject();
   for(int i = 0; i < cCorrelationUpsampling.Total(); i++)
     {
      curr = cCorrelationUpsampling[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

   curr = cFlow[0];
   uint units = cDiffEncoder.GetUnits();
   uint bottleneck = cDiffEncoder.GetFilters();
   if(!curr ||
      !Concat(cFeatureEncoder[0][-1].getOutput(), cConextEncoder[-1].getOutput(),
              cDiffEncoder.getOutput(), prev.getOutput(), curr.getOutput(),
              bottleneck, bottleneck, bottleneck, bottleneck, units))
      return false;
   prev = curr;
   for(int i = 1; i < cFlow.Total(); i++)
     {
      curr = cFlow[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

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

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

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


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

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

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

Такой подход позволяет сохранить преемственность экспериментов и корректность сравнения результатов. Даёт возможность оценить вклад EDCFlow в чистом виде — как инструмента извлечения и интерпретации сложных пространственно-временных зависимостей рынка. Полная архитектура обучаемых моделей приведена во вложении и доступна для самостоятельного изучения.

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

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

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

По результатам тестирования финансовый результат оказался положительным. При стартовом депозите 100USD модель завершила период с чистой прибылью 23.51USD, что соответствует доходности около 23.5%. Это не агрессивный, но уверенный результат. Особенно с учётом того, что стратегия не оптимизировалась под конкретный участок истории и работала в режиме обобщения ранее усвоенных закономерностей. Уже на этом этапе видно, что модель способна извлекать полезную информацию из рыночной динамики и превращать её в положительное математическое ожидание.

Соотношение валовой прибыли и убытков оказалось умеренным. При суммарной прибыли 182,09USD и убытках 158,58USD Profit Factor составил порядка 1.15. Это значение нельзя назвать высоким, однако оно устойчиво превышает единицу и подтверждает наличие положительного ожидания.

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

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

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

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


Заключение

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

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

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

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3517.81 KB)
Знакомство с языком MQL5 (Часть 23): Автоматизация торговли на пробое диапазона открытия рынка Знакомство с языком MQL5 (Часть 23): Автоматизация торговли на пробое диапазона открытия рынка
В этой статье рассматривается, как создать советник для торговли по стратегии пробоя диапазона открытия (Opening Range Breakout, ORB) на языке MQL5. В статье объясняется, как советник идентифицирует пробои из диапазона открытия рынка и открывает соответствующие сделки. Вы также научитесь контролировать количество открытых позиций и устанавливать конкретное время прекращения для автоматической остановки торговли.
От новичка до эксперта: Периоды на рынке Форекс От новичка до эксперта: Периоды на рынке Форекс
Каждый рыночный период имеет начало и конец, при каждом закрытии цена определяет его настроение — так же, как и при любой свечной сессии. Понимание этих ориентиров позволяет нам оценить преобладающее настроение рынка, определяя, какие силы контролируют ситуацию - бычьи или медвежьи. В настоящем обсуждении мы делаем важный шаг вперед, разрабатывая новую функцию в Market Periods Synchronizer, которая визуализирует сессии рынка Форекс для помощи в принятии более обоснованных торговых решений. Этот инструмент может быть особенно эффективным для определения в режиме реального времени, какая сторона — быки или медведи — доминирует на сессии. Давайте исследуем эту концепцию и раскроем те идеи, которые она дает.
Машинное обучение и Data Science (Часть 34): Разложение временных рядов, раскрываем саму суть фондового рынка Машинное обучение и Data Science (Часть 34): Разложение временных рядов, раскрываем саму суть фондового рынка
В мире, переполненном шумными и непредсказуемыми данными, выявление значимых закономерностей может быть непростой задачей. В этой статье мы рассмотрим сезонное разложение (seasonal decomposition) — мощный аналитический метод, который помогает разделить данные на ключевые компоненты: тренд, сезонные закономерности и шум. Разбив данные на такие составляющие, мы можем выявить скрытые закономерности и работать с более чистой и понятной информацией.
Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Блок разностей) Нейросети в трейдинге: Разностное моделирование рыночной микроструктуры (Блок разностей)
В статье представлена практическая реализация подходов фреймворка EDCFlow с акцентом на модуль Multi-Scale Difference. Показано, как последовательное сжатие признаков, вычисление разностей на нескольких масштабах и адаптивное мультимасштабное внимание позволяют формировать структурированное и информативное представление потоковых данных.