Русский
preview
Redes neurais em trading: Percepção adaptativa da dinâmica do mercado (Conclusão)

Redes neurais em trading: Percepção adaptativa da dinâmica do mercado (Conclusão)

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

Introdução

O mercado financeiro não é uma tabela fácil de interpretar. É um fluxo. Contínuo, esparso e, em certos momentos, explosivo. Os eventos do mercado não surgem de acordo com um cronograma. Eles aparecem onde a liquidez muda, as ordens desaparecem do livro e ocorrem picos repentinos de volume. Compreender esse fluxo é uma condição fundamental para construir um sistema de previsão confiável, sensível e adaptativo. O STE-FlowNet nasceu justamente dessa observação: enxergar o mercado como um fluxo de eventos e ensinar o modelo a perceber seu movimento.

Essa perspectiva muda tudo. Modelos básicos que operam apenas com sequências de velas ou atributos agregados perdem, por definição, parte das informações sobre a estrutura do movimento, especialmente sobre onde e quando surgiu um desequilíbrio entre oferta e demanda. A análise event-driven permite capturar sinais de alta relevância no momento em que aparecem. Isso reduz a latência de resposta. Isso aumenta a robustez diante dos efeitos de suavização, nos quais informações importantes acabam diluídas pela agregação. Há ainda outro aspecto. O fluxo de eventos combina naturalmente sensibilidade local e estabilidade global. O modelo percebe tanto um pico instantâneo quanto a tendência subjacente, sem precisar dividir artificialmente a análise entre horizontes de curto e longo prazo.

O STE-FlowNet trouxe para o universo financeiro a ideia de percepção espaço-temporal, já validada em outras áreas como método para extrair a dinâmica de dados baseados em eventos. Não há magia em sua base, mas uma engenharia cuidadosamente estruturada. Ele foi concebido como uma ferramenta para modelar com precisão e interpretabilidade a dinâmica de curto prazo do mercado, extraindo diretamente os micromovimentos dos preços e suas relações locais ao longo do tempo.

O framework STE-FlowNet pode ser comparado a uma equipe de analistas experientes do mercado financeiro, na qual cada integrante desempenha uma função específica no processamento do fluxo de eventos. O codificador atua como o principal estrategista: registra cada negociação, cada alteração de preço e volume e acumula informações sobre a dinâmica atual e passada do mercado. Sua memória ConvGRU funciona como um trader experiente, capaz de manter o histórico dos movimentos mais recentes e avaliar imediatamente a relevância de novos sinais.

Os mapas intermediários de movimento e as conexões de salto formam uma rede de apoio analítico que preserva os detalhes e encaminha imediatamente as informações críticas para as etapas seguintes. Eles permitem que o modelo identifique tanto picos rápidos de atividade quanto tendências mais lentas, porém relevantes, como uma equipe que acompanha simultaneamente as menores oscilações das cotações e as tendências mais amplas do mercado.

A camada de correlação e o refinamento iterativo funcionam como um grupo de especialistas que ajusta as previsões com base nas estimativas anteriores. A cada nova etapa, são considerados tanto os dados atuais quanto todo o histórico acumulado, criando uma estratégia dinâmica e adaptativa para prever os movimentos do mercado.

O decodificador final com Projection Head atua como um gestor de portfólio que transforma todas as estimativas e previsões internas em ações concretas: sinal de entrada ou saída, trajetória probabilística do movimento, avaliação do risco e da intensidade do impulso do mercado. Ele integra todos os componentes da equipe e gera uma decisão prática, pronta para ser aplicada em tempo real.

O aprendizado autossupervisionado e o equilíbrio entre a identificação de anomalias locais e a regularidade global do fluxo tornam o STE-FlowNet robusto a ruídos, picos e eventos raros. No contexto financeiro, isso permite detectar com precisão oscilações bruscas de preço e volume, identificar pontos críticos e dar suporte à tomada de decisões em condições de alta volatilidade.

A visualização do framework STE-FlowNet elaborada pelos autores é apresentada abaixo.

Nos trabalhos anteriores, concluímos a implementação da estrutura básica do codificador e estabelecemos a base para o processamento de dados em fluxo. Hoje, daremos continuidade à construção do framework: adicionaremos blocos residuais, implementaremos um Flow Decoder completo e criaremos um objeto de alto nível responsável pelo fluxo completo do sinal de mercado através da arquitetura.



Bloco residual

Hoje começaremos pela construção do bloco residual, uma estrutura modular dentro do modelo. Antes de partir para a implementação, isto é, antes de escrever o código, vamos examinar o esquema proposto pelos autores do framework. Dentro do bloco, vemos dois pares sequenciais de blocos convolucionais com conexões residuais, cujos resultados são então cuidadosamente concatenados com os dados originais recebidos na entrada.

As conexões residuais merecem atenção especial. Ao estudarmos o SEW-ResNet, já analisamos os problemas relacionados ao uso de estruturas semelhantes em redes neurais de spikes. O bloco Spike-Element-Wise vem imediatamente à mente: um componente já testado que permite preservar a estrutura dos sinais mesmo sob carga computacional elevada.

Por que reinventar a roda quando já existe uma solução pronta e confiável? Proponho construir nosso bloco residual com base nas abordagens do SEW, aproveitando componentes já validados em vez de reconstruir uma solução equivalente do zero. Essa abordagem proporcionará estabilidade, preservará a estrutura dos atributos locais e agilizará o desenvolvimento, tornando nosso bloco uma parte sólida e funcional de todo o modelo.

Implementaremos a solução proposta na nova classe CNeuronSTEFlowNetResidualBlock, derivada da classe base CNeuronBaseOCL. O novo objeto encapsula integralmente toda a lógica do bloco residual e sua interação com o OpenCL.

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

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

A estrutura declara apenas um componente interno, cBlock, um array dinâmico de objetos preenchido durante a inicialização.

O método Init pode ser comparado à formação de uma equipe completa de especialistas para analisar o mercado.

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

Primeiro, verificamos se a classe pai está pronta para operar. É como confirmar se o escritório e toda a infraestrutura estão preparados para receber a equipe. Se a configuração básica falhar, o bloco não é iniciado.

Em seguida, preparamos o ambiente de execução e conectamos a infraestrutura computacional do OpenCL, para que toda a equipe possa processar as informações com eficiência.

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

O primeiro componente da equipe é o Bottleneck, que desempenha o papel de analista principal, responsável por filtrar e compactar os dados analisados, destacando os atributos mais relevantes. Ele recebe os parâmetros necessários, como canais, grupos e batches, e fica pronto para executar a análise. Se, por algum motivo, não puder ser inicializado corretamente, a execução é interrompida, pois não faz sentido prosseguir sem o analista principal.

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

Depois, são adicionados dois blocos com conexões residuais (Residual Blocks). Sua função é revisar e refinar a análise, dar suporte à estratégia e preservar informações importantes. Cada novo componente é inicializado, recebe acesso aos recursos necessários e passa a integrar o fluxo geral de processamento. Se pelo menos um dos blocos não puder ser criado, a execução é interrompida para evitar que uma estrutura incompleta seja colocada em funcionamento.

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

Quando todos os componentes internos são adicionados com sucesso, o bloco está pronto para operar. Ele pode receber sinais, processar os dados e encaminhar os resultados às etapas seguintes. Assim, cBlock passa a funcionar como uma equipe integrada, na qual cada componente cumpre sua função, interage com os demais e contribui para manter a precisão das previsões mesmo em condições de rápidas mudanças no mercado.

O algoritmo de propagação para frente está implementado no método feedForward, cujo funcionamento pode ser comparado ao de uma equipe bem coordenada de analistas. Cada integrante processa o fluxo de informações em sequência.

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

Primeiro, verificamos se o array interno contém os componentes necessários. Sem eles, o processamento não pode prosseguir. Em seguida, os dados são passados ao primeiro componente do array interno cBlock.

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

Cada componente seguinte recebe o resultado do anterior, processa-o de acordo com suas próprias regras e o encaminha adiante, refinando progressivamente o resultado e filtrando o ruído do mercado. Se algum deles falhar, toda a execução é interrompida para evitar distorções nos resultados.

Depois que o sinal passa por todos os componentes, a saída acumulada dos componentes é combinada com os dados originais do bloco. É como se o analista principal reunisse todos os relatórios, acrescentasse as informações de origem e produzisse uma visão completa e equilibrada da situação atual.

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

Em seguida, o bloco residual retorna o resultado, pronto para ser utilizado nas próximas etapas da cadeia computacional do modelo.

