Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (модуль E-TROF)
Введение
Финансовый рынок не терпит упрощений. Он может выглядеть как последовательность цен, но на деле представляет собой поток событий. Каждое из них меняет не только текущую картину, но и саму динамику движения. Именно здесь большинство моделей терпят поражение. Они стараются предсказать следующий шаг, опираясь на прошлые значения, и неизбежно запаздывают. Цена уходит вперёд, режим меняется, а модель остаётся в прошлом. Поэтому мы сознательно отказались от прямого прогнозирования и перешли к представлению рынка как системы состояний, эволюционирующей под действием событийных возмущений. Этот переход оказался принципиальным, он изменил не только архитектуру, но и саму логику работы модели с данными.
В основе фреймворка, предложенного в работе "Perturbed State Space Feature Encoders for Optical Flow with Event Cameras" лежит простая, но редко реализуемая идея — рынок нельзя угадывать, его нужно интерпретировать. Для этого необходимо научиться фиксировать не цену как таковую, а движение; не бар, а процесс; не шум, а структуру изменений. Такой подход требует иной архитектуры. Здесь анализируемые данные не аккумулируются в жёсткие окна, а рассматриваются как непрерывный поток событий. Каждое новое событие обновляет внутреннее состояние модели, а не просто добавляет очередную строку в массив истории. В результате система перестаёт быть реактивной и начинает вести себя как наблюдатель, который постоянно уточняет своё представление о происходящем.
Одним из ключевых преимуществ фреймворка является его устойчивость к реальным данным. Пропуски, разрывы, нерегулярность, изменение плотности событий — всё это не разрушает модель, а становится частью её входного сигнала. Отсутствие движения тоже несёт информацию, и архитектура учитывает это на уровне обновления состояния. Благодаря этому модель сохраняет стабильность там, где классические решения начинают деградировать. Для практического трейдера это означает не только более спокойное поведение стратегии, но и предсказуемость её ошибок. Модель не сыпется неожиданно, а плавно адаптируется к изменению режима рынка.
Второе важное преимущество — контекстность. В предлагаемом подходе сигнал никогда не интерпретируется в отрыве от текущего состояния. Один и тот же импульс может означать начало движения, его продолжение или истощение — и модель различает эти ситуации, потому что они отражаются в её внутреннем состоянии. Контекст встроен в динамику, а не добавлен сверху в виде дополнительных признаков. Именно это позволяет системе избегать хаотичных входов и сосредотачиваться на действительно значимых изменениях рыночной структуры.
Третье преимущество связано с тем, как фреймворк работает с возмущениями. Традиционные модели шум стараются убрать, здесь же он используется как источник информации. Микроотклонения, которые обычно сглаживаются фильтрами, становятся триггерами обновления состояния. Это позволяет фиксировать смену режима раньше, чем она проявится в цене. Модель начинает чувствовать рынок, а не просто следовать за ним. Именно это качество делает архитектуру особенно ценной в условиях высокой событийной плотности и на малых таймфреймах, где классические индикаторы теряют смысл.
С инженерной точки зрения фреймворк спроектирован как прикладной инструмент. Архитектура модульная, прозрачная и управляемая. Каждый блок имеет чёткую роль, а вся система в целом легко масштабируется и адаптируется под различные торговые задачи. Это рабочая конструкция, которую можно тестировать, модифицировать и развивать без разрушения основы.


