Русский Español
preview
Redes neurais em trading: Sistema multiagente com validação conceitual (FinCon)

Redes neurais em trading: Sistema multiagente com validação conceitual (FinCon)

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

Introdução

Os mercados financeiros são caracterizados por alta volatilidade e complexidade, o que cria grandes desafios para a tomada de decisões de investimento ideais. Traders e gestores de carteiras precisam considerar diferentes dados multimodais, como indicadores macroeconômicos, técnicos e comportamentais dos participantes do mercado. O principal objetivo desses esforços é maximizar a rentabilidade e minimizar os riscos.

Nas empresas financeiras tradicionais, diferentes especialistas são responsáveis pelo processamento de dados e tomada de decisões: analistas fazem pesquisas sobre informações do mercado, gerentes de risco avaliam ameaças potenciais e líderes tomam decisões estratégicas. No entanto, apesar da estrutura hierárquica, o fator humano e os recursos limitados dificultam a adaptação rápida às mudanças do mercado. Por isso, o uso de sistemas automatizados se torna cada vez mais relevante, pois não apenas aceleram a análise como também reduzem a chance de erros.

Pesquisas recentes em inteligência artificial e tecnologias financeiras se concentram no desenvolvimento de soluções de software adaptativas. Esses sistemas conseguem aprender com dados históricos, identificar padrões de mercado e tomar decisões mais fundamentadas. Uma das principais direções dessas pesquisas é a integração de métodos de processamento de linguagem natural (NLP), que permitem analisar notícias financeiras, previsões de especialistas e outros dados textuais para prever com mais precisão e avaliar riscos.

O bom desempenho desses sistemas depende principalmente de dois aspectos fundamentais: a interação entre os componentes do sistema e sua capacidade de aprendizado contínuo. Pesquisas mostram que sistemas que simulam o trabalho em equipe de especialistas apresentam melhores resultados, e graças à adoção de novas abordagens, esses modelos se tornam mais adaptáveis às condições em constante mudança.

Soluções semelhantes ao FinMem e FinAgent, por exemplo, demonstram um avanço significativo na automação de operações financeiras. No entanto, tais sistemas apresentam limitações: tendem a focar em aspectos de curto prazo do mercado, sem oferecer soluções abrangentes para o gerenciamento de riscos de longo prazo. Além disso, a limitação de recursos computacionais e a flexibilidade insuficiente dos algoritmos podem comprometer a qualidade das recomendações.

Essas questões são abordadas pelos autores do trabalho "FinCon: A Synthesized LLM Multi-Agent System with Conceptual Verbal Reinforcement for Enhanced Financial Decision Making". Nele, é proposto o framework FinCon — um sistema multiagente desenvolvido especificamente para integrar os processos de trading de ações e gerenciamento de portfólio.

Os agentes estruturados no framework FinCon simulam o trabalho de uma equipe de especialistas. Enquanto os agentes-analistas coletam e analisam dados de várias fontes, incluindo indicadores de mercado, fluxos de notícias e dados históricos, os agentes-gerentes sintetizam os resultados e formulam decisões. Essa abordagem minimiza comunicações redundantes entre os participantes do processo e otimiza o uso de recursos computacionais.

Os autores do framework FinCon acreditam que ele pode ser aplicado tanto para operar com um único ativo financeiro quanto para gerenciar completamente um portfólio de ativos. Isso torna o sistema versátil.

FinCon realiza avaliação de riscos em tempo real, utilizando modelos analíticos e algoritmos de aprendizado de máquina. Após a execução das operações, o sistema realiza uma análise pós-operacional, identificando falhas e atualizando seus modelos com base nos dados mais recentes.

As pesquisas conduzidas pelos autores do framework demonstraram que FinCon melhora significativamente o desempenho na gestão de riscos e aumenta a rentabilidade total do portfólio.


Arquitetura do FinCon