A estrutura linear da propagação para frente torna a propagação dos gradientes de erro relativamente simples e transparente. Por isso, os métodos de propagação reversa ficam como exercício para o leitor. O código-fonte completo da classe e de todos os seus métodos está disponível no anexo do artigo, permitindo examinar em detalhes a implementação do bloco residual e sua lógica interna.


Decodificador

A próxima etapa é a construção do decodificador, um componente essencial da cadeia computacional que transforma as representações ocultas do codificador em uma representação reconstruída da dinâmica dos dados. Aqui, merece atenção especial a presença de dois fluxos internos complementares: o fluxo principal de atributos e o fluxo de representações intermediárias, que permitem reconstruir a dinâmica dos dados de origem com maior profundidade e precisão. Além disso, ao utilizar os estados latentes do codificador, o decodificador não se limita a reconstruir os dados: ele forma uma representação integrada da dinâmica do sistema, combinando oscilações de curto prazo com tendências de longo prazo. No contexto financeiro, isso pode ser interpretado como a capacidade do modelo de considerar simultaneamente impulsos instantâneos do mercado, negociações individuais e tendências mais amplas, algo fundamental para a construção de previsões e estratégias.

No decodificador, cada nível de atributos é processado sequencialmente enquanto os estados latentes são integrados em paralelo, aumentando a precisão das previsões e a robustez do modelo a ruídos e anomalias raras. Cada etapa da decodificação inclui convoluções transpostas e conexões de salto, permitindo reconstruir as características espaciais e temporais com alto nível de detalhamento. Essa abordagem preserva informações críticas sobre micromovimentos locais e fases globais, formando uma base completa para as etapas posteriores de análise e tomada de decisão.

Em última análise, o decodificador funciona como uma ponte entre as representações internas do codificador e as aplicações práticas do modelo, convertendo a dinâmica latente em representações preditivas utilizáveis nas etapas posteriores do modelo que pode ser utilizada para avaliar riscos, identificar pontos críticos do mercado e construir estratégias adaptativas.

Implementamos o decodificador na nova classe independente CNeuronSTEFlowNetDecoder, estruturada em torno dos componentes internos cMainLine e cFlow. Sua estrutura se baseia em dois componentes internos, cMainLine e cFlow, responsáveis, respectivamente, pelo fluxo principal de atributos e pelo processamento das representações intermediárias de fluxo. Além disso, a classe armazena um ponteiro para o codificador, cEncoder, permitindo utilizar os estados latentes para reconstruir a dinâmica com precisão.

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

public:
                     CNeuronSTEFlowNetDecoder(void) {};
                    ~CNeuronSTEFlowNetDecoder(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &chanels[], uint &units[], uint group_size,
                          uint groups, uint heads, uint dimension_k,
                          CNeuronSTEFlowNetEncoder* encoder,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //--
   virtual int       Type(void)   override const   {  return defNeuronSTEFlowNetDecoder;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual void      SetEncoder(CNeuronSTEFlowNetEncoder* encoder) { cEncoder = encoder; }
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

O objeto do decodificador é inicializado em várias etapas, nas quais cada elemento é criado, configurado e adicionado ao respectivo fluxo de processamento, cMainLine ou cFlow.

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

Primeiro, o método verifica se os parâmetros recebidos estão corretos: o tamanho do array de descrição dos canais deve coincidir com o tamanho do array que especifica o comprimento das sequências analisadas, e o número total de camadas deve ser de pelo menos uma.

Em seguida, é chamada a inicialização da classe pai, que prepara o bloco para operar, definindo as dimensões do tensor de resultados e o método de otimização dos parâmetros.

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

Depois disso, o ponteiro para o codificador é armazenado para que o decodificador tenha acesso aos estados latentes, e o número de camadas do codificador é determinado para alinhar o acesso do decodificador aos níveis correspondentes do codificador.

Os arrays internos cMainLine e cFlow são limpos e associados ao contexto OpenCL. A partir daí, começa a montagem gradual dos blocos.

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

Na primeira etapa, é criado o bloco residual principal CNeuronSSAMResNeXtBlock, que inicia o fluxo principal de processamento dos atributos.

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

Em seguida, é adicionada uma camada convolucional convencional CNeuronConvOCL, ativada pela função SoftPlus, seguida por uma camada de normalização em lote CNeuronBatchNormOCL, que estabiliza a distribuição das ativações e mantém sua escala consistente.

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

As camadas seguintes formam blocos alternados: Spike Activation no fluxo principal Main Line, responsável pela ativação não linear, e SSAM ResNeXt no fluxo Flow, que processa a combinação de atributos locais e globais com o auxílio dos estados latentes do codificador.

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

Cada bloco é validado após a inicialização e só então é adicionado ao respectivo fluxo de informação. Em cada etapa, são criados objetos intermediários para concatenar os atributos e alinhar as dimensões dos canais, permitindo integrar corretamente os dados provenientes do codificador com os do objeto atual.

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

Ao final de cada iteração, são adicionados novos blocos residuais e camadas convolucionais com função SoftPlus e normalização, concluindo a montagem da estrutura Main Line no nível correspondente.

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

Essa sequência se repete em todas as camadas internas, completando a estrutura hierárquica do decodificador. Com isso, o decodificador consegue reconstruir com precisão as características espaciais e temporais dos dados, integrar os estados latentes e preparar as representações resultantes para posterior interpretação ou previsão.

O método retorna true somente depois que todos os componentes internos são inicializados e adicionados com sucesso, confirmando que a inicialização do decodificador foi concluída com sucesso.

A propagação para frente do nosso decodificador é executada por dois fluxos paralelos, Main Line e Flow, com integração dos estados latentes do codificador, Main Line e Flow, com integração dos estados latentes do codificador.

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

Primeiro, o método prepara as referências para todos os blocos internos: blocos residuais, camadas convolucionais, normalização, camadas Spike Activation e ConvGRU no fluxo principal, além dos blocos SSAM ResNeXt e dos objetos de concatenação no fluxo Flow. A variável prev armazena um ponteiro para o objeto que contém os dados da etapa anterior, mantendo o processamento sequencial. Já layers e enc_layers armazenam, respectivamente, o número de níveis do decodificador e do codificador, determinando a ordem em que os estados latentes são integrados.

A decodificação ocorre por etapas. Primeiro, os dados passam pelo bloco residual, pela camada convolucional e pela normalização no fluxo principal.

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

Em seguida, se não estivermos na última camada, a camada Spike Activation é executada para aplicar a transformação não linear.

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

Em paralelo, o fluxo Flow processa os dados de origem e incorpora as representações intermediárias obtidas do codificador por meio do ConvGRU. Em cada nível, as saídas dos dois fluxos são reunidas no objeto concat, que combina as representações da Main Line, os mapas intermediários do Flow e, quando disponíveis, os estados latentes do codificador e, quando disponíveis, os estados latentes do codificador, formando uma representação consistente para o nível seguinte.

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

Ao mesmo tempo, o algoritmo verifica a compatibilidade das dimensões dos canais e do número de unidades entre os níveis correspondentes do decodificador e do codificador, garantindo que as informações sejam combinadas corretamente sem perda de detalhes relevantes.

Depois que os dados percorrem todas as camadas do decodificador, a Spike Activation final é executada por meio da funcionalidade da classe base, que gera a saída final do decodificador.

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

Como resultado dessa estrutura, o decodificador consegue acumular atributos locais e globais, integrar os estados latentes do codificador e convertê-los em uma representação preditiva completa, pronta para uso. Isso é especialmente importante em fluxos financeiros, nos quais é necessário considerar tanto os micromovimentos dos preços quanto as tendências de longo prazo.

No entanto, a propagação para frente gera apenas uma previsão preliminar com base nos parâmetros atuais do modelo, que, sem treinamento, oferece pouca informação útil. Para que o modelo realmente aprenda a identificar padrões e prever adequadamente a dinâmica dos dados, é necessário implementar os algoritmos de propagação reversa, capazes de ajustar os pesos com base no erro entre a previsão e o valor-alvo.

A arquitetura multithread do decodificador merece atenção especial. Os fluxos paralelos Main Line e Flow, juntamente com a integração dos estados latentes do codificador, formam uma rede complexa de dependências, na qual os gradientes provenientes de um fluxo influenciam os demais. Isso exige cuidado na propagação dos gradientes para que a contribuição de cada bloco seja considerada corretamente e não haja distorção das informações durante a atualização dos pesos.

Assim, a propagação reversa em CNeuronSTEFlowNetDecoder não deve simplesmente transferir o erro para as camadas anteriores, mas agregá-lo corretamente ao longo de todas as ramificações, combinando de forma adequada os atributos locais e globais e os estados latentes. Somente dessa forma o treinamento se torna eficiente, permitindo que o modelo se adapte a padrões espaço-temporais complexos, identifique micromovimentos e tendências globais, aspectos essenciais para analisar a dinâmica dos fluxos financeiros e construir estratégias de previsão robustas.

O algoritmo de distribuição dos gradientes de erro está implementado no método calcInputGradients e constitui um elemento essencial do treinamento do modelo. Ele garante a propagação correta dos gradientes de erro pela arquitetura multithread do decodificador, acumulando a contribuição de cada bloco.

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

No início do método, são criadas variáveis locais que armazenam ponteiros para os componentes internos do fluxo principal Main Line: blocos residuais (conv_res), camadas convolucionais (conv), camadas de normalização (norm) e camadas Spike Activation (sa). Para o fluxo Flow, são definidos os objetos flow. Já a variável prev aponta para a camada anterior que participará da propagação reversa.

Também são calculados os números de camadas do decodificador e do codificador, permitindo integrar corretamente os estados latentes durante a propagação reversa.

Na primeira etapa, são calculados os gradientes da camada final de normalização.

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

Em seguida, os gradientes são calculados sequencialmente para as camadas convolucionais e os blocos residuais do fluxo principal Main Line.

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

Essa etapa garante a propagação precisa do erro pelas transformações lineares e não lineares, estabelecendo a base para a atualização correta dos pesos do modelo.

Depois, um laço percorre as camadas das superiores para as inferiores.

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

Em cada nível, é verificada a existência da camada ConvGRU correspondente no codificador. Quando ela está disponível, sua contribuição é incorporada à propagação dos gradientes, garantindo que os gradientes associados aos estados latentes sejam considerados corretamente.

Dentro do laço, os gradientes são distribuídos entre Main Line e Flow por meio do método DeConcat, que distribui os gradientes de erro entre os ramos correspondentes aos atributos locais, aos mapas intermediários e aos vetores latentes do codificador. Isso garante que cada componente receba um sinal de treinamento adequado.

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

Após a distribuição dos gradientes em Flow, a propagação reversa prossegue pela Main Line: primeiro pela Spike Activation, depois pela camada de normalização e pela camada convolucional, até alcançar o bloco residual.

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

Para somar corretamente os gradientes provenientes dos dois fluxos de processamento, utilizamos uma técnica de substituição temporária dos ponteiros para os buffers de dados, que permite acumular as diferentes contribuições e minimizar distorções durante a propagação dos gradientes.

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

O método também considera as particularidades da última camada, encerra corretamente o laço e atualiza os gradientes de todos os objetos intermediários. Com isso, o algoritmo garante uma propagação estável e precisa dos gradientes de erro por toda a estrutura do decodificador, permitindo que a rede se adapte com eficiência a dependências espaço-temporais complexas, identifique micromovimentos e tendências globais, algo especialmente importante na análise de fluxos financeiros e na construção de estratégias de previsão.

Assim, calcInputGradients implementa um mecanismo completo e multithread de propagação reversa. Ele garante que cada bloco e cada canal recebam os gradientes necessários para a atualização dos pesos, permitindo uma atualização consistente e confiável dos parâmetros do modelo.

O método updateInputWeights implementa a atualização dos parâmetros do decodificador com o objetivo de minimizar o erro, responsável por atualizar diretamente os pesos de todos os componentes internos do módulo com base nos gradientes calculados anteriormente. Essa é a etapa final do ciclo de treinamento: após a propagação para frente, a geração da previsão e a propagação reversa dos gradientes de erro, o modelo dispõe dos gradientes que indicam a influência de cada parâmetro no resultado e pode ajustar esses parâmetros para reduzir o valor da função de perda.

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

O método começa inicializando os ponteiros para os principais componentes. Também são determinados os números de camadas do decodificador e do codificador, permitindo incorporar corretamente as dependências associadas aos estados latentes durante a atualização dos pesos.

Dentro do laço, os parâmetros de cada camada do decodificador são atualizados por etapas. Primeiro, são atualizados os pesos do bloco residual, utilizando a saída da camada anterior como sinal de entrada.

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

Em seguida, são atualizados sequencialmente os parâmetros da camada convolucional, da normalização e da Spike Activation, mantendo a atualização coordenada dos componentes da Main Line com base nos gradientes calculados nas etapas anteriores.

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

A integração das informações provenientes do codificador merece atenção especial. Se a camada ConvGRU correspondente estiver presente, as informações provenientes de seus canais e vetores latentes são incorporadas ao cálculo usado para atualizar os parâmetros do decodificador, permitindo que o decodificador incorpore corretamente as dependências de longo prazo e os atributos globais preservados durante a codificação do sinal.

O fluxo Flow é atualizado em paralelo com a Main Line, mantendo coordenadas as atualizações dos componentes de Main Line e Flow durante o treinamento e contribuindo para uma reconstrução precisa do estado dinâmico dos dados.

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

Depois que todas as camadas são percorridas, os pesos herdados da classe base são atualizados, concluindo o ajuste dos parâmetros.

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

Essa estrutura garante que todos os parâmetros internos do decodificador recebam os gradientes necessários para o treinamento, levando em conta tanto as oscilações locais do fluxo de entrada quanto as tendências globais integradas pelo codificador.

De modo geral, o método updateInputWeights implementa um mecanismo multithread e hierárquico de atualização dos parâmetros. Cada bloco recebe a contribuição de gradiente necessária para o ajuste, enquanto os gradientes são acumulados e distribuídos entre os componentes.

O código completo de CNeuronSTEFlowNetDecoder e de todos os seus métodos está disponível no anexo.



Objeto de alto nível

Na etapa seguinte, passamos à construção de um objeto integrado de alto nível, que reúne todos os principais componentes do modelo em uma única arquitetura. Depois de desenvolver separadamente o codificador, o decodificador e o bloco residual, surge a necessidade de criar uma estrutura capaz não apenas de processar sequencialmente o fluxo de eventos, mas também de sincronizar o funcionamento de todos os componentes internos. Esse objeto reúne, em um único mecanismo coordenado, a codificação espaço-temporal, a extração precisa de atributos locais e a previsão da dinâmica dos dados.

O objeto de alto nível CNeuronSTEFlowNet atua como integrador dos principais componentes do modelo. Ele herda a funcionalidade básica de CNeuronSTEFlowNetDecoder, eliminando a necessidade de criar internamente um componente separado para o decodificador. A funcionalidade do decodificador passa a ser fornecida diretamente pela classe base.

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

public:
                     CNeuronSTEFlowNet(void) {};
                    ~CNeuronSTEFlowNet(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint &chanels[], uint &units[], uint group_size,
                          uint groups, uint heads, uint dimension_k, uint stack_size,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //--
   virtual int       Type(void)   override const   {  return defNeuronSTEFlowNet;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

Dentro do objeto, é declarado cSTEEncoder, responsável pela codificação espaço-temporal do fluxo de eventos de entrada, além de cSTEResidualBlock, responsável pela extração estável e precisa dos atributos locais e dos micromovimentos. Essa abordagem permite capturar simultaneamente padrões detalhados em intervalos curtos e tendências globais acumuladas pelo codificador.

A inicialização do objeto de alto nível STE-FlowNet começa pela verificação da compatibilidade entre os tamanhos dos arrays de canais e de sequências, evitando inconsistências na configuração.

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

Para o funcionamento correto do decodificador, são criados arrays invertidos de canais e de tamanhos das sequências, obtidos pela inversão da ordem dos arrays originais. Ao mesmo tempo, o primeiro elemento do array de canais é duplicado para acomodar as saídas concatenadas do bloco residual e compatibilizar suas dimensões com a entrada do decodificador.

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

Em seguida, cada componente principal é inicializado. O codificador é criado com os arrays originais de canais e de tamanhos das sequências, além dos parâmetros definidos para agrupamento, cabeças de atenção e dimensionalidade do espaço latente.

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

Após a inicialização bem-sucedida do codificador, é inicializado o bloco residual. Ele processa as saídas do codificador e refina as saídas do codificador, preservando atributos locais e micromovimentos relevantes.

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

Por fim, o decodificador é inicializado com os arrays invertidos de canais e de tamanhos das sequências. Ele recebe um ponteiro para o objeto do codificador, permitindo utilizar os estados latentes para reconstruir com precisão as representações intermediárias do fluxo de eventos.

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

Após a conclusão bem-sucedida de todas as etapas, o objeto está pronto para operar, integrando o codificador, o decodificador e o bloco residual em um único modelo coordenado de processamento de dados espaço-temporais.

A propagação para frente do STE-FlowNet segue uma sequência em que os dados percorrem todos os principais componentes do modelo. Primeiro, o sinal de entrada alimenta o codificador, que gera uma representação latente dos eventos analisados, acumulando informações espaço-temporais e extraindo atributos relevantes.

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

Em seguida, a saída do codificador é passada ao bloco residual, que reforça os atributos extraídos e preserva detalhes locais críticos. Esse bloco aumenta a robustez do modelo a ruídos e melhora a precisão dos cálculos posteriores, preparando uma representação adequada para a decodificação.

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

Por fim, a representação refinada pelo bloco residual é passada ao decodificador, que utiliza os estados latentes do codificador e as saídas do bloco residual para gerar a previsão do fluxo de eventos.

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

Essa abordagem em etapas garante um processamento confiável de cada evento, considerando tanto os micromovimentos locais quanto as tendências globais e formando uma representação coerente e informativa da dinâmica do sistema. No contexto financeiro, isso equivale ao processamento sequencial das negociações e das variações de preço para gerar uma previsão detalhada e adaptativa do movimento do mercado.

Assim, CNeuronSTEFlowNet constitui um objeto totalmente integrado, capaz de analisar sequencialmente o fluxo de eventos, identificar micromovimentos críticos, considerar padrões globais e ajustar dinamicamente seus parâmetros para gerar previsões robustas. Nos mercados financeiros, isso significa acompanhar simultaneamente oscilações instantâneas dos preços e tendências de longo prazo, formando um modelo de análise adaptativo e de alta precisão para apoiar a tomada de decisões estratégicas e a gestão de riscos.

O código completo da classe e de todos os seus métodos está disponível no anexo do artigo.

A criação do objeto de alto nível abre novas possibilidades. Todos os módulos implementados anteriormente passam a integrar uma única estrutura e podem ser incorporados aos modelos como uma única camada lógica. Essa abordagem simplifica significativamente a construção de arquiteturas complexas de redes neurais e torna sua integração às estratégias de trading muito mais simples e transparente.

Hoje não entraremos em uma descrição detalhada da arquitetura de cada modelo treinável, pois esse não é o foco principal. Todos os detalhes de implementação e da estrutura podem ser examinados no anexo do artigo.



Testes

Chegamos à etapa final do trabalho com o framework STE-FlowNet: avaliar a eficácia das abordagens implementadas. Antes disso, porém, precisamos treinar nosso trader.

A primeira etapa é o treinamento offline com dados históricos do par de moedas EURUSD no timeframe H1, referentes ao período de janeiro de 2024 a junho de 2025. Esse período serviu como uma base completa para o treinamento. O modelo aprendeu a reconhecer padrões históricos, analisar a dinâmica dos preços e os volumes negociados e identificar relações entre os principais atributos. O STE-FlowNet desenvolveu gradualmente sua própria intuição de trader e a combinou com uma avaliação estratégica precisa, adquirindo assim a capacidade de prever os movimentos do mercado e avaliar o risco de cada operação potencial.

A etapa seguinte é o ajuste fino online no Testador de Estratégias do MetaTrader 5, que leva o treinamento para condições mais próximas das reais. Barra a barra, o modelo processa os fluxos de dados. Nessa fase, o STE-FlowNet aprende a acompanhar a dinâmica do mercado, a manter a robustez diante de oscilações provocadas por ruído, a ajustar suas ações em períodos de baixa liquidez e a reagir imediatamente a picos bruscos de preço. A estrutura básica formada com os dados históricos permanece inalterada, enquanto o modelo aprende a responder de maneira flexível às condições atuais do mercado, reduzindo o risco de sobreajuste e aumentando a precisão das previsões mesmo em condições de mercado instáveis.

A avaliação final foi realizada com dados totalmente novos, referentes ao período de julho a setembro de 2025. Todos os parâmetros obtidos nas etapas anteriores foram carregados sem alterações, o que garantiu uma avaliação imparcial da capacidade do STE-FlowNet de generalizar.


Os resultados dos testes com dados históricos são convincentes e demonstram a estabilidade da estratégia. Comecemos pelos indicadores financeiros. Com um depósito inicial de $100, o lucro líquido foi de $54,02, o que corresponde a um crescimento total do capital superior a 50% durante o período testado. O lucro bruto atingiu $119,57, enquanto a perda bruta foi de $65,55, resultando em um Profit Factor de 1,82. Esse indicador mostra que o lucro bruto da estratégia é aproximadamente 1,82 vez maior que a perda bruta, o que representa um resultado satisfatório para algoritmos de trading. O Recovery Factor de 1,31 indica a capacidade da estratégia de se recuperar após períodos de drawdown.

Os drawdowns do capital permaneceram em níveis aceitáveis. O drawdown máximo do equity foi de 28,57%, um valor razoável para uma abordagem algorítmica ativa no mercado de câmbio. Já o drawdown absoluto do saldo foi de 25,13%, indicando que a estratégia consegue administrar as posições sem provocar quedas excessivamente acentuadas do capital.

Os resultados das operações também mostram uma estratégia equilibrada. Foram realizadas 50 operações, das quais 62% foram lucrativas. A proporção de operações vendidas lucrativas foi de 63,16%, enquanto a de posições compradas foi de 61,29%, indicando que o algoritmo funciona adequadamente tanto em tendências de baixa quanto de alta. O lucro médio por operação foi de $3,86, enquanto a perda média por operação foi de $3,45, mostrando uma relação equilibrada entre ganhos e perdas. O maior lucro em uma única operação foi de $15,03, e a maior perda, de $9,96.

De modo geral, os testes confirmam que o STE-FlowNet é treinado com sucesso em dados históricos, reage adequadamente aos sinais do mercado e apresenta crescimento estável do capital. A estratégia consegue combinar precisão das previsões com controle de risco. No entanto, antes de ser utilizada em operação real, é necessário submetê-la a testes mais rigorosos e abrangentes.



Conclusão

Neste artigo, analisamos em detalhes a arquitetura e os principais componentes do framework STE-FlowNet, demonstrando sua flexibilidade e capacidade de adaptação a diferentes tarefas de análise de dados. Os testes realizados confirmaram a alta eficiência das soluções implementadas, demonstrando estabilidade e precisão mesmo em séries temporais complexas. Demos atenção especial à capacidade do framework de integrar processamento multithread e estruturas recorrentes, acelerando os cálculos e permitindo uma modelagem mais profunda das dependências presentes nos dados. A abordagem apresentada abre novas possibilidades de aplicação prática no trading e em áreas relacionadas, permitindo gerar previsões estrategicamente relevantes com baixo consumo de recursos.


Referências


Programas utilizados no artigo

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

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

Arquivos anexados |
MQL5.zip (3251.54 KB)
Últimos Comentários | Ir para discussão (1)
Chao Tang
Chao Tang | 3 nov. 2025 em 13:16
Usando o mesmo código, o STFS foi executado. Os dados do Critic estão claramente incorretos
Redes neurais em trading: modelos de refinamento iterativo de previsões (RAFT) Redes neurais em trading: modelos de refinamento iterativo de previsões (RAFT)
O framework RAFT propõe uma abordagem fundamentalmente diferente para prever a dinâmica do mercado: em vez de produzir uma previsão única, ele refina iterativamente o estado em tempo real. Ao mesmo tempo, leva em conta mudanças locais e globais, mantendo alta precisão mesmo diante de estruturas de preços complexas.
Redes neurais em trading: Modelos de refinamento iterativo de previsões (Componentes principais) Redes neurais em trading: Modelos de refinamento iterativo de previsões (Componentes principais)
Neste artigo, exploramos a mecânica interna do framework RAFT, uma das abordagens mais precisas e elegantes para a análise de processos dinâmicos. Passo a passo, adaptamos sua ideia de refinamento iterativo às séries temporais financeiras, criando uma base sólida para o desenvolvimento do futuro modelo. O leitor fará uma imersão envolvente na arquitetura, em que cada componente tem seu próprio significado e função.
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.
Modelos matemáticos em estratégias de grade Modelos matemáticos em estratégias de grade
Neste artigo, veremos como aplicar a matemática às estratégias de grade. Analisaremos os princípios básicos de funcionamento da estratégia, suas vantagens e desvantagens. Você aprenderá a construir uma grade de negociação, definir parâmetros ideais e gerenciar os riscos de forma eficiente.