Центральным элементом этой архитектуры является модуль E-TROF. Он реализует ту самую связь между потоком событий и внутренним состоянием модели, без которой вся система оставалась бы лишь набором идей. В авторской работе, посвящённой обработке данных событийных камер, E-TROF служит механизмом формирования устойчивого представления движения на основе триплетов событийных состояний. Три последовательных фрагмента потока обрабатываются совместно, что позволяет захватить не только локальное изменение, но и его направление, контекст и временную структуру. Для финансовых данных это означает возможность анализировать движение как непрерывный процесс, а не как набор несвязанных баров.
В основе E-TROF лежит P-SSE — возмущённый кодировщик пространства состояний. Он обновляет внутреннее представление через контролируемое возмущение матрицы перехода, что делает модель устойчивой к шуму и повышает её способность к обобщению. Такое решение оказалось неожиданно эффективным для рынков, где слабые сигналы часто важнее сильных, а истинные изменения начинаются с малых отклонений. P-SSE позволяет аккуратно встроить эти отклонения в динамику состояния, не разрушая общую структуру модели.
В предыдущих работах мы уже заложили фундамент практической реализации модели. И сегодня поднимаемся на уровень выше — переходим к адаптации модуля E-TROF под финансовые временные ряды.
Модуль E-TROF
Перед тем как переходить к практической реализации, необходимо сделать важную оговорку. В авторской версии фреймворка модуль E-TROF предназначен для анализа триплетов последовательных фреймов и формирует два прогнозных потока — прямой и обратный, относительно центрального кадра. Такой подход полностью оправдан в задачах компьютерного зрения, где между соседними кадрами сохраняется высокая плотность информации.
В финансовых временных рядах ситуация иная. Три последних тика или бара, как правило, не содержат достаточного контекста, чтобы на их основе можно было построить даже краткосрочное торговое решение. Рынок требует большего горизонта наблюдения, иначе модель будет реагировать на шум, а не на структуру движения.
Следовательно, при переносе архитектуры в финансовый контекст окно анализа необходимо существенно расширить. Но здесь важно не впасть в другую крайность — простое увеличение длины последовательности разрушает локальную чувствительность и делает модель инертной. Поэтому расширение контекста должно быть реализовано так, чтобы сохранить событийную природу обработки и при этом дать модели достаточно информации для интерпретации движения.
Интересно, что сами авторы фреймворка фактически подсказывают решение этой проблемы. В оригинальной работе они используют анализ перекрывающихся триплетов, тем самым расширяя эффективный временной контекст до пяти фреймов без потери локальной структуры. Это решение выглядит особенно элегантно: модель по-прежнему оперирует триплетами, но видит значительно более широкий временной горизонт, чем кажется на первый взгляд.
В финансовых данных такой приём оказывается ещё более ценным. Перекрывающиеся триплеты позволяют захватывать микро-динамику внутри более длинного фрагмента рынка, не разрушая событийную природу потока. Мы получаем расширенное окно анализа, но сохраняем локальную чувствительность к изменениям состояния. Это принципиально важно для построения торговых стратегий. Решение принимается на основании формирования контекста, внутри которого события приобретают смысл.
Однако при адаптации модуля E-TROF к финансовым данным необходимо учитывать ещё один принципиальный момент. Мы строим модель с прицелом на использование в онлайн-торговле. А значит каждое архитектурное решение должно быть не только корректным, но и продуктивным с точки зрения вычислений. В реальном рынке данные поступают непрерывно, и каждый новый тик или бар лишь дополняет уже существующий временной ряд, смещая его по временной оси. С точки зрения информации в этом нет революции — меняется лишь край последовательности, тогда как основная часть контекста остаётся прежней.
В такой ситуации постоянный повторный анализ перекрывающихся триплетов приводит к очевидной проблеме: значительная часть вычислений дублируется, не добавляя новой информации. Модель снова и снова пересчитывает одни и те же фрагменты истории просто потому, что формально окно анализа сместилось на один шаг. Для офлайн-обработки это допустимо. Для онлайн-торговли — нет. Избыточные операции напрямую превращаются в задержки, рост нагрузки и снижение стабильности всей системы.
Поэтому здесь требуется иное, более экономное решение. Оно лежит на поверхности, если смотреть на задачу не как на обработку окон, а как на обновление состояния. На каждом новом шаге нам действительно нужен только один новый анализ — анализ последнего триплета, сформированного из самых свежих событий. Всё остальное уже было обработано ранее и не требует пересчёта. Если результат работы E-TROF рассматривать как элемент временного контекста, его можно аккуратно добавить в стек, который и будет представлять собой расширенное окно анализа.
Такой подход позволяет сохранить все преимущества перекрывающихся триплетов, но избавляет систему от дублирования вычислений. Модель обновляется шаг за шагом, в такт рынку. Глубина анализа при этом легко регулируется размером стека — без изменения архитектуры модуля и без вмешательства в общую структуру фреймворка. Нужно больше контекста — увеличиваем стек. Нужно быстрее реагировать — уменьшаем его. Архитектура остаётся неизменной, меняется только горизонт восприятия.
Это решение особенно ценно для практической торговли, где важна не максимальная сложность модели, а её устойчивость и предсказуемость. Стек превращает E-TROF из тяжёлого аналитического блока в лёгкий, потоковый модуль, способный работать в режиме реального времени, не теряя чувствительности к изменениям рынка.
С этого момента архитектура E-TROF окончательно приобретает прикладной характер. Мы перестаём мыслить окнами и начинаем мыслить состояниями.
Практическая реализация предложенного подхода оформлена в виде отдельного нейронного класса CNeuronETROF, который наследуется от CNeuronAddToStack. Это наследование здесь не случайно и не декоративно. Оно напрямую отражает принятую ранее идеологию стековой обработки. Модуль E-TROF в нашей реализации перестаёт быть оконным анализатором и превращается в потоковый элемент, который на каждом новом шаге формирует очередной фрагмент контекста и добавляет его в стек, не пересчитывая уже обработанную историю. Тем самым архитектурное решение сразу закрепляется на уровне базового класса, а не прячется в логике отдельных функций.
class CNeuronETROF : public CNeuronAddToStack { protected: CNeuronPSSE cPSSE; CNeuronBaseOCL cRefFrame; CNeuronTransposeOCL cRefFrameT; CNeuronBaseOCL cForwardBackward; CNeuronBaseOCL cCorrelation; CLayer cMixFusion; CNeuronSpikeSuperKernelBlock cContext; CLayer cMotionEncoder; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronETROF(void) {}; ~CNeuronETROF(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint stack_size, uint dimension_in, uint dimension_out, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronETROF; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual void SetOpenCL(COpenCLMy *obj) override; virtual void TrainMode(bool flag) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual bool Clear(void) override; };
Внутренняя структура класса практически полностью повторяет концептуальную схему, описанную в авторской работе, но адаптирована под событийную природу финансовых данных и требования онлайн-обработки. Центральным элементом остаётся CNeuronPSSE, отвечающий за кодирование возмущённого состояния. Именно он обеспечивает устойчивость обновления и отделяет структурную динамику рынка от шумовой компоненты. Важно отметить, что P-SSE здесь не работает изолированно — он встроен в цепочку, где каждый следующий блок уточняет и обогащает представление движения.
Роль опорного кадра выполняет объект cRefFrame. Его транспонированная форма, необходимая для корреляционных операций, создается в cRefFrameT. Это решение напрямую перекликается с авторской архитектурой E-TROF, где центральный фрейм служит точкой отсчёта для оценки прямого и обратного движения. В финансовом контексте такой подход позволяет интерпретировать текущий бар или тик не как точку, а как центр локального фрагмента динамики.
Объект cForwardBackward отвечает за формирование двух направлений движения относительно центрального состояния. Несмотря на то, что в рынке нет пиксельного оптического потока, сама идея двунаправленного анализа оказывается удивительно продуктивной. Модель начинает различать импульсы, которые формируют движение, и те, которые его гасят. Это существенно повышает качество контекстного представления и делает обновление состояния более осмысленным.
Корреляционный блок cCorrelation играет роль механизма согласования. Он формирует устойчивые зависимости, которые затем используются для обновления состояния. В традиционных моделях эта роль часто выполняется рекуррентными связями или вниманием. Здесь же она реализована напрямую, как операция сопоставления текущего и эталонного представлений. Это делает поведение модуля более прозрачным и контролируемым.
После этого данные проходят через блок смешивания cMixFusion, который объединяет результаты различных путей обработки в единое представление. Этот слой важен не столько с точки зрения математики, сколько с точки зрения архитектурной чистоты. Он позволяет избежать жёстких связей между компонентами и сохраняет модульность всей конструкции. Именно здесь E-TROF превращается из набора вычислений в целостный функциональный блок.
Контекстный блок cContext, реализованный как CNeuronSpikeSuperKernelBlock, отвечает за формирование контекста внутри триплета.
Завершающим этапом служит cMotionEncoder, который переводит контекст и признаки движения в форму, удобную для дальнейших модулей фреймворка. Здесь формируется итоговое представление движения, которое уже можно использовать для принятия торговых решений, передачи в последующие уровни модели или интеграции с управляющей логикой стратегии.
Важно подчеркнуть, что вся описанная цепочка функционирует в потоковом режиме. Модель не собирает повторно контекст на каждом шаге, а аккуратно наращивает его по мере поступления новых данных. Именно это делает её пригодной для реального онлайн-использования.
За построение внутренней архитектуры отвечает метод инициализации, который формирует полноценную потоковую архитектуру. Каждый элемент выполняет строго определённую функцию в обработке временного ряда.
bool CNeuronETROF::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint stack_size, uint dimension_in, uint dimension_out, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronAddToStack::Init(numOutputs, myIndex, open_cl, stack_size, dimension_out, 1, optimization_type, batch)) ReturnFalse;
Сначала управление передается родительскому классу, который отвечает за накопление временного контекста. Далее последовательно создаётся подсистема. Первым инициализируется объект cPSSE. Он реализует возмущённое кодирование состояния (Perturbed State Space Encoding) и отвечает за трансформацию триплета в высоко размерное представление, насыщенное признаками движения.
uint index = 0; if(!cPSSE.Init(0, index, OpenCL, 3, dimension_in, 5 * dimension_out, 1, optimization, iBatch)) ReturnFalse;
Фактически cPSSE формирует интеллектуальный срез текущего состояния рынка. Он аккумулирует микродвижения, фильтрует шум и готовит данные для последующих блоков, обеспечивая устойчивость и контекстность. При этом важно подчеркнуть, что сам модуль реализован в виде стека. На каждом новом шаге обработки анализируется только последнее состояние, тогда как все предыдущие состояния сохраняются в стеке и могут быть использованы для построения более широкого временного контекста без повторных вычислений. Такая организация позволяет модели быть одновременно чувствительной к последним событиям и учитывать накопленный контекст, сохраняя при этом высокую вычислительную эффективность — критический фактор для онлайн-торговли.
На выходе блока cPSSE мы сознательно увеличиваем размерность представления данных. На первый взгляд, это может показаться ненужным усложнением и ростом вычислительной нагрузки. Однако за этой дополнительной размерностью скрывается принципиальный эффект. Она расширяет выразительность модели, позволяя фиксировать сложные взаимосвязи между событиями рынка, которые не видны в исходной, компактной форме. В отличие от оптического потока, где есть естественный объёмный кадр, у нас такого объёма нет — рынок не предоставляет заранее упакованного трёхмерного пространства. Но мы можем создать собственный объём, спроектировав анализируемую сцену в более широкое представление.
Это расширенное пространство становится источником богатых признаков. Дальнейшие блоки — корреляционный модуль, формирование прямого и обратного потоков, слой смешивания — используют это представление для построения локальных связей и выявления направлений движения. Такой подход можно сравнить с альтернативными головами внимания, которые смотрят на разные аспекты сложного паттерна одновременно, не упуская ни одну деталь. При этом высокая размерность позволяет фиксировать разнообразные комбинации признаков, создавая глубокое и многомерное представление динамики рынка, которое затем аккуратно сводится в стек и в конечный кодировщик движения.
В результате, увеличение размерности — это целенаправленная инвестиция в выразительность модели, которая компенсируется структурной обработкой данных. Модель остаётся потоковой, отзывчивой и готовой к работе в онлайн-торговле, одновременно обогащая внутреннее состояние сложными признаками движения.
Следующий уровень — это выделение центрального фрейма в качестве опорного. Он нужен для корреляционного анализа. По сути, он создает рамку отсчёта, относительно которой оцениваются прямой и обратный потоки движения.
index++; if(!cRefFrame.Init(0, index, OpenCL, 5 * dimension_out, optimization, iBatch)) ReturnFalse; cRefFrame.SetActivationFunction(None); index++; if(!cRefFrameT.Init(0, index, OpenCL, 5, dimension_out, optimization, iBatch)) ReturnFalse;
В финансовых данных это позволяет понимать, куда и с какой силой смещается рынок относительно зафиксированного состояния, а транспонирование обеспечивает согласованность измерений в разных признаковых осях.
После опорного кадра инициализируется cForwardBackward. Этот блок формирует два направления потока движения: вперёд и назад относительно центрального состояния.
index++; if(!cForwardBackward.Init(0, index, OpenCL, 10 * dimension_out, optimization, iBatch)) ReturnFalse; cForwardBackward.SetActivationFunction(None);
В контексте финансов это позволяет различать сигналы, которые усиливают текущий тренд, гасят его или сигнализируют о развороте.
Затем идёт cCorrelation. Этот объект сохраняет корреляции между потоками прямого и обратного движения, объединяя информацию о локальных сдвигах и направлениях. На практике это позволяет модели понимать движение как структуру, а не как набор случайных колебаний.
index++; if(!cCorrelation.Init(0, index, OpenCL, 10 * 5, optimization, iBatch)) ReturnFalse; cCorrelation.SetActivationFunction(None);
Следующий важный элемент — cMixFusion. Это блок, объединяющий выходы нескольких предыдущих информационных магистралей. Он действует, как инженерный фильтр — аккуратно агрегирует информацию, сохраняя баланс между локальными и глобальными признаками.
cMixFusion.Clear(); cMixFusion.SetOpenCL(OpenCL); CNeuronBaseOCL* neuron = NULL; CNeuronTransposeRCDOCL* transp = NULL; CNeuronSpikeSuperKernelBlock* sk_block = NULL; index++; neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(0, index, OpenCL, cForwardBackward.Neurons() + cCorrelation.Neurons(), optimization, iBatch) || !cMixFusion.Add(neuron)) DeleteObjAndFalse(neuron); neuron.SetActivationFunction(None);
Внутри cMixFusion находятся подмодули. Первый из них CNeuronBaseOCL используется для конкатенации данных из нескольких информационных магистралей, которые потом транспонируются в необходимое представление с помощью CNeuronTransposeRCDOCL.
index++; transp = new CNeuronTransposeRCDOCL(); if(!transp || !transp.Init(0, index, OpenCL, 2, 5, neuron.Neurons() / 10, optimization, iBatch) || !cMixFusion.Add(transp)) DeleteObjAndFalse(transp);
Непосредственно анализ данных осуществляется в CNeuronSpikeSuperKernelBlock, который формирует блок признаков с управляемым окном и шагом. Объект аккумулирует локальную динамику.
index++; uint windows[] = {3, transp.GetWindow()}; uint steps[] = {1, 1}; sk_block = new CNeuronSpikeSuperKernelBlock(); if(!sk_block || !sk_block.Init(0, index, OpenCL, transp.GetCount()*transp.GetDimension(), dimension_out, windows, steps, transp.GetWindow(), 1, optimization, iBatch) || !cMixFusion.Add(sk_block)) DeleteObjAndFalse(sk_block);
Объект cContext — это сердце формирования текущего контекста. Он аккумулирует признаки, полученные от центрального (опорного) фрейма из cPSSE, и формирует внутреннее представление движения на данном шаге. Он не хранит всю историю событий, а работает с текущим состоянием, обогащая его локальной информацией о динамике рынка. Такой подход позволяет получить интегрированное контекстное представление, которое учитывает текущее состояние и локальные взаимосвязи, выявленные корреляционным и смешивающим блоками.
index++; if(cContext.Init(0, index, OpenCL, cRefFrame.Neurons() / transp.GetWindow(), dimension_out, windows, steps, transp.GetWindow(), 1, optimization, iBatch)) ReturnFalse;
Наконец, cMotionEncoder превращает аккумулированный контекст в финальное представление движения. Здесь объединяются данные из всех предыдущих блоков.
cMotionEncoder.Clear(); cMotionEncoder.SetOpenCL(OpenCL); CNeuronSpikeGMA* gma = NULL; //--- index++; neuron = new CNeuronBaseOCL(); if(!neuron || !neuron.Init(0, index, OpenCL, 2 * dimension_out * transp.GetWindow(), optimization, iBatch) || !cMotionEncoder.Add(neuron)) DeleteObjAndFalse(neuron); neuron.SetActivationFunction(None);
А затем проходя через CNeuronSpikeGMA и второй SpikeSuperKernelBlock, которые усиливают устойчивые признаки и кодируют сложные паттерны движения в компактный вектор выходного состояния.
index++; gma = new CNeuronSpikeGMA(); if(!gma || !gma.Init(0, index, OpenCL, dimension_out, cRefFrameT.GetCount(), 1, optimization, iBatch) || !cMotionEncoder.Add(gma)) DeleteObjAndFalse(gma); index++; windows[0] = cRefFrameT.GetCount(); windows[1] = 2 * windows[0]; steps[0] = 1; steps[1] = windows[0]; sk_block = new CNeuronSpikeSuperKernelBlock(); if(!sk_block || !sk_block.Init(0, index, OpenCL, dimension_out, dimension_out, windows, steps, 1, 1, optimization, iBatch) || !cMotionEncoder.Add(sk_block)) DeleteObjAndFalse(sk_block); //--- return true; }
Этот вектор передаётся в стек, организованный средствами родительского класса CNeuronAddToStack, который аккумулирует последовательные состояния и обеспечивает расширенный контекст без необходимости повторного пересчёта уже обработанных данных.
Алгоритм прямого прохода модуля E-TROF реализован в методе feedForward и отражает потоковую природу обработки данных в модуле.
bool CNeuronETROF::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cPSSE.FeedForward(NeuronOCL)) ReturnFalse;
На первом этапе последнее событие передается в модуль P-SSE, который формирует кодированное представление триплета. Напомню, что на каждом шаге кодируются только последние состояния. Два предыдущих уже сохранены в стеке модуля.
Именно P-SSE отвечает за устойчивость к шуму, расширение признакового пространства и подготовку контекстного сигнала для последующих операций.
Далее происходит выделение признаков центрального фрейма.
uint units = cRefFrameT.GetCount(); if(!DeConcat(cRefFrame.getPrevOutput(), cRefFrame.getOutput(), cForwardBackward.getPrevOutput(), cPSSE.getOutput(), units * iDimension, units * iDimension, units * iDimension, 1)) ReturnFalse; if(!Concat(cRefFrame.getPrevOutput(), cForwardBackward.getPrevOutput(), cForwardBackward.getOutput(), units * iDimension, units * iDimension, 1)) ReturnFalse;
Эти шаги формируют линейно структурированный набор признаков, который учитывает прошлое, текущее состояние и направленные потоки движения. Такой подход позволяет модулям, следующим в цепочке, видеть как локальные изменения, так и глобальный контекст текущего окна.
После этого транспонируем представление опорного фрейма. Задача данной операции — подготовить данные для корреляционного анализа, формируя согласованное пространство признаков для вычисления связей между потоками.
if(!cRefFrameT.FeedForward(cRefFrame.AsObject()))
ReturnFalse;
С помощью матричного умножения (MatMul) формируем объем корреляций прямого и обратного движения, создавая устойчивое представление локальных взаимосвязей между событиями.
if(!MatMul(cForwardBackward.getOutput(), cRefFrameT.getOutput(), cCorrelation.getOutput(), units, iDimension, units, 2, false)) ReturnFalse;
Это эквивалентно выделению направленных зависимостей движения, аналогичных головам внимания в современных архитектурах, но реализованным через линейные операции над расширенным признаковым пространством.
Следующий шаг — формирование текущего контекста средствами cContext.
if(!cContext.FeedForward(cRefFrame.AsObject()))
ReturnFalse;
На основе признаков центрального фрейма блок аккумулирует информацию о локальной динамике.
Далее данные проходят через блок cMixFusion, который объединяет результаты корреляции, прямого и обратного потоков. Этот блок аккуратно смешивает все сигналы, формируя единое представление локальной динамики.
CNeuronBaseOCL* prev = cMixFusion[0]; CNeuronBaseOCL* curr = NULL; if(!prev || !Concat(cForwardBackward.getOutput(), cCorrelation.getOutput(), prev.getOutput(), iDimension, units, 2 * units)) ReturnFalse; for(int i = 1; i < cMixFusion.Total(); i++) { curr = cMixFusion[i]; if(!curr || !curr.FeedForward(prev)) ReturnFalse; prev = curr; }
Внутри cMixFusion применяются линейные преобразования и SpikeSuperKernel-блоки, которые обогащают признаки, создавая плотное и выразительное представление движения.
На заключительном этапе формируется выходной сигнал модуля через cMotionEncoder. Сначала объединяются контекст опорного фрейма и предыдущего смешанного представления. Затем данные проходят через GMA-модуль и SpikeSuperKernel-блок, формируя окончательный вектор движения.
curr = cMotionEncoder[0]; if(!curr || !Concat(cContext.getOutput(), prev.getOutput(), curr.getOutput(), iDimension, iDimension, units)) ReturnFalse; prev = curr; for(int i = 1; i < cMotionEncoder.Total(); i++) { curr = cMotionEncoder[i]; if(!curr || !curr.FeedForward(prev)) ReturnFalse; prev = curr; }
Этот вектор аккуратно передаётся в стек родительского класса, где накапливается для последующего анализа, поддерживая потоковую природу работы модели.
if(!CNeuronAddToStack::feedForward(prev)) ReturnFalse; //--- return true; }
На следующем этапе мы вступаем в зону, где архитектура перестаёт быть просто набором блоков и превращается в управляемую систему распространения ошибок. Именно поэтому метод calcInputGradients требует отдельного, вдумчивого описания. Он по сути зеркалит сложную геометрию прямого прохода, но делает это аккуратно, без разрушения уже сформированного контекста.
bool CNeuronETROF::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) ReturnFalse;
Алгоритм обратного распространения начинается с того, что градиент ошибки принимается на входе стекового механизма, реализованного в родительском классе. Это принципиально важно. Мы обучаем не отдельный шаг, а всё накопленное представление движения, и потому сначала восстанавливается градиент по выходу последнего элемента стека. Таким образом, ошибка распределяется сразу по всему временному контексту, который модель аккумулировала на предыдущих шагах.
if(!CNeuronAddToStack::feedForward(cMotionEncoder[-1])) ReturnFalse;
Далее градиент последовательно проходит через модуль кодирования движения cMotionEncoder в обратном порядке. Каждый внутренний блок получает ошибку от следующего слоя и вычисляет собственные скрытые градиенты.
CNeuronBaseOCL* curr = NULL; for(int i = cMotionEncoder.Total() - 2; i >= 0; i--) { curr = cMotionEncoder[i]; if(!curr || !curr.CalcHiddenGradients(cMotionEncoder[i + 1])) ReturnFalse; }
Здесь важно, что энкодер движения не просто сжимает информацию, а формирует устойчивые инварианты движения. Поэтому обратный проход через него задаёт масштаб и направление всей последующей коррекции весов.
После этого градиент разветвляется. Через операцию DeConcat он аккуратно разделяется на две части: одна возвращается в cContext, корректируя формирование текущего контекста, а вторая — в последний слой cMixFusion, который начинает обратное распространение по цепочке смешивания признаков.
uint units = cRefFrameT.GetCount(); curr = cMixFusion[-1]; if(!curr || !DeConcat(cContext.getGradient(), curr.getGradient(), cMotionEncoder[0].getGradient(), iDimension, iDimension, units)) ReturnFalse;
Важно подчеркнуть, что cMixFusion обучается как единый ансамбль. Градиент проходит через все его внутренние преобразования в обратном порядке, сохраняя согласованность между потоками движения и корреляциями.
for(int i = cMixFusion.Total() - 2; i >= 0; i--) { curr = cMixFusion[i]; if(!curr || !curr.CalcHiddenGradients(cMixFusion[i + 1])) ReturnFalse; }
Когда ошибка доходит до входа блока смешивания, она снова разделяется — теперь между блоком прямого/обратного движения и корреляционным блоком.
if(!DeConcat(cForwardBackward.getGradient(), cCorrelation.getGradient(), curr.getGradient(), iDimension, units, 2 * units)) ReturnFalse;
Для корреляции используется обратное матричное умножение (MatMulGrad), которое восстанавливает вклад каждого признака в итоговое значение корреляции.
if(!MatMulGrad(cForwardBackward.getOutput(), cForwardBackward.getPrevOutput(), cRefFrameT.getOutput(), cRefFrameT.getGradient(), cCorrelation.getGradient(), units, iDimension, units, 2, false)) ReturnFalse; if(!SumAndNormilize(cForwardBackward.getGradient(), cForwardBackward.getPrevOutput(), cForwardBackward.getGradient(), iDimension, false, 0, 0, 0, 1)) ReturnFalse;
Градиенты прямого и обратного потокам аккуратно суммируются с ранее накопленными значениями, что предотвращает перекос обучения в сторону одного направления движения.
Далее градиент возвращается к опорному фрейму (cRefFrame). Здесь применяется важный приём: корректировка происходит в два этапа. Сначала ошибка учитывает влияние контекстного блока.
if(!cRefFrame.CalcHiddenGradients(cContext.AsObject()))
ReturnFalse;
Затем — вклад транспонированного представления.
CBufferFloat* temp = cRefFrame.getGradient(); if(!cRefFrame.SetGradient(cRefFrame.getPrevOutput(), false) || !cRefFrame.CalcHiddenGradients(cRefFrameT.AsObject()) || !SumAndNormilize(cRefFrame.getGradient(), temp, temp, iDimension, false, 0, 0, 0, 1) || !cRefFrame.SetGradient(temp, false)) ReturnFalse;
После этого градиенты аккуратно суммируются, что позволяет сохранить устойчивость обучения даже при увеличенной размерности признакового пространства.
На финальном этапе градиент проходит через операции объединения и разделения, возвращаясь к выходу P-SSE.
if(!DeConcat(cRefFrameT.getPrevOutput(), cForwardBackward.getPrevOutput(), cForwardBackward.getGradient(), units * iDimension, units * iDimension, 1)) ReturnFalse; if(!Concat(cRefFrameT.getPrevOutput(), cRefFrame.getGradient(), cForwardBackward.getPrevOutput(), cPSSE.getGradient(), units * iDimension, units * iDimension, units * iDimension, 1)) ReturnFalse;
В завершение замкнутого цикла мы передаем градиент ошибки на уровень объекта исходных данных.
if(!NeuronOCL.CalcHiddenGradients(cPSSE.AsObject())) ReturnFalse; //--- return true; }
Ошибка, сформированная на выходе модуля движения, полностью возвращается к блоку первичного кодирования состояния, корректируя то самое расширенное представление, которое было создано в начале прямого прохода.
Таким образом, метод calcInputGradients обеспечивает строго согласованное распределение градиентов по всем информационным потокам модуля E-TROF. Каждый блок получает ровно ту часть ошибки, которая соответствует его вкладу в формирование контекста движения. Это позволяет обучать сложную, многопоточную архитектуру как единый механизм, не разрушая её потоковую природу и не теряя устойчивости при работе в режиме онлайн-торговли.
Иначе говоря, если прямой проход — это аккуратная сборка картины движения, то обратный — её ювелирная разборка с точным пониманием, какой элемент за что отвечает. Именно такая дисциплина делает модель не только выразительной, но и обучаемой на реальных финансовых данных, где цена ошибки всегда измеряется деньгами.
Заключение
Сегодня мы построили один из ключевых модулей фреймворка и тем самым сделали ещё один шаг от абстрактной архитектуры к рабочему инструменту для практической торговли. E-TROF в представленной реализации перестаёт быть просто адаптацией идеи из компьютерного зрения и превращается в полноценный механизм анализа динамики финансового рынка, спроектированный с учётом его реальных ограничений: потоковости данных, высокой шумности и необходимости мгновенной реакции на новое событие.
Главное преимущество предложенного подхода — инкрементальная обработка информации. Модель анализирует только последнее событие, а весь накопленный контекст хранится и используется повторно без лишних вычислений. Это принципиально отличает подход от классических оконных методов и делает его пригодным для онлайн-торговли, где каждая лишняя операция напрямую влияет на задержку принятия решения.
Второй фундаментальный плюс — искусственно расширенное представление рынка. Там, где финансовые данные изначально плоские и бедные на структуру, фреймворк сам создаёт объёмное пространство признаков. Это позволяет строить локальные корреляции, выявлять направленные потоки движения и анализировать рынок как динамическую сцену, а не как последовательность случайных чисел. Такой подход радикально повышает выразительность модели без разрушения её устойчивости.
Не менее важно и то, что архитектура изначально проектировалась как многопоточная, но согласованная система. Прямые и обратные потоки, корреляционные блоки, контекстное кодирование и стековое накопление состояния работают как единый механизм. Даже сложная схема распространения градиентов остаётся управляемой и прозрачной, что обеспечивает стабильное обучение и предсказуемое поведение модели в реальных рыночных условиях.
Ссылки
- Perturbed State Space Feature Encoders for Optical Flow with Event Cameras
- SKFlow: Learning Optical Flow with Super Kernels
- Другие статьи серии
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн обучения моделей |
| 3 | Test.mq5 | Советник | Советник для тестирования модели |
| 4 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
| 5 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
| 6 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL-программы |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Создание самооптимизирующихся советников на MQL5 (Часть 6): Самоадаптирующиеся торговые правила (II)
Знакомство с языком MQL5 (Часть 27): Освоение API и функции WebRequest в языке MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования