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

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

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

Введение

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

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

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

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

В авторской архитектуре эта идея реализуется через два ключевых механизма. Первый — Topological Trajectory Matching, или TTM. В исходной постановке модель строит несколько возможных траекторий движения автомобиля и выбирает ту, которая лучше согласуется с предыдущим планом. Для сравнения используется расстояние Хаусдорфа. Оно смотрит не только на среднее отклонение, а на форму траектории и на наиболее опасные расхождения. В торговой интерпретации это особенно важно: новый сценарий может быть похож на прежний на большей части горизонта, но в критической зоне предусматривать разворот позиции. Средняя метрика легко сгладит такое отличие. Расстояние Хаусдорфа, наоборот, подсветит проблемный участок.

Второй механизм — Momentum Planning Interactor, или MPI. Одного выбора наиболее согласованного сценария недостаточно. Текущий срез рынка может быть искажён новостным выбросом, ложным пробоем, расширением спреда, тонкой ликвидностью или обычной микроструктурной дрожью цены. Если модель слишком доверяет только текущему состоянию, она будет постоянно переоценивать такие фрагменты. MPI решает другую задачу: он уточняет выбранный сценарий через историю ранее принятых решений и снова сверяет его с актуальным состоянием рынка.

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

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

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


Модуль TTM

Первым практическим блоком Momentum-Aware Planning становится модуль Topological Trajectory Matching (TTM). Его задача — согласовать текущий набор сценариев-кандидатов с ранее принятым планом. В автомобильной задаче это защищает модель от резких изменений траектории без достаточных оснований. В торговле смысл тот же: система не должна менять торговую гипотезу только потому, что один из новых вариантов временно получил немного более высокую оценку.

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

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

Основой сравнения служит расстояние Хаусдорфа. Обычная евклидова метрика хорошо показывает среднее отклонение между точками, но плохо замечает локальные критические расхождения. Для трейдера это важная разница. Два прогноза могут быть похожи на большей части горизонта, но в одной зоне давать противоположные выводы: один предполагает удержание позиции, другой — закрытие или разворот. Средняя ошибка в таком случае может выглядеть терпимой, хотя торговые последствия будут совершенно разными. Расстояние Хаусдорфа помогает выделить именно такие опасные участки.

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

Реализация модуля вынесена в класс CNeuronMomADTTM, наследуемый от базового класса CNeuronBaseOCL. Класс получает два информационных потока: набор текущих сценариев-кандидатов и стек ранее принятых решений. На выходе формируется один латентный вектор — агрегированный сценарий, который лучше остальных согласуется с накопленной историей.

class CNeuronMomADTTM   :  public CNeuronBaseOCL
  {
protected:
   uint                 iCandidates;
   uint                 iDecisions;
   uint                 iDimension;
   uint                 iTopK;
   CNeuronBaseOCL       cDistance;
   CNeuronBaseOCL       cMeanDistance;
   CNeuronSparseSoftMax cScores;
   CBufferFloat         cCandidatesGradient;
   //---
   virtual bool      MeanScores(void);
   virtual bool      MeanScoresGradient(void);
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; }
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override { return true; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL,
                                        CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient,
                                        ENUM_ACTIVATION SecondActivation = None) override;

public:
                     CNeuronMomADTTM(void) : iCandidates(0),
                                             iDecisions(0),
                                             iDimension(0),
                                             iTopK(0) {};
                    ~CNeuronMomADTTM(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint latent_dim, uint candidates, uint decisions,
                          uint topK, ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronMomADTTM; }
   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      Clear(void) override;
  };

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

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

bool CNeuronMomADTTM::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                           uint latent_dim, uint candidates, uint decisions,
                           uint topK, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(latent_dim == 0 || candidates == 0 || decisions == 0 ||
      topK == 0 || topK > candidates)
      ReturnFalse;
//---
   iCandidates = candidates;
   iDecisions = decisions;
   iDimension = latent_dim;
   iTopK = topK;

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

