Нейросети в трейдинге: Двусторонняя адаптивная временная корреляция (Основные компоненты)
Введение
Рынок непостоянен — он то ускоряется, то замирает. Порой он выбрасывает лишние движения, которые сбивают с толку менее гибкие модели, и именно на этом фоне особенно интересно наблюдать за архитектурами, которые умеют сохранять устойчивость, не теряя способности ловить тонкие динамические паттерны. BAT относится как раз к таким решениям. В нём удачно сочетаются две вещи, которые редко встречаются вместе: прозрачность классических методик и гибкость современных подходов к обработке последовательностей. Здесь нет перегруженной математики, нет тяжёлых механизмов внимания, нет огромных массивов параметров. Наоборот, BAT строится вокруг ясной логики преобразований, в которой каждый шаг можно объяснить по-человечески, не опираясь на инженерную терминологию.
Главная идея BAT заключается в том, чтобы рассматривать поток данных как последовательность локальных участков. Модель словно делит сигнал на небольшие отрезки, внимательно изучает поведение каждого участка, а затем собирает общую картину, учитывая вклад всех локальных структур. Такой подход напоминает работу опытного аналитика, который перед тем как принять решение, всегда смотрит на рынок под разными углами. Он сравнивает короткий интервал с длинным, оценивает локальные импульсы, прислушивается к общей тенденции. Именно на этой смеси локальных и глобальных наблюдений базируется устойчивость BAT.
При этом модель не просто склеивает части сигнала, а умеет гибко менять акценты. Если рынок показывает чёткую структуру — больше внимания отдаётся соответствующим блокам, если же движение распадается на шум — модель рационально снижает влияние сомнительных участков. Такое адаптивное поведение формируется внутри простого механизма взвешивания, который не требует сложных вычислений, но позволяет модели оставаться устойчивой даже на турбулентных данных. Это ценное качество для практического трейдинга, где качество решения зачастую важнее эффектной архитектуры.
Тонкость BAT заключается в сочетании лёгкости и информативности. Несмотря на теоретическую глубину, модель не диктует трейдеру требования к мощным GPU, не требует сторонних фреймворков и не заставляет перестраивать рабочую инфраструктуру. Она хорошо чувствует себя в компактных системах. Здесь важна скорость, стабильность и предсказуемость поведения модели. Решения должны работать так же чётко, как хорошо настроенный индикатор: без задержек, без неоправданных расчётов и без неопределённости в поведении.
В предыдущей статье мы подробно разобрали структуру фреймворка BAT: какой принцип заложен в архитектуру и почему фреймворк сочетает простоту с гибкостью. Тогда мы больше уделяли внимание самой идее, смотрели на архитектуру словно на чертёж, который только предстоит оживить. Теперь же наша задача — шагнуть от теории к практике.
Но прежде чем углубляться в реализацию, мы напомним ключевые элементы BAT. В основе модели лежит простая последовательность операций: преобразовать данные, разбить их на небольшие участки, оценить вклад каждого участка и собрать результат в единый сигнал. Это звучит слишком просто, но именно такая прямолинейность создаёт устойчивость. Как опытный мастер, который перед точной работой всегда делает сначала широкие движения, затем уточняет детали, а потом выводит чистый контур. BAT работает последовательно, без скачков и резких переходов. Благодаря такой структурности он меньше подвержен переобучению и лучше переносит разные рыночные режимы.
Модель начинает с подготовительного преобразования данных. Это тот самый момент, когда сырая информация аккуратно приводится к форме, удобной для дальнейшего анализа. Следующий этап — разбиение сигнала на локальные участки, что позволяет модели увидеть внутреннюю структуру временного ряда. Затем происходит оценка этих участков, и каждый из них получает свой вес, отражающий значимость. Итогом становится сглаженное, устойчивое представление сигнала, в котором шумы отходят на второй план, а ключевые паттерны становятся заметнее. Вся эта последовательность шагов формирует тот баланс, который делает BAT легко применимым в реальной торговле.
Трейдеры нередко жалуются, что современные модели усложняют процесс принятия решений, превращая анализ в борьбу с непониманием. В BAT всё иначе. Здесь, даже не вдаваясь в детали математики, интуитивно понятно, почему модель принимает те или иные решения. Сигнал усиливается там, где локальная структура уверенная, и ослабляется там, где рынок даёт сбой. Такой подход близок к классической рыночной логике, где важны качество и форма движения. В этом смысле BAT оказывается ближе к профессиональному трейдерскому мышлению, чем большинство сложных нейросетевых моделей.
Авторская визуализация фреймворка BAT представлена ниже.


Практическую реализацию подходов, предложенных авторами фреймворка BAT, мы начали с самого основания — с представления исходных данных. Нам важно, чтобы модель могла получать рыночную информацию прямо из терминала, без лишней внешней обработки и предварительных манипуляций. Это позволяет сохранить гибкость, сделать архитектуру самодостаточной. Ведь для BAT особенно важно воспринимать рынок как живой поток, меняющийся от момента к моменту.
Чтобы адаптировать ценовые данные к логике временного потока, мы создали структуру событий, где каждая пара соседних состояний формировала миниатюрный сдвиг — тот самый аналог оптического движения. На практике это выглядит просто: модель вычисляет разницу между текущим и предыдущим состоянием, одновременно сохраняя само состояние для следующего шага. Но за этой простотой скрывается важная концепция. Мы научили систему видеть рынок динамически — ощущать направление, скорость и характер движения цен, а не фиксировать статичные числа.
Этот фундаментальный механизм стал первой рабочей деталью нашей будущей модели. Он превратил абстрактную идею в функционирующий контур, который уже способен воспринимать рыночный поток, распутывать его временную структуру и поддерживать непрерывность вычислений.
Опираясь на этот фундамент, мы переходим к следующему витку развития модели. Авторы фреймворка подчёркивают, что для извлечения признаков динамического потока используется энкодер из шести остаточных блоков, построенный по канонам RAFT, с которым мы уже имели удовольствие работать. Это даёт нам редкую роскошь — не проектировать компонент с нуля, а аккуратно перенести проверенный временем энкодер в новую архитектуру и встроить его в общий цикл обработки. Такой шаг не только ускоряет разработку, но и создаёт надёжный мост между уже протестированными решениями и задачами, которые нам предстоит решать при построении модуля BTC.
Модуль двунаправленной корреляции
Следующим этапом нашей работы становится построение модуля двунаправленной временной корреляции (Bidirectional Temporal Correlation — BTC). Здесь мы фактически поднимаемся на новый ярус модели, переходя от статичного восприятия рыночного потока к анализу его внутренней кинематики. Если на предыдущем шаге мы научились извлекать устойчивые пространственные признаки, то теперь наша задача — уловить взаимосвязи между состояниями во времени. Причём не только вперёд, но и назад. Это позволит зафиксировать всю траекторию движения сигнала.
Этот модуль играет роль своеобразного рыночного локатора, который ловит малейшие смещения, синхронизирует фазы движения и позволяет модели уверенно ориентироваться в сложных колебаниях котировок. Именно здесь формируется основа для точной оценки динамики. Корреляции объединяют соседние кадры, выявляют временные зависимости и создают цельную картину того, как развивается поток.
В предложенной авторами фреймворка схеме используется один опорный кадр, а затем вычисляется серия корреляций между его признаками и наборами фреймов, расположенных до и после него во временной последовательности. На бумаге такой подход выглядит элегантно, однако, при переносе в нашу практическую архитектуру, он сразу вызывает целый ряд вопросов, требующих аккуратного инженерного решения.
Прежде всего, мы строим систему, которая работает в режиме непрерывного анализа текущей рыночной ситуации. Модель живёт в реальном времени и наблюдает лишь то, что уже произошло или происходит сейчас. Будущие данные отсутствуют по определению. Поэтому идея рассчитывать корреляции с фреймами после опорного кадра попросту несовместима с реальной эксплуатацией торгового алгоритма.
Кроме того, наш пошаговый режим порождает естественный стек состояний — аккуратную цепочку кадров, которая обновляется с каждым новым тактом. Этот стек идеально подходит для построения базы сравнения с прошлыми состояниями: что было на шаг назад, на два, на десять — всё под рукой. Всё формируется в рабочем процессе без дополнительных операций. Но попытка встроить сюда аналоги будущих кадров неизбежно ломает логику. Мы не можем хранить то, что ещё не произошло. И не можем синтетически генерировать будущие состояния без риска исказить структуру данных.
Есть и ещё один важный нюанс. В наших разработках мы неизменно стремимся к максимальному распараллеливанию вычислений, особенно на уровне GPU-модулей, где каждая микросекунда играет роль. Однако предложенная авторами схема деления последовательности на прошлое и будущее приводит к последовательной обработке двух потоков и фактически удваивает конвейер вычислений. Это не только снижает производительность, но и ухудшает латентность — критический параметр для торговых систем, работающих под нагрузкой реального рынка.
Поэтому, рассматривая оригинальный алгоритм, мы вынуждены искать иной путь — такой, который сохранит его аналитическую силу, но не будет нарушать требований реального времени, структуры данных и параллельного исполнения. И в итоге наше решение оказывается куда проще и элегантнее, чем может показаться на первый взгляд. С целью сохранения идеи двунаправленного анализа без нарушения требований реального времени, мы просто используем в качестве опорного кадра не текущий такт и тем более не гипотетическое будущее, а состояние, расположенное в середине нашего стека. Такой выбор позволяет взглянуть на поток словно из точки равновесия: часть состояний позади, часть — впереди по внутренней логике стека, но все они уже существуют в памяти и доступны модели, поскольку формируются естественным ходом вычислительного конвейера.
По сути, мы временно замораживаем центр локального окна и используем его как стабильную точку отсчёта для вычисления корреляций. Половина кадров стека выступает как исторический контекст, плавно отражающий недавнюю динамику рынка, а другая половина — как свежие, только что полученные состояния, ещё не ставшие текущими в рамках основного цикла, но уже находящиеся в буфере. Это не нарушает принцип работы реального времени, поскольку мы опираемся исключительно на данные, которые фактически существуют в процессе исполнения, и при этом полностью сохраняем двунаправленную структуру анализа, заложенную в оригинальном фреймворке.
Благодаря такому подходу, мы избегаем искусственного разделения последовательности, не нуждаемся в моделировании будущих кадров и не теряем возможности распараллеливать вычисления. Центр стека становится естественным аналогом того самого опорного фрейма, который авторы BAT используют в своих экспериментах. Но теперь он выбран так, что идеально вписывается в торговую модель с непрерывным обновлением данных.
Сразу стоит обратить внимание на ещё один нюанс, который часто остаётся за рамками теоретических описаний. Корреляции, формируемые на выходе модуля BTC, не остаются самоцелью — они становятся входом для следующего этапа обработки. А именно для MotionEncoder. И вот тут возникает важная деталь. Архитектура MotionEncoder в оригинальной работе авторов BAT нигде не раскрыта, что создаёт вакуум для практической реализации.
Вместо того чтобы изобретать решение с нуля, мы решили опереться на проверенный подход из фреймворка TMA, где подобные задачи уже успешно решались. В TMA MotionEncoder построен так, чтобы аккуратно преобразовывать поток корреляций в компактное, информативное представление движения, сохраняя локальные и глобальные зависимости. Этот приём идеально ложится на наш текущий конвейер: BTC формирует двунаправленные корреляции, а MotionEncoder аккуратно кодирует их во внутренние признаки, которые затем могут быть использованы другими модулями BAT.
Таким образом, мы интегрируем готовое, проверенное временем решение в новую архитектуру, обеспечивая согласованность модулей и сохранение производительности. Это одновременно ускоряет разработку и даёт уверенность, что структура обработки динамического потока остаётся стабильной и надёжной.
Теперь, когда мы определили концепцию выбора опорного кадра и организацию стека состояний, логично показать, как эта логика воплощается в коде. В нашем проекте она реализована в виде нового объекта CNeuronBiDirectCorrelation, который наследуется от CNeuronTMAMFE и служит ядром модуля двунаправленной корреляции.
class CNeuronBiDirectCorrelation : public CNeuronTMAMFE { protected: CNeuronBatchNormOCL cInpNorm; CNeuronBaseOCL cRefFrame; CNeuronTransposeOCL cRefFrameT; CNeuronMultiWindowsConvOCL cWarp; CNeuronBatchNormOCL cWarpNorm; CNeuronBaseOCL cCorrelation; CNeuronBaseOCL cConcat; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronBiDirectCorrelation(void) {}; ~CNeuronBiDirectCorrelation(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint window_out, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronBiDirectCorrelation; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; //--- virtual void SetOpenCL(COpenCLMy *obj) override; };
Внутри объекта выстраивается аккуратная и логичная структура, в которой каждый компонент занимает своё место в вычислительном конвейере и готов к работе в условиях реального времени. Архитектура внутренних компонентов задается в методе инициализации.
bool CNeuronBiDirectCorrelation::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint window_out, ENUM_OPTIMIZATION optimization_type, uint batch) { { uint windows[] = {dimension, 1}; if(!CNeuronTMAMFE::Init(numOutputs, myIndex, open_cl, units * variables, windows, window_out, optimization_type, batch)) return false; }
На старте вызывается одноименный метод родительского класса, который задаёт базовую структуру нейрона, включая размер окна, количество переменных и выходов, а также параметры оптимизации.
Далее последовательно инициализируются отдельные блоки, знакомые нам по предыдущим этапам работы над алгоритмом. Сначала инициализируется нормализация входного сигнала cInpNorm, без активационной функции, чтобы сохранить чистый поток данных.
uint index = 0; if(!cInpNorm.Init(0, index, OpenCL, variables * dimension * units, iBatch, optimization)) return false; cInpNorm.SetActivationFunction(None);
Затем создаётся опорный кадр cRefFrame и его транспонированная версия cRefFrameT, также без активаций. Это позволяет использовать их в вычислениях корреляций без искажений.
index++; if(!cRefFrame.Init(0, index, OpenCL, variables * dimension, optimization, iBatch)) return false; cRefFrame.SetActivationFunction(None); index++; if(!cRefFrameT.Init(0, index, OpenCL, variables, dimension, optimization, iBatch)) return false; cRefFrameT.SetActivationFunction(None);
Следующий ключевой компонент — cWarp — отвечает за блочную свёртку и вычисление локальных смещений. И здесь важно подчеркнуть одну тонкость. Каждый фрейм в стеке находится на разном временном расстоянии от опорного, поэтому простое сравнение их признаков без учёта этого расстояния может дать не совсем репрезентативные результаты. В оригинальной реализации BAT авторы предполагают линейное движение и просто смещают фреймы пропорционально времени.
Мы же пошли более гибким путём. Используя объект многооконной свёртки, мы позволяем модели самостоятельно обучать параметры смещения для каждого состояния в пределах анализируемого окна истории. Такой подход даёт возможность точно учитывать индивидуальные особенности динамики каждого сегмента временного ряда, а не полагаться на заранее заданную линейную аппроксимацию. В результате cWarp становится полноценным механизмом адаптивного анализа локальных смещений, способным выявлять реальные зависимости между состояниями рынка и формировать более информативные признаки для последующих модулей.
{
uint windows[];
if(ArrayResize(windows, units) < (int)units)
return false;
ArrayFill(windows, 0, units, dimension);
index++;
if(!cWarp.Init(0, index, OpenCL, windows, dimension, variables, 1, optimization, iBatch))
return false;
cWarp.SetActivationFunction(None);
}
После этого подключается cWarpNorm — нормализация результатов свёртки, которая стабилизирует выход и делает последующие вычисления более предсказуемыми.
index++; if(!cWarpNorm.Init(0, index, OpenCL, cWarp.Neurons(), iBatch, optimization)) return false; cWarpNorm.SetActivationFunction(None);
Наконец, создаются блоки cCorrelation и cConcat. cCorrelation аккумулирует вычисленные корреляции, формируя единый вектор признаков.
index++; if(!cCorrelation.Init(0, index, OpenCL, units * variables, optimization, iBatch)) return false; cCorrelation.SetActivationFunction(None);
А cConcat объединяет его с исходными данными для передачи в MotionEncoder, роль которого выполняет родительский класс.
index++; if(!cConcat.Init(0, index, OpenCL, (dimension + 1)*units * variables, optimization, iBatch)) return false; cConcat.SetActivationFunction(None); //--- return true; }
Вся эта структура гарантирует, что объект готов к работе сразу после завершения работы метода инициализации. Все компоненты выстроены в логическом порядке, соблюдая поток данных от нормализации входа через опорный кадр и свёртку до финального объединения признаков. Это позволяет не только точно реализовать концепцию двунаправленной корреляции, но и эффективно интегрировать модуль в общий конвейер.
Метод feedForward аккуратно воплощает логику прямого прохода модуля двунаправленной корреляции и показывает, как поток данных преобразуется шаг за шагом.
bool CNeuronBiDirectCorrelation::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cInpNorm.FeedForward(NeuronOCL)) return false;
Алгоритм начинается с нормализации исходных данных средствами компонента cInpNorm. На этом этапе все анализируемые признаки приводятся к единообразной шкале. Это важно, потому что финансовые данные могут сильно различаться по амплитуде: котировки разных инструментов, объёмы сделок или технические индикаторы не могут быть обработаны как есть. Нормализация гарантирует, что последующие вычисления корреляций и свёртки будут корректными и устойчивыми.
После этого происходит выделение опорного кадра (cRefFrame) и создание его транспонированной версии (cRefFrameT). Используется функция DeConcat, которая аккуратно разбивает конкатенированный вектор на сегменты. В частности, определяется позиция опорного кадра в стеке (ref_position), а все предыдущие и последующие состояния выравниваются относительно него.
uint dimension = cRefFrameT.GetWindow(); uint variables = cRefFrameT.GetCount(); uint units = cCorrelation.Neurons() / variables; uint ref_position = (units + 1) / 2; if(!DeConcat(cConcat.getOutput(), cRefFrame.getOutput(), cConcat.getOutput(), cInpNorm.getOutput(), (ref_position - 1)*dimension, dimension, (units - ref_position)*dimension, variables)) return false; if(!cRefFrameT.FeedForward(cRefFrame.AsObject())) return false;
Затем выполняется прямой проход транспонированного опорного кадра. Это позволяет подготовить матрицу признаков к умножению, чтобы корректно вычислять локальные корреляции с каждым из соседних фреймов.
Следующий шаг — применение многооконной свёртки. Именно здесь происходит вычисление локальных смещений. Модель учитывает, что каждый фрейм находится на разном временном расстоянии от опорного, и обучает параметры смещения для каждого состояния в рамках анализируемого окна истории.
if(!cWarp.FeedForward(cInpNorm.AsObject())) return false;
Это ключевое отличие от оригинальной линейной аппроксимации BAT. Мы позволяем модели адаптивно подстраиваться под динамику рынка, что повышает точность извлечения признаков.
После свёртки результат нормализуется. Это стабилизирует вычисления и предотвращает чрезмерное влияние выбросов, что особенно важно на финансовых данных с резкими движениями цены.
if(!cWarpNorm.FeedForward(cWarp.AsObject())) return false;
Далее выполняется матричное умножение нормализованных признаков и транспонированного опорного кадра. На выходе формируется тензор cCorrelation, который аккумулирует локальные корреляции между опорным кадром и каждым состоянием стека.
if(!MatMul(cWarpNorm.getOutput(), cRefFrameT.getOutput(), cCorrelation.getOutput(), units, dimension, 1, variables, true)) return false;
Этот шаг превращает набор отдельных локальных смещений в единый информативный сигнал, готовый для последующего анализа.
Следующий этап — конкатенация входных данных с корреляциями. Это позволяет объединить исходный сигнал с вычисленными признаками движения, создавая полное представление о динамике рынка для следующего слоя.
if(!Concat(cInpNorm.getOutput(), cCorrelation.getOutput(), cConcat.getOutput(), dimension, 1, units * variables)) return false;
Такой подход сохраняет исходную информацию и одновременно обогащает её дополнительными характеристиками локального движения.
Наконец, объединённый тензор передаётся в метод родительского класса, который генерирует признаки движения и интегрирует их в общий конвейер BAT. Этот шаг гарантирует, что выход модуля двунаправленной корреляции корректно передан для последующих преобразований.
После того как мы подробно разобрали организацию прямого прохода, логично перейти к следующему этапу — работе с ошибками. Или, другими словами, к обратному распространению градиентов. Если прямой проход отвечает за формирование признаков, корреляций и локальных смещений, то метод calcInputGradients берёт на себя задачу оценить вклад каждого компонента в итоговую ошибку модели и корректно распределить её обратно по всем слоям.
bool CNeuronBiDirectCorrelation::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Сначала проверяется валидность полученного указателя на объект исходных данных. Если объект отсутствует, метод сразу возвращает false. Это стандартная проверка корректности входных данных и защиты от ошибок исполнения.
Далее определяются основные параметры стека: размер окна (dimension), количество переменных (variables), число элементов в последовательности (units) и позиция опорного кадра (ref_position). Эти значения используются для правильного распределения градиентов по всему стеку состояний и соответствия структуры данных, выстроенной в прямом проходе.
uint dimension = cRefFrameT.GetWindow(); uint variables = cRefFrameT.GetCount(); uint units = cCorrelation.Neurons() / variables; uint ref_position = (units + 1) / 2;
Первым шагом градиенты распределяются через компоненты родительского класса. Здесь MotionEncoder уже выполняет свою функцию: он получает объединённый вектор признаков, кодирует движение и распределяет ошибку по входным признакам.
if(!CNeuronTMAMFE::calcInputGradients(cConcat.AsObject())) return false;
После этого вызывается DeConcat, который разделяет градиенты объединённого вектора cConcat на составляющие — нормализованные исходные данные cInpNorm и корреляции cCorrelation.
if(!DeConcat(cInpNorm.getPrevOutput(), cCorrelation.getGradient(), cConcat.getGradient(), dimension, 1, units * variables)) return false; if(cInpNorm.Activation() != None) if(!DeActivation(cInpNorm.getOutput(), cInpNorm.getPrevOutput(), cInpNorm.getPrevOutput(), cInpNorm.Activation())) return false; Deactivation(cCorrelation)
Этот шаг зеркально повторяет операцию Concat из прямого прохода, обеспечивая корректную передачу ошибки каждому компоненту.
Если на входе применялась активационная функция в cInpNorm, вызывается DeActivation, которая корректирует градиенты с учётом производной функции активации. Аналогичная операция выполняется для cCorrelation, чтобы правильно распределить ошибку через линейные преобразования и свёртку.
Затем выполняется MatMulGrad, который рассчитывает градиенты матричного умножения между нормализованными признаками свёртки (cWarpNorm) и транспонированным опорным кадром (cRefFrameT).
if(!MatMulGrad(cWarpNorm.getOutput(), cWarpNorm.getGradient(), cRefFrameT.getOutput(), cRefFrameT.getGradient(), cCorrelation.getOutput(), units, dimension, 1, variables, true)) return false; Deactivation(cWarpNorm) Deactivation(cRefFrameT)
Градиенты распределяются обратно по обоим объектам, отражая влияние каждого элемента на итоговую ошибку. После этого вызываются Deactivation для cWarpNorm и cRefFrameT, чтобы учесть возможные нелинейности.
Следующий этап — обратное распространение через cWarp, где градиенты локальных смещений и свёртки передаются обратно на входы.
if(!cWarp.CalcHiddenGradients(cWarpNorm.AsObject())) return false;
После этого аналогично рассчитываются градиенты для нормализатора cInpNorm и выполняется суммирование с ранее полученными данными.
if(!cInpNorm.CalcHiddenGradients(cWarp.AsObject())) return false; if(!SumAndNormilize(cInpNorm.getPrevOutput(), cInpNorm.getGradient(), cInpNorm.getGradient(), dimension, false, 0, 0, 0, 1)) return false;
Наконец, градиенты передаются обратно объекту исходных данных.
if(!NeuronOCL.CalcHiddenGradients(cInpNorm.AsObject())) return false; //--- return true; }
Вся структура построена так, чтобы вычисления оставались максимально параллельными, а логика обработки стека состояний — непрерывной. CNeuronBiDirectCorrelation аккуратно воплощает концепцию двунаправленной корреляции. Опорный кадр берётся из центра стека. Прошлые и свежие состояния анализируются в рамках единого потока. И при этом соблюдается требование работы в режиме реального времени без обращения к будущим данным.
Адаптивная агрегация динамики
Следующим этапом нашей работы становится построение модуля SATMA — ключевого компонента, который позволит модели более глубоко анализировать временные зависимости и уточнять динамику рыночного потока. Если предыдущий модуль двунаправленной корреляции формировал локальные смещения и выявлял взаимосвязи между соседними состояниями, то SATMA расширяет эту идею на более широкие временные горизонты, аккумулируя информацию о тенденциях и структурных закономерностях рынка.
Основная цель SATMA — эффективно интегрировать признаки движения и преобразовать их в форму, удобную для прогнозирования и дальнейшего использования в стратегии. Модуль работает как сетевой фильтр времени, выявляя скрытые корреляции и паттерны, которые не очевидны при локальном анализе отдельных фреймов.
Архитектурно SATMA строится на основе адаптивного механизма внимания и многослойных преобразований, что позволяет ему учитывать как недавние изменения, так и более длительные тенденции. Важной особенностью является то, что все операции выполняются с сохранением возможности параллельной обработки, что критично для работы в режиме реального времени на рыночных данных.
В рамках данного проекта модуль SATMA реализован в виде нового объекта CNeuronSATMA, который наследуется от CNeuronBaseOCL и аккуратно интегрируется в общий конвейер BAT. Его структура построена так, чтобы эффективно работать с признаками движения и формировать информативное представление временного потока.
class CNeuronSATMA : public CNeuronBaseOCL { protected: CLayer cWarp; CNeuronBaseOCL cRefFrame; CNeuronRelativeCrossAttention cCrossAttention; CNeuronConvOCL cSpatialAttention; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronSATMA(void) {}; ~CNeuronSATMA(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint window_key, uint heads, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronSATMA; } //--- virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual void SetOpenCL(COpenCLMy *obj) override; virtual void SetActivationFunction(ENUM_ACTIVATION value) override { }; };
Объект включает несколько ключевых компонентов:
- cWarp отвечает за подготовку локальных смещений и их первичное преобразование,
- cRefFrame служит опорным кадром для выравнивания временных зависимостей,
- cCrossAttention реализует механизм кросс-внимания, позволяя модулю выделять наиболее значимые временные и пространственные связи между состояниями.
- cSpatialAttention добавляет слой пространственного внимания, который усиливает значимые признаки и подавляет шумовые сигналы, создавая более чёткое и стабильное представление динамики рынка.
Инициализация объекта строится по принципу аккуратного пошагового выстраивания всех компонентов модуля, чтобы обеспечить корректную работу прямого и обратного проходов.
bool CNeuronSATMA::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint window_key, uint heads, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, dimension * units * variables, optimization_type, batch)) return false; activation = None;
Сначала вызывается одноименный метод родительского класса, который задаёт базовые параметры нейрона. При этом активация модуля устанавливается в None, поскольку все преобразования внутри SATMA выполняются через нормализацию и внимание. Отдельная нелинейность на уровне объекта не требуется.
Далее начинается формирование проекции cWarp. Сначала создаётся объект мультиоконной свертки, который отвечает за многослойную свёртку с адаптивными окнами
cWarp.Clear(); cWarp.SetOpenCL(OpenCL); CNeuronMultiWindowsConvOCL* mwconv = NULL; CNeuronBatchNormOCL* norm = NULL; CNeuronBaseOCL* neuron = NULL; uint index = 0; { uint windows[]; if(ArrayResize(windows, units) < (int)units) return false; ArrayFill(windows, 0, units, dimension); mwconv = new CNeuronMultiWindowsConvOCL(); if(!mwconv || !mwconv.Init(0, index, OpenCL, windows, dimension, variables, 1, optimization, iBatch) || !cWarp.Add(mwconv)) { DeleteObj(mwconv) return false; } mwconv.SetActivationFunction(None); }
Размеры окон соответствуют количеству элементов в анализируемой последовательности, что позволяет эффективно учитывать локальные смещения и динамику каждого состояния. После инициализации свёртки объект добавляется в динамический массив cWarp.
Следующим шагом добавляется нормализатор, который стабилизирует выходы свёртки, обеспечивая равномерное распределение значений и предотвращая чрезмерное влияние выбросов.
index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, mwconv.Neurons(), iBatch, optimization) || !cWarp.Add(norm)) { DeleteObj(norm) return false; } norm.SetActivationFunction(None);
Нормализатор также добавляется в cWarp и работает без активации.
После подготовки блока проекции создаётся опорный кадр cRefFrame, который используется для выравнивания временных зависимостей между состояниями.
index++; if(!cRefFrame.Init(0, index, OpenCL, dimension * variables, optimization, iBatch)) return false; cRefFrame.SetActivationFunction(None);
Он инициализируется стандартным образом и не имеет активации, так как предназначен исключительно для передачи признаков в механизм внимания.
Далее инициализируется cCrossAttention, реализующий относительное внимание. Он связывает признаки каждого состояния с ключевыми позициями окна, позволяя модулю SATMA выделять наиболее значимые временные и пространственные зависимости между кадрами.
index++; if(!cCrossAttention.Init(0, index, OpenCL, dimension, window_key, units * variables, heads, dimension, units * variables, optimization, iBatch)) return false;
Наконец, создаётся cSpatialAttention, слой пространственного внимания, который усиливает значимые признаки и подавляет шумовые сигналы. На этом объекте устанавливается активационная функция SIGMOID, чтобы получить значения на выходе объекта в диапазоне [0,1].
index++; if(!cSpatialAttention.Init(0, index, OpenCL, dimension, dimension, dimension, units, variables, optimization, iBatch)) return false; cSpatialAttention.SetActivationFunction(SIGMOID); //--- return true; }
Таким образом, метод инициализации обеспечивает пошаговую сборку всех компонентов SATMA, аккуратно настраивая свёртку, нормализацию и модули внимания.
После успешной инициализации объект готов к выполнению прямого и обратного прохода, обработке рыночного потока и интеграции признаков движения в общую архитектуру.
Метод feedForward реализует прямой проход модуля SATMA и демонстрирует, как поэтапно обрабатываются признаки движения, извлечённые из модуля двунаправленной корреляции.
bool CNeuronSATMA::feedForward(CNeuronBaseOCL *NeuronOCL) { CNeuronBaseOCL* prev = NeuronOCL; CNeuronBaseOCL* curr = NULL; for(int i = 0; i < cWarp.Total(); i++) { curr = cWarp[i]; if(!curr || !curr.FeedForward(prev)) return false; prev = curr; }
Сначала используется цикл по всем компонентам cWarp. На каждом шаге текущий блок (curr) получает вход от предыдущего (prev) и выполняет прямой проход. Таким образом, локальные смещения и признаки движения аккумулируются последовательно, обеспечивая адаптивную обработку временного окна и подготовку данных для механизма внимания. После завершения цикла prev содержит финальный выход cWarp, готовый к дальнейшей обработке.
Далее осуществляется пространственное внимание. Оно усиливает значимые признаки и подавляет шум, создавая более чистое и информативное представление динамики рынка.
if(!cSpatialAttention.FeedForward(NeuronOCL)) return false;
Следующий шаг — подготовка опорного кадра. Используется DeConcat, который аккуратно разделяет объединённый вектор, полученный от предыдущего блока, выделяя признаки, соответствующие cRefFrame.
uint dimension = cSpatialAttention.GetWindow(); uint units = cSpatialAttention.GetUnits(); uint variables = cSpatialAttention.GetVariables(); if(!DeConcat(cRefFrame.getOutput(), prev.getPrevOutput(), NeuronOCL.getOutput(), dimension, dimension * (units - 1), variables)) return false;
Важно подчеркнуть ключевое отличие SATMA от модуля двунаправленной корреляции. В качестве опорного кадра здесь используется последнее состояние окружающей среды, а не средний кадр стека. Такое решение продиктовано самой природой прогнозного анализа. Для того чтобы корректно оценить будущее движение рынка, необходимо учитывать динамику потока именно относительно текущего состояния, отражающего последние изменения цен, объёма и других рыночных индикаторов. Это позволяет формировать прогнозные признаки, которые максимально релевантны для анализа краткосрочных и среднесрочных движений, повышая точность прогнозов и информативность выходного вектора.
Затем выполняется критически важная операция: динамика всего временного потока накладывается на опорный кадр. Фактически, SumVecMatrix объединяет накопленные признаки из предыдущих состояний с текущим положением рынка, создавая основу для анализа движения в контексте последнего состояния.
if(!SumVecMatrix(cRefFrame.getOutput(), prev.getOutput(), prev.getOutput(), dimension, 0, 0, 0, 1)) return false;
Благодаря этому SATMA получает возможность видеть не просто последовательность изменений, а динамику относительно текущего момента. Это позволяет модели выявлять актуальные паттерны, оценивать тенденции и формировать более точные прогнозные признаки. Такой подход превращает временной поток в информативное представление, готовое для последующего анализа через механизм относительного внимания.
После этого управление передается cCrossAttention, который реализует относительное внимание. Он соединяет признаки динамики анализируемых состояний с ключевыми позициями окна, позволяя SATMA выявлять наиболее значимые временные и пространственные зависимости.
if(!cCrossAttention.FeedForward(NeuronOCL, prev.getOutput())) return false;
Важная особенность — механизм внимания адаптивно распределяет вес между состояниями, выделяя наиболее информативные сегменты временного ряда.
На финальном этапе выполняется поэлементное умножение выходов cSpatialAttention и cCrossAttention. Эта операция объединяет локальную фильтрацию с глобальной информацией внимания, создавая окончательный выходной вектор, который аккуратно интегрирует признаки движения для последующих слоёв модели.
if(!ElementMult(cSpatialAttention.getOutput(), cCrossAttention.getOutput(), Output)) return false; //--- return true; }
Таким образом, CNeuronSATMA аккуратно объединяет подготовку локальных смещений, кросс-внимание и пространственный фильтр, создавая мощный инструмент для анализа временных и пространственных зависимостей в рыночном потоке. Этот объект становится логическим продолжением модуля двунаправленной корреляции, позволяя аккуратно интегрировать локальные признаки в более глобальное представление движения рынка.
Сегодня мы проделали серьёзную работу и можем позволить себе небольшой перерыв. Пусть полученная информация уложится в голове, а мысли о новых решениях немного отдохнут. В следующей статье, набравшись сил и свежих идей, мы продолжим и доведём начатое дело до логического завершения.
Заключение
Фреймворк BAT с нашими адаптациями открывает широкие возможности для анализа и прогнозирования рыночного потока. Его модульная архитектура, включающая двунаправленную корреляцию и SATMA, позволяет эффективно выявлять локальные смещения и глобальные временные зависимости, аккумулируя ключевые признаки движения.
Особенность подхода заключается в интеграции обработки текущего состояния рынка с историческим контекстом, что потенциально обеспечивает высокую точность прогнозов и устойчивость к шуму. Использование проверенных решений, таких как MotionEncoder, вместе с адаптивными свёртками и механизмами внимания, создаёт основу для автономной работы фреймворка и ускоряет вычислительные процессы.
Модель формирует информативное прогнозное представление динамики рынка, которое может быть интегрировано в алгоритмические системы после завершения окончательной доработки и тестирования.
Ссылки
- BAT: Learning Event-based Optical Flow with Bidirectional Adaptive Temporal Correlation
- Другие статьи серии
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн обучения моделей |
| 3 | Test.mq5 | Советник | Советник для тестирования модели |
| 4 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
| 5 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
| 6 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL-программы |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Детерминированный алгоритм дендритных клеток — Deterministic Dendritic Cell Algorithm (dDCA)
Знакомство с языком MQL5 (Часть 18): Введение в паттерн "Волны Вульфа"
Знакомство с MQL5 (Часть 19): Автоматизация обнаружения волн Вульфа
Анализ нескольких символов с помощью Python и MQL5 (Часть 3): Треугольные курсы валют
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования