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

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

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

Введение

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

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

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

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

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

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

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

Корреляция признаков строится как внутреннее произведение между векторами признаков в соседних состояниях, с учётом возможных смещений. Фактически модель измеряет, насколько хорошо текущее состояние объясняется предыдущим при различных гипотезах движения. В классических подходах для этого используется плотная квадратная область поиска, и увеличение радиуса такой области быстро приводит к взрывному росту вычислительной сложности. EEMFlow идёт другим путём. Вместо грубой силы применяется разреженная, но геометрически продуманная схема выборки — Dilated Feature Correlation.

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

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

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

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

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

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


Расширенная корреляция признаков

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

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

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

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

__kernel void DilatedCorrelation(__global const float* feature,
                                 __global const int* shifts,
                                 __global float* correlations,
                                 const int dimension
                                )
  {
   const size_t main = get_global_id(0);
   const size_t loc = get_local_id(1);
   const size_t sh = get_global_id(2);
   const size_t units = get_global_size(0);
   const size_t total_loc = get_local_size(1);
   const size_t total_corr = get_global_size(2);

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

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

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

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

   __local float temp[LOCAL_ARRAY_SIZE];
//---
   const int slave = shifts[sh >> 1] * ((sh & 1) ? -1 : 1);
   if(slave < 0 || slave >= units)
     {
      if(loc == 0)
         correlations[RCtoFlat(main, sh, units, total_corr, 0)] = 0;
      return;
     }

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

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

   float result = 0.0f;
   for(int d = loc; d < dimension; d += total_loc)
     {
      float value_main = IsNaNOrInf(feature[RCtoFlat(main, d, units, dimension, 0)], 0);
      float value_slave = IsNaNOrInf(feature[RCtoFlat(slave, d, units, dimension, 0)], 0);
      result += IsNaNOrInf(value_main * value_slave, 0);
     }

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

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

   result = LocalSum(result, 1, temp);
//---
   if(loc == 0)
      correlations[RCtoFlat(main, sh, units, total_corr, 0)] = result;
  }

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

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

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

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

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

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

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

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

__kernel void DilatedCorrelationGrad(__global const float* feature,
                                     __global float*       feature_gr,
                                     __global const int*   shifts,
                                     __global const float* corr_gr,
                                     const int total_corr
                                    )
  {
   const size_t id = get_global_id(0);
   const size_t loc = get_local_id(1);
   const size_t d = get_global_id(2);
   const size_t units = get_global_size(0);
   const size_t total_loc = get_local_size(1);
   const size_t dimension = get_global_size(2);

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

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

   __local float temp[LOCAL_ARRAY_SIZE];
//---
   float result = 0.0f;
   for(int sh = loc; sh < total_corr; sh += total_loc)
     {
      const int offset = shifts[sh >> 1];
      const int sign   = (sh & 1) ? -1 : +1;
      // id — main
      int slave = id + sign * offset;
      if(slave >= 0 && slave < units)
        {
         float g = corr_gr[RCtoFlat(id, sh, units, total_corr, 0)];
         result += IsNaNOrInf(g * feature[RCtoFlat(slave, d, units, dimension, 0)], 0.0f);
        }

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

В первом случае текущий элемент выступает в роли базового. Тогда смещённый элемент вычисляется как slave. Если такой индекс допустим, поток берёт градиент соответствующей корреляции и умножает его на значение признака смещённого элемента. Это прямое следствие производной скалярного произведения: вклад в градиент одного вектора пропорционален компонентам другого.

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

      // id — slave
      int main = id - sign * offset;
      if(main >= 0 && main < units)
        {
         float g = corr_gr[RCtoFlat(main, sh, units, total_corr, 0)];
         result += IsNaNOrInf(g * feature[RCtoFlat(main, d, units, dimension, 0)], 0.0f);
        }
     }

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

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

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

   result = LocalSum(result, 1, temp);
   if(loc == 0)
      feature_gr[RCtoFlat(id, d, units, dimension, 0)] = result;
  }

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

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

