Русский Español
preview
Redes neurais em trading: Agente multimodal com ferramentas complementares (FinAgent)

Redes neurais em trading: Agente multimodal com ferramentas complementares (FinAgent)

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

Introdução

Os mercados financeiros são fundamentais para garantir a estabilidade econômica, pois promovem a alocação de capital e a gestão de riscos. Os sistemas modernos de negociação financeira, baseados em análise técnica, aprimoram esses processos. No entanto, em um ambiente de alta volatilidade e mutabilidade dos mercados, esses sistemas frequentemente enfrentam diversas limitações. Os sistemas de negociação baseados em regras são rígidos e difíceis de adaptar às rápidas mudanças nas condições do mercado, o que geralmente resulta em perda de eficácia. Os sistemas baseados em algoritmos de aprendizado por reforço (RL) apresentam melhor adaptabilidade, mas também possuem limitações:

  • alta demanda por grandes volumes de dados de treinamento;
  • baixa explicabilidade das decisões;
  • problemas de generalização para diferentes condições de mercado;
  • sensibilidade ao ruído do mercado;
  • integração limitada de informações de mercado multimodais.

Nos últimos anos, os grandes modelos de linguagem (LLM) demonstraram grande potencial na tomada de decisões, expandindo sua aplicação para além do processamento de linguagem natural. A integração de módulos de memória e planejamento permite que esses modelos se adaptem a ambientes dinâmicos e em constante mudança. Os LLM multimodais ampliam essas capacidades ao processar informações textuais e visuais, e a adição de ferramentas externas amplia o conjunto de tarefas que esses modelos conseguem resolver, incluindo cenários financeiros complexos.

Apesar dos avanços na análise de dados financeiros, os agentes baseados em LLM ainda enfrentam várias limitações:

  • processamento multimodal limitado de dados numéricos, textuais e visuais;
  • necessidade de integração precisa de dados provenientes de diferentes fontes;
  • baixa adaptabilidade a mercados em rápida mudança;
  • dificuldade de utilização de conhecimento especializado e métodos tradicionais;
  • baixa transparência na tomada de decisões.

Essas limitações foram abordadas pelos autores do trabalho "A Multimodal Foundation Agent for Financial Trading: Tool-Augmented, Diversified, and Generalist", no qual foi apresentado o framework FinAgent — um agente multimodal básico que combina informações textuais e visuais para analisar a dinâmica do mercado e dados históricos. Os principais componentes do FinAgent incluem o processamento de dados multimodais para identificar tendências de mercado relevantes, um módulo de reflexão em dois níveis que analisa decisões de curto e longo prazo, um sistema de memória que minimiza o ruído nos resultados da análise e um módulo de tomada de decisão que integra conhecimento especializado e estratégias de negociação avançadas.


Algoritmo FinAgent

O framework FinAgent é uma ferramenta para análise de dados e tomada de decisões fundamentadas nos mercados financeiros. Ele oferece aos usuários um conjunto de ferramentas para entender os processos de mercado, prever sua dinâmica e otimizar estratégias de negociação. O FinAgent inclui cinco módulos principais que interagem entre si, formando um ecossistema de processamento de dados e geração de decisões.

O módulo de análise de mercado é responsável pela coleta, processamento e interpretação de diversos dados, incluindo notícias de bolsa, variações de preços e relatórios de empresas. Utilizando métodos modernos, o módulo identifica padrões ocultos, permitindo que as ações do agente sejam adaptadas às condições atuais do mercado.

Para alcançar máxima eficácia, o FinAgent analisa tanto dados atuais de mercado quanto informações históricas acumuladas. Por exemplo, atualizações diárias como notícias, alterações de preços e outros dados operacionais compõem a base para decisões de curto prazo. Ao mesmo tempo, a análise de eventos passados ajuda a identificar padrões de longo prazo, permitindo a elaboração de estratégias robustas para uso futuro. Essa combinação de abordagens garante ao sistema alta adaptabilidade e flexibilidade.

O processo de obtenção de dados no FinAgent é baseado no uso de um grande modelo de linguagem (LLM), que transforma informações de mercado em consultas textuais. Essas consultas são então utilizadas para buscar dados semelhantes na base histórica do módulo de memória. A aplicação de métodos de similaridade vetorial aumenta a precisão da busca e ajuda a focar nas informações mais relevantes. Adicionalmente, o sistema utiliza campos de texto especializados, o que melhora o processamento dos dados e evita a perda de detalhes importantes. Os dados finais são estruturados e resumidos, o que simplifica o processo de análise e minimiza a influência de informações secundárias.

O bloco composto por dois módulos de reflexão em níveis distintos executa funções semelhantes ao processo de aprendizado humano. O módulo de reflexão de baixo nível auxilia na identificação de correlações entre mudanças de preços e a dinâmica do mercado. Isso permite prever oscilações de curto prazo, algo especialmente relevante para traders que operam em intervalos de tempo reduzidos. Ao mesmo tempo, o módulo de reflexão de alto nível analisa conexões mais complexas e profundas com base em dados históricos e nos resultados de decisões de negociação anteriores. Isso ajuda a identificar erros e desenvolver formas de corrigi-los. Esse processo inclui a visualização de pontos-chave, como momentos de compra e venda, além da avaliação de sua eficácia. A abordagem iterativa de aprendizado permite ao agente acumular experiência e utilizá-la para aprimorar suas ações futuras.

O módulo de memória desempenha um papel central para garantir o funcionamento estável do FinAgent. Ele é responsável pelo armazenamento dos dados e sua busca eficiente. O uso de métodos de busca vetorial permite encontrar rapidamente informações relevantes em grandes volumes de dados, reduzindo o ruído e aumentando a precisão das análises. O mecanismo de memória do FinAgent tem um papel essencial na manutenção do contexto e das capacidades cognitivas do sistema. No trading financeiro, a memória é especialmente importante, pois assegura precisão, adaptabilidade e a capacidade de aprender com experiências anteriores. Isso permite que o agente utilize notícias recentes e relatórios para prever futuras mudanças no mercado, adaptar-se a condições instáveis e melhorar continuamente suas estratégias.

O módulo de tomada de decisão integra e processa dados-chave, incluindo resumos de informações de mercado, análise da dinâmica dos preços baseada nas reflexões de baixo nível, além de conclusões obtidas a partir da análise de decisões anteriores. Ele também abrange ferramentas complementadas por recomendações profissionais de investimento e estratégias de negociação comprovadas pelo tempo. Uma parte essencial do trabalho do módulo é a análise do sentimento do mercado, a previsão de tendências de alta e de baixa com base nos movimentos atuais dos preços e a reflexão sobre os aprendizados adquiridos. Além disso, o módulo considera recomendações profissionais e avalia a eficácia de indicadores tradicionais.

Com base na combinação dessas análises e levando em conta a situação financeira atual, é formada a decisão final — comprar, vender ou manter o ativo. Princípios de aprendizado contextual são usados para criar uma estrutura lógica para as decisões. Tudo isso permite justificar cada operação de negociação, garantindo que cada ação seja baseada em uma compreensão completa da dinâmica do mercado dentro de seu contexto. Essa abordagem contribui para uma melhor adaptação às condições do mercado e para decisões fundamentadas e estrategicamente corretas.

Assim, o FinAgent se apresenta como uma ferramenta abrangente que combina análise de dados, reflexão e automação de processos. Isso permite que traders e analistas se adaptem de forma eficiente ao mercado, minimizem riscos e aumentem a lucratividade, abrindo novas possibilidades para o planejamento estratégico.

A visualização original do framework FinAgent é apresentada abaixo.

Visualização original do framework FinAgent


Implementação com MQL5

Após discutirmos os aspectos teóricos do framework FinAgent, passamos à parte prática deste artigo, onde implementaremos nossa visão dos métodos propostos utilizando os recursos do MQL5.

É importante observar aqui que, assim como em trabalhos anteriores, vamos excluir o uso de grandes modelos de linguagem e nos esforçaremos para aplicar os métodos propostos com as ferramentas que temos à disposição.

Começaremos nosso trabalho com a criação dos módulos de reflexão de baixo e alto nível.

Módulo de reflexão de baixo nível

Ao iniciar a construção do módulo de reflexão de baixo nível, é importante chamar a atenção para a arquitetura do módulo de memória proposta pelos autores do framework FinAgent. A estrutura do módulo de memória pode ser dividida, de forma condicional, em três objetos que coletam informações do módulo de análise de mercado e dos dois módulos de reflexão de diferentes níveis. Isso nos permite reestruturar a organização dos módulos do modelo sem alterar o fluxo geral de informações e incluir blocos de memória separados nos respectivos módulos. Aproveitando essa propriedade, vamos integrar ao módulo de reflexão de baixo nível o bloco de memória desse fluxo informacional.

O módulo de reflexão de baixo nível modificado será implementado no objeto CNeuronLowLevelReflection, cuja estrutura é apresentada a seguir.

class CNeuronLowLevelReflection :   public CNeuronMemory
  {
protected:
   CNeuronLSTMOCL    cChangeLSTM;
   CNeuronMambaOCL   cChangeMamba;
   CNeuronRelativeCrossAttention cCrossAttention[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

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

O uso do objeto de memória como classe pai oferece a possibilidade de integrar de maneira orgânica os processos de análise de informação de mercado com as funções cognitivas de memória em um único fluxo informacional. Isso permite ao sistema não apenas processar dados brutos de forma eficiente, mas também considerar informações históricas, proporcionando assim o contexto necessário para a análise atual.

Na estrutura da classe, incluímos objetos recorrentes de diferentes arquiteturas e blocos de atenção cruzada. Esses elementos terão papel importante no processo de tratamento das informações e na tomada de decisões. A finalidade e o funcionamento desses componentes serão detalhados à medida que formos implementando os métodos do nosso novo objeto, o que permitirá entender melhor sua influência no desempenho do sistema.

Todos os objetos internos são declarados como estáticos, o que nos permite deixar o construtor e o destruidor da classe vazios. A inicialização de todos os objetos declarados e herdados é feita no método Init.

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

É importante destacar que a estrutura dos parâmetros deste método é totalmente herdada da classe pai. E, no corpo do método, chamamos diretamente o método de mesmo nome da classe pai, repassando todos os parâmetros recebidos.

Vale lembrar que, no método da classe pai, já está implementado o algoritmo de verificação dos parâmetros recebidos e a inicialização dos objetos herdados.

Em seguida, passamos à inicialização dos objetos recém-declarados. Inicialmente, são iniciados dois objetos recorrentes, cuja função é identificar a dinâmica dos parâmetros analisados. O uso de diferentes arquiteturas para esses objetos permite uma análise mais profunda, possibilitando que o sistema se adapte de forma eficaz tanto a mudanças de curto quanto de longo prazo, identificando tendências em diferentes escalas temporais.

   int index = 0;
   if(!cChangeLSTM.Init(0, index, OpenCL, window, units_count, optimization, iBatch))
      return false;
   index++;
   if(!cChangeMamba.Init(0, index, OpenCL, window, 2 * window, units_count, optimization, iBatch))
      return false;

Os resultados da análise realizada são integrados em uma única solução por meio dos blocos de atenção cruzada, que permitem combinar de forma eficiente informações de diferentes fontes e níveis, focando nas inter-relações e dependências-chave entre os parâmetros. A atenção cruzada ajuda a identificar padrões ocultos e relações, o que melhora a qualidade da tomada de decisões, proporcionando uma percepção mais precisa e coerente das informações.

   for(int i = 0; i < 2; i++)
     {
      index++;
      if(!cCrossAttention[i].Init(0, index, OpenCL, window, window_key, units_count, heads,
                                                window, units_count, optimization, iBatch))
         return false;
     }
//---
   return true;
  }

Após a inicialização de todos os objetos internos, resta retornar à aplicação chamadora o resultado lógico da execução das operações e encerrar o método.

A próxima etapa do nosso trabalho é a construção dos algoritmos de propagação para frente do módulo de reflexão de baixo nível, dentro do método feedForward.

bool CNeuronLowLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cChangeLSTM.FeedForward(NeuronOCL))
      return false;
   if(!cChangeMamba.FeedForward(NeuronOCL))
      return false;

