preview
Нейросети в трейдинге: Фреймворк кросс-доменного прогнозирования временных рядов (Окончание)

Нейросети в трейдинге: Фреймворк кросс-доменного прогнозирования временных рядов (Окончание)

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

Введение

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

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

Классические статистические методы хорошо зарекомендовали себя в анализе стационарных процессов, однако их применимость к современным рыночным данным ограничена. С другой стороны, нейронные сети — включая LSTM и GRU — получили широкое распространение как более гибкие инструменты для моделирования сложных временных зависимостей. Тем не менее, и они требуют больших объемов данных для обучения, а при переходе на новые активы или рыночные условия, часто теряют точность и стабильность. Причина в том, что большинство моделей привязано к определённым структурам и паттернам данных, которые не всегда универсальны.

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

Именно под эту концепцию разработан фреймворк TimeFound, знакомство с которым мы начали в предыдущей статье. TimeFound — это мощная и универсальная кросс-доменная модель временных рядов, основанная на Transformer-архитектуре. Transformer — революционный метод, впервые предложенный в задачах обработки естественного языка, получил широкое распространение благодаря способности эффективно захватывать длинные зависимости в последовательностях. В TimeFound его потенциал используется для анализа финансовых рядов, но с важным дополнением: применением механизма Multi-Resolution Patching.

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

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

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

Авторская визуализация фреймворка представлена ниже.

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

Сегодня мы продолжим начатую работу.



Энкодер-Декодер TimeFound

Сформированный на стадии патчинга тензор токенов передаётся для дальнейшего анализа в блок Transformer. Именно здесь начинается интеллектуальная обработка данных — выявление скрытых закономерностей, прогнозирование динамики и формирование сигналов.

Авторы фреймворка TimeFound предлагают использовать в этой части архитектуры классическую схему Энкодер–Декодер, хорошо известную по моделям машинного перевода и анализа последовательностей. Однако, в контексте временных рядов эта схема была адаптирована и усилена рядом дополнений, чтобы учесть специфику данных.

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

В Декодере, наоборот, видимость намеренно ограничена — каждый токен может видеть только те, что предшествуют ему по времени. Ключевым элементом Декодера является модуль кросс-внимания (Cross-Attention). Здесь данные, полученные от Энкодера, подаются в качестве контекста, а Декодер полученные на вход токены соотносит с этой контекстной информацией. Это позволяет более точно формировать выходные предсказания.

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

В арсенале нашей библиотеки уже собрано немало вариаций модулей Self- и Cross-Attention — от базовых до более продвинутых с маскированием и динамическим выбором голов внимания. И разумеется, мы не упустим возможности воспользоваться этими наработками в рамках реализации модели TimeFound. Однако здесь важно учесть один концептуальный нюанс: авторы фреймворка предлагают использовать авторегрессионный подход к генерации прогноза.

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

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

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

Чтобы обеспечить такую синхронность, мы создадим объект CNeuronTimeFoundTransformerUnit. Его задача — централизованно управлять потоком информации. Это позволит сохранить архитектурную чистоту и упростить дальнейшее масштабирование или замену компонентов. Структура нового объекта представлена ниже.

class CNeuronTimeFoundTransformerUnit  :  public CNeuronCrossDMHAttention
  {
protected:
   CNeuronMVMHAttentionMLKV   cSelfAttention;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override
                                                              { return feedForward(NeuronOCL); }
   virtual bool      calcInputGradients(CNeuronBaseOCL *prevLayer) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None)
                                        override
                                        { return calcInputGradients(NeuronOCL); }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override
                                        { return updateInputWeights(NeuronOCL); }

public:
                     CNeuronTimeFoundTransformerUnit(void) {};
                    ~CNeuronTimeFoundTransformerUnit(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window,
                          uint window_key, uint heads, uint heads_kv, uint units_count,
                          uint layers, uint layers_to_one_kv, uint variables,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const { return defNeuronTimeFoundTransformerUnit; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
  }; 

В представленной структуре нового класса мы наблюдаем всего один внутренний объект — модуль Self-Attention, который выполняет функции Энкодера создаваемой модели. Функциональность Декодера реализуется средствами родительского класса, в качестве которого используется модуль кросс-внимания.

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

Дополнительную эффективность даёт применение механизма Multi-Layer Key-Value (MLKV) — одной из самых элегантных идей оптимизации вычислительной сложности Transformer. В классических реализациях каждый слой Self-Attention использует собственные Key и Value. Здесь же эти тензоры сохраняются и переиспользуются для нескольких слоёв, тогда как Query уточняется на каждом уровне. Такой подход снижает вычислительную нагрузку и одновременно позволяет уточнять смысл нарастающим образом — как если бы мы перечитывали знакомый текст, каждый раз замечая новые детали. Это особенно ценно в трейдинге: на первом проходе модель может увидеть локальные закономерности, а затем — всё более общие рыночные структуры.

Таким образом, Энкодер в данной реализации выполняет двойную функцию:

  1. С одной стороны — классический Bi-Directional Self-Attention, выявляющий связи между токенами в обе стороны по временной шкале.
  2. С другой — модульный анализ каждой унитарной последовательности с возможностью многослойного осмысления её структуры.

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

Во-первых, Декодер, в соответствии с классической структурой Transformer, включает модули двух видов внимания:

  • Self-Attention — анализирует зависимости между токенами исходных данных основного потока информации;
  • Cross-Attention — обогащает эти токены контекстом, полученным от энкодера.

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

  • позиционным смещением, зависящим от содержания,
  • глобальным смещением контекста,
  • глобальным позиционным смещением.

Это позволяет модели точнее учитывать не только расстояния между событиями, но и качественные связи между ними — что критично для анализа последовательных рыночных данных.

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

Стоит также обратить внимание на важное различие между компонентами архитектуры. В отличие от Энкодера, Декодер не разделяет унитарные последовательности. Если в Энкодере анализ каждой переменной происходит изолированно, с сохранением независимых унитарных последовательностей, то в Декодере все токены сводятся в единый поток. Здесь больше нет границ между унитарными рядами — модель воспринимает их как единое целое.

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

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

Объявление Энкодера происходит статически, что позволяет нам оставить конструктор и деструктор класса пустыми. Инициализация унаследованных и объявленных компонентов осуществляется централизованно в методе Init. В параметрах метода получаем ряд констант, позволяющих однозначно интерпретировать архитектуру создаваемого объекта.

bool CNeuronTimeFoundTransformerUnit::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window,
                                           uint window_key, uint heads, uint heads_kv, uint units_count,
                                           uint layers, uint layers_to_one_kv, uint variables,
                                           ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronCrossDMHAttention::Init(numOutputs, myIndex, open_cl, window, window_key, variables, window,
                                      units_count * variables, heads, layers, optimization_type, batch))
      return false;
   SetActivationFunction(None);

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

Здесь стоит подробнее остановиться на параметрах инициализации, передаваемых в метод родительского класса. Прежде всего, надо сказать, что родительский объект, в отличие от создаваемого нами, требует 2 потока исходных данных: анализируемые данные и контекст. Мы же на вход объекта планируем получать только один. Контекст формируется Энкодером внутри модуля.

Более того, в качестве исходных данных объекту передается мультимодальная последовательность токенов, описывающих анализируемое окно исторических данных. Каждая унитарная последовательность представлена целым рядом токенов, где каждый токен соответствует одному патчу — локальному сегменту временного ряда. Эти патчи позволяют модели сосредотачиваться на локальных изменениях и паттернах, выявляя краткосрочные зависимости внутри каждой переменной.

На выходе же объекта ожидается только по 1 токену для каждой унитарной последовательности, представляющих закодированный прогноз следующего сегмента анализируемого мультимодального временного ряда. В то же время, архитектура Transformer предусматривает сохранение размерностей основного информационного потока.

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