if(!CNeuronBaseOCL::Init(0, myIndex, open_cl, latent_dim, optimization_type, batch))
      ReturnFalse;
   SetActivationFunction(None);

Далее создаются вспомогательные структуры. cDistance хранит промежуточные расстояния между текущими кандидатами и элементами истории. cMeanDistance аккумулирует итоговую оценку согласованности каждого кандидата. cScores превращает эти оценки в разреженное распределение весов. cCandidatesGradient используется при обратном проходе, чтобы сохранить градиенты по кандидатным сценариям.

if(!cMeanDistance.Init(0, 0, OpenCL, iCandidates, optimization, iBatch))
      ReturnFalse;
   cMeanDistance.SetActivationFunction(None);
   if(!cScores.Init(0, 1, OpenCL, 1, iCandidates, iTopK, optimization, iBatch))
      ReturnFalse;
   cScores.SetActivationFunction(None);
//---
   int distance_total = int(iCandidates * iDecisions);
   int top_total = int(distance_total * iTopK);
   int candidates_total = int(iCandidates * iDimension);
   if(!cDistance.Init(0, 2, OpenCL, distance_total, optimization, iBatch) ||
      !cCandidatesGradient.BufferInit(candidates_total, 0))
      ReturnFalse;
   cDistance.SetActivationFunction(None);
   if(!cCandidatesGradient.BufferCreate(OpenCL))
      ReturnFalse;

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

   CBufferFloat* dist_index=cDistance.getPrevOutput();
   dist_index.BufferFree();
   if(!dist_index.BufferInit(top_total,0) ||
      !dist_index.BufferCreate(OpenCL))
     ReturnFalse;
//---
   return true;
  }

После инициализации модуль готов к прямому проходу. Метод feedForward принимает два входа. Первый — матрица сценариев-кандидатов размером iCandidates × iDimension. Второй — история решений размером iDecisions × iDimension. Перед запуском вычислений проверяются указатели, размеры входных буферов и наличие OpenCL-буфера для второго входа.

bool CNeuronMomADTTM::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!OpenCL || !NeuronOCL || !SecondInput)
      ReturnFalse;
   if(NeuronOCL.Neurons() != int(iCandidates * iDimension) ||
      Neurons() != int(iDimension) ||
      SecondInput.Total() != int(iDecisions * iDimension))
      ReturnFalse;
   if(SecondInput.GetIndex() < 0 && !SecondInput.BufferCreate(OpenCL))
      ReturnFalse;

Дальше запускается ключевая операция SparseSoftmaxHausdorff. Алгоритмически она проходит по парам «кандидат — историческое решение», вычисляет меру расхождения в латентном пространстве и сохраняет индексы наиболее значимых связей. Для трейдера это можно представить так: каждый новый сценарий сравнивается не с абстрактным средним прошлым, а с конкретными решениями, которые модель уже принимала на предыдущих шагах.

   if(!SparseSoftmaxHausdorff(NeuronOCL.getOutput(), SecondInput, cDistance.getOutput(),
                              cDistance.getPrevOutput(), int(iDimension), int(iTopK)))
      ReturnFalse;

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

   if(!MeanScores())
      ReturnFalse;
   if(!cScores.FeedForward(cMeanDistance.AsObject()))
      ReturnFalse;

Финальный выход формируется операцией SparseMatMul. Она берёт индексы выбранных кандидатов, их веса и исходные латентные представления сценариев. Результатом становится вектор размерности iDimension. Именно он передаётся дальше как Q_TTM — выбранный и согласованный сценарий текущего шага.

   if(!SparseMatMul(cScores.GetIndexes(), cScores.getOutput(), NeuronOCL.getOutput(),
                    Output, 1, int(iTopK), int(iCandidates), int(iDimension)))
      ReturnFalse;
//---
   return true;
  }

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

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

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


Модуль MPI

После TTM мы получаем выбранный сценарий Q_TTM. Он уже согласован с историей предыдущих решений, но этого всё ещё недостаточно. Текущий рынок может содержать новую информацию, а история — устаревшие элементы. Поэтому следующий блок должен решить более тонкую задачу: не просто выбрать сценарий, а уточнить его через память модели и актуальный рыночный срез. Эту роль выполняет Momentum Planning Interactor (MPI).

В оригинальной архитектуре MomAD модуль MPI работает с выбранным Planning Query и историей предыдущих запросов планирования. В торговой адаптации смысл сохраняется, но меняется интерпретация данных. История — это не набор старых сигналов, которым нужно слепо следовать. Это стек уже уточнённых сценариев поведения. Рыночный срез — это текущий контекст, через который эту историю нужно переоценить. А Q_TTM — текущая гипотеза, которую необходимо проверить на связь с обновлённой памятью.

Реализация помещена в класс CNeuronMomADMPI, наследуемый от CNeuronCrossAttention. Наследование выбрано не случайно: финальный этап работы MPI — это кросс-внимание уточнённого сценария к текущему состоянию рынка. Внутри класса собраны стек памяти, встроенный модуль TTM и два дополнительных слоя кросс-внимания.

class CNeuronMomADMPI  :  public CNeuronCrossAttention
  {
protected:
   CNeuronAddToStack   cStack;
   CNeuronMomADTTM     cTTM;
   CNeuronCrossAttention cPolicyMarket;
   CNeuronCrossAttention cTTMHistory;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; }
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL,
                                        CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient,
                                        ENUM_ACTIVATION SecondActivation = None) override;

public:
                     CNeuronMomADMPI(void) { cContext = NULL; };
                    ~CNeuronMomADMPI(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint latent_dim, uint candidates, uint market_dim,
                          uint market_units, uint stack_size, uint topK,
                          uint heads, uint key_dimension,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronMomADMPI; }
   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;
  };

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

cTTM выполняет выбор текущего сценария из набора кандидатов с учётом этой памяти. cPolicyMarket актуализирует стек решений через рыночный срез: история используется как запрос, а рынок — как ключи и значения. cTTMHistory связывает выбранный Q_TTM с обновлённой историей. Родительский CNeuronCrossAttention выполняет финальную сверку результата с тем же самым текущим рыночным срезом. Таким образом, рынок участвует в вычислениях дважды, но это один и тот же входной поток: сначала он обновляет актуальность истории, затем уточняет выбранный сценарий.

Метод Init начинается с проверки параметров. Здесь контролируются размерности латентного пространства и рыночных признаков, число кандидатов, глубина стека, количество голов внимания и размерность ключей. Условие topK <= candidates защищает конфигурацию от невозможного отбора: нельзя выбрать больше лучших сценариев, чем было подано на вход.

bool CNeuronMomADMPI::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                           uint latent_dim, uint candidates, uint market_dim,
                           uint market_units, uint stack_size, uint topK,
                           uint heads, uint key_dimension,
                           ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(latent_dim == 0 || candidates == 0 || market_dim == 0 || market_units == 0 ||
      stack_size == 0 || topK == 0 || topK > candidates || heads == 0 ||
      key_dimension == 0)
      ReturnFalse;

После проверки создаётся родительский слой кросс-внимания. Он будет использован в конце прямого прохода, когда результат TTM_History снова сопоставляется с текущим состоянием рынка. Здесь запрос имеет размерность latent_dim, а ключи и значения берутся из рыночного потока размером market_dim × market_units.

//--- final MPI refine: Q_hist attends to market state
   if(!CNeuronCrossAttention::Init(numOutputs, myIndex, open_cl, latent_dim,
                                   key_dimension, heads, 1, market_dim,
                                   market_units, optimization_type, batch))
      ReturnFalse;
   SetActivationFunction(None);

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

//--- history stack and internal MPI components
   if(!cStack.Init(0, 0, OpenCL, stack_size, latent_dim, 1, optimization, iBatch))
      ReturnFalse;