В этом и заключается сильная сторона подхода. Корреляция остаётся простым, интерпретируемым и вычислительно эффективным оператором. А обучение происходит там, где это действительно оправдано — в пространстве признаков. Именно так Dilated Feature Correlation органично встраивается в обучаемую архитектуру EEMFlow, не ломая её философию и сохраняя строгую связь между геометрией движения и процессом оптимизации.


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

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

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

class CNeuronSpikeEEMFLow  :   public CNeuronSpikeCDC
  {
protected:
   uint                    iMaxDisplacement;
   int                     ibShifts;
   CLayer                  cPrepare;
   CLayer                  cEncoder[3];
   CNeuronBaseOCL          cCorrelations[3];
   CNeuronBaseOCL          cConcat[3];
   CLayer                  cDecoder[4];
   //---
   virtual bool      DilatedCorrelation(CNeuronBaseOCL* feature, CNeuronBaseOCL* correlations,
                                                                      uint dimension, uint units);
   virtual bool      DilatedCorrelationGrad(CNeuronBaseOCL* feature, CNeuronBaseOCL* correlations,
                                                                      uint dimension, uint units);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSpikeEEMFLow(void) : ibShifts(INVALID_HANDLE) {};
                    ~CNeuronSpikeEEMFLow(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint dimension, uint embed_size,
                          uint stack_size, uint variables,
                          uint max_displacement,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronSpikeEEMFLow;   }
   //--- 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;
   virtual CBufferFloat   *getWeights(void) override;
  };

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

Поле ibShifts хранит дескриптор буфера со статическим набором относительных смещений. Это ключевой элемент всей схемы Dilated Feature Correlation. Вектор смещений формируется один раз на стороне основной программы и затем используется всеми OpenCL-кернелами. Такой подход позволяет избежать сложной логики отбора соседей внутри кернелов и делает вычисления предсказуемыми, стабильными и хорошо масштабируемыми.

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

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

Центральное место в архитектуре занимают объекты cCorrelations[3]. Именно здесь выполняется вычисление расширенных корреляций для каждого уровня энкодера. Результаты корреляционного анализа поступают в блоки cConcat[3].

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

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

bool CNeuronSpikeEEMFLow::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint dimension, uint embed_size,
                               uint stack_size, uint variables,
                               uint max_displacement,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   uint inside_dimensions[] = {32, 32, 32, 16, 8};
   if(!CNeuronSpikeCDC::Init(numOutputs, myIndex, open_cl, embed_size, inside_dimensions,
                             dimension, stack_size, variables, optimization_type, batch))
      return false;

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

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

   CNeuronCreateFlow*               stack = NULL;
   CNeuronBatchNormOCL*             norm = NULL;
   CNeuronSpikeADM*                 adm = NULL;
   CNeuronMultiWindowsConvWPadOCL*  padconv = NULL;
   CNeuronSpikeConvBlock*           conv = NULL;
   CNeuronSpikeCDC*                 cdc = NULL;
//---
   uint index = 0;
   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;
     }
   index++;

Затем создаётся поток состояний с помощью CNeuronCreateFlow, который организует временную структуру данных.

   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;
     }
   index++;

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

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

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

   int shifts[];
   if(ArrayResize(shifts, max_displacement) <= 0)
      return false;
   uint count = 0;
   for(uint i = 0; i < max_displacement; i++)
     {
      if(i > 2 && bool(i & 1))
         continue;
      shifts[count] = (int)(i + 1);
      count++;
     }
   if(ArrayResize(shifts, count) < (int)count)
      return false;
   ibShifts = OpenCL.AddBufferFromArray(shifts, 0, count, CL_MEM_READ_ONLY);
   if(ibShifts == INVALID_HANDLE)
      return false;
   iMaxDisplacement = max_displacement;

Этот вектор один раз загружается в память OpenCL и затем многократно используется на всех уровнях пирамиды. Такое решение экономит вычислительные ресурсы и делает поведение алгоритма полностью детерминированным.

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

   uint window_in = embed_size;
   uint window_out = 16;
   uint units_in = stack_size;
   uint units_out = (units_in + 1) / 2;
   uint windows[] = { 3 * window_in };
