preview
Нейросети в трейдинге: Принятие торговых решений с учётом неопределённости (Окончание)

Нейросети в трейдинге: Принятие торговых решений с учётом неопределённости (Окончание)

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

Введение

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

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

С этой точки мы начали адаптацию фреймворка Uncertainty-aware End-to-end Autonomous Driving к задачам финансовых рынков. В оригинальной работе неопределённость помогает безопаснее планировать движение автономного автомобиля. Модель оценивает не только окружающую сцену, но и степень доверия к этой оценке. Для трейдинга такая логика выглядит естественно. Вместо дорожной сцены мы рассматриваем рыночную среду. Вместо объектов на карте — состояния цены, объёма, волатильности и производных признаков. Вместо траектории автомобиля — последовательность торговых решений, где ошибка может обернуться реальной просадкой депозита.

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

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

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

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

Теперь мы на заключительном этапе. Отдельные элементы уже реализованы, но сами по себе они не образуют торговую систему. Их нужно объединить в единый вычислительный контур: согласовать потоки данных, определить порядок прямого прохода, обеспечить передачу градиента (где это допустимо) и встроить решение в общий контур принятия торговых решений. Только после этого можно ответить на главный вопрос: даёт ли добавление неопределённости практическое преимущество на исторических данных?

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

Авторская визуализация фреймворка UncAD


Построение объекта верхнего уровня

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

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

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

class CNeuronUncAD : public CNeuronSpikeConvBlock
  {
protected:
   uint                         iInputDim;
   uint                         iAgentUnits;
   uint                         iContextUnits;
   uint                         iEmbedSize;
   uint                         iQuantiles;
   uint                         iCandidates;
   uint                         iTopK;
   //---
   CNeuronUniMixerTokenizer     cTokenizer;
   CNeuronBaseOCL               cAgents;
   CNeuronBaseOCL               cContext;
   CNeuronUncAD_MUE             cMUE;
   CNeuronUncAD_UGP             cUGP;
   CNeuronUncAD_UGPL            cUGPL;
   CNeuronSpikeMHCrossAttention cMapInteraction;
   //--- tau heads
   CNeuronConvOCL               cTauAValues;
   CNeuronConvOCL               cTauAScores;
   CNeuronSparseSoftMax         cTauASoftMax;
   CNeuronBaseOCL               cTauAWeighted;
   CNeuronConvOCL               cTauEValues;
   CNeuronConvOCL               cTauEScores;
   CNeuronSparseSoftMax         cTauESoftMax;
   CNeuronBaseOCL               cTauEWeighted;
   //--- selection/fusion
   CNeuronBaseOCL               cFusionInput;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      TauAForward(void);
   virtual bool      TauEForward(void);
   virtual bool      TauAGradients(void);
   virtual bool      TauEGradients(void);

public:
                     CNeuronUncAD(void) :  iInputDim(0),
                                           iAgentUnits(0),
                                           iContextUnits(0),
                                           iEmbedSize(0),
                                           iQuantiles(0),
                                           iCandidates(0),
                                           iTopK(0) {};
                    ~CNeuronUncAD(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &dimensions[], uint units_s, uint heads,
                          uint stack_size, uint quantiles, uint embed_size,
                          uint candidates, uint topK, uint latent_count,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronUncAD;  }
   //--- 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 и внутренними слоями. В данном случае CNeuronUncAD становится не отдельным вычислительным фильтром, а полноценным архитектурным узлом, внутри которого собрана логика uncertainty-aware анализа рыночной среды.

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

Параметр iAgentUnits задаёт размерность представления торгового агента. В нашей задаче агент — это не внешний участник рынка, а внутреннее состояние торговой системы. Параметр iContextUnits определяет размерность рыночного контекста.

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

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

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

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

После параметров конфигурации в классе объявляются внутренние модули архитектуры. Первым стоит cTokenizer. Это объект CNeuronUniMixerTokenizer, который выполняет первичное преобразование входной последовательности. Его задача — перевести исходные рыночные признаки в форму, удобную для последующей обработки нейронными слоями. Иначе говоря, tokenizer отделяет сырой поток данных от латентного представления, с которым дальше работает UncAD. Для финансовых временных рядов это особенно важно: признаки могут иметь разную природу, масштаб и динамику.

Далее объявлены два базовых буфера: cAgents и cContext. Они разделяют внутреннее представление на два смысловых потока. cAgents хранит состояние торгового агента, а cContext — рыночный контекст. Такое разделение не позволяет слишком рано смешивать всё в один общий вектор. Сначала модель отдельно формирует описание рынка и положение торгового агента, а затем организует их взаимодействие. Это делает архитектуру более прозрачной и устойчивой.

Центральным элементом первой части архитектуры является cMUE — объект CNeuronUncAD_MUE. Этот модуль отвечает за оценку неопределённости рыночной среды. Он использует карту плотности рыночных состояний и формирует представление, в котором обычное описание рынка дополняется оценкой уверенности. По сути, cMUE помогает модели ответить на вопрос: насколько текущая ситуация похожа на режимы из обучающей истории и насколько можно доверять сформированному латентному описанию.

Следующий компонент, cUGP, реализует контур Uncertainty-Guided Prediction. Его задача — использовать полученное uncertainty-aware представление при прогнозировании дальнейшего развития рыночной ситуации. Здесь модель анализирует взаимодействие между признаками, сопоставляет их с картой рыночных состояний и формирует прогнозный контекст. При этом прогноз не отрывается от оценки неопределённости. Это отличает текущую архитектуру от классической модели, которая выдаёт направление или ожидаемое значение без явного понимания надёжности текущего режима.

Компонент cUGPL отвечает за Uncertainty-Guided Planning. Это следующий логический уровень. После получения прогнозного контекста модель должна перейти к представлению возможного действия. В торговле такой переход особенно тонок. Прогноз сам по себе ещё не является сделкой: можно правильно оценить направление рынка, но ошибиться с моментом входа, размером позиции или допустимым риском. Поэтому планировщик учитывает не только рынок, но и состояние торгового агента. Именно здесь связка cContext и cAgents приобретает практический смысл.

Для организации взаимодействия с картой рыночных состояний в классе предусмотрен модуль cMapInteraction типа CNeuronSpikeMHCrossAttention. Это механизм многоголового кросс-внимания, адаптированный к уже используемой нами spiking-логике. Он позволяет одному потоку представлений обращаться к другому и извлекать из него значимую информацию. В данном случае речь идёт о взаимодействии между внутренними latent-представлениями и картой рыночного контекста. Проще говоря, текущая ситуация не анализируется в пустоте. Она постоянно сопоставляется с накопленной статистической картой рынка.

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

Использование CNeuronSparseSoftMax в tau-головах особенно важно. Обычный SoftMax распределяет вероятность между всеми элементами, и даже слабые связи получают некоторый вес. В задачах обработки изображений или текста это не всегда критично. Но в трейдинге лишние слабые зависимости часто превращаются в паразитные сигналы. Разреженная нормализация здесь выглядит более уместной. Она позволяет модели сказать: в этой ситуации значимы только несколько вариантов, остальные лучше не учитывать. Для финансовых рынков это здравый подход. Рынок и сам не любит, когда каждый тик пытаются объяснить красивой историей.

Метод инициализации выполняет основную настройку объекта и одновременно проверяет корректность конфигурации перед созданием внутренних модулей.

bool CNeuronUncAD::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                        uint &dimensions[], uint units_s, uint heads,
                        uint stack_size, uint quantiles, uint embed_size,
                        uint candidates, uint topK, uint latent_count,
                        ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(dimensions.Size() < 2 || units_s == 0 || heads == 0 || stack_size == 0 ||
      quantiles == 0 || embed_size == 0 || candidates < 2 || topK == 0 ||
      topK > candidates || latent_count == 0 || embed_size % heads != 0)
      ReturnFalse;

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

После проверки вычисляются основные внутренние размерности архитектуры. На основании массива dimensions определяется суммарная размерность входного пространства, отдельно сохраняются размеры агентского и контекстного потоков, а также параметры embedding-пространства и карты квантилей.

   iInputDim = dimensions[0] * units_s;
   for(uint i = 1; i < dimensions.Size(); i++)
     {
      if(dimensions[i] == 0)
         ReturnFalse;
      iInputDim += dimensions[i];
     }
   if(dimensions[0] == 0)
      ReturnFalse;
   iAgentUnits = units_s;
   iContextUnits = dimensions.Size() - 1;
   iEmbedSize = embed_size;
   iQuantiles = quantiles;
   iCandidates = candidates;
   iTopK = topK;

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

   uint agent_dim = iAgentUnits * iEmbedSize;
   uint context_dim = iContextUnits * iEmbedSize;
   uint qm_size = iAgentUnits * iEmbedSize * (2 * iQuantiles + 1);
   uint fusion_size = agent_dim + context_dim + qm_size;
   if(!CNeuronSpikeConvBlock::Init(numOutputs, myIndex, open_cl, fusion_size, fusion_size,
                                   latent_count, 1, 1, optimization_type, batch))
      ReturnFalse;

Следующим шагом формируется описание всех входных потоков для токенизатора. Для агентских элементов используется первая размерность входного массива. Контекстные признаки получают собственные размеры из оставшейся части массива dimensions. Это позволяет привести разнородные входные данные к единому embedding-представлению без потери информации о структуре исходных признаков.

   uint token_units = iAgentUnits + iContextUnits;
   uint all_dimensions[];
   if(ArrayResize(all_dimensions, token_units) < int(token_units))
      ReturnFalse;
   for(uint i = 0; i < iAgentUnits; i++)
      all_dimensions[i] = dimensions[0];
   for(uint i = 0; i < iContextUnits; i++)
      all_dimensions[iAgentUnits + i] = dimensions[i + 1];
//---
   uint index = 0;
   if(!cTokenizer.Init(0, index, OpenCL, all_dimensions, token_units, iEmbedSize,
                       token_units, iCandidates, iTopK, optimization, iBatch))
      ReturnFalse;

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

   index++;
   if(!cAgents.Init(0, index, OpenCL, agent_dim, optimization, iBatch))
      ReturnFalse;
   cAgents.SetActivationFunction(None);
   index++;
   if(!cContext.Init(0, index, OpenCL, context_dim, optimization, iBatch))
      ReturnFalse;
   cContext.SetActivationFunction(None);

После этого настраиваются модуль оценки неопределённости MUE, прогнозный блок UGP, планировщик UGPL и механизм взаимодействия с картой рыночных состояний на основе многоголового кросс-внимания.

   index++;
   if(!cMUE.Init(0, index, OpenCL, iEmbedSize, iEmbedSize, iAgentUnits,
                 stack_size, iQuantiles, 1.0f, optimization, iBatch))
      ReturnFalse;
   index++;
   if(!cUGP.Init(0, index, OpenCL, iEmbedSize, iAgentUnits,
                 iEmbedSize, iAgentUnits * (2 * iQuantiles + 1),
                 heads, iEmbedSize / heads, candidates, topK, optimization, iBatch))
      ReturnFalse;
   index++;
   if(!cUGPL.Init(0, index, OpenCL, iEmbedSize, iContextUnits, iEmbedSize, iAgentUnits,
                  candidates, heads, iEmbedSize / heads, optimization, iBatch))
      ReturnFalse;
   index++;
   if(!cMapInteraction.Init(0, index, OpenCL, iEmbedSize, iEmbedSize / heads, heads,
                            iContextUnits, iEmbedSize, iAgentUnits * (2 * iQuantiles + 1),
                            optimization, iBatch))
      ReturnFalse;

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

//--- tau A
   index++;
   if(!cTauAValues.Init(0, index, OpenCL, iEmbedSize, iEmbedSize,
                        iCandidates * iEmbedSize, iAgentUnits, 1, optimization, iBatch))
      ReturnFalse;
   cTauAValues.SetActivationFunction(SoftPlus);
   index++;
   if(!cTauAScores.Init(0, index, OpenCL, iEmbedSize, iEmbedSize,
                        iCandidates, iAgentUnits, 1, optimization, iBatch))
      ReturnFalse;
   cTauAScores.SetActivationFunction(None);
   index++;
   if(!cTauASoftMax.Init(0, index, OpenCL, iAgentUnits, iCandidates, iTopK, optimization, iBatch))
      ReturnFalse;
   index++;
   if(!cTauAWeighted.Init(0, index, OpenCL, agent_dim, optimization, iBatch))
      ReturnFalse;
   cTauAWeighted.SetActivationFunction(None);

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

//--- tau E
   index++;
   if(!cTauEValues.Init(0, index, OpenCL, iEmbedSize, iEmbedSize,
                        iCandidates * iEmbedSize, iContextUnits, 1, optimization, iBatch))
      ReturnFalse;
   cTauEValues.SetActivationFunction(SoftPlus);
   index++;
   if(!cTauEScores.Init(0, index, OpenCL, iEmbedSize, iEmbedSize,
                        iCandidates, iContextUnits, 1, optimization, iBatch))
      ReturnFalse;
   cTauEScores.SetActivationFunction(None);
   index++;
   if(!cTauESoftMax.Init(0, index, OpenCL, iContextUnits, iCandidates, iTopK, optimization, iBatch))
      ReturnFalse;
   index++;
   if(!cTauEWeighted.Init(0, index, OpenCL, iContextUnits * iEmbedSize, optimization, iBatch))
      ReturnFalse;
   cTauEWeighted.SetActivationFunction(None);

На завершающем этапе создаётся буфер cFusionInput. Он объединяет результаты обеих tau-ветвей и карту неопределённости в единое представление.

//--- fusion
   index++;
   if(!cFusionInput.Init(0, index, OpenCL, cTauAWeighted.Neurons() + cTauEWeighted.Neurons() + qm_size,
                         optimization, iBatch))
      ReturnFalse;
   cFusionInput.SetActivationFunction(None);
//---
   return true;
  }

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

Метод feedForward реализует полный вычислительный граф архитектуры.

bool CNeuronUncAD::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL || NeuronOCL.Neurons() < int(iInputDim))
      ReturnFalse;

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

//--- Unified tokenization: market agents first, context tokens second
   if(!cTokenizer.FeedForward(NeuronOCL))
      ReturnFalse;
   if(!DeConcat(cAgents.getOutput(), cContext.getOutput(), cTokenizer.getOutput(),
                cAgents.Neurons(), cContext.Neurons(), 1))
      ReturnFalse;

После этого результат разделяется на два потока: агентское представление сохраняется в cAgents, а рыночный контекст — в cContext.

Далее запускается контур uncertainty-aware обработки. Модуль cMUE формирует представление неопределённости на основе агентского состояния, после чего блок cUGP строит прогнозное представление с учётом полученной оценки уверенности.

//--- Uncertainty-guided prediction/planning graph
   if(!cMUE.FeedForward(cAgents.AsObject()))
      ReturnFalse;
   if(!cUGP.FeedForward(cAgents.AsObject(), cMUE.getOutput()))
      ReturnFalse;

Следующим шагом cUGPL использует рыночный контекст и прогнозное представление для формирования планового представления, а cMapInteraction организует его взаимодействие с картой рыночных состояний.

   if(!cUGPL.FeedForward(cContext.AsObject(), cUGP.getOutput()))
      ReturnFalse;
   if(!cMapInteraction.FeedForward(cUGPL.AsObject(), cMUE.getOutput()))
      ReturnFalse;

Затем выполняются методы TauAForward и TauEForward, которые формируют и взвешивают кандидатные представления для агентского и рыночного потоков соответственно.

//--- Differentiable tau proposals
   if(!TauAForward() || !TauEForward())
      ReturnFalse;

Полученные результаты объединяются вместе с выходом cMUE в буфере cFusionInput, формируя единое uncertainty-aware представление.

//--- Proposal fusion for critic/actor head
   if(!Concat(cTauAWeighted.getOutput(), cTauEWeighted.getOutput(), cMUE.getOutput(),
              cFusionInput.getOutput(),
              cTauAWeighted.Neurons(), cTauEWeighted.Neurons(), cMUE.Neurons(), 1))
      ReturnFalse;

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

   if(!CNeuronSpikeConvBlock::feedForward(cFusionInput.AsObject()))
      ReturnFalse;
//---
   return true;
  }

Таким образом, CNeuronUncAD становится итоговой оболочкой всей архитектуры. Внутри него объединяются tokenizer, представления торгового агента и рыночного контекста, модуль оценки неопределённости, прогнозный блок, планировщик, механизм взаимодействия с картой и tau-головы отбора кандидатных представлений. Такой объект завершает переход от отдельных компонентов к полноценной модели. Теперь архитектура может пройти полный путь от входной последовательности рыночных данных до компактного uncertainty-aware представления, которое будет использоваться следующим блоком для принятия торгового решения.


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

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

Именно здесь начинается главная проверка всей идеи. Можно построить сложную архитектуру и связать блоки внимания, карту плотности, tau-головы и контур планирования. Практический смысл появляется только если модель устойчиво работает на данных и сохраняет качество при переходе от обучающей выборки к новому участку рынка. Финансовый рынок быстро отделяет красивую схему от полезного инструмента.

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

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

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

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

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

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

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

Результаты тестирования Результаты тестирования

По итогам тестирования модель получила чистую прибыль 1 258.25 USD, то есть более чем удвоила стартовый капитал. Это особенно важно с учётом того, что проверка выполнялась на следующем рыночном участке после периода основного обучения.

График баланса и средств показывает неоднородную, но содержательную картину. В январе и феврале система проходила сложную фазу адаптации: баланс снижался, затем восстанавливался, а Equity демонстрировала заметные колебания. Наиболее напряжённый участок пришёлся на конец февраля — начало марта. Именно здесь проявилась основная просадка. По отчёту максимальная просадка баланса составила 641.72 USD, или 51.89%, а максимальная просадка по средствам достигла 935.19 USD, или 62.90%. Это серьёзный риск, который нельзя скрывать за итоговой прибылью. Модель получила прибыль, но сделала это ценой высокой нагрузки на депозит.

После середины марта характер кривой заметно изменился. Система вышла из зоны нестабильности и начала формировать более устойчивое восходящее движение. В марте–апреле баланс и средства двигались уже более согласованно. Рост результата стал выглядеть не как одиночный удачный скачок, а как серия рабочих решений. Всего было совершено 310 сделок. Доля прибыльных составила 43.87%. При этом средняя прибыльная сделка была заметно больше средней убыточной: 41.42 USD против 25.14 USD. Благодаря этому Gross Profit достиг 5 632.72 USD при Gross Loss: 4 374.47 USD, Profit Factor составил 1.29, а значение Sharpe Ratio достигло 1.57, что указывает на неслучайный характер итоговой прибыли.

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


Заключение

В этой статье мы завершили практическую адаптацию фреймворка UncAD к задачам финансовых рынков. Ранее были реализованы карта плотности рыночных состояний, модуль оценки неопределённости, контур прогнозирования и блок планирования. Теперь эти компоненты собраны в единую архитектуру верхнего уровня CNeuronUncAD. Она связывает рыночный контекст, состояние торгового агента, uncertainty-aware представление и механизм отбора кандидатных сценариев поведения. В результате мы получили цельную модель, способную пройти путь от входной последовательности признаков до представления, пригодного для принятия торгового решения.

Главная идея работы состояла в том, чтобы сместить акцент с простого прогноза направления цены на оценку качества рыночного контекста. Модель должна не только искать сигнал, но и понимать, насколько текущая ситуация похожа на знакомые рыночные режимы и насколько можно доверять сформированному прогнозу. Результаты тестирования подтвердили практический потенциал такого подхода: на участке с января по апрель 2026 года при начальном депозите 1 000 USD модель получила чистую прибыль 1 258.25 USD. Архитектура смогла перенести выученное представление на новый рыночный участок, пройти сложную фазу адаптации и затем сформировать устойчивый рост баланса.

При этом результаты нельзя считать окончательной победой. Максимальная просадка по средствам достигла 62.90%, а уровень загрузки депозита в отдельные моменты оказался слишком высоким. Это показывает, что uncertainty-aware архитектура помогает лучше описывать рынок и осторожнее относиться к сомнительным режимам, но не заменяет строгий риск-менеджмент. Тем не менее поставленная задача решена: логика UncAD перенесена в область алгоритмического трейдинга, реализована средствами MQL5 и OpenCL, собрана в единую модель и проверена на исторических данных.


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн-обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн-обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы

Проект представлен на forge.mql5.io/dng.

Прикрепленные файлы |
MQL5.zip (3810.6 KB)
Осциллятор Parafrac: Комбинация индикаторов Parabolic SAR и Fractals Осциллятор Parafrac: Комбинация индикаторов Parabolic SAR и Fractals
Мы рассмотрим, как объединить Parabolic SAR и индикатор Fractals для создания нового индикатора осцилляторного типа. Используя сильные стороны обоих инструментов, трейдеры могут разработать более точную и эффективную торговую стратегию.
Переосмысливаем классические стратегии (Часть 20): Современная интерпретация стохастического осциллятора Переосмысливаем классические стратегии (Часть 20): Современная интерпретация стохастического осциллятора
В этой статье демонстрируется, как стохастический осциллятор (классический технический индикатор) можно использовать не только как инструмент торговли на возврате к среднему. Рассматривая индикатор с другой аналитической точки зрения, мы показываем, как знакомые стратегии могут раскрыть новую практическую ценность и лечь в основу альтернативных торговых правил, включая интерпретации следования за трендом. В конечном счете, в статье рассказывается о том, как каждый технический индикатор в терминале MetaTrader 5 содержит скрытый потенциал и как вдумчивый подход методом проб и ошибок позволяет выявить содержательные интерпретации, которые не лежат на поверхности.
Переосмысливаем классические стратегии (Часть 21): Разработка комбинированной стратегии на основе полос Боллинджера и RSI Переосмысливаем классические стратегии (Часть 21): Разработка комбинированной стратегии на основе полос Боллинджера и RSI
В этой статье рассматривается разработка комбинированной алгоритмической торговой стратегии для рынка EURUSD. Эта стратегия сочетает в себе полосы Боллинджера и индикатор относительной силы (RSI). Исходные стратегии, основанные на правилах, давали высококачественные сигналы, но страдали от низкой частоты сделок и ограниченной прибыльностью. Мы проанализировали несколько итераций стратегии, выявив недостатки в нашем понимании рынка, повышенный уровень шума и пониженную эффективность работы стратегии. Благодаря надлежащему использованию алгоритмов статистического обучения, переносу цели моделирования на технические индикаторы, правильному масштабированию и сочетанию прогнозов машинного обучения с классическими правилами торговли, конечная стратегия позволила значительно повысить прибыльность и частоту сделок при сохранении приемлемого качества сигнала.
От начального до среднего уровня: Объекты (III) От начального до среднего уровня: Объекты (III)
В сегодняшней статье мы рассмотрим, как можно реализовать очень привлекательную и интересную систему взаимодействия, особенно для тех, кто только начинает практиковаться в программировании на MQL5. В этом нет ничего принципиально нового. Благодаря моему подходу к теме будет гораздо проще понять всё, поскольку мы увидим на практике, как разрабатывается структурное программирование с довольно интересной целью.