Следующим создаётся встроенный модуль TTM. Он будет сравнивать текущие сценарии-кандидаты со стеком решений и формировать Q_TTM. Параметр stack_size здесь используется как длина истории решений, а topK задаёт число наиболее релевантных кандидатов, участвующих в итоговой агрегации.

   if(!cTTM.Init(0, 1, OpenCL, latent_dim, candidates, stack_size, topK,
                 optimization, iBatch))
      ReturnFalse;

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

   if(!cPolicyMarket.Init(0, 2, OpenCL, latent_dim, key_dimension, heads,
                          stack_size, market_dim, market_units, optimization, iBatch))
      ReturnFalse;

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

   if(!cTTMHistory.Init(0, 3, OpenCL, latent_dim, key_dimension, heads,
                        1, latent_dim, stack_size, optimization, iBatch))
      ReturnFalse;
//---
   return Clear();
  }

После создания всех компонентов вызывается Clear. В результате формируется вычислительный контур: стек решений, выбор сценария через TTM, актуализация истории через рынок, сопоставление Q_TTM с обновлённой историей и финальная проверка полученного результата через тот же рыночный срез.

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

bool CNeuronMomADMPI::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!OpenCL || !NeuronOCL || !SecondInput)
      ReturnFalse;
   if(SecondInput.Total() != int(iWindow_K * iUnits_K))
      ReturnFalse;
   if(SecondInput.GetIndex() < 0 && !SecondInput.BufferCreate(OpenCL))
      ReturnFalse;

Первый вычислительный шаг — обновление стека. В стек помещается результат предыдущего прямого прохода MPI. Операция выполняется до перезаписи текущего Output; память получает именно прошлый итоговый сценарий поведения.

//--- push the previous MPI scenario before overwriting Output
   if(!cStack.FeedForward(GetPointer(this)))
      ReturnFalse;

Алгоритмически это можно записать как History ← Push(Previous_MPI_Output). Такой порядок делает стек устойчивым: в нём хранятся не промежуточные состояния, а решения, уже прошедшие полный цикл уточнения. Для трейдера это ближе к журналу принятых торговых гипотез, а не к набору случайных индикаторных значений.

Затем встроенный TTM выбирает текущий сценарий. На вход поступают сценарии-кандидаты и стек решений. Выходом становится Q_TTM — сценарий, который лучше остальных согласуется с предыдущей линией поведения модели.

//--- TTM builds the weighted scenario from the strategy pool and recent scenarios
   if(!cTTM.FeedForward(NeuronOCL, cStack.getOutput()))
      ReturnFalse;

После этого история переоценивается через рынок. Здесь cPolicyMarket выполняет кросс-внимание, где Query = History, а Key и Value берутся из текущего рыночного среза. Это не добавление новой записи в стек, а обновление актуальности уже накопленных решений.

//--- D_cur = actions stack attends to market
   if(!cPolicyMarket.FeedForward(cStack.AsObject(), SecondInput))
      ReturnFalse;

Формально этот этап можно обозначить как D_cur = Attention(History, Market, Market). Если рынок показывает признаки ослабления тренда, соответствующие элементы истории получают меньший вес. Если текущая структура подтверждает прежний сценарий, связанная с ним часть памяти усиливается. Так модель учится не просто помнить прошлое, а проверять его применимость к текущему рынку.

Далее Q_TTM взаимодействует с актуализированной историей. В этом слое выбранный сценарий является запросом, а обновлённая память — ключами и значениями. Результат обозначим как TTM_History.

//--- Q_hist = TTM scenario attends to the current action-aware history
   if(!cTTMHistory.FeedForward(cTTM.AsObject(), cPolicyMarket.getOutput()))
      ReturnFalse;

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

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

//--- parent cross-attention is the final MPI market refine block
   if(!CNeuronCrossAttention::feedForward(cTTMHistory.AsObject(), SecondInput))
      ReturnFalse;
//---
   return true;
  }

Формально результат можно записать как Output = Attention(TTM_History, Market, Market). Это и есть итоговый сценарий поведения, который будет использован следующими слоями модели. На следующем прямом проходе именно этот результат попадёт в стек и станет частью истории решений.

Прямой проход можно представить как пять последовательных операций. На вход поступают два потока: сценарии-кандидаты и рыночный срез. Сценарии проходят через TTM и формируют Q_TTM. Предыдущий итоговый результат MPI добавляется в стек решений. Стек взаимодействует с текущим рынком и превращается в актуализированную историю. Затем Q_TTM взаимодействует с этой историей, формируя TTM_History. Наконец, TTM_History снова проходит кросс-внимание к тому же рыночному срезу и превращается в итоговый сценарий поведения.

Momentum Planning Interactor


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

В обратном проходе градиент сначала входит в финальный слой кросс-внимания и распределяется между TTM_History и рыночным срезом. Затем он проходит через cTTMHistory к Q_TTM и актуализированной истории. Далее градиент разделяется между cPolicyMarket и cTTM. Так обучение затрагивает все ключевые связи: выбор сценария, актуализацию памяти, связь выбранного сценария с историей и финальную сверку с рынком. Поскольку один и тот же рыночный срез участвует в нескольких операциях внимания, его вклад в обучение накапливается из нескольких путей.

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


Заключение

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

Сначала мы реализовали модуль CNeuronMomADTTM. Он сопоставляет текущие сценарии-кандидаты с историей ранее принятых решений, оценивает их согласованность и формирует Q_TTM — выбранный сценарий текущего шага. Затем был собран модуль CNeuronMomADMPI. Он уточняет выбранный сценарий с учётом памяти и текущего рыночного среза. Стек пополняется предыдущим итогом, история переоценивается через рынок, затем Q_TTM сопоставляется с актуализированной историей, и результат ещё раз проверяется через тот же рыночный контекст.

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


Ссылки


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

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

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

Прикрепленные файлы |
MQL5.zip (3819.89 KB)
Моделирование рынка: Position View (I) Моделирование рынка: Position View (I)
Контент, который мы будем рассматривать с этого момента, гораздо сложнее с точки зрения теории и концепций. Я постараюсь сделать содержание как можно более простым. Сама программная часть довольно проста и понятна. Но если вы не понимаете стоящую за этим теорию, вы останетесь совершенно без ресурсов для доработки или даже адаптации системы репликации/моделирования под задачи, отличающиеся от тех, что я собираюсь показать. Я не хочу, чтобы вы просто компилировали и использовали код, который я показываю. Я хочу, чтобы вы учились, разбирались и, если возможно, могли создать что-то еще лучше.
Торговые инструменты MQL5 (Часть 27): Отрисовка параметрической кривой-бабочки средствами Canvas Торговые инструменты MQL5 (Часть 27): Отрисовка параметрической кривой-бабочки средствами Canvas
В этой статье мы исследуем кривую-бабочку, параметрическое математическое уравнение, и визуализируем ее на canvas в MQL5. Мы создаём интерактивное окно визуализации с перетаскиваемым окном canvas с изменяемым размером, рендерингом кривых с использованием технологии суперсэмплирования, градиентными фонами и легендой, сегментированной по цветам. В итоге у нас есть полнофункциональный визуальный инструмент, который отрисовывает кривую-бабочку непосредственно на графике MetaTrader 5.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От начального до среднего уровня: События в объектах (III) От начального до среднего уровня: События в объектах (III)
В данной статье мы подготовим базу для того, что будет рассмотрено в следующей публикации. Мы также рассмотрим, как разрешить редактирование и перемещение объекта типа OBJ_LABEL в полностью интерактивном режиме. Иными словами, мы можем изменить как текст, так и положение объекта OBJ_LABEL, не открывая окно свойств объекта.