//---
   for(int i = 0; i < 3; i++)
     {
      cEncoder[i].Clear();
      cDecoder[i].Clear();
      cEncoder[i].SetOpenCL(OpenCL);
      cDecoder[i].SetOpenCL(OpenCL);
      //--- Encoder
      padconv = new CNeuronMultiWindowsConvWPadOCL();
      if(!padconv ||
         !padconv.Init(0, index, OpenCL, windows, 2 * window_in, window_out,
                       units_out, variables, optimization, iBatch) ||
         !cEncoder[i].Add(padconv))
        {
         DeleteObj(padconv)
         return false;
        }
      padconv.SetActivationFunction(SoftPlus);
      index++;
      norm = new CNeuronBatchNormOCL();
      if(!norm ||
         !norm.Init(0, index, OpenCL, padconv.Neurons(), iBatch, optimization) ||
         !cEncoder[i].Add(norm))
        {
         DeleteObj(norm)
         return false;
        }
      norm.SetActivationFunction(None);
      index++;
      windows[0] = 3 * window_out;
      for(int j = 0; j < 2; j++)
        {
         padconv = new CNeuronMultiWindowsConvWPadOCL();
         if(!padconv ||
            !padconv.Init(0, index, OpenCL, windows, window_out, window_out,
                          units_out, variables, optimization, iBatch) ||
            !cEncoder[i].Add(padconv))
           {
            DeleteObj(padconv)
            return false;
           }
         padconv.SetActivationFunction(SoftPlus);
         index++;
         norm = new CNeuronBatchNormOCL();
         if(!norm ||
            !norm.Init(0, index, OpenCL, padconv.Neurons(), iBatch, optimization) ||
            !cEncoder[i].Add(norm))
           {
            DeleteObj(norm)
            return false;
           }
         norm.SetActivationFunction(None);
         index++;
        }
      conv = new CNeuronSpikeConvBlock();
      if(!conv ||
         !conv.Init(0, index, OpenCL, window_out, window_out, window_out, units_out,
                    variables, optimization, iBatch) ||
         !cEncoder[i].Add(conv))
        {
         DeleteObj(conv)
         return false;
        }
      index++;

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

      if(!cCorrelations[i].Init(0, index, OpenCL, count * units_out * variables,
                                optimization, iBatch))
         return false;
      cCorrelations[i].SetActivationFunction(None);

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

      index++;
      if(!cConcat[i].Init(0, index, OpenCL, (2 * window_out + count)*units_out * variables,
                          optimization, iBatch))
         return false;
      cConcat[i].SetActivationFunction(None);
      index++;

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

      //--- Decoder
      padconv = new CNeuronMultiWindowsConvWPadOCL();
      if(!padconv ||
         !padconv.Init(0, index, OpenCL, windows, window_out, window_out,
                       units_out, variables, optimization, iBatch) ||
         !cDecoder[i].Add(padconv))
        {
         DeleteObj(padconv)
         return false;
        }
      padconv.SetActivationFunction(SoftPlus);
      index++;
      norm = new CNeuronBatchNormOCL();
      if(!norm ||
         !norm.Init(0, index, OpenCL, padconv.Neurons(), iBatch, optimization) ||
         !cDecoder[i].Add(norm))
        {
         DeleteObj(norm)
         return false;
        }
      norm.SetActivationFunction(None);
      index++;
      padconv = new CNeuronMultiWindowsConvWPadOCL();
      if(!padconv ||
         !padconv.Init(0, index, OpenCL, windows, window_out, 2 * window_in,
                       units_out, variables, optimization, iBatch) ||
         !cDecoder[i].Add(padconv))
        {
         DeleteObj(padconv)
         return false;
        }
      padconv.SetActivationFunction(SoftPlus);
      index++;
      norm = new CNeuronBatchNormOCL();
      if(!norm ||
         !norm.Init(0, index, OpenCL, padconv.Neurons(), iBatch, optimization) ||
         !cDecoder[i].Add(norm))
        {
         DeleteObj(norm)
         return false;
        }
      norm.SetActivationFunction(None);
      index++;
      windows[0] = 3 * window_in;
      for(int j = 0; j < 2; j++)
        {
         padconv = new CNeuronMultiWindowsConvWPadOCL();
         if(!padconv ||
            !padconv.Init(0, index, OpenCL, windows, window_in, window_in,
                          units_in, variables, optimization, iBatch) ||
            !cDecoder[i].Add(padconv))
           {
            DeleteObj(padconv)
            return false;
           }
         padconv.SetActivationFunction(SoftPlus);
         index++;
         norm = new CNeuronBatchNormOCL();
         if(!norm ||
            !norm.Init(0, index, OpenCL, padconv.Neurons(), iBatch, optimization) ||
            !cDecoder[i].Add(norm))
           {
            DeleteObj(norm)
            return false;
           }
         norm.SetActivationFunction(None);
         index++;
        }
      conv = new CNeuronSpikeConvBlock();
      if(!conv ||
         !conv.Init(0, index, OpenCL, window_in, window_in, window_in, units_in,
                    variables, optimization, iBatch) ||
         !cDecoder[i].Add(conv))
        {
         DeleteObj(conv)
         return false;
        }
      index++;

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

      if(i > 0)
        {
         cdc = new CNeuronSpikeCDC();
         if(!cdc ||
            !cdc.Init(0, index, OpenCL, window_in, inside_dimensions, window_in, units_in,
                                                       variables, optimization, iBatch) ||
            !cDecoder[i].Add(cdc))
           {
            DeleteObj(cdc)
            return false;
           }
        }
      index++;
      //---
      window_in = window_out;
      window_out = 2 * window_in;
      units_in = units_out;
      units_out = (units_in + 1) / 2;
      windows[0] = 3 * window_in;
     }

После завершения трёх пирамидальных уровней формируется cDecoder[3]. Этот блок создаёт начальный поток верхнего уровня, инициализированный нулевыми значениями. Используется минимальный CNeuronBaseOCL без активации. Тем самым задаётся нейтральная стартовая точка, от которой нижележащие уровни начинают пошагово уточнять MeshFlow.

  cDecoder[3].Clear();
   cDecoder[3].SetOpenCL(OpenCL);
   CNeuronBaseOCL* neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, window_in * units_in * variables, optimization, iBatch) ||
      !cDecoder[3].Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   neuron.Clear();
//---
   return true;
  }

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

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

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

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

Далее управление переходит к Энкодерам. Обработка организована по уровням пирамиды. На каждом уровне входное представление проходит через все слои соответствующего Энкодера.

   for(uint i = 0; i < cEncoder.Size(); i++)
     {
      for(int l = 0; l < cEncoder[i].Total(); l++)
        {
         curr = cEncoder[i][l];
         if(!curr ||
            !curr.FeedForward(prev))
            return false;
         prev = curr;
        }
      if(prev.Type() != defNeuronSpikeConvBlock)
         return false;
      conv = prev;
      if(!DilatedCorrelation(prev, cCorrelations[i].AsObject(), conv.GetFilters(),
                             conv.GetUnits()*conv.GetVariables()))
         return false;
     }

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

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

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

   CNeuronMultiWindowsConvWPadOCL* padconv = NULL;
   for(int i = int(cDecoder.Size()) - 2; i >= 0; i--)
     {
      if(!cDecoder[i][0] ||
         cDecoder[i][0].Type() != defNeuronMultiWindowsConvWPadOCL)
         return false;
      if(!cEncoder[i][-1] ||
         !cDecoder[i + 1][-1])
         return false;
      //---
      padconv = cDecoder[i][0];
      uint units = padconv.GetUnits() * padconv.GetVariables();
      uint window1 = cEncoder[i][-1].Neurons() / units;
      uint window2 = cDecoder[i][-1].Neurons() / units;
      uint window3 = cCorrelations[i].Neurons() / units;
      if(!Concat(cEncoder[i][-1].getOutput(), cDecoder[i + 1][-1].getOutput(),
                 cCorrelations[i].getOutput(), cConcat[i].getOutput(),
                 window1, window2, window3, units))
         return false;
      prev = cConcat[i].AsObject();

Объединённый поток передаётся в декодер текущего уровня. Все его слои обрабатываются последовательно.

      for(int l = 0; l < cDecoder[i].Total(); l++)
        {
         curr = cDecoder[i][l];
         if(!curr)
            return false;
         if(curr.Type() == defNeuronSpikeCDC)
            if(!SumAndNormilize(cEncoder[i - 1][-1].getOutput(), prev.getOutput(), prev.getOutput(),
                                ((CNeuronSpikeCDC*)curr).GetWindow(), false, 0, 0, 0, 1))
               return false;
         if(!curr.FeedForward(prev))
            return false;
         prev = curr;
        }
     }

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

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

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

   if(!SumAndNormilize(cPrepare[-1].getOutput(), prev.getOutput(), prev.getOutput(),
                       GetWindow(), false, 0, 0, 0, 1))
      return false;
   if(!CNeuronSpikeCDC::feedForward(prev))
      return false;
