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

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

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

Введение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Для финансовых рынков мульти-масштабность — не роскошь, а необходимость. Рынок всегда живёт сразу в нескольких временных горизонтах. То, что на одном масштабе выглядит как шум, на другом может быть началом движения. И наоборот. Multi-Scale Difference Layer позволяет зафиксировать эту многослойность, не смешивая её в один усреднённый сигнал.


Модуль акцентирования масштабов

Начнем мы работу с модуля Multi-Scale Attention. Именно здесь важно сразу расставить акценты и избежать неверных ассоциаций. В рамках EDCFlow механизм внимания принципиально отличается от того Self-Attention, хорошо знакомого нам по предыдущим работам. Речь идёт не об универсальном сопоставлении всего со всем и не о поиске глобальных зависимостей внутри последовательности. Такой подход для плотных потоковых данных избыточен и, что важнее, вычислительно дорог.

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

Если Self-Attention стремится построить глобальную картину взаимосвязей, то здесь внимание выполняет роль адаптивного регулятора. Оно отвечает на простой, но важный вопрос: какой масштаб изменений наиболее информативен в текущем контексте. Где рынок движется плавно и важен крупный горизонт. А где появляются резкие локальные сдвиги, требующие более тонкого разрешения.

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

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

Наша реализация описанных выше подходов сосредоточена в специализированном классе CNeuronMultiScaleExcitation. Это ключевой вычислительный узел, в котором на практике материализуется идея мульти-масштабного внимания, предложенная авторами EDCFlow. Здесь нет операций попарного сопоставления элементов анализируемого тензора. И нет глобального внимания по всей последовательности. Вместо этого используется более сдержанный и прикладной механизм возбуждения (excitation), работающий на уровне масштабов.

class CNeuronMultiScaleExcitation   :  public CNeuronBaseOCL
  {
protected:
   CLayer            cExcitation;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

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

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

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

bool CNeuronMultiScaleExcitation::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                       uint dimension, uint units, uint scales, uint bottleneck,
                                       ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, dimension * units * scales,
                                                           optimization_type, batch))
      return false;

Первым шагом управление передается одноименному методу родительского класса. Далее формируется внутренний конвейер cExcitation. Именно он реализует механизм внимания. Архитектура строится последовательно, без скрытой магии.

   cExcitation.Clear();
   cExcitation.SetOpenCL(OpenCL);
   CNeuronSpikeConvBlock*  conv = NULL;
   CNeuronTransposeOCL*    transp = NULL;
   CNeuronSoftMaxOCL*      softmax = NULL;
   uint index = 0;
//--- [Scale, Unit, Dimension]
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, scales, units * dimension, optimization, iBatch) ||
      !cExcitation.Add(transp))
     {
      DeleteObj(transp)
      return false;
     }
   index++;

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

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

//--- [Unit, Dimension, Scale]
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, scales, scales, 1, dimension * units, 1,
                                                   optimization, iBatch) ||
      !cExcitation.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;

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

//--- [Unit, Dimension]
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, dimension, dimension, bottleneck, units, 1,
                                                      optimization, iBatch) ||
      !cExcitation.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;

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

//--- [Unit, Bottleneck]
   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, bottleneck, bottleneck, scales * dimension,
                                            units, 1, optimization, iBatch) ||
      !cExcitation.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;

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

//--- [Unit, Dimension, Scale]
   softmax = new CNeuronSoftMaxOCL();
   if(!softmax ||
      !softmax.Init(0, index, OpenCL, conv.Neurons(), optimization, iBatch) ||
      !cExcitation.Add(softmax))
     {
      DeleteObj(softmax)
      return false;
     }
   softmax.SetHeads(dimension * units);
   index++;

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

Финальное транспонирование возвращает данные к исходному формату [Scale, Unit, Dimension].

//--- [Unit, Dimension, Scale]
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, units * dimension, scales, optimization, iBatch) ||
      !cExcitation.Add(transp))
     {
      DeleteObj(transp)
      return false;
     }
   index++;
//--- [Scale, Unit, Dimension]
   return true;
  }

Модуль завершён. На выходе мы имеем набор весов, которые могут быть напрямую применены к мульти-масштабным признакам.

В итоге данный механизм внимания:

  • работает строго локально по масштабам,
  • не нарушает причинность,
  • избегает квадратичной сложности Self-Attention,
  • полностью соответствует логике EDCFlow.

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

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

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

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

После завершения прохода по всем слоям cExcitation мы получаем тензор весов внимания. Далее следует ключевая операция — покомпонентное умножение (ElementMult) исходных входных признаков на вычисленные коэффициенты возбуждения. Никаких суммирований, никакой рекомбинации каналов. Каждый масштаб, каждая единица и каждая размерность масштабируются строго своим весом.

   if(!ElementMult(NeuronOCL.getOutput(), prev.getOutput(), Output))
      return false;
//---
   return true;
  }

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

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


Блок мульти-масштабных разностей

Далее мы поднимаемся на ступень выше в иерархии архитектуры фреймворка EDCFlow и переходим к построению Multi-Scale Difference блока, который уже работает не с весами, а с самой динамикой потока.

Если модуль мульти-масштабного внимания отвечает на вопрос какому масштабу сейчас доверять, то Multi-Scale Difference отвечает на более фундаментальный вопрос — что именно изменилось. Это логичное продолжение архитектуры. Сначала мы научились взвешивать масштабы. Теперь учимся извлекать из них содержательные различия.

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

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

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

Класс CNeuronMultiScaleDifference является центральным элементом реализации модуля Multi-Scale Difference. Он встроен в основной поток обработки признаков и выполняет две ключевые функции: вычисляет структурированные разностные признаки на нескольких масштабах и оценивает их значимость с помощью встроенного мульти-масштабного внимания. Такой подход обеспечивает органичное сочетание идей EDCFlow: плотных разностных карт и адаптивного взвешивания масштабов.

class CNeuronMultiScaleDifference :  public CNeuronSpikeConvBlock
  {
protected:
   uint              iaShifts[];
   int               ibShifts;
   //---
   CLayer            cBottleneck;
   CLayer            cExcitation;
   CLayer            cProjection;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronMultiScaleDifference(void) {};
                    ~CNeuronMultiScaleDifference(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint dimension, uint units, uint bottleneck, uint &shifts[],
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronMultiScaleDifference;   }
   //--- 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;
  };

Архитектурно класс разделён на три блока:

  • cBottleneck — сжимает анализируемое представление в компактное пространство признаков, фильтруя шум и снижая вычислительную нагрузку для последующих операций.
  • cExcitation — внутри этого блока встроен объект CNeuronMultiScaleExcitation, реализующий мульти-масштабное внимание. Он получает разностные карты для всех смещений и адаптивно усиливает наиболее информативные масштабы, формируя корректные веса для каждого признака.
  • cProjection — возвращает обработанные разностные признаки в исходное пространство размерностей, обеспечивая их интеграцию с остальными ветвями фреймворка (корреляционной и контекстной).

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

Инициализация внутренних компонентов и построение архитектуры объекта организованы в методе Init. На этом этапе создаётся полный вычислительный конвейер блока Multi-Scale Difference и настраиваются ключевые параметры.

bool CNeuronMultiScaleDifference::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                       uint dimension, uint units, uint bottleneck, uint &shifts[],
                                       ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, bottleneck, bottleneck,
                                   dimension, units, 1, optimization_type, batch))
      return false;

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

   int scales = int(shifts.Size());
   if(scales == 0 ||
      ArrayResize(iaShifts, scales) < scales ||
      ArrayCopy(iaShifts, shifts, 0, 0, scales) < scales)
      return false;
   ibShifts = OpenCL.AddBufferFromArray(iaShifts, 0, scales, CL_MEM_READ_ONLY);
   if(ibShifts == INVALID_HANDLE)
      return false;

В параметр ibShifts записывается указатель на этот массив в памяти OpenCL-контекста, обеспечивая прямой доступ к данным на GPU без лишнего копирования. Это повышает производительность и гарантирует согласованность вычислений между слоями.

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

   cBottleneck.Clear();
   cExcitation.Clear();
   cProjection.Clear();
   cBottleneck.SetOpenCL(OpenCL);
   cExcitation.SetOpenCL(OpenCL);
   cProjection.SetOpenCL(OpenCL);

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

   CNeuronMultiWindowsConvWPadOCL*  padconv = NULL;
   CNeuronSpikeDepthWiseConv*       dwconv = NULL;
   CNeuronSpikeConvBlock*           conv = NULL;
   CNeuronBaseOCL*                  neuron = NULL;
   CNeuronMultiScaleExcitation*     excitation = NULL;
   CNeuronTransposeOCL*             transpose = NULL;
   uint index = 0;
//--- Bottleneck
   uint units_out = (units + 1) / 2;
   dwconv = new CNeuronSpikeDepthWiseConv();
   if(!dwconv ||
      !dwconv.Init(0, index, OpenCL, dimension, bottleneck, 5, 2, units_out, 1, optimization, iBatch) ||
      !cBottleneck.Add(dwconv))
     {
      DeleteObj(dwconv);
      return false;
     }
   index++;

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

   uint windows[] = { 3 * bottleneck };
   padconv = new CNeuronMultiWindowsConvWPadOCL();
   if(!padconv ||
      !padconv.Init(0, index, OpenCL, windows, bottleneck, bottleneck, units_out, 1, optimization, iBatch) ||
      !cBottleneck.Add(padconv))
     {
      DeleteObj(padconv)
      return false;
     }
   index++;

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

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

   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, bottleneck, bottleneck, bottleneck, units_out, 1, optimization, iBatch) ||
      !cBottleneck.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;

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

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

//--- Excitation
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, conv.Neurons()*scales, optimization, iBatch) ||
      !cExcitation.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   index++;

Установка функции активации в None подчёркивает, что на этом этапе важна чистая линейная передача информации без искажений.

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

   excitation = new CNeuronMultiScaleExcitation();
   if(!excitation ||
      !excitation.Init(0, index, OpenCL, bottleneck, units_out, scales, (bottleneck + 1) / 2, optimization, iBatch) ||
      !cExcitation.Add(excitation))
     {
      DeleteObj(excitation)
      return false;
     }
   index++;

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

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

   transpose = new CNeuronTransposeOCL();
   if(!transpose ||
      !transpose.Init(0, index, OpenCL, bottleneck * units_out, scales, optimization, iBatch) ||
      !cExcitation.Add(transpose))
     {
      DeleteObj(transpose)
      return false;
     }
   index++;

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

Завершающий слой блока объединяет все масштабные представления в согласованное и компактное пространство признаков.

   conv = new CNeuronSpikeConvBlock();
   if(!conv ||
      !conv.Init(0, index, OpenCL, scales, scales, 1, transpose.GetCount(), 1, optimization, iBatch) ||
      !cExcitation.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   index++;

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

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

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

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

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

Здесь стоит подчеркнуть нестандартное использование Depth-Wise свёртки. В блоке Bottleneck мы сжимали размерность признаков и уменьшали длину последовательности в два раза. Теперь задача обратная. Необходимо восстановить исходный масштаб данных. Для этого сначала применяется канально-независимая группировка, которая согласовывает текущее значение признака с соседями по временной оси, создавая локальные зависимости между последовательными элементами. Затем пространство признаков расширяется в два раза, фактически создавая дополнительные элементы последовательности, согласованные как по пространству признаков, так и по временной оси. Такой приём позволяет аккуратно восстановить структуру исходного потока данных, сохраняя информативность разностных карт и корректно интегрируя их в последующую обработку.

Таким образом, cProjection завершает формирование выходного тензора модуля Multi-Scale Difference. Признаки подготовлены, адаптивно взвешены и проецированы в согласованное пространство, готовое к дальнейшему использованию в анализе. Каждая строка кода здесь выполняет как техническую задачу по инициализации слоёв, так и смысловую — сохранение согласованности, масштабности и информативности признаков для надёжного анализа финансовых потоков.

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

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

Алгоритм прямого прохода реализован в методе feedForward и отражает последовательную логику обработки признаков в трёх основных блоках: Bottleneck, Excitation и Projection.

bool CNeuronMultiScaleDifference::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   CNeuronBaseOCL* prev = NeuronOCL;
   CNeuronBaseOCL* curr = NULL;

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

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

//--- Bottleneck
   for(int i = 0; i < cBottleneck.Total(); i++)
     {
      curr = cBottleneck[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

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

//--- Excitation
   curr = cExcitation[0];
   if(!curr)
      return false;
   uint dimension = cConv.GetWindow();
   uint units = prev.Neurons() / dimension;
   if(!DilatedDifference(prev, curr, ibShifts, dimension, units))
      return false;
   prev = curr;
   for(int i = 1; i < cExcitation.Total(); i++)
     {
      curr = cExcitation[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

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

//--- Projection
   curr = cProjection[0];
   if(!curr ||
      !SumAndNormilize(cBottleneck[-1].getOutput(), prev.getOutput(), curr.getOutput(), dimension, true, 0, 0, 0, 1))
      return false;
   prev = curr;
   for(int i = 1; i < cProjection.Total(); i++)
     {
      curr = cProjection[i];
      if(!curr ||
         !curr.FeedForward(prev))
         return false;
      prev = curr;
     }

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

Финальная нормализация и объединение с результатами работы родительского класса CNeuronSpikeConvBlock создаёт стабильное, готовое к использованию представление признаков.

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

Здесь модель словно подводит итог анализа, объединяя взгляд на структуру и динамику потока в единую картину.

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


Заключение

В данной работе мы продемонстрировали практическую реализацию ключевых подходов фреймворка EDCFlow, сосредоточив внимание на модуле Multi-Scale Difference. Реализация показала, как последовательное сжатие признаков, вычисление разностей на нескольких масштабах и адаптивное мульти-масштабное внимание позволяют формировать структурированное и информативное представление потоковых данных. Каждый блок — Bottleneck, Excitation и Projection — выполняет чётко определённую роль: от фильтрации и сжатия, через выявление значимых изменений, до восстановления исходной размерности и интеграции признаков в рабочее пространство.

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

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


Ссылки


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

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