Русский
preview
Redes neurais no trading: agregação do movimento ao longo do tempo (Conclusão)

Redes neurais no trading: agregação do movimento ao longo do tempo (Conclusão)

MetaTrader 5Sistemas de negociação |
32 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Introdução

Os mercados financeiros lembram um organismo vivo, pulsando ao ritmo das expectativas, do medo e da esperança. Os fluxos de cotações, como o sangue nas veias do sistema, carregam informações codificadas em cada movimento de preço, em cada pico de liquidez, em cada pequena oscilação que, à primeira vista, pode parecer aleatória. No entanto, um observador atento sabe que aqui nada acontece por acaso. Existem apenas padrões ocultos no caos. É justamente essa tênue fronteira entre ruído e estrutura que o framework Temporal Motion Aggregation (TMA) procura captar, sendo uma das abordagens mais interessantes e conceitualmente inovadoras para a análise de séries temporais surgidas nos últimos anos.

A ideia central do TMA é não tratar uma sequência de dados como um fluxo estático de valores, mas enxergá-la como uma dinâmica temporal, na qual o que importa não são os números em si, mas suas variações e relações ao longo do tempo. Esse movimento temporal constitui a base de toda a arquitetura. O foco aqui não está nas formas, mas nos ritmos. Não nos pontos, mas nas transições. Não nos próprios dados, mas na energia de seu movimento. Por um lado, essa abordagem se aproxima de uma interpretação física do mercado como um sistema de forças. Por outro, é profundamente matemática, pois opera em um espaço de padrões temporais que podem ser processados e combinados em estruturas maiores, capazes de descrever o comportamento do preço.

Do ponto de vista arquitetural, o framework é estruturado em módulos de pré-processamento, análise e agregação. No primeiro nível, os dados são filtrados e normalizados, além de ajustados a um ritmo e uma escala comuns. Nessa etapa, também são eliminadas cópias redundantes e latências desnecessárias, de modo a manter o fluxo de informações o mais limpo possível. O segundo nível é responsável pela formação dos padrões básicos de movimento, verdadeiros átomos da dinâmica do mercado. É aqui que o sistema aprende a reconhecer as formas mais simples de transição, a partir das quais serão construídas estruturas mais complexas. Por fim, o terceiro nível reúne tudo isso em uma representação única, agregando os padrões e criando representações generalizadas do movimento que podem ser utilizadas para previsão e tomada de decisões de trading.

A particularidade do TMA é que ele não opera sobre estados instantâneos isolados, mas sobre a continuidade temporal. Nas arquiteturas tradicionais de redes neurais, cada passo temporal é considerado separadamente, como um vetor estático de dados de entrada. No TMA, a lógica é diferente. A suavidade das transições, a direção e a velocidade das mudanças passam a fazer parte da análise. Isso torna o modelo sensível a ritmos ocultos que muitas vezes escapam aos algoritmos convencionais. Em essência, o TMA constrói um novo espaço de percepção, no qual o tempo se torna um eixo estrutural e o movimento, a unidade semântica da análise.

Em termos mais simples, o framework TMA pode ser visto como uma ponte entre o universo clássico das séries temporais e o conceito de dinâmica espacial. Ele combina a percepção física do fluxo do mercado com o rigor matemático da análise. É justamente aí que reside sua força singular. Essa arquitetura não apenas faz previsões, mas também explica o movimento. Ela permite compreender por que o preço se move de determinada maneira e não de outra. Por que alguns movimentos se repetem, enquanto outros desaparecem. E por que, em certos momentos, o mercado se comporta como um organismo vivo, reagindo a impulsos internos, e não a acontecimentos externos.

A visualização do framework TMA proposta pelos autores é apresentada abaixo.

Para nós, como pesquisadores e desenvolvedores, o TMA se tornou um ponto de partida para a busca de novas soluções no campo da previsão algorítmica. A implementação dessa abordagem em MQL5 permite não apenas avaliar a viabilidade das ideias propostas pelos autores, mas também adaptá-las às condições reais dos sistemas de trading.

Nos artigos anteriores, percorremos passo a passo o caminho desde os fundamentos teóricos até a implementação prática dos principais módulos do TMA. Criamos um sistema capaz de captar os fluxos de variações de preço, estruturá-los e extrair unidades semânticas de movimento, que posteriormente são combinadas em formas agregadas. Não se trata apenas de processamento de dados, mas de uma reconstrução inteligente da dinâmica do mercado. Um sistema baseado nos princípios do TMA funciona como um observador que não registra eventos isolados, mas percebe a cadência geral do movimento. Ele é capaz de identificar estrutura onde a maioria enxerga apenas oscilações aparentemente desordenadas.


Objeto de alto nível

Vale destacar que a arquitetura do TMA não surgiu de forma isolada. Sua base está em uma ideia proposta pelos autores do framework RAFT (Recurrent All-Pairs Field Transforms), que conhecemos anteriormente. O RAFT apresentou uma arquitetura elegante de processamento recorrente, na qual o movimento é representado como um campo contínuo de correspondências entre os quadros de um fluxo óptico. Esse princípio mostrou-se surpreendentemente próximo da natureza do mercado financeiro, em que cada novo valor de preço não existe de forma independente, mas está relacionado a diversos estados anteriores.

Nossa interpretação das ideias do RAFT mostrou como o conceito de campo recorrente de correspondências é versátil. É ele que permite integrar a dinâmica temporal em uma estrutura única. A cada nova iteração, a percepção do movimento é refinada, enquanto o modelo aprende a identificar relações de causa e efeito entre as mudanças.

Assim como ocorreu em nossa interpretação do framework RAFT, a etapa final da implementação das abordagens propostas pelos autores do TMA consiste na criação de um objeto de alto nível, o elemento central que reúne todos os componentes desenvolvidos anteriormente em um único sistema coerente. É nesse nível que a arquitetura se completa. Os módulos individuais passam a compor uma estrutura interna integrada, na qual os dados percorrem todo o ciclo de transformações, desde as sequências temporais originais até as representações agregadas do movimento.

Não é por acaso que o objeto desenvolvido durante a implementação do RAFT foi escolhido como classe pai. Essa herança permite reutilizar métodos já validados e preservar a continuidade dos princípios arquiteturais. Nosso objetivo é fazer com que cada novo framework seja uma extensão natural do anterior. As abordagens do RAFT forneceram uma base sólida para o processamento recorrente de dados, a interação eficiente entre os módulos e a operação no ambiente OpenCL. Por isso, o uso de uma classe pai já consolidada torna o TMA mais robusto, modular e flexível para escalar.

class CNeuronTMA  :  public CNeuronRAFT
  {
protected:
   CNeuronAddToStack cStatesStack;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronTMA(void) {};
                    ~CNeuronTMA(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &units[], uint &chanels[], uint group_size,
                          uint groups, uint segments, uint segment_size, uint levels,
                          uint window_key, uint heads,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronTMA;   }
   //--- 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      Clear(void) override;
  };

O objeto CNeuronTMA que estamos criando herda os principais mecanismos do RAFT, mas os adapta às novas tarefas de agregação temporal. Sua estrutura passa a incluir o stack de estados CNeuronAddToStack, responsável por armazenar e atualizar sequencialmente os dados temporais, algo especialmente importante no processamento de sequências de movimento.

O método Init desempenha um papel central na construção do novo objeto. É nele que o modelo ganha forma: são definidos os tamanhos das camadas, o número de canais, os parâmetros de agregação, a profundidade dos stacks de correlação e até mesmo a lógica das conexões recorrentes. Em essência, trata-se da etapa de montagem do organismo do modelo, na qual cada elemento recebe seu lugar e sua função, enquanto os recursos computacionais do OpenCL são distribuídos de modo a maximizar o paralelismo e a eficiência do processamento dos dados.

bool CNeuronTMA::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                      uint &units[], uint &chanels[], uint group_size, uint groups,
                      uint segments, uint segment_size, uint levels, uint window_key,
                      uint heads, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(units.Size() != chanels.Size())
      return false;
   uint layers = units.Size() - 1;
   if(!CNeuronSpikeActivation::Init(numOutputs, myIndex, open_cl, units[layers]*chanels[layers],
                                                                      optimization_type, batch))
      return false;

Logo no início do método, é verificada a consistência dos parâmetros recebidos. A dimensão do array que contém o número de elementos da sequência analisada em diferentes níveis (units) deve corresponder à dimensão do array que contém o número de canais (chanels). A quantidade de elementos nesses arrays determina o número de blocos neurais responsáveis pelo processamento inicial dos dados analisados.

Em seguida, é chamado o método homônimo da classe pai, responsável pela inicialização geral do módulo e que atua como uma espécie de interface para a interação externa com os componentes do modelo.

Concluída essa preparação, começa a construção da estrutura principal: o codificador (cEncoder) e o backbone de contexto (cContext). Ambos são montados a partir de estruturas repetidas CNeuronSpikeResNeXtBlock. Esses blocos realizam a extração profunda de atributos por meio de convoluções e formam uma representação robusta dos dados temporais.

   int index = 0;
   CNeuronSpikeResNeXtBlock*              resblock    = NULL;
   CNeuronSpikeConvGRU2D*                 gru         = NULL;
   CNeuronMultScalStackCorrelBySegments*  correlation = NULL;
   CNeuronTMAMFE*                         mfe         = NULL;
   CNeuronTMAMPA*                         mpa         = NULL;
   CNeuronTransposeRCDOCL*                transpose   = NULL;
   CNeuronBaseOCL*                        neuron      = NULL;
   CNeuronBatchNormOCL*                   norm        = NULL;
//---
   cEncoder.Clear();
   cContext.Clear();
   cCorrelation.Clear();
   cG.Clear();
   cEncoder.SetOpenCL(OpenCL);
   cContext.SetOpenCL(OpenCL);
   cCorrelation.SetOpenCL(OpenCL);
   cG.SetOpenCL(OpenCL);
//---
   for(uint i = 0; i < layers; i++)
     {
      resblock = new CNeuronSpikeResNeXtBlock();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, chanels[i], chanels[i + 1], units[i], units[i + 1],
                                                 group_size, groups, optimization, iBatch) ||
         !cEncoder.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      index++;
      resblock = new CNeuronSpikeResNeXtBlock();
      if(!resblock ||
         !resblock.Init(0, index, OpenCL, chanels[i], chanels[i + 1], units[i], units[i + 1], 
                                                 group_size, groups, optimization, iBatch) ||
         !cContext.Add(resblock))
        {
         DeleteObj(resblock)
         return false;
        }
      index++;
     }

O codificador é responsável pela compressão das informações, enquanto o ramo de contexto realiza seu refinamento recorrente, criando a base da memória do módulo.

Em seguida, é incorporado à estrutura o elemento recorrente CNeuronSpikeConvGRU2D, uma espécie de coração do backbone de contexto, responsável pelo processamento dinâmico das sequências temporais. Esse bloco permite que o modelo leve em conta os estados anteriores e ajuste o contexto atual de acordo com as informações acumuladas.

   gru = new CNeuronSpikeConvGRU2D();
   if(!gru ||
      !gru.Init(0, index, OpenCL, units[layers], chanels[layers], chanels[layers], optimization, iBatch) ||
      !cContext.Add(gru))
     {
      DeleteObj(gru)
      return false;
     }
   index++;

Depois que os fluxos básicos são formados, é criado o stack de estados cStatesStack, que funciona como armazenamento temporário da representação latente dos estados históricos do ambiente.

   if(!cStatesStack.Init(0, index, OpenCL, segments, chanels[layers], units[layers], optimization, iBatch))
      return false;
   index++;

EditarNa sequência, entra em cena o módulo CNeuronMultScalStackCorrelBySegments, que calcula correlações multinível entre segmentos. É justamente aqui que se materializa a ideia central do TMA: identificar padrões de movimento por meio da comparação entre múltiplas janelas temporais.

   correlation = new CNeuronMultScalStackCorrelBySegments();
   if(!correlation ||
      !correlation.Init(0, index, OpenCL, segments, segment_size, chanels[layers],
                                   units[layers], levels, optimization, iBatch) ||
      !cCorrelation.Add(correlation))
     {
      DeleteObj(correlation)
      return false;
     }
   index++;

Ao lado dele está o módulo CNeuronTMAMFE (Multi-Feature Extractor), que atua como um filtro sintetizador. Ele combina dois tipos de informação, os atributos espaço-temporais do movimento do preço e a matriz de correlações entre segmentos, em uma única representação. Sua principal função é extrair e reforçar os atributos mais relevantes desses fluxos, destacando as relações existentes entre eles.

O módulo recebe dois arrays como entrada: os resultados da agregação temporal (stack states) e os dados da correlação multiescala (correlation map). O primeiro array contém vetores compactos da dinâmica, uma espécie de quadros sucessivos da dinâmica temporal do preço que representam estados sucessivos do mercado. O segundo armazena os coeficientes de correlação entre esses estados, indicando quais segmentos do histórico se movem de forma sincronizada e quais apresentam comportamento em oposição de fase.

Dentro do MFE, essas duas fontes são combinadas de maneira coordenada. Primeiro, ambos os fluxos são normalizados e passam por filtros convolucionais multicanais, o que permite identificar combinações robustas de atributos. Em seguida, os fluxos são unidos, formando um único campo de atributos. A partir desse ponto, cada posição passa a incorporar informações tanto sobre a variação local do preço quanto sobre o contexto de correlação. Com isso, o fluxo de dados se transforma em um mapa inteligente do movimento, estruturado e autossuficiente.

   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, correlation.Neurons() + cStatesStack.Neurons(), optimization, iBatch) ||
      !cCorrelation.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   index++;
     {
      uint temp[] = { cStatesStack.GetDimension(), correlation.GetDimension() };
      mfe = new CNeuronTMAMFE();
      if(!mfe ||
         !mfe.Init(0, index, OpenCL, segments * units[layers], temp, chanels[layers], optimization, iBatch) ||
         !cCorrelation.Add(mfe))
        {
         DeleteObj(mfe)
         return false;
        }
     }
   index++;

O módulo CNeuronTransposeRCDOCL complementa essa estrutura, realizando a permutação das dimensões e garantindo o alinhamento correto dos dados para as etapas seguintes.

   transpose = new CNeuronTransposeRCDOCL();
   if(!transpose ||
      !transpose.Init(0, index, OpenCL, units[layers], segments, chanels[layers], optimization, iBatch) ||
      !cCorrelation.Add(transpose))
     {
      DeleteObj(transpose)
      return false;
     }
   index++;

Um papel especial é desempenhado por dois blocos CNeuronTMAMPA (Motion Pattern Aggregation). É por meio deles que o modelo passa a concentrar sua atenção nos movimentos realmente relevantes em meio a uma grande quantidade de pequenas variações. Depois que os dados passam pelo pré-processamento, pela correlação e pela agregação, ainda resta uma grande quantidade de fragmentos. Alguns carregam sinais importantes, enquanto outros representam apenas ruído. É nesse ponto que entra em ação o mecanismo de atenção.

Cada bloco CNeuronTMAMPA recebe como entrada segmentos formados segundo o princípio da agregação temporal. Esses blocos selecionam, em meio a essa diversidade, os padrões que se repetem com maior frequência ou apresentam maior energia de variação. Em outras palavras, destacam as partes que podem funcionar como indicadores do movimento futuro do preço. Na prática, eles formam uma máscara de pesos: alguns fragmentos são reforçados, enquanto outros têm sua influência reduzida.

Dessa forma, os blocos de atenção permitem que o modelo não processe indiscriminadamente todas as informações, mas estabeleça prioridades. É como se um trader experiente estivesse diante do monitor e dissesse: "este movimento foi relevante, guarde-o; aquilo foi apenas ruído, não leve em consideração". A diferença é que o modelo faz isso de forma automática e contínua. Na dinâmica do mercado, em que o ruído costuma superar os sinais, esse mecanismo se torna especialmente valioso.

   for(int i = 0; i < 2; i++)
     {
      mpa = new CNeuronTMAMPA();
      if(!mpa ||
         !mpa.Init(0, index, OpenCL, chanels[layers], window_key, segments, units[layers],
                                                           heads, optimization, iBatch) ||
         !cCorrelation.Add(mpa))
        {
         DeleteObj(mpa)
         return false;
        }
      index++;
     }

É importante observar que utilizamos dois blocos CNeuronTMAMPA em sequência. O primeiro pode atuar como um filtro inicial de atenção, eliminando o ruído mais evidente e identificando os fragmentos relevantes. O segundo funciona como uma camada de atenção mais profunda: recebe os fragmentos já selecionados pelo primeiro bloco e realiza uma filtragem ainda mais refinada, reforçando os elementos mais importantes e destacando as relações entre eles. Isso cria um efeito de filtragem de atenção em duas etapas e amplia a capacidade do modelo de identificar padrões na dinâmica do preço.

Assim, o CNeuronTMAMPA concretiza uma das ideias centrais do framework TMA: não apenas agregar o movimento, mas identificar nele estruturas que merecem atenção e concentrar o processamento justamente nelas. Isso torna o modelo mais seletivo e resistente ao ruído, permite que ele se adapte mais rapidamente às mudanças na dinâmica do mercado e, consequentemente, melhora a eficiência da previsão de preços.

Em seguida, todas as ramificações são combinadas em um único array de atributos por meio do cConcatenated.

   transpose = new CNeuronTransposeRCDOCL();
   if(!transpose ||
      !transpose.Init(0, index, OpenCL, segments, units[layers], chanels[layers], optimization, iBatch) ||
      !cCorrelation.Add(transpose))
     {
      DeleteObj(transpose)
      return false;
     }
   index++;
//---
   if(!cConcatenated.Init(0, index, OpenCL, units[layers] * ((segments + 2) * chanels[layers]),
                                                                        optimization, iBatch))
      return false;
   index++;

Depois disso, é ativada a segunda camada recorrente cGRU, que processa os dados já agregados. Essa etapa pode ser comparada a uma interpretação do que foi observado: o modelo não apenas memoriza a estrutura do movimento, mas começa a compreender sua evolução ao longo do tempo.

   if(!cGRU.Init(0, index, OpenCL, units[layers], (segments + 2) * chanels[layers], chanels[layers],
                                                                              optimization, iBatch))
      return false;
   index++;

Na etapa final, é formado o caminho de conexões residuais cResidual.

   if(!cResidual.Init(0, index, OpenCL, chanels[0], chanels[layers], units[0], units[layers], optimization, iBatch))
      return false;
   index++;

Como já vimos no framework RAFT, o backbone central de processamento de dados do TMA não apenas interpreta o estado do mercado, mas também prevê a variação dos atributos analisados. Cada iteração de cálculo constrói, na prática, um mapa da dinâmica esperada, mostrando como as estruturas atuais podem se transformar no instante seguinte. Esse deslocamento previsto resulta da atuação conjunta de todos os módulos, desde a correlação e a agregação até os blocos de atenção e o refinamento recorrente.

Os valores obtidos não são considerados isoladamente. Eles são somados ao estado atual do ambiente, formando um campo de atributos previstos. Graças a essa abordagem, o modelo permanece robusto mesmo quando o mercado altera seu padrão de comportamento. Em vez de perder a estrutura de atributos já acumulada, ele a refina gradualmente. A cada novo passo, o modelo não prevê um valor absoluto, mas uma correção, um pequeno deslocamento.

Depois disso, são adicionados dois componentes finais, responsáveis pela normalização final e pela estabilização do treinamento, evitando deslocamentos nas distribuições dos atributos.

   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(0, index, OpenCL, Neurons(), optimization, iBatch) ||
      !cG.Add(neuron))
     {
      DeleteObj(neuron)
      return false;
     }
   neuron.SetActivationFunction(None);
   if(!neuron.SetGradient(cGRU.getGradient(), true) ||
      !cResidual.SetGradient(cGRU.getGradient(), true))
      return false;
//---
   index++;
   norm = new CNeuronBatchNormOCL();
   if(!norm ||
      !norm.Init(0, index, OpenCL, Neurons(), iBatch, optimization) ||
      !cG.Add(norm))
     {
      DeleteObj(norm)
      return false;
     }
//---
   return true;
  }

Dessa forma, o método de inicialização realiza a montagem completa de uma arquitetura dinâmica multicamada.

O algoritmo de propagação para frente é implementado no método feedForward, que executa o laço principal de propagação dos dados por todos os componentes do framework TMA. Podemos encará-lo como o percurso de um único pacote de informações, desde os atributos iniciais do mercado até a previsão final da mudança de estado.

O algoritmo reflete a ideia de refinamento iterativo do movimento, característica de toda a filosofia do RAFT e do TMA, em que cada cálculo constitui um elo de uma cadeia contínua de adaptação.

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

Primeiro, os dados de entrada são projetados ao longo do caminho de conexões residuais. Esse bloco gera uma espécie de referência sobre a qual todas as alterações subsequentes serão aplicadas. Ele preserva a continuidade da representação e funciona como uma base para as atualizações posteriores.

Em seguida, são processados dois fluxos paralelos, cEncoder e cContext. O primeiro forma atributos locais, microestruturas do movimento que refletem os impulsos de curto prazo do mercado. Cada camada do codificador extrai sucessivamente novos níveis de abstração, partindo das oscilações elementares até chegar a padrões robustos de variação do preço.

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

O segundo fluxo, cContext, opera em um nível mais profundo. Ele estabelece dependências temporais mais amplas entre os atributos locais, garantindo a continuidade da análise. A cada passo, a representação de contexto se torna cada vez mais suave e robusta, acumulando a memória do modelo sobre os estados anteriores.

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

Quando ambos os fluxos concluem o processamento, os resultados do codificador são encaminhados ao módulo cStatesStack. Esse bloco acumula a representação latente do estado do ambiente, formando um stack temporal, ou seja, um histórico do movimento que pode ser analisado em conjunto.

   if(!cStatesStack.FeedForward(cEncoder[-1]))
      return false;

Ao mesmo tempo, o CNeuronMultScalStackCorrelBySegments gera o mapa de correlação, calculando o grau de sincronia entre diferentes fragmentos do histórico.

   CNeuronMultScalStackCorrelBySegments* correlation = cCorrelation[0];
   if(!correlation ||
      !correlation.FeedForward(cEncoder[-1]))
      return false;

Essas duas estruturas, o stack de estados e os coeficientes de correlação, passam a constituir o núcleo dos cálculos seguintes, pois concentram tanto a dinâmica quanto as relações entre os segmentos.

A etapa seguinte consiste em combinar os resultados por meio de uma operação de concatenação. Aqui, as duas fontes de informação, o stack de estados e o mapa de correlação, são reunidas em um único tensor. Essa combinação permite que o modelo analise o movimento não apenas ao longo do tempo, mas também no espaço das relações entre os diferentes segmentos.

   uint variables = cStatesStack.GetVariables();
   uint segments = cStatesStack.GetStackSize();
   uint dimension = cStatesStack.GetDimension();
   uint corr_dim = correlation.GetDimension();
//---
   prev = cCorrelation[1];
   if(!Concat(cStatesStack.getOutput(), correlation.getOutput(), prev.getOutput(),
              dimension, corr_dim, variables * segments))
      return false;

Em seguida, entra em operação o bloco de análise da dinâmica, no qual os módulos MFE e MPA são ativados sequencialmente.

  • O CNeuronTMAMFE extrai atributos conjuntos a partir dos segmentos e de suas correlações, reforçando as relações que se mantêm consistentes ao longo do tempo.
  • O CNeuronTMAMPA acrescenta um mecanismo de atenção seletiva, destacando os fragmentos mais relevantes do movimento, aqueles que carregam sinal em vez de ruído.

   for(int i = 2; i < cCorrelation.Total(); i++)
     {
      current = cCorrelation[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }

Cada passagem por esses blocos refina a representação do mercado. É como se o modelo aprendesse gradualmente a concentrar sua atenção apenas nos pontos em que algo realmente relevante está acontecendo.

Depois disso, os fluxos cEncoder, cContext e cCorrelation são novamente combinados em uma única estrutura. Essa etapa pode ser vista como um momento de integração das informações: atributos locais, contexto e relações de correlação são reunidos em uma representação coerente.

   if(!Concat(cEncoder[-1].getOutput(), cContext[-1].getOutput(), cCorrelation[-1].getOutput(),
              cConcatenated.getOutput(), dimension, dimension, segments * dimension, variables))
      return false;

O tensor resultante é passado ao cGRU, o bloco recorrente responsável pelo refinamento temporal das mudanças previstas. É aqui que o modelo acumula e atualiza a memória do movimento, formando uma previsão da evolução para o próximo estado.

   if(!cGRU.FeedForward(cConcatenated.AsObject()))
      return false;

Na etapa final, as saídas do GRU e do bloco residual são combinadas. É justamente aqui que se concretiza uma das ideias centrais do framework: somar os incrementos previstos ao estado atual do ambiente. O resultado é um novo mapa de atributos que representa a evolução esperada do mercado.

   current = cG[0];
   if(!current ||
      !SumAndNormilize(cGRU.getOutput(), cResidual.getOutput(), current.getOutput(), cGRU.GetChanels(),
                                                                                   false, 0, 0, 0, 1))
      return false;

Após a normalização, a ativação spiking introduz uma transformação não linear e leva o resultado a uma forma adequada à interpretação posterior.

   prev = current;
   for(int i = 1; i < cG.Total(); i++)
     {
      current = cG[i];
      if(!current ||
         !current.FeedForward(prev))
         return false;
      prev = current;
     }
//---
   return CNeuronSpikeActivation::feedForward(prev);
  }

Dessa forma, o método feedForward constitui um processo completo de modelagem da dinâmica do mercado, no qual cada módulo desempenha uma função específica. O codificador extrai a forma, o contexto estabelece suas relações ao longo do tempo, os blocos de correlação refinam as interdependências, enquanto o GRU e a atualização residual aproximam o modelo de uma previsão efetiva do comportamento futuro.

Em conjunto, todos esses componentes formam o backbone de processamento do movimento do framework TMA. Aqui, a informação não apenas avança pelo sistema, mas evolui, formando passo a passo uma compreensão cada vez mais precisa dos padrões do mercado financeiro.

Após a conclusão da propagação para frente, durante a qual o modelo gera sua própria previsão, começa a propagação reversa, um processo igualmente importante e ainda mais delicado. Agora, o sistema precisa identificar exatamente onde errou e em que medida cada transformação interna influenciou o resultado final. É justamente essa a função do método calcInputGradients. Ele transforma o erro em uma fonte de aprendizado, distribuindo-o pelas camadas do modelo como o sangue que percorre os capilares de um organismo vivo.

bool CNeuronTMA::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;
//---
   if(!CNeuronSpikeActivation::calcInputGradients(cG[-1]))
      return false;

O algoritmo começa verificando o ponteiro recebido para o objeto de dados de entrada, ao qual será passado o gradiente do erro. Se o objeto não estiver inicializado, a propagação reversa é interrompida. Não se trata apenas de uma precaução técnica, mas de uma proteção contra distorções no treinamento. Em seguida, são acionados os algoritmos correspondentes da classe pai, que implementa o módulo de ativação spiking responsável por encerrar a propagação para frente. Agora, é ele o primeiro a receber o sinal de erro propagado no sentido inverso, avaliando o quanto a saída efetiva da rede se desviou do valor-alvo. É nesse ponto que surge o gradiente inicial do erro, que então começa a percorrer os componentes do modelo em sentido inverso.

Na etapa seguinte, esse sinal passa pelo bloco cG, responsável pelo processamento final dos dados. O laço percorre as camadas da última para a primeira, chamando o método CalcHiddenGradients para cada elemento.

   CNeuronBaseOCL* curr = NULL;
//---
   for(int i = cG.Total() - 2; i >= 0; i--)
     {
      curr = cG[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cG[i + 1]))
         return false;
     }

O processo lembra uma reação em cadeia: cada neurônio determina com maior precisão em que medida sua própria contribuição aproximou ou afastou o modelo da resposta correta. O erro é gradualmente distribuído pelas camadas, eliminando componentes redundantes e preservando apenas a informação relevante para o aprendizado.

Em seguida, o gradiente percorre a ramificação residual cResidual. Seu papel pode ser comparado ao de um mecanismo de compensação: ele corrige os desvios acumulados e equilibra a influência das camadas adjacentes. Graças a isso, o modelo evita reajustes excessivamente bruscos e aplica correções mais graduais e precisas.

   if(!NeuronOCL.CalcHiddenGradients(cResidual.AsObject()))
      return false;

Depois disso, o processamento passa para o buffer concatenado cConcatenated, que durante a propagação para frente reunia as saídas dos diferentes fluxos em uma única estrutura. Agora, a tarefa é inversa: dividir o gradiente conjunto em suas partes constituintes e devolver a cada fluxo a parcela correspondente de responsabilidade pelo erro.

   if(!cConcatenated.CalcHiddenGradients(cGRU.AsObject()))
      return false;
//---
   uint variables = cStatesStack.GetVariables();
   uint segments = cStatesStack.GetStackSize();
   uint dimension = cStatesStack.GetDimension();
//---
   if(!cEncoder[-1] || !cContext[-1] || !cCorrelation[-1] ||
      !DeConcat(cEncoder[-1].getGradient(), cContext[-1].getGradient(), cCorrelation[-1].getGradient(),
                cConcatenated.getGradient(), dimension, dimension, segments * dimension, variables))
      return false;
   Deactivation(cContext[-1])
   Deactivation(cCorrelation[-1])
   if(cEncoder[-1].Activation() != None)
      if(!DeActivation(cEncoder[-1].getOutput(), cEncoder[-1].getPrevOutput(),
                       cEncoder[-1].getGradient(), cEncoder[-1].Activation()))
         return false;

É nesse ponto que entra em ação a operação DeConcat. Não se trata apenas de uma separação matemática dos dados, mas de algo semelhante a decompor uma sinfonia nas partes de cada instrumento. O erro é distribuído entre o codificador, o contexto e o bloco de correlação, três componentes fundamentais da propagação para frente. O codificador responde pela extração de atributos, o contexto preserva as dependências temporais e o bloco de correlação identifica as relações entre os estados do mercado. Após a separação dos fluxos, são desativados o contexto e a correlação, reduzindo gradualmente a atividade excedente para evitar a amplificação do ruído e interferências cruzadas durante o treinamento.

Em seguida, a atenção se volta para os blocos de correlação. A propagação reversa por esses blocos lembra o trabalho de um analista que revisita o histórico dos dados para determinar quais relações realmente se mostraram relevantes. Assim, o modelo aprende a distinguir padrões genuínos de coincidências aleatórias.

  for(int i = cCorrelation.Total() - 2; i >= 1; i--)
     {
      curr = cCorrelation[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cCorrelation[i + 1]))
         return false;
     }

Depois disso, é realizada uma nova desconcatenação, desta vez seguida pela soma dos dados dos dois fluxos de informação no nível da última camada do codificador.

   CNeuronMultScalStackCorrelBySegments* correlation = cCorrelation[0];
   uint corr_dim = correlation.GetDimension();
   if(!DeConcat(cStatesStack.getGradient(), correlation.getGradient(), curr.getGradient(),
                dimension, corr_dim, variables * segments))
      return false;
   curr = cEncoder[-1];
   if(!curr.CalcHiddenGradients(cStatesStack.AsObject()) ||
      !SumAndNormilize(curr.getGradient(), curr.getPrevOutput(),
                       curr.getPrevOutput(), dimension, false, 0, 0, 0, 1))
      return false;
   if(!curr.CalcHiddenGradients(correlation) ||
      !SumAndNormilize(curr.getGradient(), curr.getPrevOutput(),
                       curr.getGradient(), dimension, false, 0, 0, 0, 1))
      return false;

Em seguida, o algoritmo retorna ao codificador. É como se o modelo revisasse sua própria percepção do mercado, analisando quais atributos realmente continham informação relevante e quais se mostraram enganosos. Cada camada do codificador recebe sua parcela do sinal de retroalimentação e ajusta os pesos internos, refinando os filtros usados para interpretar os dados.

   for(int i = cEncoder.Total() - 2; i >= 0; i--)
     {
      curr = cEncoder[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cEncoder[i + 1]))
         return false;
     }
   if(!NeuronOCL.CalcHiddenGradients(curr))
      return false;

Depois, um processo semelhante percorre o bloco de contexto. Aqui, o modelo reconstrói as relações temporais, como se revisitasse os acontecimentos recentes para identificar em que ponto perdeu o encadeamento de causa e efeito.

   for(int i = cContext.Total() - 2; i >= 0; i--)
     {
      curr = cContext[i];
      if(!curr ||
         !curr.CalcHiddenGradients(cContext[i + 1]))
         return false;
     }

Ao final, o método soma, no nível do objeto de dados de entrada, os gradientes recebidos pelos diferentes fluxos de informação. As diversas ramificações do modelo, cada uma com sua própria lógica, são reconciliadas em um resultado comum, de modo que a atualização final dos pesos permaneça coerente.

   CBufferFloat* temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(NeuronOCL.getPrevOutput(), false) ||
      !NeuronOCL.CalcHiddenGradients(curr) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

Dessa forma, o método calcInputGradients transforma uma operação matemática aparentemente simples em um verdadeiro processo de autoaprendizado. Ele não apenas distribui o erro, mas percorre em sentido inverso todo o caminho de processamento do modelo, ajudando-o a identificar onde e por que errou. É aí que se revela o caráter vivo do framework TMA: o sistema não se limita a reagir aos dados, mas também avalia suas próprias decisões, aprende a identificar seus pontos fracos e, passo a passo, torna suas previsões mais precisas.

A etapa final do ciclo de treinamento consiste na atualização dos parâmetros do modelo. Nesse ponto, toda essa operação se resume à transferência sequencial do controle para os componentes internos que contêm pesos treináveis. Cada bloco já recebeu sua parcela do gradiente calculado e ajusta seus parâmetros de acordo com a direção indicada pela propagação reversa do erro. O processo lembra o funcionamento coordenado de um mecanismo complexo, no qual cada engrenagem sabe exatamente quando e quanto deve girar para tornar o sistema como um todo mais preciso e robusto.

O método updateInputWeights implementa essa etapa de forma bastante transparente. Não há nele armadilhas ocultas nem decisões ambíguas. Sua função é simplesmente encaminhar o comando de atualização aos módulos que contêm parâmetros treináveis. Essa abordagem torna a arquitetura flexível e extensível, permitindo adicionar novos componentes sem comprometer a lógica geral de interação.

O algoritmo do método não apresenta particularidades que exijam uma análise adicional, por isso proponho deixá-lo para estudo por conta própria. O código completo da classe CNeuronTMA e de todos os seus métodos está disponível no anexo.


Arquitetura dos modelos

Chegamos a um ponto importante. Até aqui, realizamos um trabalho extenso na criação de todos os principais componentes do framework TMA, em sua integração e na consolidação de toda a estrutura sob o gerenciamento de um único objeto de alto nível. Agora começa uma nova etapa, essencialmente prática: a construção da arquitetura dos modelos treináveis, que não apenas preverão a dinâmica do mercado, mas também tomarão decisões reais de trading.

Nesta etapa, voltamos a seguir o princípio da continuidade. Como antes, o framework evolui de forma gradual, sem descartar desnecessariamente o que já foi desenvolvido, mas aperfeiçoando continuamente a base existente. Por isso, utilizamos como ponto de partida uma arquitetura já consolidada, criada durante a implementação do framework RAFT. Onde antes o Codificador do estado do ambiente utilizava o módulo CNeuronRAFT, agora entra em cena o novo componente CNeuronTMA. À primeira vista, essa substituição pode parecer pequena, mas é justamente ela que permite ao modelo alcançar uma percepção mais profunda e multicamada dos processos de mercado.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTMA;
     {
      uint temp[] = {prev_out,                  // Chanels In
                     32,
                     64,
                     128                        // Chanels Out
                    };
      if(ArrayCopy(descr.windows, temp) < (int)temp.Size())
         return false;
     }
     {
      uint temp[] = {prev_count,                // Units In
                     128,
                     64,
                     32                         // Units Out
                    };
      if(ArrayCopy(descr.units, temp) < (int)temp.Size())
         return false;
     }
     {
      uint temp[] = {StackSize,                 // Segments
                     4                          // Segments Size
                    };
      if(ArrayCopy(descr.heads, temp) < (int)temp.Size())
         return false;
     }
   descr.window = EmbeddingSize / NHeads;       // Group size
   descr.count = NHeads;                        // Groups
   descr.layers = 3;                            // Correlation's Levels
   descr.window_out = EmbeddingSize / NHeads;   // Dimension K
   descr.step = NHeads;                         // Attention Heads
   descr.optimization = ADAM;
   descr.batch = BatchSize;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
   iLatentLayer++;

O bloco TMA incorporado à arquitetura preserva os principais pontos fortes do RAFT, como consistência recorrente, resistência ao ruído e capacidade de refinamento dos atributos, acrescentando a eles um novo mecanismo de agregação multinível e análise criteriosa dos padrões dinâmicos.


Testes

Depois que a arquitetura do modelo treinável está totalmente definida e o módulo TMA é integrado ao Codificador do estado do ambiente, começa a etapa mais importante e interessante: o treinamento e os testes práticos. É aqui que podemos verificar se o TMA é capaz não apenas de processar os dados, mas de fato interpretar o mercado em seus próprios termos, reconhecendo padrões, reagindo à dinâmica dos preços e tomando decisões como faria um trader experiente.

A primeira etapa do treinamento pode ser comparada a um ensaio no palco histórico do mercado. Utilizamos dados do par de moedas EURUSD no timeframe M15, referentes ao período de janeiro de 2024 a junho de 2025. Nesse intervalo, o modelo é exposto a um amplo conjunto de condições históricas de mercado e aprende a reconhecer seus padrões internos: volatilidade, reação a notícias, relações entre os principais atributos e a estrutura oculta das tendências. Nessa fase, o framework atua como um analista sistêmico, formando uma compreensão robusta do estado do mercado. Gradualmente, o modelo desenvolve a capacidade de prever a evolução dos acontecimentos e avaliar o nível de risco associado a cada operação em potencial.

Em seguida, começa uma etapa mais dinâmica: o ajuste online no Testador de Estratégias do MetaTrader 5. Agora, o modelo opera em condições próximas às de tempo real, processando os dados candle a candle. Cada mudança de preço representa uma nova situação à qual é preciso responder com rapidez e precisão. O TMA mantém sua robustez diante de oscilações bruscas, suaviza o ruído e ajusta a estratégia em períodos de baixa liquidez. O que antes havia sido aprendido com o histórico se transforma em um comportamento dinâmico, flexível, adaptativo e consistente.

A etapa final consiste no teste com dados novos do período de julho a setembro de 2025. Aqui, o modelo entra em um ambiente ainda não observado, sem qualquer orientação prévia. Todos os parâmetros obtidos durante o treinamento são carregados sem alterações. Essa etapa mostra até que ponto o TMA é capaz de generalizar e operar em novas condições de mercado, nas quais a experiência passada ajuda, mas não garante o sucesso.

Ao longo dos três meses de teste do framework TMA, o modelo realizou 59 operações, mantendo uma atividade moderada e um equilíbrio entre posições compradas e vendidas. Isso, por si só, já indica que o framework interpreta adequadamente os sinais do ambiente, não apresenta tendência a uma exposição excessiva e atua de forma consistente.

O resultado financeiro foi modesto: lucro líquido de 1,44% sobre um depósito inicial de 100 USD. Ao mesmo tempo, o lucro bruto e a perda bruta ficaram praticamente equilibrados, 40,52 USD contra -39,08 USD, resultando em um Profit Factor de 1,04, muito próximo do ponto de equilíbrio. Esse desempenho é típico de um modelo ainda em fase de adaptação: ele ainda não foi otimizado quanto às condições de entrada e à estratégia de gestão de capital, mas já demonstra estabilidade sem incorrer em perdas catastróficas.

O drawdown de 16-17% indica um nível de risco moderado, bastante aceitável nesta etapa de pesquisa. O saldo e o equity evoluem de forma sincronizada, o que confirma que o modelo encerra as posições em tempo hábil, sem acumular perdas flutuantes perigosas. Esse é um indicador importante da maturidade da lógica interna de tomada de decisões: o sistema não permanece preso a operações perdedoras e reage corretamente aos sinais de saída.

A análise das sequências de operações também confirma o equilíbrio do comportamento: o máximo de operações vencedoras consecutivas foi de 4, enquanto o de operações perdedoras chegou a 6. A proporção de operações lucrativas, 52,5%, supera apenas ligeiramente o patamar de 50%, mas observa-se uma tendência de simetria entre posições vendidas e compradas. Isso indica a neutralidade da estratégia, que não apresenta viés direcional significativo.

A evolução do capital apresenta um comportamento ondulatório: após um período de crescimento consistente na primeira metade de julho, ocorre um drawdown, seguido por uma fase de estagnação. Esse é um cenário clássico para um modelo que ainda busca o equilíbrio ideal entre capacidade de reação e estabilidade.

De modo geral, os resultados dos testes mostram que o framework TMA é capaz de desenvolver uma lógica de trading própria, analisar os dados do mercado e tomar decisões sem depender de suporte externo. Ele supera com sucesso um teste fundamental: opera de forma estável, sem perder o controle do risco, e apresenta sinais de capacidade de aprendizado. Esse resultado pode ser considerado um ponto de partida: o sistema demonstrou sua viabilidade e agora precisa evoluir.


Conclusão

Concluímos a construção do framework TMA e realizamos os primeiros testes do modelo. Agora podemos afirmar com segurança que a arquitetura desenvolvida não apenas funciona, mas apresenta todas as características de um sistema plenamente treinável. Ela analisa o mercado, extrai padrões dos dados e toma decisões com base em sua lógica interna, em vez de seguir regras previamente definidas.

Os resultados dos testes mostraram que o modelo já é capaz de atuar de forma robusta em um ambiente dinâmico, mantendo o controle do risco e evitando os erros típicos do sobreajuste. É verdade que o lucro ainda é pequeno, mas o aspecto mais importante é outro: o TMA comprovou a viabilidade de seu conceito. Ele foi testado com dados reais, manteve a estabilidade e estabeleceu uma base sólida para seu desenvolvimento futuro.

O próximo passo é evidente: otimização aprofundada, ajuste fino dos parâmetros e ampliação dos modelos de comportamento. A partir dessa base, será possível construir sistemas mais complexos. Nosso framework já mostrou que é capaz de pensar em termos do futuro. Agora é hora de ensiná-lo a agir com a confiança de um profissional.


Referências


Programas utilizados no artigo

# Nome Tipo Descrição
1 Study.mq5 Expert Advisor EA para treinamento offline de modelos
2 StudyOnline.mq5 Expert Advisor EA para treinamento online de modelos
3 Test.mq5 Expert Advisor EA para teste do modelo
4 Trajectory.mqh Biblioteca de classes Estrutura para descrição do estado do sistema e da arquitetura dos modelos
5 NeuroNet.mqh Biblioteca de classes Biblioteca de classes para criação de redes neurais
6 NeuroNet.cl Biblioteca Biblioteca de código do programa OpenCL

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/20228

Arquivos anexados |
MQL5.zip (3307.17 KB)
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Integração rápida de um grande modelo de linguagem com o MetaTrader 5 (Parte I): Criando o modelo Integração rápida de um grande modelo de linguagem com o MetaTrader 5 (Parte I): Criando o modelo
O artigo explora uma integração revolucionária entre grandes modelos de linguagem (LLM) e a plataforma de trading MetaTrader 5, na qual a AI não se limita a prever preços, mas toma decisões de negociação de forma autônoma, analisando o contexto do mercado como faria um trader experiente. O autor mostra a diferença fundamental entre os LLM e modelos clássicos de machine learning, como o CatBoost: a capacidade de metacognição e autorreflexão, que permite ao sistema aprender com os próprios erros e aprimorar sua estratégia.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Redes neurais em trading: Agregação temporal do movimento (Componentes principais) Redes neurais em trading: Agregação temporal do movimento (Componentes principais)
Neste artigo, a teoria encontra a prática. Vamos implementar os principais módulos do framework TMA: MPE e MPA. É aqui que os dados passam a ganhar significado e a atenção cruzada se transforma em uma ferramenta de análise precisa da dinâmica do mercado. Menos operações redundantes, mais eficiência: mais um passo rumo ao trading inteligente de nova geração.