Redes neurais em trading: uma visão unificada do espaço e do tempo (final)
Introdução
Os mercados financeiros podem ser comparados a um organismo vivo, no qual cada movimento de preço lembra uma pulsação, e cada notícia ou decisão macroeconômica funciona como uma batida do coração que desencadeia ondas de mudança por todo o corpo do sistema. Eles respiram, oscilam, crescem e caem em um ritmo complexo, que reflete o comportamento de milhões de participantes. Nesse ambiente, o analista e o trader enfrentam o desafio de não apenas captar impulsos instantâneos, mas também prever tendências mais longas, muitas vezes ocultas sob o ruído superficial. É aqui que a análise e o processamento de dados espaço-temporais ganham protagonismo. Afinal, qualquer situação de mercado se desenvolve simultaneamente em duas dimensões: no eixo do tempo e no eixo do preço, que reflete o espaço das decisões de trading.
O framework Extralonger tem como objetivo superar as barreiras dos modelos tradicionais e elevar a previsão a um novo patamar. Sua principal vantagem é a capacidade de operar com segurança em horizontes extremamente longos. Para comparação, a ampla maioria dos algoritmos existentes se limita a intervalos de minutos ou de horas. Nesses intervalos, é possível acompanhar impulsos de curto prazo ou tendências locais, mas não construir uma visão sistêmica que permita olhar além do horizonte e enxergar a formação de movimentos de grande porte. O Extralonger, por sua vez, abre caminho para a previsão de longo prazo, preservando precisão e estabilidade mesmo onde outros métodos perdem referência.
Essa qualidade é especialmente importante no trading. Para o trader que opera em timeframes intradiários, cada minuto pode ser decisivo. Mas ainda mais valioso é compreender como o mercado se comportará amanhã ou dentro de alguns dias. Para o investidor, conhecer as tendências futuras em um horizonte de uma semana ou mais se torna uma ferramenta para gerir riscos e construir estratégias. O Extralonger combina essas duas abordagens, permitindo observar o mercado de perto e de longe ao mesmo tempo, como um viajante que usa uma lupa para estudar os detalhes de um mapa e uma luneta para avaliar os contornos distantes do terreno.
A segunda grande vantagem do framework é sua alta eficiência computacional. O problema dos métodos clássicos está na análise separada das dimensões temporal e espacial. Ao analisar a dinâmica das séries temporais, o algoritmo precisa repetir os cálculos para cada ponto de mercado ou instrumento; ao processar as relações espaciais, ele repete operações na dimensão temporal. Essa repetição de operações aumenta rapidamente o consumo de recursos. O modelo passa a exigir cada vez mais memória e tempo, o que limita a extensão do horizonte de previsão. Nas condições dos mercados financeiros, em que a velocidade de reação às mudanças de preço é crítica, essas limitações se transformam em um obstáculo sério.
O Extralonger resolve esse problema de uma forma fundamentalmente nova. Sua base é o conceito de Unified Spatial-Temporal Representation, ou Representação Espaço-Temporal Unificada. Essa ideia é inspirada na teoria da relatividade de Einstein. Assim como, na física, espaço e tempo formam um único contínuo, nos dados financeiros um momento no tempo não pode ser analisado isoladamente do nível de preço, e uma estrutura local de mercado não pode ser interpretada sem levar em conta o contexto temporal. Assim, cada elemento dos dados contém simultaneamente informações temporais e espaciais. Isso elimina a necessidade de cálculos repetidos e reduz a complexidade do modelo em uma ordem de grandeza.
Como consequência, o Extralonger oferece vantagens expressivas:
- redução do consumo de memória,
- aceleração do treinamento,
- aumento da velocidade de previsão.
Em termos de aplicação prática, isso significa que o modelo pode ser treinado e funcionar até mesmo em ambientes com recursos computacionais limitados.
No nível arquitetural, o framework Extralonger é construído em torno de três rotas paralelas de processamento de dados: temporal, espacial e mista. Cada uma delas desempenha um papel específico. A rota temporal analisa a sequência de eventos de mercado e busca padrões na dinâmica dos preços. A rota espacial examina a rede de interdependências entre instrumentos e ativos, identificando correlações e estruturas ocultas. A rota mista combina as duas abordagens, formando uma visão integrada.
No Extralonger, destaca-se o módulo Global-Local Spatial Transformer, que pode ser comparado a uma lente dupla do analista. De um lado, a atenção global permite levar em conta relações entre elementos distantes do mercado. De outro, a atenção local se concentra nas relações mais próximas, como correlações de curto prazo entre moedas correlatas dentro de uma mesma sessão de trading. Essa combinação permite que o modelo trabalhe com igual eficiência tanto com grandes tendências quanto com impulsos locais.
Outra qualidade única do Extralonger é seu campo receptivo completo. Isso significa que o modelo é capaz de considerar simultaneamente todo o histórico disponível e relacioná-lo a qualquer evento atual. Ao contrário dos algoritmos limitados por uma janela deslizante ou por um passo temporal fixo, o Extralonger enxerga o mercado em sua totalidade. Ele pode comparar o comportamento do preço hoje com eventos de uma semana atrás ou detectar uma dependência de longo prazo que só se manifesta em intervalos temporais amplos. Para os mercados financeiros, isso é especialmente valioso, pois muitos movimentos não são causados por eventos isolados, mas pelo acúmulo de fatores que gradualmente se combinam e se materializam em tendências de grande escala.
A visualização do framework Extralonger apresentada pelo autor é mostrada abaixo.

Nosso projeto avança de forma gradual, do simples para o complexo, dos elementos isolados para uma arquitetura completa. No início, nos concentramos na base conceitual do Extralonger e demos os primeiros passos práticos para sua implementação em MQL5. Nessa etapa, implementamos os módulos de codificação espacial e temporal, que permitiram associar os dados à estrutura de mercado e formar a base para a integração posterior.
Depois, passamos ao arranjo arquitetural do framework. Se antes lidávamos mais com os tijolos, agora começamos a erguer paredes e lajes. Examinamos em detalhes a implementação do módulo Global-Local Spatial Transformer. Nessa etapa, surgiu a possibilidade de conectar os elementos individuais em um fluxo computacional integrado. Em outras palavras, começamos a formar aquela arquitetura de observação que, mais adiante, permitirá abranger o mercado em sua multidimensionalidade.
Essas duas etapas podem ser comparadas à preparação do palco para um grande espetáculo. Primeiro, posicionamos os cenários e definimos onde ficariam os elementos-chave; depois, trouxemos ao palco os primeiros atores: os módulos de processamento de dados. Agora, abre-se diante de nós a possibilidade de mostrar ao espectador todo o arranjo da montagem. Passamos ao próprio algoritmo, ao coração do Extralonger. É justamente aqui, na interseção da atenção global-local, da representação unificada e das três rotas paralelas, que a singularidade da abordagem se revela.
Objeto de nível superior
No topo de toda a hierarquia da nossa implementação do framework Extralonger está o objeto CNeuronExtralonger, que pode ser visto como o coração da arquitetura, integrando os principais módulos em um único ciclo computacional. Se as classes e os blocos analisados anteriormente eram responsáveis por componentes separados da implementação, aqui lidamos com o nó central, que conecta todos esses elementos em uma estrutura única.
class CNeuronExtralonger : public CNeuronMHAttentionPooling { protected: CLayer cProjectionT; CLayer cTimeModule; CLayer cSpatialModule; CLayer cMixModule; CNeuronBaseOCL cConcatResults; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override {return false;} virtual bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override {return false;} virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None ) override; public: CNeuronExtralonger(void) {}; ~CNeuronExtralonger(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint time_steps_in, uint time_steps_out, uint variables, uint dimension, uint emb_dimension, uint period1, uint frame1, uint period2, uint frame2, uint layers, uint heads, uint dimension_k, uint m_units, float sparse, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) const { return defNeuronExtralonger; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual void SetOpenCL(COpenCLMy *obj) override; virtual void SetActivationFunction(ENUM_ACTIVATION value) override { }; };
Em sua estrutura, CNeuronExtralonger herda da classe CNeuronMHAttentionPooling, o que já indica sua orientação funcional. A base do processamento dos dados continua sendo o mecanismo de atenção multi-head com pooling, sobre o qual são adicionadas novas camadas que implementam as ideias exclusivas do Extralonger.
E aqui chegamos a uma diferença importante entre a nossa implementação e a solução proposta pelos autores. Na versão original do Extralonger, os coeficientes de ponderação das três rotas do fluxo de dados (temporal, espacial e mista) são definidos de forma rígida e permanecem fixos. Essa abordagem é conveniente por sua simplicidade, mas, nas condições instáveis dos mercados financeiros, inevitavelmente leva à perda de flexibilidade. Em algumas situações, os padrões temporais de curto prazo desempenham o papel principal; em outras, são as correlações espaciais globais que ganham destaque; e, às vezes, o fator decisivo é a combinação entre elas. Coeficientes fixos não conseguem levar em conta dinamicamente essa mudança de ponderação, o que reduz a precisão das previsões.
Por isso, na nossa implementação, fomos além e incorporamos um mecanismo adaptativo de Attention Pooling, adaptado do framework R-MAT. A lógica central desse mecanismo é que os coeficientes de ponderação das rotas não são definidos antecipadamente, mas calculados durante a operação do modelo. Cada novo sinal dos dados de entrada recebe sua própria distribuição de ponderação entre os módulos temporal, espacial e misto. Com isso, o sistema se torna sensível ao contexto de mercado. Em uma fase de consolidação tranquila, o papel da análise temporal se intensifica; diante de choques externos, as relações espaciais passam a ter maior importância; e, nos momentos de alta volatilidade, a rota mista ganha protagonismo.
Essa solução transforma o CNeuronExtralonger em um instrumento muito mais dinâmico. Ao contrário do esquema estático original, no qual a distribuição de atenção é sempre a mesma, o Attention Pooling adaptativo torna o sistema flexível e autoajustável. Ele se comporta como um trader experiente, capaz de mudar o foco conforme a situação.
Dentro do objeto, concentram-se quatro componentes principais, representados por arrays dinâmicos. Neles são armazenadas sequências de camadas neurais dos fluxos internos de dados. À primeira vista, a criação de quatro componentes internos pode parecer estranha. Afinal, a arquitetura do Extralonger menciona apenas três rotas: temporal, espacial e mista. Aqui, é importante observar uma particularidade. As rotas de análise temporal e mista usam o mesmo bloco de preparação dos dados. Para evitar duplicação e aumentar a controlabilidade, decidimos separar o bloco de preparação dos dados em um componente independente, o cProjectionT. Em essência, trata-se de um gateway comum, que viabiliza a transformação consistente das sequências de entrada em embeddings adequados para o processamento posterior.
Ao mesmo tempo, combinamos a rota de análise espacial com o respectivo módulo de preparação dos dados em um único bloco, o cSpatialModule. Essa decisão decorre das particularidades das dependências espaciais. Aqui, o pré-processamento dos dados e o próprio modelo estão tão estreitamente conectados que faz sentido tratá-los como uma única estrutura interna.
Assim, chegamos a quatro componentes:
- cProjectionT: bloco de preparação dos dados para as rotas temporal e mista,
- cTimeModule: modelo de análise temporal,
- cSpatialModule: modelo combinado de preparação dos dados e análise espacial,
- cMixModule: rota mista, que integra atributos temporais e espaciais.
Esse arranjo permite preservar a lógica da arquitetura original do Extralonger e, ao mesmo tempo, tornar a implementação mais modular e de manutenção mais simples.
Do contorno geral da arquitetura, passamos gradualmente à sua estrutura interna, e é justamente aqui que se revela a verdadeira complexidade do objeto. No nível superior, vemos apenas quatro arrays dinâmicos: o bloco preparatório da projeção temporal, o módulo temporal, o módulo espacial e a rota mista. Mas, por trás dessa estrutura aparentemente simples, há todo um ecossistema de componentes menores: camadas convolucionais e de normalização, componentes de transposição, embeddings treináveis e blocos de atenção. Eles não existem isoladamente; cada um desempenha um papel rigorosamente definido, e sua interação forma o fluxo vivo de informação que atravessa o CNeuronExtralonger.
Todo esse mundo interno é construído no método Init. Pode-se dizer que é justamente aqui que são estabelecidas a estrutura e a lógica de comportamento do modelo. A inicialização começa com a chamada do método homônimo da classe pai, responsável pela que fornece a estrutura geral para o Attention Pooling multi-head.
bool CNeuronExtralonger::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint time_steps_in, uint time_steps_out, uint variables, uint dimension, uint emb_dimension, uint period1, uint frame1, uint period2, uint frame2, uint layers, uint heads, uint dimension_k, uint m_units, float sparse, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, variables, time_steps_out, 3, optimization_type, batch)) return false;
Em seguida, começa a parte mais interessante. Passo a passo, montamos um organismo complexo, no qual cada camada acrescenta seu próprio tom ao quadro geral. Mas, antes disso, uma pequena preparação: vamos declarar algumas variáveis locais para armazenar temporariamente ponteiros para objetos.
CNeuronBatchNormOCL *norm = NULL; CNeuronConvOCL *conv = NULL; CNeuronTransposeOCL *transp = NULL; CNeuronLearnabledPE *lnoise = NULL; CNeuronSpatialEmbedding *semb = NULL; CNeuronTempEmbedding *temb = NULL; CNeuronMLMHAttentionOCL *att = NULL; CNeuronGlobalLocalAttention *glatt = NULL;
O primeiro a entrar em cena é o bloco de preparação dos dados de entrada para as rotas de análise temporal e mista. Ele funciona como um afinador, que prepara os instrumentos antes do concerto. O objeto de codificação posicional adiciona ao fluxo de dados de entrada um viés treinável, que os autores do framework chamaram de ruído treinável. Aqui é importante destacar: não se trata de uma perturbação aleatória, mas de uma tentativa deliberada de incorporar ao modelo a capacidade de perceber o ritmo temporal do mercado. Os dados de preços e volumes nunca são puros; sempre oscilam sob o efeito de microflutuações. A incorporação do ruído treinável transforma essa característica em uma ferramenta: o modelo passa a distinguir melhor os padrões do caos e a construir sua percepção do tempo com base nos ritmos reais do mercado.
//--- Time projection cProjectionT.Clear(); cProjectionT.SetOpenCL(OpenCL); int index = 0; lnoise = new CNeuronLearnabledPE(); if(!lnoise || !lnoise.Init(0, index, OpenCL, time_steps_in * variables, optimization, iBatch) || !cProjectionT.Add(lnoise)) { DeleteObj(lnoise); return false; }
Logo depois vem a camada convolucional, que desempenha um papel especial. Ela transforma cada passo temporal em um embedding contendo informações sobre todas as features analisadas naquele exato momento. Na prática, a saída forma uma representação compacta e informativa, que se torna uma espécie de retrato do estado do mercado em cada passo. Preço, volumes, indicadores e outras variáveis se combinam em um único vetor. Assim, o modelo passa a operar não com números dispersos, mas com representações completas, que refletem todo o espectro das observações ao longo do tempo.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, variables, variables, dimension, time_steps_in, 1, optimization, iBatch) || !cProjectionT.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None);
O passo seguinte é o embedding temporal, no qual as séries recebem uma dimensão adicional, capaz de refletir ciclos e períodos. A arquitetura incorpora aqui dois tipos de regularidade temporal: uma de curto prazo e outra mais prolongada, ligadas aos ritmos diários e semanais do mercado. Isso lembra a forma como um músico sente o compasso e a métrica. Independentemente da melodia, sempre existe uma base rítmica, sem a qual é impossível construir a composição. Nos dados financeiros, isso se expressa na alternância das sessões de trading, nas oscilações sazonais ou nas fases regulares de atividade dos grandes participantes. O embedding temporal insere essa rítmica nos dados, tornando o modelo sensível ao que se repete dia após dia ou semana após semana.
index++; temb = new CNeuronTempEmbedding(); uint half_emb = (emb_dimension + 1) / 2; if(!temb || !temb.Init(0, index, OpenCL, time_steps_in, dimension, half_emb, period1, frame1, dimension - half_emb, period2, frame2, optimization, iBatch) || !cProjectionT.Add(temb)) { DeleteObj(temb); return false; }
A normalização encerra o bloco preparatório. Ela pode ser comparada ao nivelamento da tela antes da aplicação das tintas. Se a superfície estiver irregular, o desenho sairá distorcido. A normalização reduz desvios aleatórios, leva os dados a uma distribuição estável e, com isso, estabelece a base para o processamento posterior pelos módulos mais complexos.
index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, temb.Neurons(), iBatch, optimization) || !cProjectionT.Add(norm)) { DeleteObj(norm); return false; }
Quando os dados deixam o bloco preparatório, eles seguem para o módulo de análise temporal. Aqui entra em jogo a atenção multi-head, que abre várias perspectivas paralelas ao mesmo tempo. Ela busca relações entre momentos separados por dezenas de passos temporais e aprende a perceber padrões inacessíveis a uma visão linear simples.
//--- Time Module cTimeModule.Clear(); cTimeModule.SetOpenCL(OpenCL); index++; att = new CNeuronMLMHAttentionOCL(); if(!att || !att.Init(0, index, OpenCL, dimension + emb_dimension, dimension_k, heads, time_steps_in, layers, optimization, iBatch) || !cTimeModule.Add(att)) { DeleteObj(att); return false; }
Em seguida, entra em operação o bloco de projeção dos dados para o horizonte de previsão definido. Ele pode ser comparado a uma ponte que conecta o histórico ao futuro. O bloco inclui várias camadas convolucionais, que transformam os embeddings em uma sequência temporal multimodal e, ao mesmo tempo, alteram o comprimento da sequência, estendendo-a até o horizonte de previsão necessário. Essas camadas atuam como lentes com diferentes profundidades de foco: algumas destacam movimentos locais, enquanto outras constroem uma perspectiva ampla.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables, time_steps_in, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(TANH); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) || !cTimeModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, variables, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cTimeModule.Add(norm)) { DeleteObj(norm); return false; } index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) || !cTimeModule.Add(transp)) { DeleteObj(transp); return false; }
Na saída, obtemos não apenas um conjunto de embeddings, mas uma trajetória de previsão adaptada ao horizonte de previsão.
Em paralelo, o módulo misto entra em operação, e é justamente aqui que os dados percorrem uma jornada especialmente densa. Seu papel é único: combinar features temporais e espaciais, transformando-as em um único fluxo de dados. O primeiro a entrar em operação é o bloco clássico de atenção multi-head, que analisa a sequência temporal sem a interferência da dimensão espacial. Ele busca padrões, correspondências e sinais recorrentes ocultos na estrutura da sequência, criando assim uma base sólida para a análise posterior.
//--- Mix Module cMixModule.Clear(); cMixModule.SetOpenCL(OpenCL); uint att_layers = (layers + 1) / 2; index++; att = new CNeuronMLMHAttentionOCL(); if(!att || !att.Init(0, index, OpenCL, dimension + emb_dimension, dimension_k, heads, time_steps_in, att_layers, optimization, iBatch) || !cMixModule.Add(att)) { DeleteObj(att); return false; }
Em seguida, os dados passam pela transposição, uma espécie de mudança de perspectiva. Se, no início, observávamos as séries a partir dos passos temporais, agora a atenção se desloca para as features, e é nesse novo sistema de coordenadas que o módulo de atenção global-local entra em ação. Aqui, duas forças opostas são equilibradas: a visão global, que abrange todo o mercado, e o foco local, que permite perceber relações quase imperceptíveis entre instrumentos específicos ou picos momentâneos. Essa combinação é especialmente importante no contexto financeiro: o mercado pode se mover durante semanas dentro de uma grande tendência, enquanto ativos específicos mudam repentinamente sua dinâmica por causa de notícias ou eventos locais.
index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; } for(uint i = (att_layers == layers ? 0 : att_layers - 1); i < layers; i++) { index++; glatt = new CNeuronGlobalLocalAttention(); if(!glatt || !glatt.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_in, dimension_k, heads, m_units, sparse, optimization, iBatch) || !cMixModule.Add(glatt)) { DeleteObj(glatt); return false; } }
O acorde final é o bloco de previsão, responsável por converter todas as informações acumulada em um formato adequado para o planejamento no horizonte definido. Várias camadas convolucionais nesse bloco transformam os embeddings em uma sequência de valores de previsão, alinhada ao comprimento da janela necessária. É justamente aqui que as representações abstratas se transformam em um resultado concreto: uma previsão que reflete tanto o pano de fundo global quanto as particularidades locais.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, dimension + emb_dimension, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, dimension + emb_dimension, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(TANH); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_out, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables, time_steps_out, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cMixModule.Add(norm)) { DeleteObj(norm); return false; }
Como resultado, o módulo misto funciona como um laboratório de síntese: primeiro são capturados os padrões temporais; depois, eles são combinados com as relações espaciais; por fim, o sinal resultante passa pelo bloco de previsão e se torna a saída final, na qual vários níveis de análise se entrelaçam ao mesmo tempo.
A etapa final da jornada interna dos dados é o módulo espacial. Ele é responsável por fazer com que o mercado deixe de ser percebido como um conjunto de séries independentes. No início, aqui são formados os embeddings espaciais, que codificam relações de longo prazo entre instrumentos, setores e índices.
//--- Spatial Module cSpatialModule.Clear(); cSpatialModule.SetOpenCL(OpenCL); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) || !cSpatialModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, dimension, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; semb = new CNeuronSpatialEmbedding(); if(!semb || !semb.Init(0, index, OpenCL, variables, dimension, emb_dimension, optimization, iBatch) || !cSpatialModule.Add(semb)) { DeleteObj(semb); return false; }
Com base neles, os blocos de atenção global-local constroem, alternadamente, uma visão ampla das correlações e relações detalhadas entre ativos específicos. Pode-se dizer que esse módulo transforma o mercado financeiro em uma rede viva, na qual cada elo se conecta aos demais, e é justamente essa rede que se torna o objeto da análise.
index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, semb.Neurons(), iBatch, optimization) || !cSpatialModule.Add(norm)) { DeleteObj(norm); return false; } for(uint i = 0; i < layers; i++) { index++; glatt = new CNeuronGlobalLocalAttention(); if(!glatt || !glatt.Init(0, index, OpenCL, dimension + emb_dimension, variables, dimension_k, heads, m_units, sparse, optimization, iBatch) || !cSpatialModule.Add(glatt)) { DeleteObj(glatt); return false; } }
De modo análogo às duas rotas apresentadas anteriormente, os resultados da análise são enviados ao bloco de previsão, que organiza cuidadosamente toda a informação em um formato adequado para o planejamento no período definido.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, time_steps_out, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cMixModule.Add(norm)) { DeleteObj(norm); return false; } index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; }
As três rotas, temporal, mista e espacial, convergem no ponto de concatenação. Aqui, seus resultados são combinados, e é justamente nessa etapa que entra em ação o nosso mecanismo principal: o Attention Pooling adaptativo. Ao contrário da versão dos autores, com coeficientes fixos, damos às rotas a possibilidade de negociar entre si qual delas deve receber maior coeficiente de ponderação em cada momento. Como resultado, o sinal final não é uma estrutura estática, mas um instrumento flexível, que respira no ritmo do mercado.
index++; if(!cConcatResults.Init(0, index, OpenCL, 3 * variables * time_steps_out, optimization, iBatch)) return false; cConcatResults.SetActivationFunction(None); //--- return true; }
Assim, o método Init transforma uma sequência árida de chamadas em uma arquitetura viva, na qual cada elo cumpre uma função técnica bem definida, e sua interação dá origem a um instrumento único de análise. É aqui que nasce o organismo chamado CNeuronExtralonger, e cada detalhe dele contribui para o resultado comum.
Após a inicialização do objeto, passamos à construção do algoritmo de propagação para frente, implementado no método feedForward. A assinatura do método, por si só, já diz muito. Como parâmetros, ele recebe um ponteiro para o objeto de dados de entrada NeuronOCL e um buffer adicional, SecondInput, que contém as marcas de tempo da sequência analisada.
bool CNeuronExtralonger::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { CNeuronBaseOCL *prev = NeuronOCL; CNeuronBaseOCL *current = NULL;
A primeira operação interna é a declaração de duas variáveis locais. Uma delas recebe o ponteiro para o objeto de dados de entrada. Esse é uma técnica padrão para passagem encadeada: prev desempenha o papel do último resultado disponível, que é transmitido à próxima camada. Já current funciona como variável temporária e, a cada iteração, recebe o ponteiro para a camada em processamento. Essa abordagem permite construir linearmente a cadeia de camadas, evitando cópias desnecessárias e mantendo o controle sobre a sequência de cálculos.
Em seguida, é iniciado o laço que percorre o contêiner de preparação dos dados de entrada cProjectionT. Para cada projeção, obtemos o ponteiro para o objeto atual e verificamos imediatamente sua validade. Depois disso, chamamos o método de propagação para frente do objeto. Se ocorrer algum erro em qualquer etapa, o método encerra imediatamente a execução retornando false. Esse é o padrão fail-fast: é melhor parar no primeiro problema do que continuar trabalhando com dados corrompidos.
//--- Time projection for(int i = 0; i < cProjectionT.Total(); i++) { current = cProjectionT[i]; if(!current || !current.FeedForward(prev, SecondInput)) return false; prev = current; }
É importante observar que, nesse bloco, o método FeedForward dos objetos internos recebe dois argumentos: o resultado anterior e as marcas de tempo SecondInput. Afinal, para gerar os embeddings temporais, será necessário um contexto adicional.
Se tudo ocorrer corretamente, a variável prev é atualizada com o ponteiro para current, para que a próxima iteração use a saída da camada recém-processada.
Quando as projeções são concluídas, o controle passa para o módulo temporal. O laço que percorre o contêiner cTimeModule é quase igual, mas aqui o método FeedForward dos objetos internos é chamado com apenas um argumento. Isso indica que o módulo temporal processa a sequência resultante sem um buffer adicional.
//--- Time Module for(int i = 0; i < cTimeModule.Total(); i++) { current = cTimeModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
Se alguma camada do módulo temporal estiver ausente ou se seu FeedForward sinalizar um erro, interrompemos novamente o processamento e retornamos false.
Em seguida, o foco se desloca para o módulo misto. Mas aqui é preciso lembrar que esse módulo também usa os resultados do pré-processamento dos dados; por isso, antes do início do laço, atribuímos a prev o ponteiro para a última camada do contêiner cProjectionT. As ramificações temporal e mista seguem caminhos paralelos, processando as mesmas features de entrada de maneiras diferentes.
//--- Mix Module prev = cProjectionT[-1]; for(int i = 0; i < cMixModule.Total(); i++) { current = cMixModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
Depois, para cada elemento de cMixModule, executamos a mesma verificação de validade do ponteiro e chamamos o método de propagação para frente, atualizando em seguida prev com o ponteiro correspondente. Qualquer erro novamente encerra a execução imediatamente e retorna false.
O módulo espacial é estruturado separadamente e recebe NeuronOCL como dados de entrada. Isso significa que a ramificação Spatial analisa o sinal de entrada em paralelo às outras ramificações, sem herdar as transformações intermediárias. Em seguida, executamos sequencialmente as camadas de cSpatialModule, mantendo a lógica fail-fast habitual e atualizando prev.
//--- Spatial Module prev = NeuronOCL; for(int i = 0; i < cSpatialModule.Total(); i++) { current = cSpatialModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
Como resultado, obtemos três ramificações concluídas: temporal, mista e espacial. A etapa seguinte é a concatenação. Concatenamos as saídas das três ramificações em um único buffer, formando a janela e definindo a dimensionalidade final. Se a concatenação falhar, o método retorna false.
//--- Concatenate if(!Concat(cTimeModule[-1].getOutput(), cMixModule[-1].getOutput(), cSpatialModule[-1].getOutput(), cConcatResults.getOutput(), iWindow, iWindow, iWindow, iUnits)) return false; //--- return CNeuronMHAttentionPooling::feedForward(cConcatResults.AsObject()); }
Após a concatenação bem-sucedida, resta passar o resultado concatenado para o módulo Attention Pooling.
Do ponto de vista arquitetural, o sistema é construído como um conjunto de caminhos paralelos, cada um deles analisando os dados de entrada com seu próprio objetivo: as projeções preparam as representações temporais, o módulo temporal analisa a sequência, o módulo misto combina as projeções, e Spatial se concentra nas features espaciais. Essa estrutura lembra uma orquestra: cada instrumento tem sua própria partitura, e o maestro é o Attention Pooling, que integra tudo em um conjunto único.
O método de distribuição dos gradientes de erro repete em grande parte a estrutura de propagação para frente, mas com a diferença de que aqui os dados seguem no sentido inverso, e é preciso somar cuidadosamente os fluxos de erro.
bool CNeuronExtralonger::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) { if(!NeuronOCL) return false; //--- if(!CNeuronMHAttentionPooling::calcInputGradients(cConcatResults.AsObject())) return false;
Primeiro, verificamos a validade do ponteiro para o objeto de dados de entrada. Se ele estiver vazio, o método retorna false imediatamente. Em seguida, chamamos o método homônimo da classe pai. Essa etapa permite propagar o gradiente de erro recebido da camada neural seguinte até o buffer concatenado. Depois disso, os valores obtidos são distribuídos de volta para as três ramificações: temporal, mista e espacial. Aqui é importante que o erro acumulado no buffer comum seja dividido estritamente de acordo com a arquitetura do modelo. Cada ramificação recebeu seu gradiente correto. Se alguma das operações falhar, o processamento é interrompido.
//--- DeConcatenate if(!cTimeModule[-1] || !cMixModule[-1] || !cSpatialModule[-1] || !DeConcat(cTimeModule[-1].getGradient(), cMixModule[-1].getGradient(), cSpatialModule[-1].getGradient(), cConcatResults.getOutput(), iWindow, iWindow, iWindow, iUnits)) return false;
Em seguida, os gradientes começam a se propagar pelos módulos em ordem inversa. Primeiro, pela rota espacial. O laço percorre os elementos do fim para o início; cada elemento atual obtém a referência para a camada posterior e chama o método de distribuição do gradiente de erro do objeto interno. O erro é propagado cuidadosamente para trás, camada por camada.
CNeuronBaseOCL *next = NULL; CNeuronBaseOCL *current = NULL; //--- Spatial Module for(int i = cSpatialModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cSpatialModule[i - 1] : NeuronOCL); next = cSpatialModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; }
A mesma lógica se repete no módulo misto, mas, aqui, o último elemento das projeções temporais é usado como ponto de apoio. Isso reflete a estrutura paralela do modelo: diferentes ramificações propagam os erros por seus próprios canais, mas sempre de forma sincronizada.
//--- Mix Module for(int i = cMixModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cMixModule[i - 1] : cProjectionT[-1]); next = cMixModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; }
O módulo temporal merece atenção especial. Aqui, vale observar que ele transmite o gradiente de erro para a última camada do módulo de preparação dos dados. Foi justamente para ela que acabamos de transmitir os valores vindos do módulo misto. Por isso, antes de executar a propagação reversa do módulo de análise temporal, salvamos na variável local temp o ponteiro para o buffer atual de gradientes de erro, que já contém os valores acumulados. Em seguida, atribuímos ao objeto o ponteiro para um buffer livre.
//--- Time Module CBufferFloat *temp = current.getGradient(); if(!current.SetGradient(current.getPrevOutput(), false)) return false; for(int i = cTimeModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cTimeModule[i - 1] : cProjectionT[-1]); next = cTimeModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; } if(!SumAndNormilize(temp, current.getGradient(), temp, 1, false, 0, 0, 0, 1) || !current.SetGradient(temp, false)) return false;
Depois, no laço, novamente do fim para o início, os gradientes são propagados ao longo do módulo. Após a conclusão da propagação reversa pela ramificação temporal, somamos os valores obtidos pelos dois fluxos de dados e restauramos os ponteiros originais dos buffers de dados.
A última etapa é o processamento das projeções temporais. Aqui, o gradiente é propagado até o objeto de dados de entrada. Porém, vale lembrar que já propagamos para ele os gradientes de erro vindos da rota espacial. Por isso, repetimos a mesma substituição do buffer de dados e, em seguida, executamos as operações de distribuição do gradiente de erro.
//--- Time projection temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(NeuronOCL.getPrevOutput(), false)) return false; for(int i = cProjectionT.Total() - 1; i >= 0; i--) { current = (i > 0 ? cProjectionT[i - 1] : NeuronOCL); next = cProjectionT[i]; if(!current || !current.CalcHiddenGradients(next, SecondInput, SecondGradient, SecondActivation)) return false; } if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) || !NeuronOCL.SetGradient(temp, false)) return false; //--- return true; }
Assim, o método distribui cuidadosamente os gradientes pelas três ramificações paralelas: Spatial, Mix e Temporal. Em cada ramificação, a propagação reversa percorre as camadas uma a uma, em ordem inversa, e, nos pontos de junção entre as ramificações, os erros são somados. Isso reflete a mesma ideia de arquitetura da propagação para frente: os dados seguem por canais paralelos, mas, no fim, convergem para um resultado comum.
O código completo da classe, com todos os métodos, é apresentado no anexo, o que permite examinar a estrutura geral e a mecânica interna da implementação.
Arquitetura do modelo
Depois que concluímos a construção de todos os objetos necessários para implementar o framework Extralonger, passamos naturalmente à descrição da arquitetura do próprio modelo. Aqui, é importante destacar: não nos limitamos ao problema de previsão de séries temporais, como ocorre na implementação dos autores. Nosso objetivo é mais amplo e mais prático: criamos um robô de trading completo, capaz de tomar decisões de forma autônoma e executar operações de trading no mercado.
Nessa formulação, o problema de previsão das séries de preços deixa de ser o objetivo final e passa a atuar apenas como parte do sistema, especificamente como Codificador do estado do ambiente. Uma espécie de sensor que transforma os dados de mercado em um formato compreensível para o algoritmo. Mantendo o conceito Ator-Crítico, formamos três modelos funcionais: Codificador, Ator e Crítico.
Para descrever sua arquitetura, usamos o método CreateDescriptions. Nele, são cuidadosamente criados e inicializados os arrays de descrição das camadas para cada uma das três partes do modelo.
bool CreateDescriptions(CArrayObj *&encoder, CArrayObj *&actor, CArrayObj *&critic ) { //--- CLayerDescription *descr; //--- if(!encoder) { encoder = new CArrayObj(); if(!encoder) return false; } if(!actor) { actor = new CArrayObj(); if(!actor) return false; } if(!critic) { critic = new CArrayObj(); if(!critic) return false; }
O código começa verificando os ponteiros recebidos para os arrays. Se algum deles ainda não existir, um novo é criado. Essa etapa garante robustez: evitamos que o preenchimento subsequente das descrições cause falhas pela ausência da estrutura.
Em seguida, os arrays são limpos e começa a construção gradual das camadas do Codificador. A primeira a ser adicionada é a camada de dados de entrada.
//--- Encoder encoder.Clear(); //--- Input layer if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronBaseOCL; uint prev_count = descr.count = (HistoryBars * BarDescr); descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
O passo seguinte é a camada de normalização com adição de ruído, que ajuda a melhorar a estabilidade do treinamento por meio de distorções estocásticas.
//--- layer 1 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronBatchNormWithNoise; descr.count = prev_count; descr.batch = BatchSize; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
Depois, é criada a camada de adição de features da primeira diferença. Sua função é formar fatias temporais, relacionando os valores das barras entre si. Na saída, essa camada gera o dobro de features, pois são combinados os próprios valores e suas diferenças.
//--- layer 2 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronConcatDiff; prev_count = descr.count = HistoryBars; descr.layers = BarDescr; descr.step = 1; descr.batch = BatchSize; descr.optimization = ADAM; descr.activation = None; if(!encoder.Add(descr)) { delete descr; return false; } uint prev_out = descr.layers*2 ;
A camada seguinte, defNeuronExtralonger, merece atenção especial. Ela define a arquitetura do objeto de nível superior que criamos, CNeuronExtralonger. Na prática, trata-se de toda a estrutura completa do framework Extralonger. Nela, especificamos todos os parâmetros necessários: janelas temporais, horizonte de previsão, períodos curto e longo, bem como a dimensionalidade das features ocultas. Esse bloco transforma o Codificador em um filtro inteligente, que conecta o histórico e o futuro em uma representação unificada.
//--- layer 3 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronExtralonger; { uint temp[] = {HistoryBars, // Time steps history NForecast, // Time steps forecast ShortPeriod, // Period 2 LongPeriod, // Period 2 BarDescr/2 // M units }; if(ArrayCopy(descr.units, temp) < (int)temp.Size()) return false; } prev_count = descr.units[1]; descr.window = prev_out; // Variables descr.window_out = EmbeddingSize; // Inside Dimension { uint temp[] = {EmbeddingSize, // Embedding Dimension PeriodSeconds(PERIOD_H1), // Frame 1 PeriodSeconds(PERIOD_D1), // Frame 2 2*EmbeddingSize/NHeads }; if(ArrayCopy(descr.windows, temp) < (int)temp.Size()) return false; } descr.layers=2; descr.step=NHeads; descr.probability=0.3f; descr.optimization=ADAM; descr.batch=BatchSize; descr.activation = None; if(!encoder. Add(descry)) { delete descry; return false; } uint window=descr.window; uint count=prev_count;
Na saída do módulo Extralonger, obtemos um conjunto consolidado de valores de previsão, pronto para uso posterior. Mas é importante lembrar que, antes de alimentar esse módulo com os dados, nós já os havíamos enriquecido com features de primeira diferença. Essa técnica tornou as séries mais informativas, mas, ao mesmo tempo, aumentou a dimensionalidade dos dados.
À primeira vista, a forma mais simples de resolver esse problema seria simplesmente descartar as features excedentes. No entanto, esse caminho levaria à perda justamente da informação que motivou a introdução da primeira diferença. Em vez de uma solução simplificada, usamos uma camada convolucional, que não apenas reduz a dimensionalidade, mas também seleciona os padrões locais mais relevantes. Com isso, a saída do módulo passa por uma filtragem refinada: os dados se tornam compactos, mas preservam todo o espectro de características essenciais necessárias para tomar decisões de trading.
//--- layer 4 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronConvOCL; descr.count = prev_count; descr.window = prev_out; descr.step = prev_out; prev_out = descr.window_out = BarDescr; descr.activation = TANH; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
O toque final do Codificador do estado do ambiente é a camada de denormalização de instância reversível (defNeuronRevInDenormOCL). Essa camada devolve os valores à escala familiar dos dados analisados, compensando o deslocamento introduzido durante a codificação e a normalização de instância reversível. Assim, o resultado se torna comparável aos valores reais de mercado.
//--- layer 5 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronRevInDenormOCL; descr.count = prev_count * prev_out; descr.layers = 1; if(!encoder.Add(descr)) { delete descr; return false; }
A arquitetura dos modelos do Ator e do Crítico foi integralmente aproveitada de nossos trabalhos anteriores e não sofreu nenhuma alteração. Por isso, não nos deteremos em sua análise detalhada neste artigo. Para completar o quadro, o leitor tem acesso à descrição completa da arquitetura de todos os modelos treináveis no anexo, onde é possível acompanhar cada detalhe da implementação e confirmar a integridade da solução construída.
Testes
O treinamento do modelo é uma verdadeira preparação operacional para trading. Antes de colocá-lo em produção no mercado, validamos cuidadosamente a estratégia com dados históricos. A primeira etapa, o treinamento offline, foi realizada em uma amostra do par de moedas EURUSD no timeframe H1, no período de janeiro de 2024 a junho de 2025. Esse intervalo se mostrou rico e variado: fases tranquilas de mercado lateral se alternavam com movimentos tendenciais bruscos, enquanto a divulgação de notícias provocava picos de alta volatilidade. Essa combinação de condições permitiu que o modelo aprendesse a distinguir um amplo espectro de cenários de mercado e a produzir decisões de trading robustas, sem perder a referência em situações complexas.
Quando a primeira etapa de preparação foi concluída, passamos à segunda: o ajuste fino online no testador de estratégias do MetaTrader 5. Aqui, os dados eram recebidos em tempo real, vela a vela, e o modelo assimilava a dinâmica de processamento em fluxo contínuo. Nessa dinâmica, aprendia a manter a estabilidade em meio ao ruído, lidar com baixa liquidez e não se desorientar diante de picos repentinos de preço. Essa etapa funcionou como um refinamento final da estratégia. Ela não alterava a estrutura construída com base no histórico, mas ajudava a adaptá-la às condições reais e reduzia o risco de o modelo sofrer sobreajuste.
A verificação final foi realizada com dados de julho de 2025. Esses dados não haviam sido usados anteriormente e eram totalmente novos para o modelo. Todos os parâmetros obtidos nas etapas anteriores foram carregados sem alterações. Esse teste fora da amostra permitiu avaliar objetivamente a capacidade de generalização do modelo, sem nenhum ajuste aos dados ou calibração posterior.
Os resultados dos testes são apresentados abaixo.


Os resultados dos testes permitem avaliar o modelo em condições reais de mercado, sem nenhum ajuste posterior. Comecemos pelos números principais. Com um depósito inicial de 100 dólares, o resultado líquido final foi de 3,02 dólares, ou seja, o saldo cresceu três por cento durante o mês de teste. O lucro total chegou a 27,26 dólares, enquanto as perdas somaram 24,24 dólares. O Profit Factor foi de 1,12. Isso indica que as operações lucrativas predominaram ligeiramente sobre as operações perdedoras.
No entanto, também é possível ver um ponto fraco: o fator de recuperação foi de apenas 0,22. Isso significa que, após sequências de perdas, o modelo recupera o capital lentamente. O drawdown máximo do saldo foi de 12,75%, um valor bastante perceptível, mas ainda não crítico para um trading totalmente automatizado. A expectativa matemática média por operação foi modesta, 0,06 dólar, mas o Sharpe Ratio (3,04) indica uma boa relação entre retorno e risco, considerando a volatilidade.
A estatística das operações também é interessante. Foram 50 operações ao todo: 28 operações short, com taxa de acerto de 53,57%, e 22 operações long,com ganho em apenas 36,36% dos casos. No geral, a proporção de operações lucrativas foi de 46%. O maior lucro em uma única operação foi de 6,70 dólares, e a maior perda foi de 4,73 dólares. As sequências também revelaram o comportamento do modelo: houve até cinco operações vencedoras consecutivas, enquanto a maior sequência de perdas chegou a três operações.
O gráfico da curva de capital confirma o quadro mostrado pelos números. Os primeiros dias de julho foram acompanhados por oscilações e por uma sequência de recuos; depois, o saldo começou a crescer gradualmente. No meio do mês, surgiram algumas sequências bem-sucedidas de operações, que permitiram levar o capital para o território positivo. No fim de julho, observou-se estabilização e manutenção do resultado, sem novas quedas profundas.
Assim, o teste mostrou que o modelo é capaz de gerar lucro moderado, mantendo o equilíbrio entre risco e retorno. No entanto, o período de teste é bastante curto, e o uso em produção exigirá otimização adicional. Antes de tudo, será preciso reduzir o drawdown e aumentar a proporção de operações lucrativas. Mas o mais importante é que o sistema passou por um teste realmente fora da amostra e demonstrou capacidade de generalização, que é um critério essencial de qualidade para um modelo algorítmico.
Também não devemos esquecer que o treinamento de modelos Transformer é bastante complexo e exige conjuntos de treinamento extensos.
Conclusão
Ao longo deste trabalho, confirmamos que as abordagens propostas pelo framework Extralonger são capazes de integrar de forma orgânica fatores espaciais e temporais, formando uma base robusta para a análise da dinâmica de mercado.
Os testes confirmaram a eficácia prática dos algoritmos, enquanto a flexibilidade da arquitetura permite adaptar o sistema a diferentes horizontes de previsão e classes de instrumentos financeiros. A implementação em MQL5 mostrou que abordagens clássicas, combinadas com métodos modernos de redes neurais, geram um resultado comparável ao de sistemas mais complexos, mas permanecem extremamente transparentes e gerenciáveis.
Referências
- Extralonger: Toward a Unified Perspective of Spatial-Temporal Factors for Extra-Long-Term Traffic Forecasting
- Outros artigos da série
Programas usados 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 testar o modelo |
| 4 | Trajectory.mqh | Biblioteca de classes | Estrutura de descrição do estado do sistema e da arquitetura dos modelos |
| 5 | NeuroNet.mqh | Biblioteca de classes | Biblioteca de classes para criação da rede neural |
| 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/19564
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Robô de trading multithreaded com machine learning: da concepção à concretização
Criando um Painel de Administração de Trading em MQL5 (Parte IX): Organização do Código (V): Classe AnalyticsPanel
Redes neurais em trading: sinais de negociação robustos em qualquer regime de mercado (ST-Expert)
Redes neurais no trading: uma visão unificada sobre espaço e tempo (Global-Local Attention)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso