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

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

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

Введение

Финансовый рынок — это не удобочитаемая таблица. Он — поток. Непрерывный, разреженный, местами взрывной. События на рынке появляются не по расписанию. Они возникaют там, где меняется ликвидность, распадаются заявки и мелькают всплески объёма. Понимание этого потока — основное условие для построения надёжной, чувствительной и адаптивной системы прогнозирования. STE-FlowNet родился именно из этого наблюдения: смотреть на рынок как на поток событий и научить модель чувствовать его движение.

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

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

Фреймворк STE-FlowNet можно представить как команду опытных аналитиков на финансовом рынке, каждый из которых выполняет свою роль в обработке потока событий. Энкодер — это главный стратег, который фиксирует каждую сделку, каждое изменение цены и объема, аккумулируя знания о текущей и прошлой динамике рынка. Его ConvGRU-память работает как опытный трейдер, помнящий всю историю последних движений и мгновенно оценивающий значимость новых сигналов.

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

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

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

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

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

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



Residual-блок

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

Особое внимание стоит уделить остаточным связям. При знакомстве с SEW-ResNet мы уже рассмотрели проблемы использования похожих конструкций в спайк-нейросетях. На ум сразу приходит Spike-Element-Wise блок — проверенный компонент, который позволяет сохранять структуру сигналов даже при сложной нагрузке.

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

Предложенное решение реализуем в рамках нового класса CNeuronSTEFlowNetResidualBlock, который наследует функционал от базового класса CNeuronBaseOCL. Новый объект полностью инкапсулирует всю логику Residual-блока и взаимодействие с OpenCL.

class CNeuronSTEFlowNetResidualBlock :  public CNeuronBaseOCL
  {
protected:
   CLayer            cBlock;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSTEFlowNetResidualBlock(void) {};
                    ~CNeuronSTEFlowNetResidualBlock(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels, uint units, uint group_size, uint groups,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //--
   virtual int       Type(void)   override const   {  return defNeuronSTEFlowNetResidualBlock;   }
   //--- 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 bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

В представленной структуре объявлен лишь один внутренний компонент cBlock — динамический массив объектов, который мы наполняем при инициализации.

Метод Init можно представить как формирование полноценной команды специалистов для анализа рынка.

bool CNeuronSTEFlowNetResidualBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                          uint chanels, uint units, uint group_size, uint groups,
                                          ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, 2 * chanels * units, optimization_type, batch))
      return false;
   activation = None;

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

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

   CNeuronSpikeResNeXtBottleneck* conv = NULL;
   CNeuronSpikeResNeXtBlock*      residual = NULL;
//---
   cBlock.Clear();
   cBlock.SetOpenCL(OpenCL);

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

   uint index = 0;
   conv = new CNeuronSpikeResNeXtBottleneck();
   if(!conv ||
      !conv.Init(0, index, OpenCL, chanels, chanels, units, units, group_size, groups, optimization, iBatch) ||
      !cBlock.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }

Затем в команду добавляются два блока с остаточными связями (Residual Blocks). Их роль — проверка и уточнение анализа, поддержка стратегии и сохранение важной информации. Каждый новый аналитик проходит инициализацию, получает доступ к ресурсам и включается в общий поток работы. Если хотя бы один блок не создаётся, команда останавливается, чтобы не запускать неполную стратегию.

   for(int i = 0; i < 2; i++)
     {
      index++;
      residual = new CNeuronSpikeResNeXtBlock();
      if(!residual ||
         !residual.Init(0, index, OpenCL, chanels, chanels, units, units,
                             group_size, groups, optimization, iBatch) ||
         !cBlock.Add(residual))
        {
         DeleteObj(residual)
         return false;
        }
     }
//---
   return true;
  }

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

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

bool CNeuronSTEFlowNetResidualBlock::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(cBlock.Total() <= 0)
      return false;

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

   CNeuronBaseOCL* prev = NeuronOCL;
   CNeuronBaseOCL* current = NULL;
   for(int i = 0; i < cBlock.Total(); i++)
     {
      current = cBlock[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

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

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

   if(!Concat(prev.getOutput(), NeuronOCL.getOutput(), Output, 1, 1, Neurons() / 2))
      return false;
//---
   return true;
  }

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

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


Декодер

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

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

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

Мы строим Декодер в виде отдельного нового класса CNeuronSTEFlowNetDecoder, который выполняет ключевую роль в превращении скрытых состояний Энкодера в прогностические карты потока. Декодер организован вокруг двух внутренних компонентов — cMainLine и cFlow, которые обрабатывают основные признаки и промежуточные потоки соответственно. Кроме того, он хранит указатель на Энкодер cEncoder, что позволяет использовать латентные состояния для точного восстановления динамики.

class CNeuronSTEFlowNetDecoder   :  public CNeuronSpikeActivation
  {
protected:
   CLayer                        cMainLine;
   CLayer                        cFlow;
   CNeuronSTEFlowNetEncoder*     cEncoder;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSTEFlowNetDecoder(void) {};
                    ~CNeuronSTEFlowNetDecoder(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &chanels[], uint &units[], uint group_size,
                          uint groups, uint heads, uint dimension_k,
                          CNeuronSTEFlowNetEncoder* encoder,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //--
   virtual int       Type(void)   override const   {  return defNeuronSTEFlowNetDecoder;   }
   //--- 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      SetEncoder(CNeuronSTEFlowNetEncoder* encoder) { cEncoder = encoder; }
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

Инициализация объекта Декодера представляет собой тщательный и поэтапный процесс, где каждый элемент декодера создаётся, настраивается и добавляется в соответствующие информационные потоки — cMainLine и cFlow.

bool CNeuronSTEFlowNetDecoder::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                    uint &chanels[], uint &units[], uint group_size, uint groups,
                                    uint heads, uint dimension_k,
                                    CNeuronSTEFlowNetEncoder *encoder,
                                    ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(chanels.Size() != units.Size())
      return false;
//---
   const uint layers = chanels.Size() - 1;
   if(layers < 1)
      return false;

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

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

   if(!CNeuronSpikeActivation::Init(numOutputs, myIndex, open_cl, units[layers]*chanels[layers],
                                                                      optimization_type, batch))
      return false;
   cEncoder = encoder;
   uint enc_layers = (!cEncoder ? 0 : cEncoder.Layers());

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

Внутренние массивы cMainLine и cFlow очищаются и подключаются к OpenCL-контексту, после чего начинается поэтапное формирование блоков.

  CNeuronSSAMResNeXtBlock*   conv_res = NULL;
   CNeuronConvOCL*            conv = NULL;
   CNeuronBatchNormOCL*       norm = NULL;
   CNeuronSpikeActivation*    sa = NULL;
   CNeuronSpikeConvGRU*       gru = NULL;
   CNeuronBaseOCL*            concat = NULL;
//---
   cMainLine.Clear();
   cFlow.Clear();
   cMainLine.SetOpenCL(OpenCL);
   cFlow.SetOpenCL(OpenCL);

На первом шаге создаётся основной Residual-блок CNeuronSSAMResNeXtBlock, который инициирует главный поток обработки признаков.

   uint index = 0;
//--- Main Line
   conv_res = new CNeuronSSAMResNeXtBlock();
   if(!conv_res ||
      !conv_res.Init(0, index, OpenCL, chanels[0], chanels[1],
                     units[0], units[1], group_size, groups, heads,
                     dimension_k, optimization, iBatch) ||
      !cMainLine.Add(conv_res))
     {
      DeleteObj(conv_res)
      return false;
     }

Затем добавляется обычный сверточный слой CNeuronConvOCL, активируемый функцией SoftPlus. И слой пакетной нормализации CNeuronBatchNormOCL, который обеспечивает стабильность и согласованность данных.

   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, chanels[1], chanels[1], chanels[1], units[1], 1, optimization, iBatch) ||
      !cMainLine.Add(conv))
     {
      DeleteObj(conv)
      return false;
     }
   conv.SetActivationFunction(SoftPlus);
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
      !cMainLine.Add(norm))
     {
      DeleteObj(norm)
      return false;
     }
   norm.SetActivationFunction(None);

Дальнейшие слои формируют чередующиеся блоки. Spike Activation в основном потоке для нелинейной активации и SSAM ResNeXt в потоке Flow, обрабатывающем объединённые локальные и глобальные признаки с использованием латентных состояний Энкодера.

   uint ch_in = chanels[0];
//---
   for(uint i = 1; i < layers; i++)
     {
      //--- Main Line
      index++;
      sa = new CNeuronSpikeActivation();
      if(!sa ||
         !sa.Init(0, index, OpenCL, norm.Neurons(), optimization, iBatch) ||
         !cMainLine.Add(sa))
        {
         DeleteObj(sa)
         return false;
        }
      //--- Flow
      index++;
      conv_res = new CNeuronSSAMResNeXtBlock();
      if(!conv_res ||
         !conv_res.Init(0, index, OpenCL, ch_in, chanels[i],
                        units[i - 1], units[i], group_size, groups, heads,
                        dimension_k, optimization, iBatch) ||
         !cFlow.Add(conv_res))
        {
         DeleteObj(conv_res)
         return false;
        }

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

      index++;
      ch_in = 2 * chanels[i];
      if(enc_layers - i - 1 >= 0)
        {
         gru = cEncoder.GetLayer(enc_layers - i - 1);
         if(!gru)
            return false;
         if(gru.GetUnits() != units[i])
            return false;
         ch_in += gru.GetChanels();
        }
      concat = new CNeuronBaseOCL();
      if(!concat ||
         !concat.Init(0, index, OpenCL, ch_in * units[i], optimization, iBatch) ||
         !cFlow.Add(concat))
        {
         DeleteObj(concat)
         return false;
        }
      concat.SetActivationFunction(None);

В конце каждого цикла добавляются дополнительные Residual-блоки и сверточные слои с функцией SoftPlus и нормализацией, что завершает формирование структуры Main Line на данном уровне.

      //--- Main Line
      conv_res = new CNeuronSSAMResNeXtBlock();
      if(!conv_res ||
         !conv_res.Init(0, index, OpenCL, ch_in, ch_in,
                        units[i], units[i + 1], group_size, groups, heads,
                        dimension_k, optimization, iBatch) ||
         !cMainLine.Add(conv_res))
        {
         DeleteObj(conv_res)
         return false;
        }
      index++;
      conv = new CNeuronConvOCL();
      if(!conv ||
         !conv.Init(0, index, OpenCL, chanels[i + 1], chanels[i + 1], chanels[i + 1], units[i + 1], 1,
                                                                              optimization, iBatch) ||
         !cMainLine.Add(conv))
        {
         DeleteObj(conv)
         return false;
        }
      conv.SetActivationFunction(SoftPlus);
      index++;
      norm = new CNeuronBatchNormOCL();
      if(!norm ||
         !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) ||
         !cMainLine.Add(norm))
        {
         DeleteObj(norm)
         return false;
        }
      norm.SetActivationFunction(None);
     }
//---
   return true;
  }

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

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

Алгоритм прямого прохода нашего Декодера организован как тщательно скоординированная последовательность обработки данных через два параллельных потока (Main Line и Flow) с интеграцией латентных состояний Энкодера.

bool CNeuronSTEFlowNetDecoder::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
//--- Main Line
   CNeuronSSAMResNeXtBlock*   conv_res = NULL;
   CNeuronConvOCL*            conv = NULL;
   CNeuronBatchNormOCL*       norm = NULL;
   CNeuronSpikeActivation*    sa = NULL;
   CNeuronSpikeConvGRU*       gru = NULL;
//--- Flow
   CNeuronSSAMResNeXtBlock*   flow = NULL;
   CNeuronBaseOCL*            concat = NULL;
//---
   CNeuronBaseOCL* prev = NeuronOCL;
   int layers = (cMainLine.Total() + 1) / 4;
   int enc_layers = int(!cEncoder ? 0 : cEncoder.Layers());
   int enc_dim = 0;

Сначала метод подготавливает ссылки на все внутренние блоки: Residual-блоки, сверточные слои, нормализацию, Spike-активации и ConvGRU для основного потока, а также блоки SSAM ResNeXt и объекты для конкатенации в потоке Flow. Переменная prev хранит указатель на объект с данными предыдущего шага, чтобы обеспечить последовательную обработку. А layers и enc_layers содержат количество уровней Декодера и Энкодера, соответственно, определяя порядок интеграции латентных состояний.

Декодирование происходит поэтапно. Сначала через основной поток проходят Residual-блок, сверточный слой и нормализация.

   for(int i = 0; i < layers; i++)
     {
      conv_res = cMainLine[i * 4];
      conv = cMainLine[i * 4 + 1];
      norm = cMainLine[i * 4 + 2];
      sa = cMainLine[i * 4 + 3];
      if(enc_layers - i - 2 >= 0)
        {
         gru = cEncoder.GetLayer(uint(enc_layers - i - 2));
         if(!gru)
            return false;
         enc_dim = (int)gru.GetChanels();
        }
      else
        {
         gru = NULL;
         enc_dim = 0;
        }
      flow = cFlow[i * 2];
      concat = cFlow[i * 2 + 1];
      //--- Main Line
      if(!conv_res ||
         !conv_res.FeedForward(prev))
         return false;
      if(!conv ||
         !conv.FeedForward(conv_res))
         return false;
      if(!norm ||
         !norm.FeedForward(conv))
         return false;
      if(i == (layers - 1))
         break;

Далее, если это не последний слой, активируется Spike-слой для нелинейного преобразования.

      if(!sa ||
         !sa.FeedForward(norm))
         return false;

Параллельно поток Flow обрабатывает исходные данные и добавляет промежуточные представления, полученные от Энкодера через ConvGRU. На каждом уровне выходы этих двух потоков объединяются в объект concat, который аккуратно соединяет локальные признаки из Main Line, промежуточные карты Flow и, при наличии, латентные состояния Энкодера, создавая согласованное представление для следующего уровня.

      //--- Flow
      if(!flow ||
         !flow.FeedForward(prev))
         return false;
      if(!concat)
         return false;
      if(!gru)
        {
         if(!Concat(sa.getOutput(), flow.getOutput(), concat.getOutput(),
                    conv.GetFilters(), flow.GetChannelsOut(), conv.GetUnits()))
            return false;
        }
      else
        {
         if(conv.GetUnits() != gru.GetUnits())
            return false;
         if(!Concat(sa.getOutput(), flow.getOutput(), gru.getOutput(), concat.getOutput(),
                    conv.GetFilters(), flow.GetChannelsOut(), enc_dim, conv.GetUnits()))
            return false;
        }
      prev = concat;
     }

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

После прохождения всех слоёв Декодера вызывается финальный Spike-Activation средствами родительского объекта, который формирует окончательный результат работы Декодера.

   if(!CNeuronSpikeActivation::feedforward(norm))
      return false;
//---
   return true;
  }

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

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

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

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

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

bool CNeuronSTEFlowNetDecoder::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
//--- Main Line
   CNeuronSSAMResNeXtBlock*   conv_res = cMainLine[-3];
   CNeuronConvOCL*            conv = cMainLine[-2];
   CNeuronBatchNormOCL*       norm = cMainLine[-1];
   CNeuronSpikeActivation*    sa = cMainLine[-4];
   CNeuronSpikeConvGRU*       gru = NULL;
//--- Flow
   CNeuronSSAMResNeXtBlock*   flow = cFlow[-2];
//---
   int layers = cMainLine.Total() / 4;
   CNeuronBaseOCL* prev = (layers > 1 ? cFlow[-1] : NeuronOCL);
   int enc_layers = int(!cEncoder ? 0 : cEncoder.Layers());
   int enc_dim = 0;

В начале метода создаются локальные переменные для хранения указателей на внутренние компоненты основного потока: Residual-блоки (conv_res), сверточные слои (conv), нормализацию (norm) и Spike-активации (sa). Для потока Flow определяются объекты flow. А переменная prev указывает на предыдущий слой, который будет участвовать в обратном распространении.

Также вычисляется количество слоёв в Декодере и Энкодере, что позволяет корректно интегрировать латентные состояния для обратного прохода.

Первым шагом вычисляются градиенты для конечного слоя нормализации.

   if(!CNeuronSpikeActivation::calcInputGradients(norm))
      return false;

После чего происходит последовательное вычисление градиентов для сверточных слоёв и Residual-блоков основного потока.

   if(!conv ||
      !conv.CalcHiddenGradients(norm))
      return false;
   if(!conv_res ||
      !conv_res.CalcHiddenGradients(conv))
      return false;

Этот этап обеспечивает точное распространение ошибки через линейные и нелинейные преобразования, создавая основу для корректной коррекции весов модели.

Далее реализован цикл, проходящий от верхних слоёв к нижним.

   if(!prev ||
      !prev.CalcHiddenGradients(conv_res))
      return false;
//---
   for(int i = layers - 1; i >= 0; i--)
     {
      if(enc_layers - i - 2 >= 0)
        {
         gru = cEncoder.GetLayer(uint(enc_layers - i - 1));
         if(!gru)
            return false;
         enc_dim = (int)gru.GetChanels();
        }
      else
        {
         gru = NULL;
         enc_dim = 0;
        }
      if(!sa || !flow)
         return false;

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

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

      if(!gru)
        {
         if(!DeConcat(sa.getGradient(), flow.getGradient(), prev.getGradient(),
                      conv.GetFilters(), flow.GetChannelsOut(), conv.GetUnits()))
            return false;
        }
      else
         if(!DeConcat(sa.getGradient(), flow.getGradient(), gru.getGradient(), prev.getGradient(),
                      conv.GetFilters(), flow.GetChannelsOut(), enc_dim, conv.GetUnits()))
            return false;

После распределения ошибки в Flow, осуществляется обратное распространение в Main Line: сначала через Spike-активацию, затем через нормализацию и сверточные слои в Residual-блок.

      //--- Flow
      prev = (i > 0 ? cFlow[i * 2 - 1] : NeuronOCL);
      if(!prev ||
         !prev.CalcHiddenGradients(flow))
         return false;
      //--- Main Line
      conv_res = cMainLine[i * 4];
      conv = cMainLine[i * 4 + 1];
      norm = cMainLine[i * 4 + 2];
      if(!norm.CalcHiddenGradients(sa))
         return false;
      if(!conv ||
         !conv.CalcHiddenGradients(norm))
         return false;
      if(!conv_res ||
         !conv_res.CalcHiddenGradients(conv))
         return false;

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

      CBufferFloat* temp = prev.getGradient();
      if(!prev.SetGradient(prev.getPrevOutput(), false) ||
         !prev.CalcHiddenGradients(conv_res) ||
         !SumAndNormilize(temp, prev.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
         !prev.SetGradient(temp, false))
         return false;
      if(i==0)
         continue;
      sa = cMainLine[i * 4 - 1];
      //--- Flow
      flow = cFlow[(i-1) * 2];
     }
//---
   return true;
  }

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

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

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

bool CNeuronSTEFlowNetDecoder::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
//--- Main Line
   CNeuronSSAMResNeXtBlock*   conv_res = NULL;
   CNeuronConvOCL*            conv = NULL;
   CNeuronBatchNormOCL*       norm = NULL;
   CNeuronSpikeActivation*    sa = NULL;
   CNeuronSpikeConvGRU*       gru = NULL;
//--- Flow
   CNeuronSSAMResNeXtBlock*   flow = NULL;
   CNeuronBaseOCL*            concat = NULL;
//---
   CNeuronBaseOCL* prev = NeuronOCL;
   int layers = (cMainLine.Total() + 1) / 4;
   int enc_layers = int(!cEncoder ? 0 : cEncoder.Layers());
   int enc_dim = 0;

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

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

   for(int i = 0; i < layers; i++)
     {
      conv_res = cMainLine[i * 4];
      conv = cMainLine[i * 4 + 1];
      norm = cMainLine[i * 4 + 2];
      sa = cMainLine[i * 4 + 3];
      if(enc_layers - i >= 0)
        {
         gru = cEncoder.GetLayer(uint(enc_layers - i - 1));
         if(!gru)
            return false;
         enc_dim = (int)gru.GetChanels();
        }
      else
        {
         gru = NULL;
         enc_dim = 0;
        }
      flow = cFlow[i * 2];
      concat = cFlow[i * 2 + 1];
      //--- Main Line
      if(!conv_res ||
         !conv_res.UpdateInputWeights(prev))
         return false;

Затем последовательно обновляются параметры сверточного слоя, нормализации и Spike-активации, обеспечивая согласованность всей Main Line и предотвращая потерю информации о градиентах на промежуточных этапах.

      if(!conv ||
         !conv.UpdateInputWeights(conv_res))
         return false;
      if(!norm ||
         !norm.UpdateInputWeights(conv))
         return false;
      if(i == (layers - 1))
         break;
      if(!sa ||
         !sa.UpdateInputWeights(norm))
         return false;

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

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

      //--- Flow
      if(!flow ||
         !flow.UpdateInputWeights(prev))
         return false;
      prev = concat;
     }

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

   if(!CNeuronSpikeActivation::updateInputWeights(norm))
      return false;
//---
   return true;
  }

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

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

Полный код CNeuronSTEFlowNetDecoder и всех его методов представлен во вложении.



Объект верхнего уровня

Следующим этапом мы переходим к построению интегрированного объекта верхнего уровня, который объединяет все ключевые компоненты модели в единую архитектуру. После тщательной разработки отдельных модулей (Энкодера, Декодера и Residual-блока) возникает необходимость создать структуру, способную не просто последовательно обрабатывать поток событий, но и синхронизировать работу всех внутренних компонентов. Такой объект позволяет объединить пространственно-временное кодирование, точное извлечение локальных признаков и прогнозирование динамики данных в одном согласованном механизме.

Объект верхнего уровня CNeuronSTEFlowNet выступает интегратором всех ключевых компонентов модели. Он наследует базовую функциональность от CNeuronSTEFlowNetDecoder, что позволяет отказаться от создания внутреннего компонента Декодера. А его функционал реализовать средствами родительского класса.

class CNeuronSTEFlowNet  :  public CNeuronSTEFlowNetDecoder
  {
   //---
protected:
   CNeuronSTEFlowNetEncoder         cSTEEncoder;
   CNeuronSTEFlowNetResidualBlock   cSTEResidualBlock;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSTEFlowNet(void) {};
                    ~CNeuronSTEFlowNet(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &chanels[], uint &units[], uint group_size,
                          uint groups, uint heads, uint dimension_k, uint stack_size,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //--
   virtual int       Type(void)   override const   {  return defNeuronSTEFlowNet;   }
   //--- 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 bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

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

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

bool CNeuronSTEFlowNet::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                             uint &chanels[], uint &units[], uint group_size,
                             uint groups, uint heads, uint dimension_k, uint stack_size,
                             ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(chanels.Size() != units.Size())
      return false;

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

   uint rev_chanels[], rev_units[];
   if(ArrayResize(rev_chanels, chanels.Size()) < 0 ||
      ArrayResize(rev_units, units. Size()) < 0)
      return false;
   uint total = chanels.Size();
   for(uint i = 0; i < total; i++)
      rev_chanels[total - i - 1] = chanels[i];
   rev_chanels[0] *= 2;
//---
   for(uint i = 0; i < total; i++)
      rev_units[total - i - 1] = units[i];

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

   if(!cSTEEncoder.Init(0, 0, open_cl, chanels, units, group_size, groups, heads,
                        dimension_k, stack_size, optimization_type, batch))
      return false;

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

   if(!cSTEResidualBlock.Init(0, 1, open_cl, chanels[total - 1], units[total - 1], group_size, groups,
                              optimization, iBatch))
      return false;

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

   rev_chanels[0] = cSTEResidualBlock.Neurons() / rev_units[0];
   if(!CNeuronSTEFlowNetDecoder::Init(numOutputs, myIndex, open_cl, rev_chanels,
                                      rev_units, group_size, groups, heads, dimension_k,
                                      cSTEEncoder.AsObject(), optimization_type, batch))
      return false;
//---
   return true;
  }

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

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

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

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

   if(!cSTEResidualBlock.FeedForward(cSTEEncoder.AsObject()))
      return false;

Наконец, обновленные данные поступают в Декодер, который на основе латентного состояния Энкодера и выходов Residual-блока строит прогноз потока событий.

   return CNeuronSTEFlowNetDecoder::feedForward(cSTEResidualBlock.AsObject());
  }

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

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

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

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

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



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

Мы подошли к завершающему этапу работы с фреймворком STE-FlowNet — оценке эффективности реализованных подходов. Но прежде необходимо обучить нашего трейдера.

Первый этап — офлайн-обучение на исторических данных валютной пары EURUSD таймфрейм H1 за период с Января 2024 по Июнь 2025 года. Этот временной отрезок стал полноценной тренировочной площадкой. Модель училась распознавать исторические паттерны, анализировать динамику цен и объёмы сделок, выявлять взаимосвязи между ключевыми признаками. STE-FlowNet формировал собственную интуицию трейдера, объединяя её с точной стратегической оценкой, постепенно нарабатывая способность прогнозировать движение рынка и оценивать риск каждой потенциальной сделки.

Следующий этап — тонкая онлайн-настройка в тестере стратегий MetaTrader 5 — переносит обучение в более реалистичные условия. Свеча за свечой модель обрабатывает потоки данных. Здесь STE-FlowNet осваивает динамику рынка, учится сохранять устойчивость на фоне шумовых колебаний, корректировать действия при низкой ликвидности и мгновенно реагировать на резкие ценовые всплески. Базовая структура, сформированная на исторических данных, остаётся неизменной, но модель учится гибко реагировать на текущую рыночную обстановку, минимизируя риск переобучения и повышая точность прогнозов даже в нестабильной среде.

Финальная проверка проводилась на полностью новых данных за Июль—Сентябрь2025 года. Все параметры, полученные на предыдущих этапах, загружались без изменений, что обеспечивало честную оценку способности STE-FlowNet к обобщению.


Результаты тестирования на исторических данных выглядят убедительно и демонстрируют стабильность стратегии. Начнем с финансовых показателей. При начальном депозите $100 чистая прибыль составила $54,02, что соответствует общему росту капитала более чем на 50% за тестируемый период. Валовая прибыль достигла $119,57, при этом валовые убытки составили $65,55, что дает коэффициент прибыльности (Profit Factor) 1,82. Этот показатель отражает, что стратегия генерирует почти в два раза больше дохода по сравнению с убытками, что считается достойным результатом для торговых алгоритмов. Recovery Factor1,31 указывает на способность стратегии восстанавливаться после просадок.

Просадки капитала находятся на приемлемом уровне. Максимальная просадка по Эквити составила 28,57%, что является разумным для активного алгоритмического подхода на валютном рынке. При этом абсолютная просадка баланса была 25,13%, демонстрируя способность стратегии эффективно удерживает позиции и не приводит к резкому падению капитала.

Результаты по сделкам также показывают сбалансированность стратегии. Всего было совершено 50 сделок, из которых 62% оказались прибыльными. Процент выигрышных коротких-позиций составил 63,16%, а длинных — 61,29%, что говорит о корректной работе алгоритма как в нисходящем, так и в восходящем тренде. Средний профит на сделку $3,86, а средний убыток $3,45 — соотношение положительных и отрицательных сделок хорошо сбалансировано. Наибольшая прибыль в одной сделке составила $15,03, а наибольший убыток — $9,96.

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



Заключение

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


Ссылки


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

# Имя Тип Описание
1 Study.mq5 Советник Советник офлайн обучения моделей
2 StudyOnline.mq5 Советник Советник онлайн обучения моделей
3 Test.mq5 Советник Советник для тестирования модели
4 Trajectory.mqh Библиотека класса Структура описания состояния системы и архитектуры моделей
5 NeuroNet.mqh Библиотека класса Библиотека классов для создания нейронной сети
6 NeuroNet.cl Библиотека Библиотека кода OpenCL-программы
Прикрепленные файлы |
MQL5.zip (3251.54 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Chao Tang
Chao Tang | 3 нояб. 2025 в 13:16
使用相同的代码,执行的STFS。Critic数据明显不对
Таблицы в парадигме MVC на MQL5: настраиваемые и сортируемые столбцы таблицы Таблицы в парадигме MVC на MQL5: настраиваемые и сортируемые столбцы таблицы
В статье сделаем изменяемую ширину столбцов таблицы при помощи курсора мышки, сортировку таблицы по данным столбцов, и добавим новый класс для упрощенного создания таблиц на основании любых наборов данных.
От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (X) — Представление графика с несколькими символами для торговли на новостях От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (X) — Представление графика с несколькими символами для торговли на новостях
Сегодня мы разработаем систему просмотра нескольких диаграмм с использованием объектов диаграмм. Цель состоит в том, чтобы улучшить торговлю на новостях за счет применения алгоритмов на MQL5, которые помогают сократить время реакции трейдера в периоды высокой волатильности, такие как выход крупных новостей. В этом случае мы предоставляем трейдерам интегрированный способ мониторинга нескольких основных инструментов в рамках единого инструмента для торговли на новостях. Наша работа постоянно продвигается с появлением советника News Headline EA («Заголовки новостей»), который теперь обладает растущим набором функций, которые привносят действительное значение как для трейдеров, использующих полностью автоматизированные системы, так и для тех, кто предпочитает ручную торговлю с помощью алгоритмов. Ознакомьтесь с новыми знаниями, информацией и практическими идеями, перейдя по ссылке и присоединившись к настоящему обсуждению.
От новичка до эксперта: Анимированный советник News Headline с использованием MQL5 (XI) - Корреляция при торговле на новостях От новичка до эксперта: Анимированный советник News Headline с использованием MQL5 (XI) - Корреляция при торговле на новостях
В настоящем обсуждении рассмотрим, как концепция финансовой корреляции может быть применена для повышения эффективности принятия решений при торговле несколькими инструментами во время анонсов крупных экономических событий. Основное внимание уделяется решению проблемы повышенной подверженности риску, вызванной повышенной волатильностью во время выпуска новостей.
Осваиваем JSON: Разработка пользовательского JSON-ридера с нуля на MQL5 Осваиваем JSON: Разработка пользовательского JSON-ридера с нуля на MQL5
В статье приведено пошаговое руководство по созданию пользовательского парсера JSON на языке MQL5, включающего обработку объектов и массивов, проверку ошибок и сериализацию. Вы сможет объединить торговую логику и структурированные данные с помощью гибкого решения для обработки JSON в MetaTrader 5.