Nos parâmetros do método, recebemos um ponteiro para o objeto de dados brutos, que contém a descrição do estado atual do ambiente. Esses dados são repassados para os métodos homônimos dos nossos objetos recorrentes, com o objetivo de identificar a dinâmica dos indicadores, o que permite acompanhar as mudanças no ambiente e adaptar as decisões em resposta a essas alterações.

Em seguida, utilizamos os blocos de atenção cruzada para enriquecer a descrição do estado atual do ambiente com as informações sobre as mudanças identificadas. Isso permite integrar os novos dados com os já existentes, fortalecendo o contexto e aprimorando a percepção da dinâmica do ambiente. Essa abordagem cria uma espécie de “rastro” da trajetória do movimento, que reflete as mudanças no estado analisado.

   if(!cCrossAttention[0].FeedForward(NeuronOCL, cChangeLSTM.getOutput()))
      return false;
   if(!cCrossAttention[1].FeedForward(cCrossAttention[0].AsObject(), cChangeMamba.getOutput()))
      return false;

Os resultados da análise são enviados ao módulo de memória, cuja função é desempenhada pela classe pai. Esse módulo permite identificar tendências persistentes, a partir das quais é formado o cenário de tendência para o próximo movimento de preços.

   return CNeuronMemory::feedForward(cCrossAttention[1].AsObject());
  }

Como pode ser observado, no algoritmo de propagação para frente, os dados brutos que descrevem o estado analisado do ambiente foram utilizados por três objetos internos. E, como você já deve ter imaginado, ao organizarmos os processos de propagação reversa, precisaremos reunir os gradientes de erro de todos os três fluxos informacionais. O algoritmo de distribuição dos gradientes de erro é implementado no método calcInputGradients.

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

Nos parâmetros do método, recebemos um ponteiro para o mesmo objeto de dados brutos. No entanto, desta vez, será necessário repassar a ele o gradiente de erro, que reflete a influência dos dados brutos sobre o resultado da execução do modelo. No corpo do método, imediatamente verificamos a validade do ponteiro recebido, pois, caso contrário, não será possível realizar a transferência de dados.

Em seguida, com os recursos da classe pai, propagamos o gradiente de erro através do módulo de memória até o nível dos blocos de atenção cruzada.

   if(!CNeuronMemory::calcInputGradients(cCrossAttention[1].AsObject()))
      return false;
   if(!cCrossAttention[0].calcHiddenGradients(cCrossAttention[1].AsObject(),
                       cChangeMamba.getOutput(), cChangeMamba.getGradient(), 
                                (ENUM_ACTIVATION)cChangeMamba.Activation()))
      return false;

E distribuímos o erro obtido entre os blocos recorrentes.

Depois, devemos propagar o gradiente de erro pelos três fluxos informacionais até o nível dos dados brutos. Para isso, primeiro conduzimos o gradiente através da linha dos blocos de atenção cruzada.

   if(!NeuronOCL.calcHiddenGradients(cCrossAttention[0].AsObject(), cChangeLSTM.getOutput(),
                      cChangeLSTM.getGradient(), (ENUM_ACTIVATION)cChangeLSTM.Activation()))
      return false;

Em seguida, realizamos a substituição do ponteiro para o buffer de gradientes de erro do objeto de dados brutos e propagamos a informação de erro pelas linhas dos objetos recorrentes, somando posteriormente os valores obtidos de cada fluxo informacional.

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

Após essa etapa, é necessário restaurar os ponteiros para os buffers de dados ao estado original, o que garante a preparação correta das informações para uso futuro.

   if(!NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

E, ao final da execução do método, retornamos à aplicação chamadora o resultado lógico das operações realizadas.

Com isso, encerramos a análise dos algoritmos de construção dos métodos do módulo de reflexão de baixo nível. O código completo desta classe e de todos os seus métodos pode ser consultado separadamente no anexo.

Módulo de reflexão de alto nível

A próxima etapa do nosso trabalho será a criação do módulo de reflexão de alto nível, que terá como foco uma análise mais profunda e abrangente das inter-relações. Diferente do módulo de reflexão de baixo nível, que se concentra em mudanças de curto prazo e na dinâmica do estado atual, o módulo de reflexão de alto nível investigará tendências de longo prazo e conexões identificadas durante operações de negociação anteriores.

A principal função deste módulo é avaliar cuidadosamente a fundamentação das decisões tomadas anteriormente, Terrestrial, além de analisar os resultados efetivamente obtidos. Esse processo permite determinar o quão eficaz foi a aplicação da estratégia de negociação atual e em que medida ela atende aos objetivos propostos. Uma parte essencial dessa análise é a identificação dos pontos fortes e fracos da estratégia.

Além disso, o módulo de reflexão de alto nível deve apresentar recomendações concretas para otimizar a estratégia de negociação. Essas recomendações têm como objetivo aumentar a rentabilidade das operações e/ou reduzir os riscos.

Aqui se torna evidente que, para uma implementação completa das funcionalidades do módulo de reflexão de alto nível, será necessário um volume significativamente maior de dados brutos. Em primeiro lugar, são indispensáveis as ações efetivas do agente, que serão objeto de análise.

Também é necessário o registro do estado do ambiente no momento em que as decisões foram tomadas. Isso nos permitirá avaliar o quão justificadas foram essas decisões dentro das condições específicas do mercado, além de entender como tais condições influenciaram os resultados.

Um elemento igualmente importante é a informação sobre lucros e perdas, que nos ajudará a avaliar como as decisões impactaram o resultado financeiro. Esses dados permitem não apenas mensurar o sucesso das operações, mas também identificar possíveis pontos fracos da estratégia que precisam ser ajustados.

Além disso, para que o módulo de reflexão de alto nível funcione de forma eficaz, será preciso armazenar os resultados da análise realizada, a fim de reutilizá-los no futuro. Isso permitirá ao sistema considerar conclusões passadas e aprimorar suas decisões com base na experiência acumulada. Os autores do framework FinAgent previram essa necessidade e incorporaram na arquitetura um bloco correspondente no módulo de memória.

Assim, chegamos a quatro fluxos de dados brutos:

  • Ações do agente;
  • Estado do ambiente;
  • Resultado financeiro (estado da conta);
  • Memória.

Vale lembrar que, na implementação atual de nossos interfaces de troca de dados entre os objetos do modelo, é permitida apenas a utilização de dois fluxos informacionais.

Assim como no módulo de reflexão de baixo nível, ao implementar o módulo de reflexão de alto nível, utilizamos o módulo de memória como classe pai do novo objeto. Essa decisão permite formar o fluxo de memória diretamente dentro do objeto, eliminando a necessidade de fontes adicionais de dados brutos. Dessa forma, o módulo se torna mais autônomo e capaz de processar e armazenar dados de forma eficiente em sua própria estrutura.

Além disso, os resultados do trabalho do módulo de reflexão de alto nível podem, com alta probabilidade, ser interpretados como uma representação latente do tensor de ações do agente. Isso se deve ao fato de que as ações do agente são, essencialmente, uma função dos resultados fornecidos por esse bloco. Consequentemente, mudanças nos resultados da análise realizada pelo módulo influenciam diretamente na correção das ações do agente. Essa abordagem permite formar um modelo de comportamento mais adaptativo, no qual as ações do agente são dinamicamente otimizadas com base nas informações fornecidas pela reflexão de alto nível.

Dessa forma, utilizando as premissas e decisões arquitetônicas descritas acima, estruturamos o modelo básico de interfaces do novo objeto, que opera com duas fontes de dados brutos. Elas fornecem as informações necessárias para a análise das condições atuais e dos resultados das ações anteriores.

No entanto, há ainda outro aspecto importante. Para analisar a política de comportamento do agente como um todo — e não apenas focar em operações individuais — os autores do framework FinAgent propõem incluir a análise do gráfico de saldo com marcações das operações de negociação. Essa abordagem permite obter uma visão geral dos resultados de toda a estratégia do agente. Em nossa implementação, buscamos reproduzir essa solução adicionando objetos recorrentes para o processamento dos tensores que descrevem o estado da conta e as ações do agente. Esses objetos possibilitam modelar a relação entre a dinâmica do saldo e as ações de negociação, oferecendo uma análise mais aprofundada da política utilizada.

As soluções descritas acima serão implementadas dentro do novo objeto CNeuronHighLevelReflection, cuja estrutura é apresentada a seguir.

class CNeuronHighLevelReflection :  public CNeuronMemory
  {
protected:
   CNeuronBaseOCL    cAccount;
   CNeuronLSTMOCL    cHistoryAccount;
   CNeuronRelativeCrossAttention cActionReason;
   CNeuronLSTMOCL    cHistoryActions;
   CNeuronRelativeCrossAttention cActionResult;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { return false; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
                                        CBufferFloat *SecondGradient, 
                                        ENUM_ACTIVATION SecondActivation = None) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override {return false; }
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, 
                                        CBufferFloat *SecondInput) override;

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

A estrutura do objeto já contém o conhecido conjunto de métodos sobrescrevíveis, que garantem flexibilidade e adaptabilidade na implementação das funcionalidades, sem comprometer a arquitetura geral do sistema. Além disso, a nova classe inclui alguns objetos internos cujo funcionamento será detalhado durante a construção dos algoritmos para os métodos indicados.

Todos os objetos internos são declarados como estáticos, o que nos permite deixar o construtor e o destruidor da classe vazios. A inicialização de todos os objetos declarados e herdados é feita no método Init.

bool CNeuronHighLevelReflection::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                      uint window, uint window_key, uint units_count,
                                      uint heads, uint desc_account, uint actions_state,
                                      ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronMemory::Init(numOutputs, myIndex, open_cl, 3, window_key, actions_state / 3,
                           heads, optimization_type, batch))
      return false;