После успешного выполнения метода родительского класса, переходим к инициализации Энкодера. Здесь мы передаем информацию о полном размере анализируемой последовательности.

   if(!cSelfAttention.Init(0, 0, OpenCL, window, window_key, heads, heads_kv, units_count, layers, layers_to_one_kv,
                           variables, optimization, iBatch))
      return false;
   cSelfAttention.SetActivationFunction(None);
//---
   return true;
  }

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

Следующим этапом нашей работы является построение алгоритма прямого прохода нашего объекта. Алгоритм довольно прост — надо лишь вызывать одноименные методы, сначала Энкодера, а затем Декодера. Однако необходимо обратить внимание на некоторые нюансы информационных потоков. В параметрах метода получаем указатель на объект исходных данных. В нём мы ожидаем получить данные в представлении трёхмерного тензора [Длина ряда × Переменная × Размер токена]. Именно такой порядок данных нам необходим для Энкодера, и мы сразу передаем  ему указатель на объект.

bool CNeuronTimeFoundTransformerUnit::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cSelfAttention.FeedForward(NeuronOCL))
      return false;

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

   if(!CNeuronCrossDMHAttention::feedForward(NeuronOCL, cSelfAttention.getOutput()))
      return false;
//---
   return true;
  }

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

С алгоритмом методов обратного прохода я предлагаю вам ознакомиться самостоятельно. Полный код класса CNeuronCrossDMHAttention и всех его методов представлен во вложении.



Архитектура модели

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

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

В рамках же общего торгового Агента, мы придерживаемся структуры Actor–Director–Critic, где каждый компонент решает строго определённую задачу. В текущей реализации система включает 4 автономные модели, работающие совместно:

  • Энкодер состояния окружающей среды (Encoder) — отвечает за глубокий анализ поступающих рыночных данных. Он выявляет устойчивые закономерности в анализируемых временных рядах, создавая богатое представление о текущем контексте торговли. Именно это представление передаётся в дальнейшем Актёру.
  • Актёр (Actor) — центральный элемент Агента. Получая на вход информацию от Энкодера, он формирует конкретные торговые решения.
  • Режиссёр (Director) и Критик (Critic) — оценочные подсистемы. Они оценивают решения Актёра, используя внутреннюю модель будущего, и прогнозируют возможные последствия. Причём Director даёт более жёсткую бинарную оценку — стоит ли вообще рассматривать подобное поведение, а Critic — количественную метрику ожидаемой награды. Вместе они формируют сигнал обратной связи, необходимый для обучения и адаптации Агента к изменяющимся условиям рынка.

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

Архитектура всех моделей задается в методе CreateDescriptions, в параметрах которого передаются указатели на 4 динамических массива (по числу моделей). В каждый из них будет сохранена уникальная последовательность объектов, дающая полное представление о создаваемой модели.

bool CreateDescriptions(CArrayObj *&encoder,
                        CArrayObj *&actor,
                        CArrayObj *&director,
                        CArrayObj *&critic
                       )
  {
//---
   CLayerDescription *descr;
//---
   if(!encoder)
     {
      encoder = new CArrayObj();
      if(!encoder)
         return false;
     }
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }
   if(!director)
     {
      director = new CArrayObj();
      if(!director)
         return false;
     }
   if(!critic)
     {
      critic = new CArrayObj();
      if(!critic)
         return false;
     }

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

//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormWithNoise;
   descr.count = prev_count;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConcatDiff;
   prev_count = descr.count = HistoryBars;
   descr.layers = BarDescr;
   descr.step = 1;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Затем идёт Mamba4CastEmbedding — здесь внедряем временное кодирование каждого бара истории. Мы задаём гармоники двух периодичностей — часовой и дневной, чтобы модель захватывала суточные закономерности и внутрисуточные колебания. После этого, эмбеддинги каждого бара объединяются в общее представление.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defMamba4CastEmbeding;
   prev_count = descr.count = HistoryBars;
   descr.window = 2 * BarDescr;
   int prev_out = descr.window_out = NSkills;
     {
      int temp[] = {PeriodSeconds(PERIOD_H1), PeriodSeconds(PERIOD_D1)};
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTimeFoundPatching;
   descr.count = prev_count;
   prev_count=descr.window = Segments;
   descr.variables = prev_out;
   descr.window_out = EmbeddingSize;
   descr.step = 8;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

И вот здесь появляется наш ключевой слой — TimeFoundTransformerUnit. Этот объект позволяет модели одновременно анализировать каждую унитарную последовательность в отдельности и улавливать кросс-доменные зависимости.

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTimeFoundTransformerUnit;
   descr.count = prev_count;
   descr.window = EmbeddingSize;
   descr.window_out =descr.window/4;
   {
      int temp[]={4,2};
      if(ArrayCopy(descr.heads,temp)<ArraySize(temp))
        return false;
   }
   descr.layers=3;
   descr.step=3;
   descr.variables=prev_out;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

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

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 1;
   descr.step = EmbeddingSize;
   prev_count=descr.layers = prev_out;
   descr.window = EmbeddingSize;
   prev_out=descr.window_out = NForecast;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = SoftPlus;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     } 

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

//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTransposeOCL;
   descr.window=prev_out;
   descr.count = prev_count;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_out;
   descr.window = prev_count;
   descr.step = prev_count;
   descr.layers = 1;
   prev_out=descr.window_out = BarDescr;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   descr.activation = TANH;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   prev_count=descr.count;

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

//--- layer 9
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = prev_count * prev_out;
   descr.layers = 1;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

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

Кроме того, это позволяет нам обучать Энкодер в режиме Self‑Supervised Learning, прогнозируя будущее движение на большом объёме неразмеченных исторических данных. Что, в свою очередь, позволит сформировать информативные латентные представления прогнозных токенов. Именно их мы планируем передавать Актёру, Режиссёру и Критику, обеспечивая стабильность, адаптивность и обобщающую способность модели.

CLayerDescription *latent = encoder. At(LatentLayer);

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

Для получения исходных данных и предварительной их обработки, мы используем связку полносвязного слоя и слоя пакетной нормализации. Только, в отличие от Энкодера, мы не добавляем шум — ведь наше финансовое состояние не терпит искусственных флуктуаций. Сначала тензор счёта пропускается через полносвязный слой достаточной размерности, после чего, нормализуется, обеспечивая стабильное и детерминированное представление.

//--- Actor
   actor.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = AccountDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = AccountDescr;
   descr.batch = BatchSize;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

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

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronCrossDMHAttention;
     {
      int temp[] = {AccountDescr,    // Inputs window
                    latent.window    // Cross window
                   };
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
     {
      int temp[] = {1,                 // Inputs units
                    latent.variables    // Cross units
                   };
      if(ArrayCopy(descr.units, temp) < (int)temp.Size())
         return false;
     }
   descr.step = 4;                  // Heads
   descr.window_out = 32;
   descr.batch = 1e4;
   descr.layers = 3;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

Для окончательного принятия решения, используется трёхслойный MLP с различными функциями активации между слоями, добавляющими необходимую нелинейность. На первом слое применяется гиперболический тангенс (tanh), что позволяет разделить влияния сигналов на положительные и отрицательные направления. После второго слоя используется SoftPlus, подчёркивающий вес положительных откликов и сглаживающий низкие значения. Наконец, выходной слой контролируется сигмоидой, нормирующей пространство действий Актёра и обеспечивающей его значение в рамках [0,1], что удобно для масштабирования торговых параметров.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.batch = BatchSize;
   descr.activation = TANH;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   descr.count = LatentCount;
   descr.activation = SoftPlus;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = NActions;
   descr.activation = SIGMOID;
   descr.batch = BatchSize;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

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



Программа обучения Энкодера окружающей среды

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

Реализация данного этапа обучения организована в советнике "…\Experts\TimeFound\StudyEncoder.mq5". Здесь за управление буферами отвечает метод CreateBuffers, в параметрах которого передаются индекс начального состояния и указатели на два буфера: один для анализируемых исходных данных, другой для целевых значений прогнозного движения. Этот подход позволяет строить Self‑Supervised обучение Энкодера без дополнительных затрат на разметку исторических рядов.

bool CreateBuffers(const int start_bar, CBufferFloat* state, CBufferFloat *time, CBufferFloat* forecast)
  {
   if(!state || !time || start_bar < 0 ||
      (start_bar + HistoryBars + NForecast) >= int(Rates.Size()))
      return false;
//---
   vector<float> vState = vector<float>::Zeros(HistoryBars * BarDescr);
   vector<float> vForecast = vector<float>::Zeros(NForecast * BarDescr);
   time.Clear();
   time.Reserve(HistoryBars);

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

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

int bar = start_bar + NForecast;
for(int b = 0; b < (int)HistoryBars; b++)
  {
   float open = (float)Rates[b + bar].open;
   float rsi = (float)RSI.Main(b + bar);
   float cci = (float)CCI.Main(b + bar);
   float atr = (float)ATR.Main(b + bar);
   float macd = (float)MACD.Main(b + bar);
   float sign = (float)MACD.Signal(b + bar);
   if(rsi == EMPTY_VALUE || cci == EMPTY_VALUE || atr == EMPTY_VALUE ||
      macd == EMPTY_VALUE || sign == EMPTY_VALUE)
      return false;
   //---
   int shift = b * BarDescr;
   vState[shift] = (float)(Rates[b + bar].close - open);
   vState[shift + 1] = (float)(Rates[b + bar].high - open);
   vState[shift + 2] = (float)(Rates[b + bar].low - open);
   vState[shift + 3] = (float)(Rates[b + bar].tick_volume / 1000.0f);
   vState[shift + 4] = rsi;
   vState[shift + 5] = cci;
   vState[shift + 6] = atr;
   vState[shift + 7] = macd;
   vState[shift + 8] = sign;
   if(!time.Add(float(Rates[b + bar].time)))
      return false;
  }

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

bar--;
for(int b = 0; b < (int)NForecast; b++)
  {
   float open = (float)Rates[bar - b].open;
   float rsi = (float)RSI.Main(bar - b);
   float cci = (float)CCI.Main(bar - b);
   float atr = (float)ATR.Main(bar - b);
   float macd = (float)MACD.Main(bar - b);
   float sign = (float)MACD.Signal(bar - b);
   if(rsi == EMPTY_VALUE || cci == EMPTY_VALUE || atr == EMPTY_VALUE ||
      macd == EMPTY_VALUE || sign == EMPTY_VALUE)
      return false;
   //---
   int shift = (NForecast - b - 1) * BarDescr;
   vForecast[shift] = (float)(Rates[bar - b].close - open);
   vForecast[shift + 1] = (float)(Rates[bar - b].high - open);
   vForecast[shift + 2] = (float)(Rates[bar - b].low - open);
   vForecast[shift + 3] = (float)(Rates[bar - b].tick_volume / 1000.0f);
   vForecast[shift + 4] = rsi;
   vForecast[shift + 5] = cci;
   vForecast[shift + 6] = atr;
   vForecast[shift + 7] = macd;
   vForecast[shift + 8] = sign;
  }

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

   if(!state.AssignArray(vState))
      return false;
   if(!forecast.AssignArray(vForecast))
      return false;
   if(time.GetIndex() >= 0)
      if(!time.BufferWrite())
         return false;
//---
   return true;
  }

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

void Train(void)
  {
   int start = iBarShift(Symb.Name(), TimeFrame, Start);
   int end = iBarShift(Symb.Name(), TimeFrame, End);
   int bars = CopyRates(Symb.Name(), TimeFrame, 0, start, Rates);

На основании этих границ, рассчитывается количество баров, и выделяется необходимый объём памяти для буферов данных.

if(!RSI.BufferResize(bars) || !CCI.BufferResize(bars) ||
   !ATR.BufferResize(bars) || !MACD.BufferResize(bars))
  {
   PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
   ExpertRemove();
   return;
  }

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

   int count = -1;
   bool load = false;
   do
     {
      RSI.Refresh();
      CCI.Refresh();
      ATR.Refresh();
      MACD.Refresh();
      count++;
      load = (RSI.BarsCalculated() >= bars &&
              CCI.BarsCalculated() >= bars &&
              ATR.BarsCalculated() >= bars &&
              MACD.BarsCalculated() >= bars
             );
      Sleep(100);
      count++;
     }
   while(!load && count < 100);
   if(!load)
     {
      PrintFormat("%s -> %d The training data has not been loaded", __FUNCTION__, __LINE__);
      ExpertRemove();
      return;
     }
//---
   if(!ArraySetAsSeries(Rates, true))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      ExpertRemove();
      return;
     }
   bars -= end + HistoryBars + NForecast;
   if(bars < 0)
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      ExpertRemove();
      return;
     }

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

   vector<float> result, target, neg_target;
   bool Stop = false;
//---
   uint ticks = GetTickCount();
//---
   for(int iter = 0; (iter < Iterations && !IsStopped() && !Stop); iter ++)
     {
      int posit = (int)((MathRand() * MathRand() / MathPow(32767, 2)) * bars);
      if(!CreateBuffers(posit + end, GetPointer(bState), GetPointer(bTime), Result))
        {
         PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
         ExpertRemove();
         return;
        }

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

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

for(int r = 0; r < Repeats; r++)
  {
   //--- Feed Forward
   if(!cEncoder.feedForward((CBufferFloat*)GetPointer(bState), 1, false, (CBufferFloat*)GetPointer(bTime)))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      Stop = true;
      break;
     }
   //--- Study
   if(!cEncoder.backProp(Result, (CBufferFloat*)NULL))
     {
      PrintFormat("%s -> %d", __FUNCTION__, __LINE__);
      Stop = true;
      break;
     }

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

    if(GetTickCount() - ticks > 500)
      {
       double percent = double(iter) * 100.0 / (Iterations);
       string str = StringFormat("%-12s %6.2f%% -> Error %15.8f\n", "Encoder",
                                   percent, cEncoder.getRecentAverageError());
       Comment(str);
       ticks = GetTickCount();
      }
   }
}

После завершения всех итераций, в журнал выводятся итоги обучения,  и мы завершаем работу программы.

   Comment("");
//---
   PrintFormat("%s -> %d -> %-15s %10.7f", __FUNCTION__, __LINE__, "Encoder", cEncoder.getRecentAverageError());
   ExpertRemove();
//---
  }

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

Весь исходный код программ, использованных в рамках подготовки данной статьи, представлен во вложении.


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

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

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

Следом идёт второй этап — офлайн обучение основных действующих лиц нашей архитектуры: Актёра, Режиссёра и Критика. Для этого была собрана выборка из рыночных данных за 2024 год с сохранением всех параметров, применённых при обучении Энкодера. В процессе обучения использовалась концепция почти идеальной траектории: действия Агента подбирались не произвольно, а на основе анализа последующего ценового движения. Иными словами, имея в распоряжении всю ценовую траекторию, мы заранее знали, какие действия привели бы к наилучшим результатам, и использовали именно их для обучения. Такой подход позволяет показать модели, как нужно торговать, а не заставлять её вслепую искать эффективную стратегию методом проб и ошибок, блуждая в окружающей среде без карты. Благодаря этому, Агент учится на заранее проверенных примерах — чётких, обоснованных и максимально приближенных к идеальным с точки зрения результата. Это не просто упрощает процесс обучения, а делает его целенаправленным и экономически осмысленным.

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

После завершения всей цепочки обучения, модель была протестирована на новых данных — котировках за Январь 2025 года. Все параметры и настройки, использованные при обучении, были сохранены без изменений, что обеспечивает полную объективность и честность в оценке. Результаты тестирования представлены ниже.

Результаты тестирования можно назвать обнадёживающими, но с оговорками. Итоговая доходность составила +26,5% за месяц, однако график баланса не демонстрирует устойчивого восходящего тренда. Наоборот — большую часть времени наблюдается боковое движение с периодами нестабильности и выраженными просадками. Максимальная просадка достигала 58%, что говорит о высокой волатильности и нестабильности решений в отдельных рыночных ситуациях.

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



Заключение

В ходе этой работы мы прошли путь от концепции универсального кросс-доменного прогнозирования временных рядов до её практической реализации в среде MetaTrader 5. Мы подробно разобрали структуру Энкодера, освоили механизм многомасштабного патчинга, а затем интегрировали полученные латентные представления в архитектуру Actor–Director–Critic. Каждый шаг — от подготовки данных и Self-Supervised обучения Энкодера до офлайн- и онлайн-настройки ключевых модулей — показал, насколько гармоничное сочетание современных исследований и инженерных решений способно повысить качество анализа рыночной динамики.

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


Ссылки


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

# Имя Тип Описание
1 Research.mq5 Советник Советник сбора примеров
2 ResearchRealORL.mq5
Советник
Советник сбора примеров методом Real-ORL
3 StudyEncoder.mq5 Советник Советник обучения Энкодера окружающей среды
4 Study.mq5 Советник Советник офлайн обучения моделей
5 StudyOnline.mq5
Советник
Советник онлайн обучения моделей
6 Test.mq5 Советник Советник для тестирования модели
7 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
8 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
9 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы

Прикрепленные файлы |
MQL5.zip (2829.04 KB)
Переосмысливаем классические стратегии (Часть XI): Пересечение скользящих средних (II) Переосмысливаем классические стратегии (Часть XI): Пересечение скользящих средних (II)
Скользящие средние и стохастический осциллятор можно использовать для генерации торговых сигналов, следующих за трендом. Однако эти сигналы будут наблюдаться только после того, как произойдет ценовое движение. Мы можем эффективно преодолеть этот неизбежный лаг в технических индикаторах с помощью искусственного интеллекта. В настоящей статье мы расскажем, как создать полностью автономный советник на базе ИИ таким образом, чтобы улучшить любую из ваших существующих торговых стратегий. Даже самая старая торговая стратегия может быть улучшена.
Возможности Мастера MQL5, которые вам нужно знать (Часть 45): Обучение с подкреплением с помощью метода Монте-Карло Возможности Мастера MQL5, которые вам нужно знать (Часть 45): Обучение с подкреплением с помощью метода Монте-Карло
Монте-Карло — четвертый алгоритм обучения с подкреплением, который мы рассматриваем в контексте его реализации в советниках, собранных с помощью Мастера. Хотя алгоритм основан на случайной выборке, он предоставляет обширные возможности моделирования.
Создание Python-классов для торговли в MetaTrader 5, аналогичных представленным в MQL5 Создание Python-классов для торговли в MetaTrader 5, аналогичных представленным в MQL5
Python-пакет MetaTrader 5 предлагает простой способ создания торговых приложений для платформы MetaTrader 5 на языке Python. Будучи мощным и полезным инструментом данный модуль не так прост как язык программирования MQL5, когда дело касается разработки решений для алгоритмической торговли. В данной статье мы создадим классы для торговли, аналогичные предлагаемым в языке MQL5, чтобы создать схожий синтаксис и сделать разработку торговых роботов на Python такой же простой как и на MQL5.
Разработка системы репликации (Часть 74): Новый Chart Trade (I) Разработка системы репликации (Часть 74): Новый Chart Trade (I)
В этой статье мы изменим последний код, показанный в данной серии о Chart Trade. Эти изменения необходимы, чтобы адаптировать код к текущей модели системы репликации/моделирования. Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте его как окончательное приложение, целью которого не является изучение представленных концепций.