A arquitetura do FinCon segue uma hierarquia de dois níveis e inclui dois componentes principais: o grupo de agentes "Gerente-Analista" e o sistema de "Controle de Riscos". Essa estrutura garante um processamento eficiente da informação, reduz a probabilidade de erros, minimiza os custos na tomada de decisões e permite uma análise aprofundada dos dados de mercado.

O framework FinCon assemelha-se a uma empresa de investimentos bem estruturada, onde todos os recursos são otimizados para alcançar o máximo desempenho. Seu principal objetivo é aprimorar a percepção e a análise da informação, reduzindo ao mínimo os custos de comunicação e processamento de dados.

Os agentes-analistas desempenham um papel fundamental no funcionamento do FinCon, destacando as principais ideias de investimento a partir de grandes volumes de dados de mercado. Cada agente possui uma especialização restrita, o que evita sobrecarga de informação e duplicação de dados. Na implementação original do framework, são utilizados 7 agentes-analistas. Agentes de texto analisam artigos de notícias, comunicados de imprensa e relatórios financeiros com o objetivo de identificar riscos e oportunidades potenciais. Agentes de áudio interpretam gravações disponíveis de ligações sobre lucros com a gerência das empresas dos ativos analisados, identificando nuances emocionais e pontos-chave das discussões. Agentes de análise de dados e seleção de ações calculam métricas que permitem ao gerente prever a dinâmica do mercado com alta precisão.

Os agentes-analistas fornecem os dados ao gerente, que é o único responsável pela tomada de decisões de trading. Para tomar essas decisões, o gerente executa 4 funções principais: consolidação dos resultados dos analistas, controle de riscos em tempo real, análise contínua de decisões anteriores e refinamento de suas convicções de investimento.

O framework FinCon incorpora um mecanismo de controle de riscos em dois níveis. A avaliação de riscos dentro do episódio permite ajustar ações em tempo real, minimizando perdas de curto prazo. O controle de riscos entre episódios, por sua vez, compara os resultados de diferentes episódios, ajudando a identificar erros e aprimorar estratégias. Essa abordagem garante resiliência do modelo frente a mudanças externas e promove seu aperfeiçoamento contínuo.

A atualização das convicções de investimento tem papel essencial na adaptação da política de comportamento do modelo. Permite que o modelo ajuste os focos durante as etapas de extração de informações pelos analistas e de tomada de decisão pelo gerente. O mecanismo Actor-Critic ajuda o FinCon a otimizar periodicamente suas abordagens de investimento com base nas metas de trading estabelecidas. Essa abordagem se baseia na análise de ações bem-sucedidas e mal-sucedidas, incentivando o aperfeiçoamento constante da estratégia.

A reflexão episódica no sistema FinCon é sustentada por um mecanismo exclusivo de reforço verbal conceitual (CVRF). Esse componente analisa o desempenho de episódios sequenciais, comparando as informações fornecidas pelos analistas com as decisões do gerente. CVRF vincula conclusões-chave a aspectos específicos da estratégia de trading. Ao comparar as conclusões conceituais de episódios mais e menos bem-sucedidos, o modelo gera recomendações para ajustar as convicções de investimento. Isso ajuda os agentes a focarem nas informações de mercado mais relevantes para aumentar a rentabilidade geral.

As recomendações para ajuste das convicções são inicialmente transmitidas ao gerente e, em seguida, distribuídas seletivamente entre os analistas. Isso permite minimizar interações redundantes e evitar sobrecarga de informação. Esse método prevê a medição da proporção de ações de trading coincidentes entre duas trajetórias de treinamento consecutivas, o que contribui para uma maior eficiência do sistema. Isso se torna especialmente evidente quando cada agente possui uma função claramente definida e especializada, promovendo sinergia no funcionamento do modelo.

O framework FinCon incorpora um algoritmo avançado de trabalho com o módulo de memória, que é dividido em três componentes principais: memória de trabalho, memória procedural e memória episódica.

Memória de trabalho é usada para armazenar temporariamente dados necessários para a execução das operações atuais. Isso permite o processamento rápido de grandes volumes de informação sem perda de contexto.

Memória procedural guarda os algoritmos e estratégias que foram aplicados com sucesso em episódios anteriores. Ela garante adaptação rápida do sistema a tarefas recorrentes e possibilita o uso de métodos já testados na resolução de problemas semelhantes.

Memória episódica registra eventos-chave, ações e resultados, sendo particularmente importante para análise e ajuste de políticas em um nível mais elevado. Esse componente de memória exerce um papel crítico no aprendizado do modelo, permitindo considerar sucessos e erros passados para aumentar a eficácia futura.

A estrutura multinível torna o FinCon um sistema eficaz e adaptativo. A visualização original do framework é apresentada a seguir.

Visualização original do framework FinCon


Implementação com MQL5

Após explorar os aspectos teóricos do framework FinCon, passamos agora à parte prática do nosso artigo, na qual implementaremos nossa interpretação das abordagens propostas por meio do MQL5.

É importante destacar que a implementação original do framework FinCon se baseia no uso de grandes modelos de linguagem previamente treinados. Nós, por outro lado, como nas ocasiões anteriores, não utilizamos modelos de linguagem. E realizamos a implementação das abordagens propostas com os recursos atualmente disponíveis. Começaremos nosso trabalho com a modernização do módulo de memória.

Abordagens para modernização do módulo de memória


Como você já sabe, o bloco de memória que desenvolvemos anteriormente inclui dois módulos recorrentes com arquiteturas distintas. Isso permitiu criar um sistema de memória em dois níveis, com diferentes velocidades de esquecimento da informação, o que o torna mais flexível e adaptável a diversas tarefas. Tal abordagem é especialmente útil em situações que exigem considerar dependências de curto e longo prazo.

É importante destacar que, em nosso modelo, a velocidade de esquecimento não é definida diretamente. Ela varia em função das diferenças na arquitetura dos módulos recorrentes utilizados. Um dos módulos se concentra na memória de curto prazo, garantindo atualização rápida das informações, enquanto o outro é orientado para dependências de longo prazo, permitindo a retenção de dados relevantes por um período prolongado. Essas diferenças arquitetônicas criam um equilíbrio natural entre a velocidade de processamento de dados e a profundidade da análise.

O armazenamento de informações no bloco de memória é feito por meio dos estados ocultos dos módulos recorrentes. Esse método de armazenamento permite reduzir significativamente o volume de memória ocupada, mas, ao mesmo tempo, cria dificuldades quando é necessário recuperar e comparar episódios específicos. Isso se torna crítico no contexto da implementação do mecanismo de validação conceitual, que exige a capacidade de comparar a situação atual com episódios previamente armazenados.

Além disso, os autores do framework FinCon enfatizam a importância de se utilizar um sistema de memória em três níveis para uma análise e previsão mais precisa. Por essa razão, torna-se necessário modernizar o bloco de memória existente, adicionando um nível adicional de armazenamento episódico de informações.

O processo de otimização do bloco de memória envolve diversas etapas importantes. Em primeiro lugar, é necessário minimizar o uso de recursos computacionais. Isso pode ser alcançado através da adoção de algoritmos de compressão de dados. Esses algoritmos eliminam informações redundantes, reduzindo o espaço de memória necessário para armazenar um único episódio, preservando, ao mesmo tempo, as características essenciais dos dados.

Outra tarefa importante é garantir acesso rápido e preciso à informação relevante. Para isso, é apropriado utilizar algoritmos de similaridade vetorial. Esses algoritmos permitem localizar rapidamente os episódios mais semelhantes em termos de características, o que é especialmente importante para aplicações em tempo real.

Como resultado, o bloco de memória modernizado se tornará a base para o aumento da eficiência geral do modelo. Ele proporcionará não apenas armazenamento compacto dos dados, mas também acesso rápido às informações necessárias, o que melhorará significativamente o processo de tomada de decisão.

Implementação do novo módulo de memória


As abordagens propostas serão implementadas no novo objeto CNeuronMemoryDistil, cuja estrutura é apresentada a seguir.

class CNeuronMemoryDistil  :  public CNeuronMemory
  {
protected:
   CNeuronBaseOCL       cConcatenated;
   CNeuronTransposeOCL  cTransposeConc;
   CNeuronConvOCL       cConvolution;
   CNeuronEmbeddingOCL  cDistilMemory;
   CNeuronRelativeCrossAttention cCrossAttention;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronMemoryDistil(void) {};
                    ~CNeuronMemoryDistil(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint stack_size,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override
 const   {  return defNeuronMemoryDistil; }
   //---
   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 bool      Clear(void) override;
  };

Como classe pai, utilizamos o objeto de memória previamente criado, do qual herdamos todas as interfaces básicas e os dois módulos recorrentes de memória de curto e longo prazo. Os objetos internos declarados dentro da estrutura da nossa nova classe formam a base para a construção da memória episódica. Eles permitem armazenar e processar dados que representam episódios específicos. Cada um dos objetos cumpre uma tarefa específica dentro da estrutura geral da memória, contribuindo para uma análise de dados mais precisa e abrangente. Suas funcionalidades serão abordadas com mais detalhes na implementação dos métodos deste bloco de memória.

Todos os objetos internos são declarados de forma estática, o que nos permite deixar o construtor e o destrutor da classe vazios. Essa abordagem otimiza o uso da memória e aumenta a estabilidade do sistema ao minimizar o risco de erros durante a criação ou destruição dos objetos.

A inicialização de todos os objetos herdados e recém-declarados é realizada no método Init. Os parâmetros desse método incluem um conjunto de constantes que definem claramente a arquitetura do objeto criado e fornecem a flexibilidade necessária para ajustar o modelo a tarefas específicas, mantendo ao mesmo tempo sua integridade e funcionalidade.

bool CNeuronMemoryDistil::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                               uint window, uint window_key, uint units_count,
                               uint heads, uint stack_size,
                               ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronRelativeCrossAttention::Init(numOutputs, myIndex, open_cl, window,
                                         window_key, units_count, heads, window,
                                       2 * stack_size, optimization_type, batch))
      return false;

No corpo do método, normalmente chamamos o método homônimo da classe pai para inicializar os objetos herdados, passando os parâmetros apropriados. No entanto, neste caso, devido às características únicas do novo objeto, não podemos usar diretamente o método de inicialização do módulo de memória base. Em vez disso, a melhor solução é utilizar um método semelhante do objeto de atenção cruzada, que é o pai do módulo de memória base. Isso garante consistência e assegura o funcionamento correto de todos os elementos da nova arquitetura.

Antecipando um pouco, é importante destacar que, na saída do nosso módulo de memória, realizaremos a análise do estado atual no contexto da memória episódica. Nesse processo, o bloco de atenção cruzada desempenha um papel fundamental, executando duas funções principais. A primeira é a busca pelos episódios mais relevantes. Para isso, são utilizados algoritmos de similaridade vetorial, que servem de base para o cálculo dos coeficientes de dependência dentro do mecanismo de atenção.

A segunda função do bloco de atenção cruzada é o enriquecimento do estado original com o contexto dos episódios selecionados. Isso contribui para a construção de um panorama mais completo e informativo, o que, por sua vez, melhora a qualidade do processamento de dados e a precisão das decisões tomadas.

Portanto, ao chamar o método de inicialização da classe pai, para o fluxo principal de informações indicamos os parâmetros recebidos dos dados brutos, e o comprimento da sequência de contexto é o dobro do tamanho do buffer da memória episódica que está sendo criada. A utilização de um buffer com tamanho dobrado se deve à necessidade de preservar os resultados da memória de curto e longo prazo.

Após a execução bem-sucedida das operações do método de inicialização do objeto de atenção cruzada, chamamos os métodos homônimos dos módulos recorrentes herdados de memória de curto e longo prazo. Esse processo foi integralmente transferido do método da classe pai.

   uint index = 0;
   if(!cLSTM.Init(0, index, OpenCL, iWindow, iUnits, optimization, iBatch))
      return false;
   index++;
   if(!cMamba.Init(0, index, OpenCL, iWindow, 2 * iWindow, iUnits, optimization,
                                                                        iBatch))
      return false;

Os resultados dos módulos recorrentes são concatenados em um único buffer para possibilitar o acesso simultâneo aos dados das memórias de longo e curto prazo. Para isso, criamos um objeto base com tamanho suficiente.

   index++;
   if(!cConcatenated.Init(0, index, OpenCL, 2 * iWindow * iUnits, optimization,
                                                                       iBatch))
      return false;
   cConcatenated.SetActivationFunction(None);

Na etapa seguinte, precisamos comprimir as informações obtidas e integrá-las ao objeto de armazenamento da memória episódica.

Vale destacar que estamos lidando com a representação de uma série temporal multimodal que descreve um único estado do ambiente. Isso implica na necessidade de preservar as principais características das sequências unitárias durante a compressão dos dados. Para atingir esse objetivo, foi desenvolvido um método de compressão em duas etapas. Na primeira etapa, realiza-se a compressão preliminar das informações dentro de cada sequência unitária, o que permite destacar suas principais características e reduzir o volume de dados sem comprometer sua integridade.

Neste ponto, inicialmente transponemos o tensor de dados concatenados para representá-los como sequências unitárias.

   index++;
   if(!cTransposeConc.Init(0, index, OpenCL, iUnits, 2 * iWindow, optimization,
                                                                       iBatch))
      return false;

Em seguida, os dados são comprimidos por meio de uma camada convolucional.

   index++;
   if(!cConvolution.Init(0, index, OpenCL, iUnits, iUnits, iWindowKey, iWindow,
                                                      1, optimization, iBatch))
      return false;
   cConvolution.SetActivationFunction(GELU);

Para o armazenamento dos dados da memória episódica, utilizamos uma camada de embedding, que permite organizar de forma eficiente uma pilha de memória de comprimento fixo, implementada com base no princípio FIFO (First In, First Out).

Além disso, essa camada incorpora a funcionalidade de projetar blocos individuais do tensor de dados brutos em um subespaço latente, o que é realizado com o uso de matrizes de projeção treináveis e específicas. Essas matrizes permitem converter os dados multimodais de seu formato original para uma representação unificada. Essa abordagem simplifica as etapas posteriores de análise e assegura a consistência dos dados ao integrar informações de diferentes fontes. Neste caso, as fontes são os módulos de memória de curto e longo prazo.

   index++;
   uint windows[] = {iWindowKey * iWindow, iWindowKey * iWindow};
   if(!cDistilMemory.Init(0, index, OpenCL, iUnitsKV / 2, iWindow, windows))
      return false;

Como já mencionado anteriormente, a análise dos dados brutos no contexto da memória episódica é realizada por meio do objeto base de atenção cruzada. No entanto, para uma análise mais completa, é necessário introduzir mais um objeto de atenção cruzada, voltado para o processamento dos dados brutos no contexto das memórias de curto e longo prazo. Essa abordagem permite considerar o efeito sinérgico resultante da combinação de diferentes tipos de memória.

Vale lembrar que foi criado anteriormente um objeto para a concatenação de dados das memórias de curto e longo prazo, o que simplifica o processo de integração dessas informações. Como resultado, um único objeto de atenção cruzada pode enriquecer de forma eficiente os dados brutos com contexto, considerando simultaneamente as informações armazenadas nos dois módulos de memória. Essa solução otimiza o uso de recursos computacionais e aumenta a precisão da análise.

   index++;
   if(!cCrossAttention.Init(0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads,
                                       iWindow, 2 * iUnits, optimization, iBatch))
      return false;
//---
   return true;
  }

Após a inicialização de todos os objetos internos, resta apenas retornar o resultado lógico da execução das operações ao programa chamador e encerrar a execução do método.

Na próxima etapa, passamos à construção do método de propagação para frente do nosso novo bloco de memória, no método feedForward.

bool CNeuronMemoryDistil::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cLSTM.FeedForward(NeuronOCL))
      return false;
   if(!cMamba.FeedForward(NeuronOCL))
      return false;

Nos parâmetros do método, recebemos um ponteiro para o objeto de dados brutos, que é imediatamente repassado aos métodos homônimos dos nossos módulos recorrentes de memória de curto e longo prazo. Os resultados dessas execuções são concatenados em um único buffer de dados.

   if(!Concat(cLSTM.getOutput(), cMamba.getOutput(), cConcatenated.getOutput(),
                                                     iWindow, iWindow, iUnits))
      return false;
   if(!cTransposeConc.FeedForward(cConcatenated.AsObject()))
      return false;

É importante observar que a concatenação dos dados é feita ao longo de passos temporais distintos. Isso nos permite preservar, no tensor final, a estrutura de todas as sequências unitárias resultantes da operação dos dois módulos de memória.

O tensor obtido é então transposto para permitir a compressão subsequente dos dados dentro de cada sequência unitária, utilizando uma camada convolucional.

   if(!cConvolution.FeedForward(cTransposeConc.AsObject()))
      return false;

Os resultados da compressão preliminar são enviados para a camada de embedding, onde ocorre a projeção dos estados parcialmente comprimidos do ambiente em uma representação latente mais compacta, sendo então adicionados à pilha de memória episódica.

   if(!cDistilMemory.FeedForward(cConvolution.AsObject()))
      return false;

Neste ponto, completamos o armazenamento do volume necessário de informações nos três níveis do nosso bloco de memória, e agora resta enriquecer o estado atual do ambiente com o contexto dos eventos-chave. Primeiro, analisamos os dados brutos no contexto das memórias de curto e longo prazo.

   if(!cCrossAttention.FeedForward(NeuronOCL, cConcatenated.getOutput()))
      return false;

Em seguida, enriquecemos a informação com o contexto da memória episódica.

   return CNeuronRelativeCrossAttention::feedForward(cCrossAttention.AsObject(),
                                                     cDistilMemory.getOutput());
  }

O resultado lógico da execução das operações é retornado ao programa chamador e a execução do método é finalizada.

Após a construção do método de propagação para frente, segue-se a implementação dos algoritmos de propagação reversa. Como você sabe, aqui é prevista a construção de dois métodos:

  • distribuição dos gradientes de erro calcInputGradients;
  • atualização dos parâmetros do modelo updateInputWeights.

O fluxo de dados no método de distribuição dos gradientes de erro é completamente idêntico ao algoritmo da propagação para frente, mas ocorre em ordem inversa.

Nos parâmetros do método calcInputGradients, recebemos um ponteiro para o mesmo objeto de dados brutos, mas desta vez devemos transmitir a ele o gradiente de erro correspondente à sua influência no resultado final do funcionamento do modelo.

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

No corpo do método, verificamos imediatamente a validade do ponteiro recebido, pois, do contrário, todas as operações subsequentes do método perdem o sentido.

O método de propagação para frente foi finalizado com a chamada ao método homônimo da classe pai de atenção cruzada. Assim, as operações de distribuição do gradiente de erro começam pelo método correspondente do mesmo objeto. Neste caso, estamos distribuindo o gradiente de erro pelos fluxos de informação dos níveis de memória.

  if(!CNeuronRelativeCrossAttention::calcInputGradients(cCrossAttention.AsObject(),
                                                         cDistilMemory.getOutput(),
                                                       cDistilMemory.getGradient(),
                                      (ENUM_ACTIVATION)cDistilMemory.Activation()))
      return false;

O gradiente de erro da memória episódica é propagado através dos objetos de compressão de dados até o nível do objeto de concatenação das memórias de curto e longo prazo.

   if(!cConvolution.calcHiddenGradients(cDistilMemory.AsObject()))
      return false;
   if(!cTransposeConc.calcHiddenGradients(cConvolution.AsObject()))
      return false;
   if(!cConcatenated.calcHiddenGradients(cTransposeConc.AsObject()))
      return false;

Entretanto, o objeto de concatenação também é utilizado na análise dos dados brutos no contexto das memórias de curto e longo prazo. Portanto, precisamos transmitir o gradiente de erro para esse objeto também através do segundo fluxo de informação.

   if(!NeuronOCL.calcHiddenGradients(cCrossAttention.AsObject(),
                                     cConcatenated.getOutput(),
                                     cConcatenated.getPrevOutput(),
                                     (ENUM_ACTIVATION)cConcatenated.Activation()))
      return false;

Vale observar que, neste caso, para obter o gradiente de erro, utilizamos o buffer livre do objeto de concatenação de dados em vez do buffer especializado. Isso se deve à necessidade de preservar os dados obtidos anteriormente.

Em seguida, somamos os valores recebidos pelos dois fluxos de informação e distribuímos os dados resultantes entre os objetos de memória correspondentes.

   if(!SumAndNormilize(cConcatenated.getGradient(), cConcatenated.getPrevOutput(),
                       cConcatenated.getGradient(), iWindow, false, 0, 0, 0, 1) ||
      !DeConcat(cLSTM.getGradient(), cMamba.getGradient(), 
                cConcatenated.getGradient(), iWindow, iWindow, iUnits))
      return false;

É importante destacar que evitamos intencionalmente o uso de função de ativação no objeto de concatenação, a fim de não distorcer os dados. Afinal, os objetos de memória de curto e longo prazo podem possuir funções de ativação distintas. No entanto, após distribuir os gradientes de erro entre os objetos, é necessário verificar a existência de função de ativação nos respectivos objetos e, se for o caso, ajustar os valores dos gradientes com as derivadas dessas funções de ativação.

   if(cLSTM.Activation() != None)
      if(!DeActivation(cLSTM.getOutput(), cLSTM.getGradient(), 
                       cLSTM.getGradient(), cLSTM.Activation()))
         return false;
   if(cMamba.Activation() != None)
      if(!DeActivation(cMamba.getOutput(), cMamba.getGradient(), 
                       cMamba.getGradient(), cMamba.Activation()))
         return false;

Agora, resta propagar o gradiente de erro pelas linhas de memória de curto e longo prazo até o nível dos dados brutos. Contudo, devemos lembrar que o buffer de gradientes de erro deste objeto já recebeu dados referentes à linha de análise de informações no contexto dos níveis de memória. Portanto, para preservar os valores anteriormente obtidos, antes de executar as próximas operações precisamos realizar a substituição dos ponteiros dos buffers de dados.

   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(cConcatenated.getPrevOutput(), false) ||
      !NeuronOCL.calcHiddenGradients(cLSTM.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, iWindow,
                                                  false, 0, 0, 0, 1))
      return false;

Após isso, propagamos sequencialmente os erros pelos fluxos de informação dos respectivos módulos de memória, com a obrigatória adição dos valores obtidos aos dados já acumulados.

   if(!NeuronOCL.calcHiddenGradients(cMamba.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, iWindow,
                                               false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

Em seguida, restauramos os ponteiros dos buffers de dados para o estado original. Retornamos o resultado lógico da execução das operações ao programa chamador e encerramos o método.

Com isso, finalizamos a análise dos métodos do bloco de memória modernizado CNeuronMemoryDistil. O código completo deste objeto e de todos os seus métodos pode ser consultado no anexo.

A próxima etapa do nosso trabalho será a construção do objeto Agente-analista. No entanto, estamos praticamente no limite do formato deste artigo. Por isso, faremos uma breve pausa e continuaremos o trabalho no próximo artigo.


Considerações finais

Neste artigo, conhecemos o framework FinCon, uma inovadora sistema multiagente desenvolvida para aprimorar a tomada de decisões financeiras. O framework integra uma estrutura hierárquica de interação "gerente-analista" e mecanismos de reforço verbal conceitual, promovendo a coordenação entre agentes e uma gestão eficaz de riscos. Essas características permitem ao modelo lidar com diversas tarefas financeiras com alto nível de adaptabilidade e desempenho em um ambiente financeiro dinâmico.

Na parte prática do nosso trabalho, iniciamos a implementação das abordagens propostas com o uso do MQL5. Esse trabalho terá continuidade no próximo artigo, que será concluído com uma análise da eficácia das soluções implementadas em dados históricos reais.


Referências


Programas utilizados no artigo

# Nome Tipo Descrição
1 Research.mq5 Expert Advisor EA de coleta de exemplos
2 ResearchRealORL.mq5
Expert Advisor
EA de coleta de exemplos com método Real-ORL
3 Study.mq5 Expert Advisor EA de treinamento de modelos
4 Test.mq5 Expert Advisor EA para teste do modelo
5 Trajectory.mqh Biblioteca de classe Estrutura de descrição do estado do sistema e da arquitetura dos modelos
6 NeuroNet.mqh Biblioteca de classe Biblioteca de classes para criação de rede neural
7 NeuroNet.cl Biblioteca Biblioteca de código para programas OpenCL

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

Arquivos anexados |
MQL5.zip (2352.32 KB)
Simulação de mercado: Iniciando o SQL no MQL5 (III) Simulação de mercado: Iniciando o SQL no MQL5 (III)
No artigo anterior vimos como poderíamos desenvolver uma classe em MQL5, que seria capaz de nos dar algum suporte. Cuja finalidade, se dá justamente para que possamos colocar o código SQL dentro de um arquivo de script. Isto de forma que não precisaríamos, ter que digitar o mesmo código em uma string, no código MQL5. Mas apesar de daquela solução, ser funcional. Ela contem alguns detalhes, que podemos melhorar e devemos melhorar.
Funções de ativação de neurônios durante o aprendizado: chave para uma convergência rápida? Funções de ativação de neurônios durante o aprendizado: chave para uma convergência rápida?
Este trabalho apresenta uma análise da interação entre diferentes funções de ativação e algoritmos de otimização no contexto do treinamento de redes neurais. A atenção principal está voltada para a comparação entre o ADAM clássico e sua versão populacional ao lidar com uma ampla gama de funções de ativação, incluindo as funções oscilatórias ACON e Snake. Mediante uma arquitetura MLP minimalista (1-1-1) e um único exemplo de treino, isola-se a influência das funções de ativação no processo de otimização, eliminando interferências de outros fatores. Propomos um método de controle dos pesos da rede por meio dos limites das funções de ativação e um mecanismo de reflexão de pesos, permitindo evitar problemas de saturação e estagnação no aprendizado.
Redes neurais em trading: Sistema multiagente com confirmação conceitual (Conclusão) Redes neurais em trading: Sistema multiagente com confirmação conceitual (Conclusão)
Continuamos a implementação das abordagens propostas pelos autores do framework FinCon. O FinCon é um sistema multiagente baseado em grandes modelos de linguagem (LLM). Hoje vamos implementar os módulos necessários e realizar testes abrangentes do modelo com dados históricos reais.
Computação quântica e trading: Um novo olhar sobre as previsões de preços Computação quântica e trading: Um novo olhar sobre as previsões de preços
Este artigo analisa uma abordagem inovadora para prever os movimentos de preços nos mercados financeiros mediante computação quântica. O foco principal está na aplicação do algoritmo de estimativa de fase quântica (QPE) para buscar precursores de padrões de preços, o que permite acelerar significativamente o processo de análise de dados de mercado.