Nos parâmetros do método de inicialização, recebemos um conjunto de constantes que permite definir de forma inequívoca a arquitetura do objeto a ser criado. Um desses parâmetros indica a dimensionalidade do vetor de ações do agente.

Como já foi mencionado anteriormente, a saída deste objeto deverá fornecer uma certa representação latente do tensor de ações do agente. Cada operação de negociação do nosso agente é caracterizada por três parâmetros: volume da operação e os níveis de negociação (stop-loss e take-profit). As ordens de compra e venda são representadas por linhas diferentes no tensor, o que elimina a necessidade de um parâmetro adicional para o sentido da operação.

No corpo do método, seguindo a prática já estabelecida, chamamos o método de mesmo nome da classe pai (neste caso, o módulo de memória). Nos parâmetros da chamada, são passados os dados do tensor de ações do agente, estruturados de acordo com as premissas descritas anteriormente. Essa abordagem permite manter a continuidade funcional e utilizar os recursos básicos da classe pai para o processamento dos dados. Isso possibilita integrar os resultados da análise realizada no objeto atual à estrutura geral do modelo, garantindo sua consistência e acessibilidade para os objetos subsequentes.

Após a execução bem-sucedida das operações do método da classe pai, passamos à inicialização dos objetos internos. Primeiro, inicializamos a camada totalmente conectada básica, que será usada para tratar corretamente os dados do segundo fluxo informacional.

   int index = 0;
   if(!cAccount.Init(0, index, OpenCL, desc_account, optimization, iBatch))
      return false;

Em seguida, inicializamos o bloco recorrente para acompanhar as mudanças no estado da conta.

   index++;
   if(!cHistoryAccount.Init(0, index, OpenCL, desc_account, 1, optimization, iBatch))
      return false;

Para analisar a justificativa da última decisão de negociação, utilizamos um bloco de atenção cruzada, no qual analisamos as dependências entre as ações do agente e o tensor que descreve o estado do ambiente.

   index++;
   if(!cActionReason.Init(0, index, OpenCL, iWindow, iWindowKey, iUnits, iHeads,
                          window, units_count, optimization, iBatch))
      return false;

A dinâmica das ações é processada no bloco recorrente correspondente.

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

Depois disso, avaliamos a eficácia da política de comportamento do agente, comparando a dinâmica das operações de negociação com o estado da conta no bloco de atenção cruzada.

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

Concluída a inicialização de todos os objetos internos, resta apenas retornar à aplicação chamadora o resultado lógico da execução das operações e finalizar o método.

Na etapa seguinte, passamos à construção do algoritmo de propagação para frente do nosso módulo de reflexão de alto nível dentro do método feedForward.

bool CNeuronHighLevelReflection::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!NeuronOCL || !SecondInput)
      return false;

Nos parâmetros do método, recebemos ponteiros para os objetos de dados brutos dos dois fluxos informacionais. Imediatamente verificamos a validade dos ponteiros recebidos.

Aqui vale destacar que o segundo fluxo informacional é representado por um buffer de dados. E, antes de continuar com as operações seguintes, utilizamos o ponteiro recebido para substituir o buffer de resultados por um objeto interno especialmente preparado. Isso permite utilizar os interfaces básicos dos objetos internos para processar os dados obtidos.

   if(cAccount.getOutput() != SecondInput)
     {
      if(cAccount.Neurons() != SecondInput.Total())
         if(!cAccount.Init(0, 0, OpenCL, SecondInput.Total(), optimization, iBatch))
            return false;
      if(!cAccount.SetOutput(SecondInput, true))
         return false;
     }

Em seguida, avaliamos a mudança no estado da conta por meio do bloco recorrente.

   if(!cHistoryAccount.FeedForward(cAccount.AsObject()))
      return false;

E verificamos a justificativa da última decisão de negociação, comparando-a com o estado atual do ambiente, utilizando o bloco de atenção cruzada.

   if(!cActionReason.FeedForward(this.AsObject(), NeuronOCL.getOutput()))
      return false;

Aqui, é importante observar a diferença entre o tensor que descreve o estado atual do ambiente e aquele utilizado para a tomada de decisão de negociação. O tensor do estado do ambiente entra no modelo já após a execução da ação e a transição do sistema para um novo estado. No entanto, espera-se que essa diferença não exerça impacto significativo sobre os resultados da análise.

Está previsto o disparo de uma nova iteração do modelo a cada formação de uma nova barra. Ao mesmo tempo, a profundidade do histórico analisado é significativamente maior do que uma única barra, o que garante alta resolução e completude da análise. Como resultado da transição para um novo estado, ocorre o deslocamento dos dados em um elemento dentro da série temporal multimodal analisada, sendo que a barra mais antiga é removida da análise. Espera-se que essa barra tenha influência mínima sobre a ação atual e, portanto, sua exclusão não compromete de forma relevante a precisão da análise. Isso permite avaliar com segurança suficiente a fundamentação da decisão de negociação tomada anteriormente.

Além disso, a adição de uma nova barra, que não era conhecida no momento anterior, oferece informações adicionais para avaliar a correção da operação realizada. Esse mecanismo contribui para identificar não apenas as consequências da decisão de negociação, mas também para verificar o alinhamento entre as mudanças reais nas condições de mercado e as previsões feitas anteriormente.

Os resultados da análise são encaminhados para o bloco recorrente de rastreamento da política de comportamento do agente.

   if(!cHistoryActions.FeedForward(cActionReason.AsObject()))
      return false;

Em seguida, analisamos a política de comportamento no contexto do resultado financeiro utilizando o bloco de atenção cruzada.

   if(!cActionResult.FeedForward(cHistoryActions.AsObject(), cHistoryAccount.getOutput()))
      return false;

Depois, realizamos a substituição do buffer de resultados para armazenar as últimas ações do agente, garantindo a organização correta do processo de propagação reversa, e chamamos o método homônimo da classe pai, que desempenha as funções do módulo de memória.

   if(!SwapBuffers(Output, PrevOutput))
      return false;
//---
   return CNeuronMemory::feedForward(cActionResult.AsObject());
  }

O resultado lógico das operações é retornado à aplicação chamadora, e o método é finalizado.

Concluído o trabalho de construção do algoritmo do método de propagação para frente, passamos à organização dos processos de propagação reversa. Neste caso, são utilizados algoritmos lineares de fácil implementação. Por esse motivo, não há necessidade de detalhá-los aqui. Você pode consultá-los separadamente no anexo, onde está disponível o código completo do objeto de reflexão de alto nível e todos os seus métodos.

Neste ponto, encerramos o conteúdo desta parte do artigo, mas ainda não concluímos a implementação dos métodos propostos no framework FinAgent. Vamos fazer uma breve pausa e, no próximo artigo, levaremos a construção do framework até sua conclusão lógica.


Considerações finais

Neste artigo, conhecemos o framework FinAgent, que representa uma solução inovadora ao integrar informações textuais e visuais para uma análise abrangente da dinâmica do mercado e de dados históricos. Graças aos seus cinco componentes principais, o FinAgent oferece alta precisão e adaptabilidade na tomada de decisões de negociação. Isso torna o framework uma ferramenta promissora para o desenvolvimento de estratégias de negociação eficazes e flexíveis, capazes de operar em mercados voláteis.

Na parte prática, implementamos nossa visão dos dois módulos de reflexão de níveis distintos utilizando os recursos do MQL5. E na próxima parte, daremos continuidade ao trabalho iniciado, concluindo com a verificação da eficácia das soluções implementadas com base 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 usando o método Real-ORL
3 Study.mq5 Expert Advisor EA de treinamento de modelos
4 Test.mq5 Expert Advisor EA para teste de 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 do programa OpenCL

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

Arquivos anexados |
MQL5.zip (2327.7 KB)
Últimos Comentários | Ir para discussão (1)
Dominic Michael Frehner
Dominic Michael Frehner | 9 jan. 2025 em 13:19

Seria ótimo se você também pudesse escrever artigos em inglês.

Analisamos o código binário dos preços no mercado (Parte I): Um novo olhar sobre a análise técnica Analisamos o código binário dos preços no mercado (Parte I): Um novo olhar sobre a análise técnica
Este artigo apresenta uma abordagem inovadora para a análise técnica, baseada na conversão dos movimentos de preço em código binário. O autor mostra como diferentes aspectos do comportamento do mercado - desde movimentos simples de preço até padrões complexos - podem ser codificados em sequências de zeros e uns.
Simulação de mercado: Iniciando o SQL no MQL5 (II) Simulação de mercado: Iniciando o SQL no MQL5 (II)
Apesar de muitos imaginarem que podemos usar tranquilamente códigos em SQL dentro de outros códigos. Isto normalmente não se aplica. Devido ao fato, de que um código SQL, será sempre colocado dentro de um executável, como sendo uma string. E este fato de colocar o código SQL como sendo uma string, apesar de não ser problemático, para pequenos trechos de código. Podem sim ser algo que nos causará muitos transtornos e uma baita de uma dor de cabeça.
Do básico ao intermediário: Eventos de mouse Do básico ao intermediário: Eventos de mouse
Este artigo, é uns dos que definitivamente, é necessário não apenas ver o código e o estudar para compreender o que estará acontecendo. É de fato, necessário, criar uma aplicação executável e a utilizar em um gráfico qualquer. Isto maneira a conseguir entender pequenos detalhes, que de outra forma são muito complicados de serem compreendidos. Como por exemplo, a combinação de teclado com o mouse, a fim de construir certos tipos de coisas.
Redes neurais em trading: Agente com memória multinível (Conclusão) Redes neurais em trading: Agente com memória multinível (Conclusão)
Damos continuidade ao desenvolvimento do framework FinMem, que utiliza abordagens de memória multinível, imitando os processos cognitivos humanos. Isso permite que o modelo não apenas processe dados financeiros complexos de forma eficiente, mas também se adapte a novos sinais, aumentando significativamente a precisão e a efetividade das decisões de investimento em mercados altamente dinâmicos.