//---
   return true;
  }

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

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

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

Полный код класса и всех его методов представлен во вложении.


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

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

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

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

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

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

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

Результаты тестирования торговой системы демонстрируют высокую эффективность и устойчивость предложенного подхода. Тест показал положительную чистую прибыль в размере 56,1 % при умеренной максимальной просадке по балансу 11 % и по средствам 15,7 %.

Анализ ключевых коэффициентов подтверждает качество торговой логики: Profit Factor равен 1.61, Recovery Factor — 2.42, а средняя прибыль на сделку составляет 0.79, что свидетельствует о положительном математическом ожидании стратегии.

Торговая активность распределилась равномерно. Из 71 сделки прибыльными оказались 62 %, при этом Short-направление показало более высокую эффективность (69 % выигрышных сделок) по сравнению с Long (54 %). Соотношение средних выигрышей и проигрышей близко к 1:1, а преимущество достигается за счёт высокой доли прибыльных сделок. Серии выигрышных и проигрышных сделок оставались короткими и управляемыми, что обеспечивает комфортный профиль риска.

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


Заключение

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

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

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3485.34 KB)
Знакомство с языком MQL5 (Часть 22): Создание советника для торговли по паттерну 5-0 Знакомство с языком MQL5 (Часть 22): Создание советника для торговли по паттерну 5-0
В этой статье объясняется, как с помощью языка MQL5 обнаружить гармонический паттерн 5-0 и торговать по нему, проверить его с помощью уровней Фибоначчи и отобразить его на графике.
Объединяем 3D-бары, квантовые вычисления и машинное обучение в единую торговую систему Объединяем 3D-бары, квантовые вычисления и машинное обучение в единую торговую систему
Представлена полная интеграция модуля 3D-баров в квантово-усиленную торговую систему для прогнозирования движения валютных пар. Система объединяет стационарные четырёхмерные признаки, квантовый энкодер на 8 кубитах и градиентный бустинг CatBoost с 52+ признаками. Система реализована на Python с использованием MetaTrader 5, Qiskit, CatBoost и опциональной интеграцией LLM Llama 3.2 для интерпретации прогнозов.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Оптимизатор на основе экологического цикла — Ecological Cycle Optimizer (ECO) Оптимизатор на основе экологического цикла — Ecological Cycle Optimizer (ECO)
Алгоритм ECO (Ecological Cycle Optimizer) представляет собой интересную метафору переноса экологического круговорота в область метаэвристической оптимизации. Идея разделения популяции на трофические уровни — продуцентов, травоядных, плотоядных, всеядных и редуцентов — создаёт иерархическую структуру поиска, где каждая группа вносит свой вклад в общий процесс оптимизации.