Русский
preview
Redes neurais em trading: sinais de negociação robustos em qualquer regime de mercado (módulos de atenção)

Redes neurais em trading: sinais de negociação robustos em qualquer regime de mercado (módulos de atenção)

MetaTrader 5Sistemas de negociação |
49 1
Dmitriy Gizlyk
Dmitriy Gizlyk

Introdução

Os mercados financeiros são um organismo vivo, que respira, evolui e está em constante transformação. Não existe estabilidade absoluta. Correlações habituais entre ativos podem desaparecer em apenas algumas sessões de negociação. O mercado não se resume aos números exibidos nos gráficos. Ele também reflete emoções, expectativas e decisões humanas, que se entrelaçam em uma rede extremamente complexa de relações. Nesse ambiente de caos e incerteza, ferramentas capazes de identificar uma ordem subjacente tornam-se especialmente valiosas.

O framework ST-Expert, que começamos a abordar no artigo anterior, foi criado justamente para encontrar padrões onde os métodos convencionais de análise se mostram ineficazes. Sua principal característica é a capacidade de combinar duas dimensões: a temporal e a espacial. O aspecto temporal corresponde à abordagem clássica da previsão financeira, baseada na análise de dados históricos e na tentativa de prever o futuro com base em cotações passadas. Já o aspecto espacial acrescenta um novo nível de compreensão: o mercado passa a ser visto como uma rede na qual cada ativo está conectado aos demais, e as mudanças em um segmento inevitavelmente repercutem nos segmentos relacionados.

Imagine que estejamos analisando o mercado de ações. Quando o preço do petróleo começa a subir, isso se reflete quase imediatamente nas cotações das empresas petrolíferas. Em seguida, o movimento pode atingir as moedas de países produtores de petróleo e setores inteiros de atividades econômicas relacionadas. Um modelo clássico, construído apenas com séries temporais de preços, enxerga somente a trajetória do instrumento analisado. O ST-Expert, por sua vez, é capaz de captar toda essa cascata de mudanças, pois trabalha não apenas com o tempo, mas também com a dimensão espacial das relações de mercado.

A arquitetura do framework baseia-se no princípio Mixture of Experts, ou mistura de especialistas. Essa abordagem pode ser comparada ao funcionamento de um comitê de investimentos. Nele, reúnem-se analistas com diferentes especializações: um é especializado em análise técnica, outro em macroeconomia e um terceiro acompanha as correlações entre setores. Isoladamente, cada um deles tem capacidades limitadas. No entanto, quando suas avaliações são combinadas, a decisão final se aproxima muito mais da realidade. O mesmo ocorre no ST-Expert: cada módulo especialista responde por uma parte específica da análise, enquanto o sistema combina suas previsões para formar uma solução integrada.

Essa distribuição de tarefas proporciona ao sistema três vantagens principais:

  • Adaptabilidade. O mercado muda a cada minuto, e o sistema precisa acompanhar essas mudanças. Se ontem o setor de commodities era o principal fator de movimento e hoje esse papel passou ao mercado cambial, o ST-Expert se reajusta, redistribuindo a importância entre os especialistas.
  • Robustez. Quando um dos especialistas erra, a previsão geral mantém sua qualidade graças à combinação das avaliações do conjunto. Isso reduz o risco de o sistema ficar preso a uma única estratégia e aumenta sua confiabilidade.
  • Interpretabilidade. Para o trader, é importante compreender não apenas o resultado da previsão, mas também os motivos que levaram a ele. No ST-Expert, é sempre possível identificar qual especialista teve a maior contribuição e quais relações se mostraram relevantes. Assim, o sistema deixa de ser uma caixa-preta e se torna uma ferramenta que pode ser usada de forma consciente.

Na linguagem do trading, o ST-Expert não é apenas um indicador, mas um verdadeiro sistema de navegação. Ele considera as correntes de tendência, capta impulsos locais, analisa as correlações entre os ativos e constrói um mapa do mercado em tempo real. Além disso, o sistema consegue atualizar esse mapa quando as rotas habituais deixam de funcionar. Na prática, isso significa menos sinais falsos e mais decisões alinhadas à dinâmica real do mercado.

A arquitetura do framework inclui vários elementos fundamentais. No centro está um modelo em grafo, no qual os vértices representam ativos ou pontos temporais, enquanto as arestas representam as relações entre eles. Essas relações podem ser diretas e evidentes, como a dependência do ouro em relação à cotação do dólar, ou ocultas, como as correlações entre determinadas ações de um mesmo setor. A função do sistema não é apenas identificar essas relações, mas também reconfigurá-las quando o mercado impõe novas condições.

O módulo de combinação adaptativa de grafos desempenha um papel essencial. Ele pode ser comparado ao maestro de uma orquestra. Cada instrumento tem sua própria partitura, mas é o maestro que decide qual voz deve ganhar destaque em cada momento. Quando algumas relações perdem relevância, o sistema reforça outras, preservando a consistência da previsão.

O mecanismo de treinamento do ST-Expert é igualmente importante. Ele foi estruturado para expor o sistema a diferentes cenários. Isso permite que ele esteja preparado para novas situações, seja um súbito aumento no fluxo de notícias, uma mudança nas correlações ou o surgimento de novos fatores de mercado. Na linguagem do trading, isso equivale a se preparar para qualquer cenário, desde um mercado lateral e tranquilo até uma tendência intensa.

A visualização do framework ST-Expert criada pelos autores é apresentada a seguir.

Visualização do framework ST-Expert criada pelos autores

No artigo anterior, não nos limitamos à teoria e demos o primeiro passo rumo à implementação prática das abordagens do ST-Expert com os recursos do MQL5. Construímos um objeto de geração de graphons, elemento fundamental da arquitetura responsável pela modelagem de relações probabilísticas. Esse componente estabelece a base para a análise adaptativa de estruturas espaciais.

Hoje, damos continuidade a esse desenvolvimento. Nosso objetivo é desenvolver a implementação do framework passo a passo, transformando-o de um conjunto de ideias em uma ferramenta completa de trading algorítmico.



Ideias de aplicação

Ao iniciar o desenvolvimento do modelo, vale lembrar outra característica importante do framework ST-Expert: sua versatilidade. Os autores destacam que as abordagens propostas não se limitam ao uso em modelos em grafo. Pelo contrário, elas podem ser incorporadas a diversos modelos clássicos, ampliando suas capacidades e acrescentando novas propriedades. Em essência, trata-se de uma arquitetura modular. O ST-Expert pode ser visto como um conjunto de componentes adaptativos capazes de funcionar tanto de forma independente quanto como parte de sistemas mais amplos.

Essa versatilidade é especialmente valiosa no contexto financeiro. Afinal, traders e pesquisadores raramente se limitam a um único modelo. Em geral, utilizam um conjunto completo de ferramentas. A possibilidade de integrar as abordagens do ST-Expert a essas ferramentas abre um amplo campo para experimentação. É possível aumentar sua robustez a deslocamentos espaciais, ampliar as capacidades de análise ou acrescentar um novo nível de interpretabilidade.

Neste artigo, decidimos desenvolver justamente essa linha. Como base experimental, escolhemos o framework Extralonger. Sua arquitetura combina fatores locais e globais, o que permite modelar com maior precisão os processos complexos do mercado financeiro.

A integração entre o ST-Expert e o Extralonger é um passo natural. O primeiro acrescenta ao sistema mecanismos para trabalhar com grafos dinâmicos e uma mistura de especialistas capaz de se adaptar às mudanças na estrutura das relações. O segundo oferece um ambiente consolidado para o processamento de séries temporais e módulos já implementados de análise espaço-temporal. Juntos, eles formam uma combinação em que cada framework amplia as capacidades do outro. O Extralonger ganha um nível adicional de adaptabilidade e robustez, enquanto o ST-Expert passa a contar com uma plataforma já testada para sua aplicação em trading algorítmico.

Vejamos como isso pode funcionar na prática. Suponha que o Extralonger analise o comportamento de um par de moedas e gere uma previsão com base em sua dinâmica temporal. Em determinado momento, porém, as cotações de commodities ou os índices acionários passam a exercer forte influência sobre o mercado. Com apenas os recursos básicos do Extralonger, essa mudança nos principais fatores de mercado pode passar despercebida. Já os módulos integrados do ST-Expert, que trabalham com graphons e módulos especialistas, conseguem identificar as novas relações e reorganizar a representação do mercado. Como resultado, a previsão mantém sua precisão mesmo diante de mudanças radicais nas condições de mercado.

Para transformar essa ideia em uma implementação prática, é necessário definir os pontos de integração. Vale lembrar que o framework Extralonger se baseia em um Transformer de três rotas, construído segundo um princípio modular. Ele combina três tipos de análise: temporal, espacial e combinada. Cada rota oferece uma perspectiva própria sobre os dados de mercado, e sua atuação conjunta permite gerar uma previsão integrada.

A análise temporal do Extralonger é implementada por meio de um Transformer clássico, que processa sequências de dados e identifica padrões nas séries temporais. Esse módulo funciona como a base da arquitetura: ele sustenta a análise ao captar as relações dinâmicas recorrentes.

A análise espacial baseia-se em uma abordagem global-local. Nessa arquitetura, o mercado é visto como uma rede de nós interconectados, em que os elementos vizinhos formam padrões locais, enquanto as relações mais distantes proporcionam uma visão global. Essa abordagem é especialmente importante no contexto financeiro, pois os instrumentos podem estar relacionados tanto de forma direta quanto por mecanismos mais complexos, como ocorre entre o setor de tecnologia e os índices acionários.

Por fim, a rota mista integra as duas abordagens, temporal e espacial. Ela permite que o modelo sintetize diferentes níveis de análise, criando uma espécie de visão tridimensional. Com isso, o Extralonger consegue acompanhar simultaneamente a dinâmica das tendências e considerar as dependências presentes na rede.

É justamente nesse ponto que surge a possibilidade de integrar o ST-Expert. Seus especialistas baseados em graphons podem ser incorporados de forma natural ao módulo, ampliando sua capacidade de trabalhar com estruturas de relações em constante mudança. Além disso, o próprio conceito de mistura de especialistas, Mixture of Experts, adapta-se perfeitamente à arquitetura modular do Extralonger, que já distribui as tarefas entre diferentes rotas.

Comecemos pela rota temporal. A arquitetura do Transformer clássico pode ser convenientemente comparada a um modelo em grafo. Na prática, o mecanismo de Self-Attention constrói uma matriz de logits que representa a influência probabilística de cada elemento da sequência sobre os demais. Ao considerar os elementos da sequência como nós, essa matriz de logits se transforma em um grafo cujas arestas expressam a intensidade das interações. Nesse sentido, o Transformer não apenas analisa a sequência, mas a converte em uma rede dinâmica de interações, permitindo identificar dependências locais e globais que, de outra forma, permaneceriam ocultas.

Isso abre uma possibilidade interessante. Podemos substituir o mecanismo clássico de geração de logits do Self-Attention por um grafo construído de acordo com os princípios do framework ST-Expert. Essa abordagem permite estabelecer relações mais significativas entre os elementos da sequência, com base em correlações estatísticas, regras definidas por especialistas e dependências estruturadas. Esperamos que, dessa forma, o modelo seja capaz de compreender o mercado com maior profundidade, identificando relações sutis que o Self-Attention convencional pode não captar.

Uma abordagem semelhante pode ser aplicada ao módulo de análise de dependências globais e locais. Nesse caso, o modelo constrói uma espécie de estrutura geral do mercado, registrando padrões e tendências de maior amplitude. As relações globais ajudam a prever os movimentos gerais do mercado, permitindo que o modelo enxergue a floresta, e não apenas as árvores isoladas.

As dependências locais, porém, exigem uma abordagem diferente. É necessário construir um grafo esparso que considere apenas as relações mais relevantes e prováveis entre os nós da sequência. Isso se assemelha à atuação de um trader experiente, que concentra sua atenção nos principais pontos de entrada e saída e ignora sinais ruidosos de pouca relevância. Os métodos do framework ST-Expert também podem ser aplicados nesse contexto, mas com atenção especial à seleção das relações mais importantes. Essa abordagem permite que o modelo mantenha uma visão ampla sem perder a atenção aos detalhes, identificando influências em pequena escala que afetam de maneira significativa as oscilações de curto prazo.

O conceito está claro. Agora é hora de passar da teoria à prática e implementar essas ideias com os recursos do MQL5.



Módulo de atenção em grafos

Para implementar a variante do Transformer baseada em graphons apresentada acima, criamos o objeto CNeuronGraphAttention, que herda a funcionalidade básica do objeto de camada totalmente conectada CNeuronBaseOCL e se torna o núcleo do nosso modelo de atenção em grafos. Esse objeto não apenas combina o processamento clássico de valores com estruturas em grafo, mas também transforma a sequência de dados em uma rede dinâmica de relações.

class CNeuronGraphAttention   :  public CNeuronBaseOCL
  {
protected:
   CNeuronConvOCL    cValue;
   CNeuronGraphons   cGraphs;
   CNeuronSoftMaxOCL cScores;
   CNeuronBaseOCL    cAttention;
   CNeuronBaseOCL    cResidual;
   CNeuronConvOCL    cFeedForward[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronGraphAttention(void) {};
                    ~CNeuronGraphAttention(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint emb_dimension,
                          uint experts, float dropout,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const   {  return defNeuronGraphAttention;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      TrainMode(bool flag) override;
  };

Dentro do modelo, os grafos construídos de acordo com os princípios do ST-Expert definem a estrutura das relações entre os elementos da sequência, permitindo destacar conexões relevantes e ignorar o ruído. Ao mesmo tempo, a função SoftMax forma uma distribuição probabilística da atenção sobre o grafo, dando maior peso às interações mais importantes, enquanto as conexões residuais preservam e integram tanto as novas dependências quanto as já acumuladas, criando uma espécie de memória do modelo.

É importante destacar que, dentro do objeto, abandonamos o esquema convencional de Self-Attention e não criamos as entidades Query e Key. No Transformer clássico, elas são usadas para construir a matriz de logits que representa a influência mútua entre os elementos da sequência. Em nossa abordagem, porém, essa função é desempenhada pelo grafo construído de acordo com os princípios do ST-Expert. Com isso, podemos controlar diretamente as relações entre os nós e concentrar a atenção nas interações mais relevantes, sem gerar matrizes intermediárias desnecessárias. Permanecem apenas os valores Value, usados para formar os resultados finais da atenção. Como matéria-prima nas mãos de um artesão habilidoso, eles percorrem o grafo e os blocos Feed-Forward, transformando-se em sinais informativos para a previsão do mercado.

Essa abordagem permite que o modelo opere de forma mais direcionada. A atenção se concentra nas relações fundamentais para a série financeira, em vez de se distribuir de maneira uniforme entre todos os elementos. Como resultado, obtemos um mecanismo que preserva a eficiência do Transformer, mas oferece uma interpretação muito mais significativa e estruturada das relações presentes na sequência.

O método Init é responsável por preparar o objeto CNeuronGraphAttention para a operação. É nele que todos os componentes do modelo são integrados, formando um mecanismo cuidadosamente ajustado e pronto para processar dados financeiros.

bool CNeuronGraphAttention::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                 uint units, uint window, uint emb_dimension,
                                 uint experts, float dropout,
                                 ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units * window, optimization_type, batch))
      return false;
   activation = None;

Primeiro, é realizada a inicialização da classe pai, na qual são definidos os parâmetros gerais do neurônio. Em seguida, os componentes internos do objeto são configurados um a um.

O primeiro componente inicializado é o bloco cValue, responsável pela codificação inicial dos valores da sequência. Para ele, são definidos a janela de análise e o número de elementos da sequência analisada. A função de ativação é desativada para passar os dados com o mínimo possível de transformação.

   int index = 0;
   if(!cValue.Init(0, index, OpenCL, window, window, window, units, 1, optimization, iBatch))
      return false;
   cValue.SetActivationFunction(None);

Em seguida, é criado o bloco de graphons cGraphs, construído de acordo com os princípios do ST-Expert, que define a estrutura das relações entre os elementos da sequência.

   index++;
   if(!cGraphs.Init(0, index, OpenCL, units, window, emb_dimension, experts, dropout, optimization, iBatch))
      return false;

Na saída do módulo cGraphs, obtemos um grafo que representa as probabilidades de haver conexões entre os nós da sequência. Essa informação já é valiosa, mas, para o módulo de atenção, precisamos transformar essas probabilidades em uma distribuição completa da influência exercida por cada elemento da sequência sobre o objeto analisado. É nesse ponto que entra em ação o bloco cScores com a função SoftMax. Ela normaliza cuidadosamente as probabilidades, reforçando as relações principais e reduzindo o peso das secundárias, formando assim uma distribuição de atenção precisa e interpretável. Como resultado, cada elemento da sequência recebe um peso específico na análise, e o modelo consegue se concentrar nas interações realmente relevantes, ignorando o ruído e as oscilações pouco significativas.

Essa abordagem permite integrar de forma natural as relações em grafo criadas pelo ST-Expert ao mecanismo clássico de atenção, transformando probabilidades abstratas em sinais concretos, prontos para o processamento posterior e para uso na previsão de séries financeiras.

   index++;
   if(!cScores.Init(0, index, OpenCL, units * units, optimization, iBatch))
      return false;
   cScores.SetHeads(units);

Em seguida, são configurados os blocos cAttention e cResidual, responsáveis por armazenar os resultados do mecanismo de atenção e das conexões residuais. A função de ativação também é desativada nesses blocos para evitar distorções nos sinais.

   index++;
   if(!cAttention.Init(0, index, OpenCL, Neurons(), optimization, iBatch))
      return false;
   cAttention.SetActivationFunction(None);
   index++;
   if(!cResidual.Init(0, index, OpenCL, Neurons(), optimization, iBatch))
      return false;
   cResidual.SetActivationFunction(None);

Por fim, são configuradas duas camadas consecutivas do bloco Feed-Forward. A primeira utiliza a função de ativação SoftPlus, ampliando a não linearidade e a capacidade do modelo de identificar dependências complexas. A segunda comprime e transforma as informações no sinal final de atenção, sem aplicar uma ativação adicional.

   index++;
   if(!cFeedForward[0].Init(0, index, OpenCL, window, window, 2 * window, units, 1, optimization, iBatch))
      return false;
   cFeedForward[0].SetActivationFunction(SoftPlus);
   index++;
   if(!cFeedForward[1].Init(0, index, OpenCL, cFeedForward[0].GetFilters(), cFeedForward[0].GetFilters(),
                                                                  window, units, 1, optimization, iBatch))
      return false;
   cFeedForward[1].SetActivationFunction(None);
//---
   return true;
  }

Dessa forma, o método Init integra cuidadosamente todos os componentes em um único sistema. Os blocos de processamento de valores, os grafos, a distribuição de atenção e as camadas Feed-Forward ficam prontos para operar, formando um mecanismo completo de atenção em grafos, capaz de analisar com eficiência a dinâmica das séries financeiras.

Após a inicialização do objeto, a etapa seguinte do funcionamento do modelo é a propagação para frente, implementada no método feedForward.

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

Trata-se de uma expedição por um cenário financeiro complexo e dinâmico. Tudo começa no objeto cValue, que atua como um explorador. Ele examina atentamente cada sinal do mercado e transforma os dados brutos em uma representação adequada à análise, como se preparasse um mapa do terreno antes de iniciar a jornada.

Em seguida, entra em ação o módulo de grafos cGraphs, que constrói as rotas de interação entre os nós da sequência.

   if(!cGraphs.FeedForward(NeuronOCL))
      return false;

Podemos imaginá-lo como um sistema de estradas e trilhas nesse mapa, em que cada relação representa a intensidade da influência de um elemento sobre outro. O bloco cScores com SoftMax atua como o comandante da expedição, determinando quais caminhos são mais importantes e devem ser percorridos primeiro. Ele transforma as probabilidades brutas das relações em uma estratégia clara de deslocamento, destacando as interações principais e ignorando sinais ruidosos de pouca relevância.

   if(!cScores.FeedForward(cGraphs.AsObject()))
      return false;

Quando essa etapa de reconhecimento é concluída, as informações de cValue e a distribuição de atenção são combinadas por meio de multiplicação matricial, formando os resultados do módulo de atenção em cAttention. É como reunir todos os dados coletados em um único mapa para visualizar quais forças e influências atuam sobre o objeto analisado.

   if(!MatMul(cScores.getOutput(), cValue.getOutput(), cAttention.getOutput(),
              cValue.GetUnits(), cValue.GetUnits(), cValue.GetFilters(), 1, false))
      return false;

Na etapa seguinte, o sinal de atenção é combinado com o sinal residual e normalizado em cResidual. Essas operações estabilizam os dados e impedem que oscilações aleatórias desviem o modelo de sua trajetória.

   if(!SumAndNormilize(NeuronOCL.getOutput(), cAttention.getOutput(), cResidual.getOutput(),
                       cValue.GetFilters(), true, 0, 0, 0, 1))
      return false;

Em seguida, inicia-se a etapa do bloco Feed-Forward, que atua como uma equipe de analistas experientes da expedição. O primeiro bloco, com a função de ativação SoftPlus, identifica interdependências ocultas e reforça os sinais importantes, como pesquisadores que localizam no mapa os principais recursos. O segundo bloco transforma as informações em uma representação compacta e clara, pronta para ser usada na previsão.

   if(!cFeedForward[0].FeedForward(cResidual.AsObject()))
      return false;
   if(!cFeedForward[1].FeedForward(cFeedForward[0].AsObject()))
      return false;
   if(!SumAndNormilize(cFeedForward[1].getOutput(), cResidual.getOutput(), Output,
                       cFeedForward[1].GetFilters(), true, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

A normalização final combina os dados residuais com os resultados do Feed-Forward, formando o sinal definitivo, que representa a visão do modelo sobre o mercado, ao mesmo tempo ampla e detalhada.

Como resultado, o método feedForward transforma uma sequência complexa de dados financeiros em conhecimento estruturado, significativo e pronto para ser usado na previsão.

A propagação para frente é apenas a primeira parte do funcionamento do modelo. Para obter previsões realmente informativas, é necessário implementar a análise dos erros, realizada no método calcInputGradients.

bool CNeuronGraphAttention::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

Essa etapa pode ser vista como uma expedição de retorno pelo trajeto já percorrido, na qual o modelo avalia quais decisões foram precisas e em quais pontos ocorreram desvios.

O procedimento começa no último bloco Feed-Forward da propagação para frente, onde o sinal passa pela etapa reversa da ativação para que os gradientes do erro sejam calculados corretamente.

   if(!DeActivation(cFeedForward[1].getOutput(), cFeedForward[1].getGradient(),
                    Gradient, cFeedForward[1].Activation()))
      return false;
   if(!cFeedForward[0].CalcHiddenGradients(cFeedForward[1].AsObject()))
      return false;
   if(!cResidual.CalcHiddenGradients(cFeedForward[0].AsObject()))
      return false;

Em seguida, os gradientes são propagados para a primeira camada do bloco e para o bloco residual cResidual, permitindo ajustar as informações processadas nas etapas anteriores. Nesse ponto, os gradientes são somados, o que contribui para a robustez do modelo e evita a divergência dos sinais durante a propagação reversa.

   if(!SumAndNormilize(Gradient, cResidual.getGradient(), cAttention.getOutput(),
                       cValue.GetFilters(), false, 0, 0, 0, 1))
      return false;

Depois, a multiplicação matricial propaga os gradientes pelo bloco de distribuição da atenção cScores e pelo bloco cValue, permitindo ajustar a intensidade da influência de cada elemento sobre os demais. O bloco de grafos cGraphs recebe seus próprios gradientes e refina as probabilidades das relações entre os nós com base nos erros acumulados.

   if(!MatMulGrad(cScores.getOutput(), cScores.getGradient(),
                  cValue.getOutput(), cValue.getGradient(),
                  cAttention.getGradient(), cValue.GetUnits(),
                  cValue.GetUnits(), cValue.GetFilters(), 1, false))
      return false;
   if(!cGraphs.CalcHiddenGradients(cScores.AsObject()))
      return false;

Na sequência, os gradientes chegam ao nível dos dados de origem, no objeto NeuronOCL do bloco cValue, onde é determinado com maior precisão como cada elemento da sequência contribuiu para o erro do modelo.

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

É importante destacar que, durante a propagação para frente, os dados de origem percorrem simultaneamente três rotas: o bloco de valores cValue, as relações em grafo de cGraphs e o mecanismo de conexões residuais. Cada uma dessas rotas forma seu próprio fluxo de informações, refletindo diferentes aspectos das relações presentes na sequência.

Por isso, durante a propagação reversa, é necessário reunir cuidadosamente os gradientes provenientes das três rotas. É como uma expedição em que, após retornarem por vários caminhos paralelos, os pesquisadores combinam suas observações para obter uma visão completa do terreno. Primeiro, os gradientes são formados na saída de cada rota. Em seguida, são somados levando em conta as conexões residuais, para preservar a integridade e o equilíbrio das informações. Somente depois dessa integração o modelo obtém uma representação precisa dos pontos em que ocorreram erros e das influências que foram subestimadas ou superestimadas.

   if(!DeActivation(NeuronOCL.getOutput(), cResidual.getGradient(),
                    cAttention.getGradient(), NeuronOCL.Activation()))
      return false;
   if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getGradient(), cResidual.getGradient(),
                       cValue.GetWindow(), false, 0, 0, 0, 1))
      return false;
   if(!NeuronOCL.CalcHiddenGradients(cValue.AsObject()))
      return false;
   if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getGradient(), NeuronOCL.getGradient(),
                       cValue.GetWindow(), false, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

Com essa abordagem, o Transformer baseado em grafos consegue ser treinado com eficiência. Ele considera tanto as relações globais quanto as locais, ajustando-as ao longo de todo o percurso, e não apenas em segmentos isolados. Como resultado, cada nó da sequência recebe uma avaliação adequada de sua contribuição para o erro do modelo, tornando o treinamento consistente, estável e altamente informativo.

O treinamento dos parâmetros do modelo é implementado no método updateInputWeights, que pode ser visto como o coordenador de todo o sistema. Nessa etapa, o objeto não ajusta diretamente os pesos, mas delega essa tarefa aos seus componentes internos.

bool CNeuronGraphAttention::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cValue.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cGraphs.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cFeedForward[0].UpdateInputWeights(cResidual.AsObject()))
      return false;
   if(!cFeedForward[1].UpdateInputWeights(cFeedForward[0].AsObject()))
      return false;
//---
   return true;
  }

Em outras palavras, o método updateInputWeights atua como um maestro que coordena todos os músicos dentro do objeto. Cada bloco oferece sua contribuição, mas a harmonia do conjunto só é alcançada por meio da atuação conjunta. Assim, o modelo melhora gradualmente suas previsões, aprendendo com os erros identificados durante a propagação reversa e reforçando as relações mais relevantes entre os elementos da sequência.

O código completo da classe CNeuronGraphAttention, com a implementação de todos os métodos, está disponível no anexo e demonstra como os conceitos de atenção em grafos se transformam em um mecanismo funcional, pronto para analisar a dinâmica das séries financeiras.



SoftMax esparso

A próxima etapa consiste em criar um grafo esparso para o módulo de atenção local. Não é necessário reformular completamente o objeto de geração de grafos. Podemos fazer apenas alterações mínimas, preservando a arquitetura básica e sua lógica de funcionamento. No módulo de atenção em grafos apresentado anteriormente, o grafo de dependências gerado na saída é convertido em uma distribuição probabilística pela função SoftMax, que atribui maior peso às interações mais relevantes.

Na etapa de atenção local, podemos avançar um pouco mais. Vamos definir como zero as relações menos prováveis, preservando apenas as interdependências principais. Essa técnica permite concentrar os recursos computacionais nas interações mais relevantes entre os nós, o que é especialmente importante na análise das oscilações locais das séries financeiras. Na prática, isso se assemelha à atuação de um analista experiente: em vez de examinar cada sinal de pouca importância, ele concentra sua atenção apenas nos fatores críticos que realmente influenciam a tendência atual.

Para otimizar ainda mais o modelo, é criada na saída uma matriz esparsa que armazena somente as relações ativas e relevantes. Isso reduz significativamente o consumo de memória e acelera os cálculos sem comprometer a capacidade informativa. A estrutura esparsa permite que o modelo processe janelas temporais amplas e sequências complexas sem sobrecarregar o bloco computacional, enquanto a atenção local se torna uma ferramenta rápida e precisa para identificar influências em pequena escala.

Como resultado, essa abordagem combina duas vantagens estratégicas. De um lado, são preservadas as relações globais definidas pelos grafos do ST-Expert, que proporcionam uma visão geral do mercado. De outro, as relações locais destacadas pela matriz esparsa permitem concentrar a análise nas pequenas interações de maior relevância.

A implementação prática da atenção local esparsa começa com a criação do algoritmo Sparse SoftMax no programa OpenCL. Sua função é transformar as probabilidades das relações obtidas no bloco de grafos em uma distribuição de atenção, definindo simultaneamente como zero os elementos menos relevantes e gerando uma matriz esparsa.

__kernel void SparseSoftMax(__global const float *data,
                            __global float *outputs,
                            __global float *indexes,
                            const int out_dimension
                           )
  {
   const size_t row = get_global_id(0);
   const size_t col_in = get_local_id(1);
   const int total_rows = (int)get_global_size(0);
   const int total_cols_in = (int)get_local_size(1);
//---
   __local float Temp[LOCAL_ARRAY_SIZE];
   const int ls = min(total_cols_in, (int)LOCAL_ARRAY_SIZE);

Cada fluxo de execução processa uma linha separada do array de dados de origem, que representa as probabilidades das relações entre os nós. Primeiro, verifica-se se os valores são válidos: valores NaN ou infinitos são descartados e substituídos pelo valor mínimo. Em seguida, cada fluxo determina a posição de seu valor em relação aos demais elementos da linha. Caso ele esteja entre os menos relevantes, será definido como zero.

   const int shift_in = RCtoFlat(row, col_in, total_rows, total_cols_in, 0);
//--- calc position
   float value = IsNaNOrInf(data[shift_in], MIN_VALUE);
   int position = 0;
   for(int l = 0; l < total_cols_in; l += ls)
     {
      if(col_in >= l && col_in < (l + ls))
         Temp[col_in - l] = value;
      BarrierLoc;
      for(int i = 0; i < ls; i++)
        {
         if(i == (col_in - l))
            continue;
         if(Temp[i] > value)
            position++;
         else
            if(Temp[i] == value && i < (col_in - l))
               position++;
        }
      BarrierLoc;
     }

Depois disso, a função SoftMax clássica é aplicada aos elementos restantes, mas com normalização local por subgrupos de elementos.

//--- SoftMax
   if(position >= out_dimension)
      value = MIN_VALUE;
   value = LocalSoftMax(value, 1, Temp);
//--- result
   const int shift_out = RCtoFlat(row, position, total_rows, out_dimension, 0);
   if(position < out_dimension)
     {
      outputs[shift_out] = value;
      indexes[shift_out] = (float)col_in;
     }
  }

Essa abordagem permite considerar apenas as relações mais relevantes e, ao mesmo tempo, preservar a estabilidade numérica dos cálculos. Na saída, é formada uma matriz esparsa: os valores que ultrapassam o limite definido são preservados, enquanto todos os demais são ignorados, reduzindo drasticamente o volume de dados e aumentando a eficiência do modelo.

O resultado final consiste em dois arrays:

  • outputs, que contém os valores normalizados apenas das relações relevantes,
  • indexes, que armazena as posições desses elementos ativos.

Essas informações ficam prontas para uso no módulo de atenção local, permitindo que o modelo se concentre nas relações locais mais importantes entre os nós da sequência sem sobrecarregar a memória e os recursos computacionais.

No programa principal, criamos o novo objeto CNeuronSparseSoftMax, que herda a funcionalidade básica da camada CNeuronSoftMaxOCL e é especializado no processamento de distribuições esparsas. Diferentemente do SoftMax convencional, esse objeto permite concentrar a atenção apenas nas relações mais relevantes entre os elementos da sequência, reduzindo ao mesmo tempo o consumo de memória e aumentando a eficiência dos cálculos.

class CNeuronSparseSoftMax    :  public CNeuronSoftMaxOCL
  {
protected:
   uint              iDimensionIn;
   CBufferFloat      cIndexes;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSparseSoftMax(void) {};
                    ~CNeuronSparseSoftMax(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint dimension_in, uint dimension_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   virtual int       Type(void) override const { return defNeuronSparseSoftMax; }
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual CBufferFloat* GetIndexes(void)  { return GetPointer(cIndexes); }
   virtual uint      DimensionOut(void) const {  return uint(Neurons() / iHeads); }
  };

A classe armazena a dimensão dos dados de origem em iDimensionIn e o buffer de índices das relações ativas em cIndexes, cujos valores indicam os elementos considerados na geração da distribuição de atenção. Os métodos feedForward e calcInputGradients funcionam como invólucros dos kernels de propagação para frente e propagação reversa implementados no programa OpenCL, permitindo que o modelo processe os dados corretamente e aprenda com os erros considerando apenas as relações ativas e relevantes.

A inicialização do objeto é realizada no método Init, que prepara o modelo para trabalhar com distribuições esparsas de atenção.

bool CNeuronSparseSoftMax::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                uint units, uint dimension_in, uint dimension_out,
                                ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(dimension_in < dimension_out)
      return false;
   if(!CNeuronSoftMaxOCL::Init(numOutputs, myIndex, open_cl, units * dimension_out, optimization_type, batch))
      return false;

Primeiro, verifica-se se a dimensão de entrada é válida: dimension_in não pode ser menor que dimension_out, para que o modelo consiga formar corretamente a distribuição. Em seguida, é realizada a inicialização da classe base.

Depois disso, são definidos o número de cabeças de atenção e a dimensão dos dados de origem. Também é criado o buffer de índices cIndexes, que armazenará apenas as relações ativas e relevantes.

   SetHeads(units);
   iDimensionIn = dimension_in;
   if(!cIndexes.BufferInit(Neurons(), -1) ||
      !cIndexes.BufferCreate(OpenCL))
      return false;
//---
   return true;
  }

O buffer é inicializado e tem sua memória reservada, inclusive com suporte ao OpenCL para acelerar os cálculos. Com isso, o objeto fica totalmente pronto para operar: ele consegue receber os dados de origem, selecionar as principais relações e formar uma distribuição esparsa de atenção, mantendo o uso eficiente dos recursos.

Dessa forma, o CNeuronSparseSoftMax transforma o grafo esparso de atenção local em uma distribuição prática e eficiente, na qual a atenção se concentra nas relações mais relevantes. Isso permite que o modelo preserve a visão global definida pelo grafo principal do ST-Expert e, ao mesmo tempo, considere com precisão as influências locais, gerando previsões detalhadas e informativas para as séries financeiras.

Para implementar a atenção local em nosso modelo, basta fazer uma alteração mínima, porém eficaz. Podemos usar o objeto de Transformer baseado em grafos já implementado e substituir seu módulo de projeção probabilística SoftMax pela versão esparsa CNeuronSparseSoftMax. Essa abordagem dispensa a reformulação de toda a arquitetura, preservando as relações globais em grafo e a lógica dos blocos, ao mesmo tempo que permite selecionar com precisão as interações locais.

O SoftMax esparso concentrará a atenção nas conexões mais relevantes entre os nós, ignorando relações pouco significativas e otimizando, assim, o uso da memória e dos recursos computacionais.

class CNeuronSparseGraphAttention   :  public CNeuronBaseOCL
  {
protected:
   CNeuronConvOCL       cValue;
   CNeuronGraphons      cGraphs;
   CNeuronSparseSoftMax cScores;
   CNeuronBaseOCL       cAttention;
   CNeuronBaseOCL       cResidual;
   CNeuronConvOCL       cFeedForward[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSparseGraphAttention(void) {};
                    ~CNeuronSparseGraphAttention(void) {};
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint experts, float dropout,
                          uint emb_dimension, uint sparse_dimension,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const   {  return defNeuronSparseGraphAttention;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual void      SetActivationFunction(ENUM_ACTIVATION value) override { };
   virtual void      TrainMode(bool flag) override;
  };

O código completo de todas as classes apresentadas, bem como a implementação de cada um de seus métodos, está disponível no anexo do artigo.

Avançamos significativamente e alcançamos resultados importantes. Este é um bom momento para fazer uma breve pausa, permitindo que as informações sejam assimiladas e consolidadas. No próximo artigo, daremos continuidade ao que iniciamos, avaliaremos a eficiência das soluções implementadas e veremos até que ponto o modelo consegue analisar séries financeiras com sucesso.


Conclusão

Ao longo deste artigo, percorremos um caminho complexo, explorando a arquitetura do Transformer baseado em grafos e conhecendo seus principais componentes: os blocos de valores, os grafos de dependências e a distribuição da atenção. Cada um desses elementos desempenhou seu papel, como integrantes de uma expedição que, em conjunto, traçam uma rota por um cenário financeiro complexo e em constante transformação.

Dedicamos atenção especial à implementação do SoftMax esparso para a atenção local. Esse recurso permitiu que o modelo destacasse as relações mais relevantes, ignorasse sinais pouco significativos, economizasse recursos e aumentasse a precisão da análise da dinâmica do mercado. A propagação para frente, a propagação reversa e a atualização dos pesos garantiram o ajuste contínuo da trajetória, permitindo que o modelo aprendesse com os erros e aprimorasse gradualmente suas previsões.

Os resultados obtidos estabeleceram uma base sólida para estudos futuros. No próximo artigo, continuaremos essa jornada, avaliando a eficiência das soluções implementadas, analisando a precisão das previsões e verificando até que ponto o modelo consegue acompanhar a dinâmica das séries financeiras.


Referências


Programas usados no artigo

#NomeTipoDescrição
1Study.mq5Expert AdvisorEA de treinamento offline de modelos
2StudyOnline.mq5 Expert Advisor EA de treinamento online de modelos
3Test.mq5Expert AdvisorEA para teste do modelo
4Trajectory.mqhBiblioteca de classesEstrutura de descrição do estado do sistema e da arquitetura dos modelos
5NeuroNet.mqhBiblioteca de classesBiblioteca de classes para criação de rede neural
6NeuroNet.clBibliotecaBiblioteca de código do programa OpenCL

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

Arquivos anexados |
MQL5.zip (3108.58 KB)
Últimos Comentários | Ir para discussão (1)
Evgeniy Chernish
Evgeniy Chernish | 19 set. 2025 em 14:31
Dmitriy, olá!

Você poderia criar um pequeno script para demonstrar o funcionamento do modelo, de forma acessível para nós, leigos? Usando dados sintéticos bem simples, por exemplo, 10 pontos de dados e 2 características. Com a possibilidade de exibir o gráfico da função de perda ou gerar um log. Se possível, apenas na CPU.

Porque os modelos que você escreve são complexos e difíceis de entender. Precisamos de um exemplo mais ilustrativo :)
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.
Modificação do algoritmo de otimização Dingo: Dingo Optimization Algorithm M (DOAm) Modificação do algoritmo de otimização Dingo: Dingo Optimization Algorithm M (DOAm)
A modificação do algoritmo Dingo proposta pelo autor e apresentada neste artigo elevou consideravelmente o patamar na busca pelo melhor algoritmo de otimização. Será que ainda é possível alcançar resultados superiores?
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.
Desenvolvendo uma Estratégia de Pullback: Construção do Modelo Base Desenvolvendo uma Estratégia de Pullback: Construção do Modelo Base
Este artigo apresenta a evolução de um EA de pullback em MQL5, desde a definição do regime por EMA até a identificação da retração e a confirmação do gatilho. São abordados a otimização integrada ao MetaTrader 5, o stop ajustado ao tick size, o filtro de distância por ATR e a gestão com breakeven e trailing, priorizando platôs paramétricos, controle dos graus de liberdade e redução do risco de overfitting.