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

Объект распределения рыночных состояний
Начнём работу с построения объекта-обёртки для кернела квантования состояний, реализованного в практической части предыдущей статьи. Именно этот модуль становится фундаментом всей дальнейшей архитектуры, поскольку фактически выполняет роль рыночного аналога BEV-представления — того самого вида с высоты птичьего полёта, который широко используется в системах автономного вождения для формирования целостной пространственной картины окружающей среды.
В задачах компьютерного зрения подобное представление позволяет автомобилю воспринимать окружающее пространство как единую структурированную карту среды. Для финансового рынка идея оказывается удивительно близкой по смыслу. Поток свечей, индикаторов и признаков сам по себе мало говорит о структуре происходящего. Но если перенести наблюдаемые состояния в латентное пространство и сформировать карту их распределения, рынок начинает восприниматься уже не как хаотическая последовательность цен, а как система областей различной плотности, устойчивости и неопределённости.
Именно такую задачу и решает реализованный ранее кернел квантования. Его цель заключается не в прогнозировании цены и даже не в классификации режима рынка. Кернел формирует компактное статистическое представление пространства состояний, отображая концентрацию рыночных режимов внутри латентной карты. В результате модель получает возможность анализировать текущее состояние и его положение относительно накопленной структуры исторических наблюдений.
При этом сразу необходимо обратить внимание на один важный архитектурный момент. Во время разработки OpenCL-кернела мы говорили о передаче в него нормализованного тензора рыночных состояний. Однако непосредственно внутри создаваемого объекта процессы нормализации и накопления данных реализовываться не будут. Мы сознательно выносим их за пределы данного модуля.
На первый взгляд такое решение может показаться избыточным усложнением архитектуры. На практике это сохраняет важное свойство системы: разделение ответственности между этапами обработки данных. Сам объект распределения должен заниматься исключительно построением карты состояний. Подготовка признаков, их масштабирование, сглаживание, накопление статистики и контроль корректности исходных данных остаются задачами внешнего конвейера обработки. Подобный подход делает систему значительно более гибкой и позволяет использовать модуль с различными источниками признаков без необходимости модифицировать сам механизм квантования.
Кроме того, здесь появляется ещё одна принципиально важная особенность, о которой необходимо сказать заранее. Процесс квантования является недифференцируемым. А значит, через данный модуль невозможно напрямую прокинуть градиент ошибки.
В классических задачах глубокого обучения подобное ограничение нередко становится серьёзной проблемой, поскольку обучение нейросети опирается именно на механизм обратного распространения ошибки. Однако в нашем случае ситуация выглядит значительно спокойнее. Когда модуль работает непосредственно с исходными рыночными данными, необходимости в коррекции самих входных наблюдений не существует. Рыночные данные поступают извне торговой системы и не являются обучаемыми параметрами модели. Мы не пытаемся изменить рынок — мы лишь анализируем его структуру.
По этой причине отсутствие дифференцируемости на входном уровне не создаёт критических ограничений. Градиент ошибки необходим модели для корректировки весов предшествующих слоёв, а не для изменения исторических котировок. Поэтому использование квантования на этапе формирования карты состояний оказывается вполне естественным и архитектурно допустимым решением.
Однако при встраивании таких модулей в глубокую нейросетевую архитектуру требуется осторожность. Если разместить недифференцируемый блок в середине обучаемого конвейера, распространение ошибки окажется разорванным, а предшествующие слои потеряют возможность адаптации. В результате часть модели может фактически перестать обучаться. Именно поэтому подобные компоненты обычно располагаются либо на границе системы наблюдения, либо используются как отдельные аналитические ветви, не участвующие напрямую в сквозном backpropagation.
Для задач анализа рыночной неопределённости такой подход выглядит вполне оправданным. Мы строим систему оценки структуры рыночного пространства. И в этой архитектуре карта распределения состояний выступает скорее как статистический сенсор среды — своеобразный радар рыночной устойчивости, чем как обычный слой нейронной сети.
Объект CNeuronUncAD_MSD — это специализированный вычислительный модуль, который переводит рыночные данные из привычной формы последовательности в статистическую карту распределения состояний.
class CNeuronUncAD_MSD : public CNeuronBaseOCL { protected: uint iDimension; uint iUnits; uint iVariables; uint iQuantiles; float fQuantSize; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return true; } virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { return (!!NeuronOCL && NeuronOCL.getGradient().Fill(0)); } public: CNeuronUncAD_MSD(void) {}; ~CNeuronUncAD_MSD(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint quantiles, float range, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronUncAD_MSD; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; };
Класс работает в общей OpenCL-архитектуре проекта и получает всю инфраструктуру базового нейронного блока. Однако логика этого объекта заметно отличается от классического обучаемого слоя. Он не хранит весовые коэффициенты в привычном смысле и не пытается подстраиваться под целевую функцию. Его задача более сдержанная и одновременно более фундаментальная — аккуратно разложить входной поток рыночных состояний по квантилям и сформировать плотностное представление, пригодное для последующего анализа неопределённости.
Внутренние поля класса хорошо отражают его назначение. Переменная iDimension задаёт размерность пространства признаков, то есть число координат, по которым мы описываем рыночное состояние. iUnits определяет количество наблюдений или элементов последовательности, которые участвуют в построении распределения. iVariables фиксирует число переменных, то есть сколько независимых каналов состояния обрабатывается параллельно. iQuantiles задаёт число квантилей, на которые разбивается диапазон значений, а fQuantSize хранит шаг квантования, рассчитанный как отношение общего диапазона к числу квантилей. В этом наборе полей уже видна вся логика объекта: он не просто принимает тензор, а переводит непрерывное пространство состояний в дискретную, компактную и удобную для анализа сетку.
Метод инициализации выполняет важнейшую подготовительную работу.
bool CNeuronUncAD_MSD::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension, uint units, uint variables, uint quantiles, float range, ENUM_OPTIMIZATION optimization_type, uint batch) { if(units < (10 * quantiles)) ReturnFalse; if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, dimension * (2 * quantiles + 1) * variables, optimization_type, batch)) ReturnFalse; //--- iDimension = dimension; iUnits = units; iVariables = variables; iQuantiles = quantiles; fQuantSize = range / iQuantiles; if(fQuantSize < FLT_EPSILON) ReturnFalse; //--- return true; }
Сначала он проверяет, достаточно ли велика размерность анализируемой последовательности относительно выбранного числа квантилей. Это вполне здравое ограничение: слишком короткая последовательность при слишком тонком разбиении пространства дала бы статистически слабую, шумную карту. Иначе говоря, мы не можем требовать от модели тонкой картины, если ей выдали слишком мало мазков.
Затем вызывается инициализация родительского класса, где рассчитывается базовая конфигурация объекта и выделяются ресурсы под OpenCL-исполнение. Здесь особенно важно, что размер входного пространства задаётся как произведение dimension * (2 * quantiles + 1) * variables. Такой выбор показывает, что слой работает не с одним числом на ось, а с расширенным описанием локального распределения, где вокруг каждого состояния учитывается квантильная окрестность. Это и есть тот слой статистической глубины, который отличает карту состояний от простого численного буфера.
После инициализации сохраняются параметры конфигурации и вычисляется шаг квантования. Это ключевой момент, потому что именно шаг квантования задаёт зерно всей карты. Слишком крупный шаг сделает распределение грубым и потеряет важные переходные зоны; слишком мелкий — распылит статистику и превратит карту в набор почти случайных фрагментов. Проверка fQuantSize < FLT_EPSILON — не формальность, а предохранитель от вырожденной конфигурации, где модель фактически потеряла бы способность различать соседние уровни состояния.
Основная вычислительная работа сосредоточена в методе прямого прохода. Здесь объект получает на вход предыдущий нейросетевой блок NeuronOCL, проверяет корректность указателя и доступность OpenCL-контекста. А затем убеждается, что входной буфер содержит достаточное количество нейронов для обработки всей размерности задачи. Это важная проверка на целостность: слой должен быть уверен, что перед ним лежит не обрывок данных, а полный тензор рыночных состояний.
bool CNeuronUncAD_MSD::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL || !OpenCL) ReturnFalse; if(NeuronOCL.Neurons() < (iDimension * iUnits * iVariables)) ReturnFalse; //--- uint global_work_offset[] = { 0, 0, 0 }; uint global_work_size[] = { iDimension, (uint)MathMin(iUnits, OpenCL.GetMaxLocalSize(1)), iVariables }; uint local_work_size[] = { 1, global_work_size[1], 1 }; //--- const int kernel = def_k_MarketStateDensity; setBuffer(kernel, def_k_msd_sequence, NeuronOCL.getOutputIndex()); setBuffer(kernel, def_k_msd_distribution, getOutputIndex()); setArgument(kernel, def_k_msd_sequence_size, iUnits); setArgument(kernel, def_k_msd_quantiles, iQuantiles); setArgument(kernel, def_k_msd_quant_step, fQuantSize); //--- kernelExecuteLoc(kernel, global_work_offset, global_work_size, local_work_size); //--- return true; }
Далее формируется конфигурация запуска кернела. В качестве глобального размера работы используются три оси:
- размерность пространства признаков;
- длина анализируемой последовательности, ограниченная максимальным локальным размером устройства;
- количество независимых последовательностей.
Такой трёхмерный запуск очень хорошо соответствует самой природе задачи. Мы не просто обрабатываем поток последовательно; мы раскладываем вычисление по измерениям, наблюдениям и переменным одновременно. Это позволяет OpenCL-реализации работать быстро и естественно, словно карта строится не строка за строкой, а одновременно по всей своей поверхности.
Отдельного внимания заслуживает поведение методов, связанных с обучением. Метод оптимизации параметров (updateInputWeights) здесь фактически пустой и сразу возвращает true. Это прямое указание на то, что у объекта нет настраиваемых весов, которые необходимо корректировать по градиенту. Он не является обучаемым в классическом смысле и не вмешивается в параметрическую часть модели.
Ещё более показательным является метод распределения градиента ошибки (calcInputGradients), который заполняет буфер градиентов предыдущего слоя нулевыми значениями. Это очень важная архитектурная деталь. Квантование — операция недифференцируемая, а значит, через этот модуль нельзя провести обычный backpropagation. Для прикладной рыночной аналитики это не проблема, потому что входные котировки и признаки не являются обучаемыми параметрами. Но если бы такой объект оказался внутри обучаемого нейросетевого тракта, он стал бы своеобразным разрывом в цепи обратного распространения ошибки. Поэтому его место — либо на входной границе системы, либо в отдельной аналитической ветви, где он работает как сенсор структуры рынка, а не как слой, подлежащий обучению.
Именно в этом и заключается сила CNeuronUncAD_MSD. Он не пытается быть универсальным слоем на все случаи жизни. Напротив, он выполняет одну задачу, но делает это строго, последовательно и без лишней декоративности. Он превращает хаотичный поток рыночных наблюдений в структурированную карту состояний, где можно увидеть статистическую опору рыночного движения. Для проекта UncAD это особенно ценно: здесь мы не ищем очередной способ угадать следующую свечу. Мы строим механизм, который позволяет понять, насколько вообще можно доверять текущему состоянию рынка.
Энкодер неопределённости состояния
На предыдущем этапе мы построили карту распределения рыночных состояний. Теперь поднимаемся на следующую ступень: к этой карте добавляется оценка неопределённости, то есть степень доверия модели к самому наблюдаемому состоянию рынка. Это уже не просто описание структуры среды, а попытка понять, насколько эта структура устойчива и пригодна для принятия решения.
Для трейдера это принципиальный вопрос. Модель может выдавать сигнал и в спокойном, и в сломанном рынке. Разница лишь в том, что во втором случае ошибка особенно дорога. Поэтому нам недостаточно знать, где находится рынок внутри латентного пространства. Нужно ещё понимать, насколько надёжно это положение интерпретируется моделью.
Именно эту задачу решает энкодер неопределённости. Он формирует не жёсткую точку в пространстве признаков, а представление, в котором вместе с состоянием оценивается и его достоверность. В устойчивых режимах карта получается плотной и уверенной. В редких, переходных или аномальных участках рынка уверенность падает, а латентное представление становится размытым. Это естественный и полезный сигнал: рынок вышел из знакомой области.
Важно, что такая уверенность не задаётся вручную. Она формируется в процессе обучения как внутреннее свойство модели. Мы не навязываем системе внешний коэффициент риска, а позволяем ей самой выучить зоны устойчивого представления и сомнений. В этом и состоит практическая ценность подхода: модель начинает учитывать не только рыночное состояние, но и пределы собственной компетентности.
Для торговой системы это особенно полезно. Когда рынок остается внутри знакомой структуры, можно действовать смелее. Когда структура распадается, система должна снижать уверенность и, как следствие, осторожнее подходить к сигналам. Так неопределённость перестает быть помехой и становится рабочим параметром принятия решений.
Объект CNeuronUncAD_MUE становится первым по-настоящему Uncertainty-Aware модулем всей архитектуры UncAD. Система начинает одновременно анализировать состояние рынка и степень уверенности в этом состоянии.
class CNeuronUncAD_MUE : public CNeuronSpikeConvBlock { protected: CLayer cMSDFlow; CLayer cUncertainty; CNeuronBaseOCL cConcatenated; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronUncAD_MUE(void) {}; ~CNeuronUncAD_MUE(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_out, uint variables, uint stack_size, uint quantiles, float range, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronUncAD_MUE; } //--- 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; };
По своей сути данный объект уже нельзя рассматривать как обычный нейросетевой слой. Это композиционный модуль, объединяющий сразу несколько вычислительных потоков в единую систему оценки рыночной среды. Именно здесь начинается переход от простой статистической карты к вероятностному описанию рынка.
Класс наследуется от CNeuronSpikeConvBlock, что сразу показывает его положение внутри архитектуры. Перед нами уже не изолированный OpenCL-кернел, а полноценный вычислительный блок, способный объединять несколько внутренних подсистем и организовывать их совместную работу. Такой подход особенно важен для задач Uncertainty Modeling, где недостаточно просто обработать входные признаки — необходимо ещё синхронизировать различные представления среды.
Внутри объекта расположены три ключевых компонента. Первый — cMSDFlow. Это поток обработки карты распределения рыночных состояний. Фактически он отвечает за формирование базового пространственного представления рынка. Здесь система анализирует плотность состояний, их концентрацию и положение внутри латентной структуры.
Второй компонент — cUncertainty. Это уже специализированная ветвь оценки неопределённости. Именно она начинает анализировать устойчивость наблюдаемой структуры и формировать внутреннюю оценку доверия к текущему состоянию рынка. Модель пытается понять насколько она уверена в том, что правильно понимает происходящее.
Третий элемент — cConcatenated. Это буфер объединенного представления. В нем система собирает вместе карту состояния и оценку неопределённости, формируя единое латентное описание среды. На практике это очень важный момент. Вместо раздельного существования двух независимых ветвей модель начинает работать с совместным пространством: состояние рынка и степень доверия к нему становятся связанными величинами.
Подобная архитектура хорошо отражает саму природу финансового рынка. В реальной торговле невозможно отделить сигнал от уверенности в сигнале. Два одинаковых паттерна могут выглядеть совершенно по-разному в зависимости от качества рыночной структуры, ликвидности, стабильности режима или близости переходной зоны. Поэтому объединение этих потоков в единую карту выглядит значительно естественнее классических Deterministic-подходов.
Метод инициализации фактически собирает весь вычислительный контур блока по частям, как инженер собирает сложный прибор из нескольких согласованных узлов. На вход подаются параметры, задающие не только размеры окна и число признаков, но и внутреннюю геометрию будущего представления:
- window определяет размерность пространства признаков на входе объекта,
- window_out — размерность пространства признаков на выходе,
- variables — число анализируемых каналов,
- stack_size — объём внутренней памяти,
- quantiles — количество квантилей,
- range — рабочий диапазон значений до квантования.
Иначе говоря, здесь мы заранее задаем, какой именно ландшафт рынка будет видеть модель: широкую панораму, плотную локальную сцену или тонко разлинованную статистическую карту.
bool CNeuronUncAD_MUE::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_out, uint variables, uint stack_size, uint quantiles, float range, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, window + window_out, window + window_out, window_out, 2 * quantiles + 1, variables, optimization_type, batch)) ReturnFalse;
Инициализация родительского класса задаёт базовый каркас всего блока. А дальше начинается сборка первой ветви — cMSDFlow. Она отвечает за формирование самой карты распределения рыночных состояний. Первым узлом здесь идет CNeuronBatchNormOCL. Его задача очень приземленная, но критически важная: привести входные данные к сопоставимому масштабу и убрать лишнюю дисбалансировку между каналами.
cMSDFlow.Clear(); cUncertainty.Clear(); cMSDFlow.SetOpenCL(OpenCL); cUncertainty.SetOpenCL(OpenCL); //--- MSD Flow uint index = 0; CNeuronBatchNormOCL* norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, window * variables, iBatch, optimization) || !cMSDFlow.Add(norm)) DeleteObjAndFalse(norm); norm.SetActivationFunction(None);
Для рыночных данных это особенно ценно, потому что разные признаки часто живут в разных числовых мирах. Один канал может колебаться плавно, другой — резко, третий — быть почти статичным. Без нормализации карта состояний получится кривой, как старый телеграфный столб после шторма. Поэтому этот узел выравнивает исходный поток и делает его пригодным для дальнейшего структурного анализа. То, что у него отключена активация, тоже логично. Здесь важно не исказить статистику нелинейностью, а сохранить исходную форму распределения в очищенном виде.
Следом идет CNeuronAddToStack. Этот блок собирает локальную временную глубину. Он переводит разрозненные наблюдения в стек состояний, то есть формирует не просто точку рынка, а кусок его динамики.
index++; CNeuronAddToStack* stack = new CNeuronAddToStack(); if(!stack || !stack.Init(0, index, OpenCL, stack_size, window, variables, optimization, iBatch) || !cMSDFlow.Add(stack)) DeleteObjAndFalse(stack);
В этом и состоит практический смысл stack_size: модель получает не одиночный снимок, а короткую киноленту. Для неопределённости это особенно важно, потому что сама уверенность в рынке редко рождается из одной свечи. Она возникает из повторяемости, из связности соседних состояний, из инерции структуры. Именно стек и дает этот контекст.
После этого в cMSDFlow подключается CNeuronUncAD_MSD. Это центральный узел первой ветви. Он превращает нормализованный и упакованный в стек поток в карту распределения рыночных состояний. В тот самый рыночный аналог BEV-представления, о котором мы говорили ранее.
index++; CNeuronUncAD_MSD* msd = new CNeuronUncAD_MSD(); if(!msd || !msd.Init(0, index, OpenCL, window, stack_size, variables, quantiles, range, optimization, iBatch) || !cMSDFlow.Add(msd)) DeleteObjAndFalse(msd);
Здесь происходит переход от последовательности к геометрии. Модель перестает смотреть на рынок как на набор чисел и начинает воспринимать его как поле плотности. Одни зоны заполнены привычными режимами, а другие остаются разреженными. Вся первая ветвь, по сути, отвечает за ответ на вопрос: что это за состояние рынка и где оно находится относительно накопленной структуры?
Далее запускается вторая ветвь — cUncertainty. Она уже работает с тем, что можно назвать оценкой доверия к рыночной картине. Первым здесь стоит CNeuronMultiMixAttention. Его роль — извлечь из квантованного пространства наиболее значимые связи между элементами карты.
//--- Uncertainty Flow index++; CNeuronMultiMixAttention* att = new CNeuronMultiMixAttention(); if(!att || !att.Init(0, index, OpenCL, window * (2 * quantiles + 1), variables, 2 * quantiles + 1, quantiles, 5, 2, optimization, iBatch) || !cUncertainty.Add(att)) DeleteObjAndFalse(att);
В такой архитектуре attention нужен не ради моды, а ради дисциплины восприятия: он помогает модели не распыляться на все признаки сразу, а выделять те участки пространства, где структура действительно несет информацию об устойчивости или ее потере. Иными словами, этот узел учится смотреть не на шум, а на отношения между квантильными слоями, временными фрагментами и каналами состояния.
Следующий компонент — CNeuronDictionaryCrossAtt. Это уже более строгий механизм. Его можно воспринимать как словарь прототипов рыночной неопределённости. Он сравнивает текущее представление с накопленными шаблонами и помогает понять, похоже ли наблюдаемое состояние на что-то знакомое, устойчивое и статистически подтвержденное, или же модель вошла в область, где прежний опыт перестает быть надёжной опорой.
index++; CNeuronDictionaryCrossAtt* dic = new CNeuronDictionaryCrossAtt(); if(!dic || !dic.Init(0, index, OpenCL, window * (2 * quantiles + 1), variables, 4, stack_size / 2, optimization, iBatch) || !cUncertainty.Add(dic)) DeleteObjAndFalse(dic); index++;
Такой узел особенно полезен именно в Uncertainty-Aware архитектуре, потому что уверенность редко строится только на текущем кадре. Обычно она рождается из сопоставления текущего сигнала с внутренним архивом уже виденных режимов. Здесь и работает Cross-Attention. Он связывает настоящее с тем, что модель уже знает о прошлом.
После этого в ветви появляется промежуточный объект конкатенации, который аккуратно подготавливает расширенное представление к дальнейшему объединению.
CNeuronBaseOCL* concat = new CNeuronBaseOCL(); if(!concat || !concat.Init(0, index, OpenCL, 2 * dic.Neurons(), optimization, iBatch) || !cUncertainty.Add(concat)) DeleteObjAndFalse(concat); concat.SetActivationFunction(None); index++;
Завершается вторая ветвь блоком CNeuronSpikeConvBlock, который выполняет пространственную доработку Uncertainty-представления.
CNeuronSpikeConvBlock* conv = new CNeuronSpikeConvBlock(); if(!conv || !conv.Init(0, index, OpenCL, 2 * window, 2 * window, window_out, 2 * quantiles + 1, variables, optimization, iBatch) || !cUncertainty.Add(conv)) DeleteObjAndFalse(conv);
Если говорить по сути, он доводит собранные признаки до компактного и устойчивого вида, где уже можно извлекать итоговую оценку неопределённости. В этом блоке архитектура снова возвращается к идее локального поля: важно не просто иметь набор признаков, а связать их в согласованную структуру.
На финальном шаге обе ветви сводятся в cConcatenated. Это уже не просто буфер, а единое представление, в котором карта рыночного состояния и карта неопределённости соединяются в одну рабочую структуру.
index++; if(!cConcatenated.Init(0, index, OpenCL, msd.Neurons() + conv.Neurons(), optimization, iBatch)) ReturnFalse; cConcatenated.SetActivationFunction(None); //--- return true; }
Именно в этот момент архитектура UncAD приобретает свой ключевой смысл: рынок описывается не только как пространство состояний, но и как пространство доверия к этим состояниям. То есть модель получает не один сигнал, а пару: где мы находимся и насколько надёжно мы это понимаем. Для практического трейдинга это очень сильная конструкция, потому что она позволяет отличать просто плохой сигнал от сигнала, который исходит из по-настоящему нестабильной среды.
Метод прямого прохода показывает механику архитектуры. Входной поток проходит последовательные стадии обработки, и на выходе формируется связанное представление состояния с учётом неопределённости. Схема начинает дышать и работать: входной поток рыночных данных проходит через несколько последовательных стадий, на каждой из которых он либо уточняется, либо преобразуется, либо подготавливается к следующему уровню анализа. В результате на выходе появляется не просто очередной тензор, а уже связанное представление рыночного состояния с учётом неопределённости.
bool CNeuronUncAD_MUE::feedForward(CNeuronBaseOCL *NeuronOCL) { CNeuronBaseOCL* prev = NeuronOCL; CNeuronBaseOCL* curr = NULL; //--- MSD Flow for(int i = 0; i < cMSDFlow.Total(); i++) { curr = cMSDFlow[i]; if(!curr || !curr.FeedForward(prev)) ReturnFalse; prev = curr; }
В начале метода объявляются два указателя: prev и curr. Первый хранит текущий источник данных, а второй — узел, в который мы будем передавать результат на очередном шаге. Такая структура упрощает логику функции: блоки выполняются последовательно, по одной цепочке. Сначала запускается MSD Flow, затем ветвь неопределённости, а в конце оба результата сводятся в единое представление.
Первая часть — cMSDFlow. Здесь поток проходит по узлам, которые мы уже описывали: нормализация, стекование и построение карты распределения состояний. Цикл устроен просто и строго: каждый следующий модуль получает результат предыдущего. Если хотя бы один элемент цепочки не существует или не смог выполнить прямой проход, вся функция немедленно завершается с ошибкой. Это важная дисциплина. В подобной архитектуре нельзя позволить одному сбою тихо раствориться в дальнейшем расчёте, потому что тогда искажение начнёт распространяться по всей карте состояний. Здесь блок ведёт себя как хорошо настроенный механизм: если шестерня не провернулась, вся передача останавливается.
После завершения первой ветви начинается вторая — cUncertainty. И вот здесь архитектура становится особенно интересной. На обычном линейном пути каждый узел получает вход от предыдущего. Но в этой ветви есть исключение, связанное с CNeuronDictionaryCrossAtt.
//--- Uncertainty Flow for(int i = 0; i < cUncertainty.Total(); i++) { curr = cUncertainty[i]; if(!curr) ReturnFalse; if(prev.Type() != defNeuronDictionaryCrossAtt) { if(!curr.FeedForward(prev)) ReturnFalse; }
Это уже не просто последовательная обработка, а точка, где модель начинает сопоставлять текущее представление с внутренним словарём или эталонной структурой. Именно поэтому код проверяет тип предыдущего узла: если это не NeuronDictionaryCrossAtt, то используется обычный вызов прямого прохода. А если предыдущий узел именно словарного кросс-внимания, тогда логика меняется.
else { CNeuronDictionaryCrossAtt* dic = prev; uint count = dic.GetUnits() * dic.GetVariables(); uint dimension = dic.GetFilters(); prev = dic.GetSimilarity(); if(!prev || !Concat(dic.getOutput(), prev.getOutput(), curr.getOutput(), dimension, dimension, count)) ReturnFalse; } prev = curr; }
В этом случае из предыдущего блока извлекаются признаки соответствия. Это сильный момент архитектуры. Система не просто хранит эталоны, а сравнивает текущее состояние с ними и получает структуру похожести. И здесь объединяются выходы словаря и карты сходства. Фактически модель собирает не один признак, а пару взаимосвязанных представлений: что именно было найдено и насколько текущая конфигурация совпадает с известным шаблоном. Для Uncertainty-Aware механизма это именно то, что нужно. Уверенность формируется не в вакууме, а через сравнение с внутренней памятью.
После этого цикл продолжается обычным образом, и каждый следующий блок получает уже преобразованное представление. Здесь важно заметить, что архитектура специально допускает разветвлённую логику внутри потока. Это более зрелая схема, где часть узлов работает как преобразователи, а часть — как механизмы сопоставления и оценки согласованности структуры.
Дальше метод переходит к объединению двух главных ветвей. Для этого из MSD Flow берётся последний узел. Это и есть итоговая карта распределения рыночных состояний. Затем вычисляется число элементов count, которое задаёт количество параллельных каналов, участвующих в финальном сведении. И определяются размеры по двум направлениям:
- dim_msd для карты состояния;
- dim_unc для карты неопределённости.
Таким образом, перед финальной сборкой код явно знает, какие размеры у каждой из двух проекций.
prev = cMSDFlow[-1]; uint count = cConv.GetUnits() * cConv.GetVariables(); uint dim_msd = prev.Neurons() / count; uint dim_unc = curr.Neurons() / count; if(!Concat(prev.getOutput(), curr.getOutput(), cConcatenated.getOutput(), dim_msd, dim_unc, count)) ReturnFalse;
Далее выполняется конкатенация: обе ветви объединяются в cConcatenated. Это ключевая архитектурная операция. Карта состояния и карта уверенности становятся частями единого латентного пространства. Именно в этом месте UncAD перестает быть просто анализатором рынка и начинает работать как система совместной оценки структуры и надёжности структуры. Условно говоря, модель больше не отвечает только на вопрос: что происходит? Она начинает отвечать и на вопрос: можно ли этому состоянию доверять?
После объединения вызывается метод прямого прохода родительского класса. Это означает, что итоговое совместное представление передается в следующий уровень обработки, уже внутри базового Spike-Convolution блока. То есть финальная свёртка работает не на исходных данных, а на подготовленной композиции из состояния и неопределённости.
if(!CNeuronSpikeConvBlock::feedForward(cConcatenated.AsObject())) ReturnFalse; //--- return true; }
Если смотреть на метод в целом, то его сила в строгой последовательности и в аккуратной стыковке двух разных логик. Первая ветвь строит карту рынка. Вторая оценивает доверие к этой карте. Третья сводит оба результата в единый объект обработки. Внутри этого контура нет лишней декоративности. Всё подчинено одной задаче: превратить рыночный поток в структурированное, вероятностно насыщенное представление, которое уже можно использовать для принятия решений.
В итоге CNeuronUncAD_MUE становится одним из центральных элементов всей архитектуры UncAD. Именно здесь рынок впервые начинает восприниматься как вероятностная среда с различной степенью структурной устойчивости. Для классического трейдинга это непривычный подход. Но именно он позволяет системе раньше замечать разрушение рыночного режима и осторожнее работать в тех областях, где статистическая уверенность начинает исчезать.
Заключение
Работа посвящена решению ключевой проблемы, с которой трейдер приходит к подобным системам в самом начале. Обычная модель умеет выдавать сигнал, но не умеет показать, насколько этому сигналу вообще можно доверять в текущем рыночном режиме. Именно это и делает ее уязвимой в моменты, когда рынок перестает быть знакомым и устойчивым. В таких условиях ошибка опасна не только сама по себе — опасна уверенность, с которой она совершается.
В первой части был построен объект распределения рыночных состояний CNeuronUncAD_MSD, который переводит исходный поток наблюдений в компактную карту плотности. Тем самым анализ рынка переводится в пространство состояний, где одни зоны оказываются устойчивыми и знакомыми, а другие — разреженными и потенциально нестабильными. Это стало базой для дальнейшего анализа неопределённости.
На следующем этапе реализован энкодер неопределённости CNeuronUncAD_MUE, который поднял архитектуру на уровень выше. Он объединил карту состояния и оценку доверия к этому состоянию, сформировав совместное латентное представление рынка. Важнейший результат здесь состоит в том, что модель начала учитывать не только сам факт рыночной конфигурации, но и степень собственной уверенности в ее интерпретации. Именно это позволяет вовремя замечать переход к новым режимам и снижать риск работы в областях, где прежняя структура рынка уже не действует.
Таким образом, задача, сформулированная во введении, получила практическое решение на данном этапе работы. Мы не пытались заставить модель угадать рынок лучше любой другой. Мы построили механизм, который дает ей более важное качество — понимание границ собственной уверенности. Для трейдера это означает переход от слепого следования сигналу к более зрелой логике принятия решений, где состояние рынка и надёжность этого состояния рассматриваются совместно. При этом работа над фреймворком UncAD не завершена. В следующей статье мы продолжим его адаптацию и перейдем к дальнейшему развитию архитектуры.
Ссылки
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн-обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн-обучения моделей |
| 3 | Test.mq5 | Советник | Советник для тестирования модели |
| 4 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
| 5 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
| 6 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL-программы |
Проект представлен на forge.mql5.io/dng.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Фундаментальные предобученные модели в трейдинге: прогнозирование временных рядов с TimesFM 2.5 от Google в MetaTrader 5
Разработка инструментария для анализа Price Action (Часть 49): Интеграция индикаторов тренда, моментума и волатильности в единую систему на MQL5
Разработка инструментария для анализа Price Action (Часть 50): Создание модуля согласования сигналов RVGI, CCI и SMA на MQL5
Архитектура машинного обучения для MetaTrader 5 (Часть 13): Реализация расчета размера позиции в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования