Redes neurais em trading: sinais de negociação robustos em qualquer regime de mercado (Conclusão)
Introdução
Os mercados financeiros são regidos pela mudança. Hoje, podem apresentar um crescimento consistente e, amanhã, reagir de forma turbulenta ao menor estímulo externo. Novos instrumentos, iniciativas regulatórias, choques macroeconômicos e até mesmo o sentimento social alteram o cenário do mercado de maneira tão profunda quanto a construção de uma nova rodovia reorganiza o fluxo de veículos em uma cidade. É justamente por isso que os algoritmos clássicos de previsão costumam perder eficácia assim que se deparam com condições novas, não representadas no conjunto de treinamento.
O framework ST-Expert foi proposto como uma das possíveis soluções para esse problema. Sua ideia central é simples e, ao mesmo tempo, profunda. Em vez de depender de uma única estratégia universal, ele constrói um conjunto de especialistas, cada um voltado para um segmento específico do comportamento do mercado. Essa abordagem pode ser comparada à gestão de uma carteira de investimentos. Um gestor experiente não utiliza uma única estratégia para todas as situações, mas dispõe de todo um arsenal de ferramentas. Algumas funcionam melhor em períodos de tendência sustentada, outras geram lucro em períodos de alta volatilidade, enquanto outras ajudam a preservar o capital durante longos movimentos laterais. À semelhança de uma carteira diversificada, o ST-Expert pondera a contribuição de seus especialistas e produz uma solução equilibrada para as novas condições do mercado.
A principal vantagem dessa abordagem é sua robustez. Ao contrário dos modelos que estabelecem relações rígidas entre os ativos e deixam de funcionar adequadamente quando essas relações mudam, o ST-Expert é capaz de se adaptar. Sua arquitetura é estruturada em torno de uma camada de graphons especialistas, que atuam como geradores de relações entre os ativos. Cada graphon constrói sua própria representação das possíveis interações entre mercados ou instrumentos específicos, enquanto o módulo de mistura seleciona dinamicamente a combinação de especialistas mais adequada à situação atual.
Em termos de arquitetura, o framework se baseia no princípio de Mixture of Experts. Em vez de depender de um único especialista universal, utiliza-se um conjunto de modelos treinados em diferentes intervalos temporais e cenários. Um dos elementos mais importantes dessa abordagem é o treinamento episódico, no qual o modelo é submetido deliberadamente a uma série de testes de estresse. Os dados são divididos de acordo com diferentes regimes de mercado, como fases de tendência, correção ou movimento lateral. Em seguida, é formulada uma tarefa na qual cada especialista é treinado em condições específicas. Ao mesmo tempo, o módulo de mistura aprende a combinar os especialistas para obter a máxima eficácia. Isso se assemelha ao treinamento de um trader com dados históricos. Ele aprende a tomar decisões diante de informações incompletas e mudanças imprevisíveis. E é justamente isso que o torna experiente.
Do ponto de vista técnico, a arquitetura do ST-Expert é composta por várias camadas. Primeiro, as séries temporais brutas são convertidas em embeddings e, em seguida, encaminhadas aos blocos de especialistas, cada um dos quais constrói sua própria representação das relações latentes. Depois, o módulo de mistura define o peso de cada especialista para o estado atual do mercado. O resultado é um sistema dinâmico de previsão que, em vez de buscar uma fórmula universal, equilibra diferentes estratégias. É como um gestor de carteira que, todas as manhãs, seleciona os instrumentos mais adequados para aquele dia.
Essa abordagem reúne três qualidades fundamentais. A primeira é a flexibilidade. A camada de especialistas pode ser incorporada a praticamente qualquer arquitetura moderna sem alterar sua estrutura básica. A segunda é a robustez. Ao contrário dos modelos clássicos, que tendem a se fixar em um único padrão, o ST-Expert mantém a precisão mesmo quando o contexto do mercado muda. Por fim, há a eficiência. A ampliação das funcionalidades do modelo não exige um crescimento excessivo do número de parâmetros. Isso torna o framework adequado para sistemas financeiros em operação, nos quais tanto a precisão quanto o tempo de processamento são fatores essenciais.
Outro aspecto que diferencia o ST-Expert é a eficiência computacional da implementação. Em geral, o aumento da robustez exige maior capacidade de processamento: mais parâmetros, mais dados e mais tempo de execução. Neste caso, porém, os ganhos são obtidos sem elevar significativamente a carga computacional. Em termos de mercado financeiro, isso pode ser comparado a uma estratégia de investimento capaz de oferecer alta rentabilidade, risco moderado e custos mínimos.
A visualização do framework ST-Expert elaborada pelos autores é apresentada a seguir.

Nos trabalhos anteriores, abordamos desde os fundamentos teóricos até a implementação prática do framework ST-Expert. Antes de avançar, convém sistematizar os resultados já obtidos para avaliar o escopo da implementação e identificar as possibilidades oferecidas por essa arquitetura.
Inicialmente, analisamos em detalhes o princípio de Mixture of Experts. Essa abordagem se assemelha à carteira diversificada de um trader. Cada especialista atua em seu próprio nicho, modela determinados padrões e, ao mesmo tempo, não interfere nos demais.
Em seguida, traduzimos as ideias arquiteturais em soluções algorítmicas concretas. Na prática, isso resultou na implementação do módulo de graphons especialistas, cada um responsável por construir sua própria representação da estrutura e das relações presentes nos dados. Dedicamos atenção especial ao bloco de mistura, que constitui o núcleo de todo o sistema. É justamente esse módulo que proporciona a principal vantagem do ST-Expert: a capacidade de selecionar dinamicamente os especialistas mais adequados para cada situação do mercado. Isso se assemelha a um gestor de carteira experiente, capaz de alternar entre estratégias a qualquer momento sem perder o equilíbrio entre risco e retorno.
A implementação demonstrou que o framework não se limita a analisar dados históricos: ele também é capaz de se adaptar às mudanças observadas nesses dados. Isso é especialmente importante nos mercados financeiros, onde as mudanças ocorrem rapidamente e estratégias ultrapassadas podem gerar perdas significativas. A capacidade de adaptação do sistema permite prever os movimentos do mercado, minimizar perdas e explorar novas oportunidades com alta precisão.
Esses resultados estabeleceram a base para as etapas seguintes.
Atenção global-local
No artigo anterior, discutimos em detalhes e fundamentamos as decisões adotadas para incorporar as abordagens do framework ST-Expert à arquitetura Extralonger. O principal objetivo era integrar os módulos de graphons à arquitetura Transformer, ampliando a capacidade do modelo de analisar dependências complexas nos mercados financeiros. Na prática, implementamos objetos de atenção completa e esparsa, o que nos permitiu processar dados em diferentes níveis da hierarquia.
Na arquitetura Extralonger, o objeto de atenção global-local recebe atenção especial. Ele permite a operação paralela de dois módulos: um global, capaz de identificar padrões de grande escala, e outro local, concentrado em detalhes e anomalias. Essa abordagem permite acompanhar simultaneamente as tendências gerais do mercado e detectar pequenos sinais que podem ser decisivos para operações de curto prazo.
Para facilitar a integração dessa ideia à arquitetura linear tradicional dos modelos, criamos um novo módulo de atenção global-local baseado em graphons. Esse módulo coordena os objetos de atenção global e local em fluxos de dados paralelos e, em seguida, combina seus resultados de forma adaptativa, produzindo uma representação dos dados mais precisa e flexível.
A classe CNeuronGlobLocGraphAtt pode ser comparada a um trader experiente que acompanha tendências de longo prazo e, ao mesmo tempo, identifica sinais de curto prazo. Ela é construída com base em um mecanismo de pooling multi-head e herda as funcionalidades do objeto CNeuronMHAttentionPooling, o que oferece uma base sólida para o processamento flexível dos dados.
class CNeuronGlobLocGraphAtt : public CNeuronMHAttentionPooling { protected: CNeuronGraphAttention cGlobal; CNeuronSparseGraphAttention cLocal; CNeuronBaseOCL cConcat; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronGlobLocGraphAtt(void) {}; ~CNeuronGlobLocGraphAtt(void) {}; virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint units, uint window, uint experts, float dropout, uint emb_dimension, uint sparse_dimension, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronGlobLocGraphAtt; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual void SetOpenCL(COpenCLMy *obj) override; virtual void SetActivationFunction(ENUM_ACTIVATION value) override { }; virtual void TrainMode(bool flag) override; };
A classe contém dois componentes principais: cGlobal e cLocal. O primeiro atua como um analista sênior que observa o mercado como um todo. Ele percorre toda a sequência de dados e identifica padrões gerais e tendências de longo prazo que ajudam a elaborar previsões estratégicas. O segundo se comporta como um estrategista ágil, concentrando-se nos eventos mais próximos e nas anomalias capazes de afetar imediatamente a posição do trader.
Todos os componentes internos do módulo são declarados como membros estáticos, o que permite manter vazios o construtor e o destrutor da classe. A inicialização ocorre no método Init. Essa abordagem se assemelha à de um trader experiente que chega ao mercado com uma equipe de analistas já preparada. Cada especialista conhece sua função. Cabe ao gestor posicioná-los corretamente e coordenar a interação entre eles.
bool CNeuronGlobLocGraphAtt::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint units, uint window, uint experts, float dropout, uint emb_dimension, uint sparse_dimension, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, window, units, 2, optimization_type, batch)) return false;
O método Init é responsável por configurar a arquitetura do módulo de atenção global-local. Primeiro, é chamado o método de inicialização da classe-base CNeuronMHAttentionPooling. Caso ocorra algum erro nessa etapa, a inicialização é interrompida. Isso se assemelha à análise dos fundamentos de um ativo antes da abertura de uma posição: sem uma base confiável, não faz sentido prosseguir.
Após a inicialização bem-sucedida da classe-base, começa a configuração dos objetos internos. O primeiro deles é cGlobal, responsável pela atenção global. Ele processa toda a sequência de dados e constrói uma visão estratégica do mercado, identificando tendências de longo prazo e padrões recorrentes. Caso esse componente não seja inicializado corretamente, o módulo não poderá prosseguir, o que ressalta a importância crítica da análise global.
int index = 0; if(!cGlobal.Init(0, index, OpenCL, iUnits, iWindow, emb_dimension, experts, dropout, optimization, iBatch)) return false;
Em seguida, é criado cLocal, o objeto de atenção local. Ele se concentra nos eventos mais próximos e nos padrões esparsos, permitindo que o modelo detecte sinais relevantes e anomalias que poderiam passar despercebidos pela análise global. Também se verifica se cLocal foi inicializado corretamente. Em caso de erro, o módulo não é inicializado, evitando que o sistema tome decisões com base em sinais inadequados. É como um trader que não abre uma posição sem dispor de dados confiáveis.
index++; if(!cLocal.Init(0, index, OpenCL, iUnits, iWindow, experts, dropout, emb_dimension, sparse_dimension, optimization, iBatch)) return false;
Por fim, os resultados da atenção global e local são concatenados em cConcat. Esse objeto serve apenas para armazenar o tensor resultante da concatenação das duas perspectivas sobre o mercado. A combinação propriamente dita, que produzirá o valor final, será realizada por meio dos mecanismos da classe-base.
index++; if(!cConcat.Init(0, index, OpenCL, 2 * iWindow * iUnits, optimization, iBatch)) return false; cConcat.SetActivationFunction(None); //--- return true; }
Quando todas as etapas são concluídas com sucesso, o método Init retorna true, indicando que o módulo está pronto para operar. Caso contrário, a inicialização é interrompida, preservando a estabilidade e a confiabilidade do modelo.
Assim, obtemos uma arquitetura completa na qual as atenções global e local operam em fluxos paralelos, enquanto a combinação adaptativa proporciona uma interpretação precisa e flexível dos dados de mercado. O módulo funciona como um instrumento dinâmico, capaz de manter uma visão estratégica e, ao mesmo tempo, reagir às mudanças de curto prazo, como um trader experiente sempre atento ao pulso do mercado.
Concluída a inicialização do objeto, começa a fase de execução. Nela, o método feedForward implementa a propagação para frente dos dados no módulo. Essa etapa pode ser comparada à atuação de um trader em tempo real. Os sinais do mercado chegam continuamente, e o sistema precisa processá-los de imediato, conciliando a visão estratégica com a resposta às oscilações de curto prazo.
bool CNeuronGlobLocGraphAtt::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cGlobal.FeedForward(NeuronOCL)) return false;
Primeiro, os dados passam pelo objeto de atenção global cGlobal, que constrói uma visão geral do mercado e identifica tendências de longo prazo e padrões estruturais. Caso ocorra algum erro nessa etapa, a propagação para frente é interrompida, pois, sem o contexto global, a análise subsequente perde o sentido, assim como uma estratégia de carteira sem considerar as condições macroeconômicas.
Em seguida, os dados são encaminhados ao objeto de atenção local cLocal, que destaca detalhes importantes, pequenos sinais e anomalias locais. Esse componente opera em paralelo ao global e mantém o foco nos eventos capazes de afetar imediatamente a posição de mercado assumida pelo modelo.
if(!cLocal.FeedForward(NeuronOCL)) return false;
Após o processamento em cada módulo, suas saídas são concatenadas separadamente para cada elemento da sequência.
if(!Concat(cGlobal.getOutput(), cLocal.getOutput(), cConcat.getOutput(), iWindow, iWindow, iUnits)) return false; //--- return CNeuronMHAttentionPooling::feedForward(cConcat.AsObject()); }
Em seguida, os resultados das análises global e local são combinados de forma adaptativa, gerando um sinal único e coerente para o processamento subsequente. Essa etapa pode ser comparada à consolidação dos relatórios estratégicos e táticos de duas equipes de analistas, para que o trader tenha uma visão completa e possa tomar decisões fundamentadas.
Dessa forma, o método mantém um fluxo contínuo e coordenado de sinais por todos os níveis do modelo, permitindo acompanhar simultaneamente as tendências globais e os eventos locais do mercado. O módulo funciona como um instrumento dinâmico, capaz de responder de forma adaptativa às mudanças e gerar previsões precisas em um contexto de rápida variação dos dados financeiros.
No entanto, uma previsão obtida com parâmetros aleatórios do modelo tem pouco valor preditivo. Seria como tentar adivinhar o futuro pela borra de café ou prever os movimentos do mercado sem dados históricos e sem analisá-los. Para que o modelo funcione com eficiência, é necessário distribuir corretamente o erro total entre seus componentes, levando em conta a influência de cada um sobre o resultado final. É essa a função do método calcInputGradients.
bool CNeuronGlobLocGraphAtt::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Primeiro, o método verifica se o ponteiro recebido para o objeto de dados de entrada NeuronOCL é válido, pois os cálculos não podem ser realizados sem ele. Em seguida, é acionado o mecanismo básico de retropropagação do erro da classe-base CNeuronMHAttentionPooling, que distribui os gradientes no nível do sinal combinado.
if(!CNeuronMHAttentionPooling::calcInputGradients(cConcat.AsObject())) return false; if(!DeConcat(cGlobal.getGradient(), cLocal.getGradient(), cConcat.getGradient(), iWindow, iWindow, iUnits)) return false;
Depois, os gradientes são separados. O método DeConcat distribui cuidadosamente o erro entre as atenções global e local, garantindo que o gradiente atribuído a cada parte reflita corretamente sua contribuição para a previsão global.
Na etapa seguinte, precisamos propagar os gradientes até o buffer associado aos dados de entrada pelos dois ramos da arquitetura: o da atenção global e o da atenção local. Primeiro, calculamos e propagamos os gradientes pelo módulo global.
if(!NeuronOCL.CalcHiddenGradients(cGlobal.AsObject())) return false; CBufferFloat *temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(PrevOutput, false)) return false; if(!NeuronOCL.CalcHiddenGradients(cLocal.AsObject())) return false;
Em seguida, o ponteiro para o buffer de gradientes é armazenado temporariamente em uma variável local. Isso permite encaminhar corretamente os gradientes ao módulo de atenção local sem perder os gradientes já calculados pelo ramo global. Somente depois disso são calculados os gradientes ocultos do módulo local.
Por fim, os gradientes provenientes dos dois ramos são somados no buffer de gradientes de entrada, preservando a contribuição dos módulos global e local. Depois disso, os ponteiros dos buffers são restaurados ao estado original.
if(!SumAndNormilize(temp, PrevOutput, temp, iWindow, false, 0, 0, 0, 1) || !NeuronOCL.SetGradient(temp, false)) return false; //--- return true; }
Desse modo, o método distribui corretamente o erro entre todos os componentes do módulo, garantindo um treinamento equilibrado e permitindo que cada elemento do modelo contribua para a precisão das previsões. Esse processo pode ser comparado ao de um trader experiente que, após analisar os resultados de sua equipe, ajusta a atuação de cada analista para manter a carteira de estratégias otimizada e robusta diante das oscilações do mercado.
Após o cálculo dos gradientes, inicia-se a atualização dos parâmetros do modelo, implementada no método updateInputWeights. Aqui, não criamos novos algoritmos. Apenas transferimos o controle sequencialmente para os componentes internos do módulo, permitindo que cada um atualize seus próprios pesos com base nos gradientes recebidos.
bool CNeuronGlobLocGraphAtt::updateInputWeights(CNeuronBaseOCL *NeuronOCL) { if(!cGlobal.UpdateInputWeights(NeuronOCL)) return false; if(!cLocal.UpdateInputWeights(NeuronOCL)) return false; //--- return CNeuronMHAttentionPooling::updateInputWeights(cConcat.AsObject()); }
De modo geral, o CNeuronGlobLocGraphAtt atua como um analista em tempo real no mercado, combinando visão estratégica com uma resposta imediata aos acontecimentos. Ele permite que o modelo analise com eficiência fluxos multidimensionais de dados, preveja o comportamento dos mercados financeiros e se adapte à volatilidade. É como um trader experiente que nunca perde de vista o ritmo das mudanças do mercado e consegue ajustar suas posições imediatamente diante de novos sinais.
O código completo do objeto e de todos os seus métodos está disponível no anexo.
Objeto de nível superior
Com todos os componentes implementados, incorporamos os novos objetos diretamente à arquitetura do framework Extralonger. Vale lembrar que, ao desenvolver esse framework, criamos inicialmente um objeto de nível superior contendo quatro arrays dinâmicos destinados a armazenar ponteiros para as sequências de componentes internos. Essa solução facilita a extensão da arquitetura. Para adicionar novos elementos, não é necessário reconstruir o objeto do zero: basta herdar as funcionalidades do objeto existente e redefinir apenas o método de inicialização, no qual é especificada a arquitetura do novo módulo. É assim que criamos a classe CNeuronExtralongerGraph.
class CNeuronExtralongerGraph : public CNeuronExtralonger { public: CNeuronExtralongerGraph(void) {}; ~CNeuronExtralongerGraph(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 experts, uint m_units, float sparse, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) const { return defNeuronExtralongerGraph; } };
O construtor e o destrutor permanecem vazios, pois todas as estruturas internas são inicializadas no método Init. Sua função é reunir os elementos separados em uma única estrutura capaz de acompanhar simultaneamente tendências de longo prazo, oscilações de curto prazo e relações espaciais entre as variáveis.
bool CNeuronExtralongerGraph::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 experts, 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;
A inicialização começa pelo pooling multi-head básico. É como lançar os alicerces de um edifício alto. Quando a base não é estável, as camadas seguintes perdem o sentido. Em seguida, é montado o bloco Time Projection, que pode ser comparado a um "radar" temporal do modelo, capaz de acompanhar a evolução do mercado em diferentes horizontes. Essa etapa converte as séries temporais originais em embeddings multidimensionais, gerando uma representação estruturada e informativa para o processamento subsequente.
CNeuronBatchNormOCL *norm = NULL; CNeuronConvOCL *conv = NULL; CNeuronTransposeOCL *transp = NULL; CNeuronLearnabledPE *lnoise = NULL; CNeuronSpatialEmbedding *semb = NULL; CNeuronTempEmbedding *temb = NULL; CNeuronGraphAttention *att = NULL; CNeuronGlobLocGraphAtt *glatt = NULL; //--- 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; } 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); 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, emb_dimension - half_emb, period2, frame2, optimization, iBatch) || !cProjectionT.Add(temb)) { DeleteObj(temb); return false; } index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, temb.Neurons(), iBatch, optimization) || !cProjectionT.Add(norm)) { DeleteObj(norm); return false; }
Essa etapa foi integralmente herdada da classe-base, sem qualquer alteração. Relembrando brevemente, a projeção começa pelo objeto Learnable Positional Encoding (LPE). Neste caso, ele adiciona um ruído treinável que ajuda a tornar o modelo mais robusto à volatilidade do mercado e ao ruído presente nos dados. Depois, os dados passam por camadas convolucionais, que extraem padrões locais e características estruturais da série temporal, identificando tendências de curto prazo e anomalias. Em seguida, são adicionados embeddings temporais, ou TempEmbedding. A etapa final é a Batch Normalization, que estabiliza os sinais e corrige o desequilíbrio entre os diferentes componentes dos embeddings. Com isso, o modelo recebe uma entrada limpa e coerente para os módulos de atenção subsequentes, reduzindo o risco de acúmulo de erros logo nas etapas iniciais.
A etapa seguinte é o Time Module. Ele é composto por várias camadas de atenção em grafos, complementadas pelas camadas convolucionais do bloco de projeção dos dados.
//--- Time Module cTimeModule.Clear(); cTimeModule.SetOpenCL(OpenCL); for(uint i = 0; i < layers; i++) { index++; att = new CNeuronGraphAttention(); if(!att || !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, emb_dimension, experts, sparse, optimization, iBatch) || !cTimeModule.Add(att)) { DeleteObj(att); return false; } } 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; }
Em termos estruturais, a arquitetura do bloco permanece inalterada. Apenas substituímos os módulos do Transformer clássico pelos novos módulos de atenção em grafos. Esses componentes permitem que o modelo identifique dependências complexas nas séries temporais, como uma equipe de analistas que acompanha as tendências e, ao mesmo tempo, reage a anomalias de curto prazo.
Após a análise temporal, construímos o Mix Module. Esse bloco incorpora novas camadas de atenção em grafos e módulos de atenção global-local baseados em graphons.
//--- Mix Module cMixModule.Clear(); cMixModule.SetOpenCL(OpenCL); uint att_layers = (layers + 1) / 2; for(uint i = 0; i < att_layers; i++) { index++; att = new CNeuronGraphAttention(); if(!att || !att.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, emb_dimension, experts, sparse, optimization, iBatch) || !cMixModule.Add(att)) { DeleteObj(att); return false; } } 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 CNeuronGlobLocGraphAtt(); if(!glatt || !glatt.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_in, experts, sparse, emb_dimension, uint(sparse * (dimension + emb_dimension)), optimization, iBatch) || !cMixModule.Add(glatt)) { DeleteObj(glatt); return false; } } 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; }
As camadas convolucionais projetam os resultados da análise para o horizonte de planejamento definido. Cada objeto é validado e somente depois integrado ao fluxo geral de dados, garantindo a continuidade e a coerência do processamento.
O Spatial Module conclui a cadeia, processando as dependências espaciais entre as variáveis. Primeiro, os dados originais são projetados em embeddings espaciais, que passam posteriormente por normalização.
//--- 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; } index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, semb.Neurons(), iBatch, optimization) || !cSpatialModule.Add(norm)) { DeleteObj(norm); return false; }
Os módulos de atenção global-local baseados em graphons permitem considerar simultaneamente as correlações locais e os padrões globais, produzindo um sinal preciso. Essa etapa pode ser comparada à atuação de um gestor de carteira que coordena várias equipes de analistas e leva em conta tanto as condições macroeconômicas quanto as microvariações do mercado.
for(uint i = 0; i < layers; i++) { index++; glatt = new CNeuronGlobLocGraphAtt(); if(!glatt || !glatt.Init(0, index, OpenCL, variables, dimension + emb_dimension, experts, sparse, emb_dimension, m_units, optimization, iBatch) || !cSpatialModule.Add(glatt)) { DeleteObj(glatt); return false; } } 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; }
Assim como nos dois módulos anteriores, os resultados da análise são projetados para o horizonte de planejamento definido por meio de um bloco de camadas convolucionais.
Ao final, os três fluxos de dados são concatenados no objeto cConcatResults, que os encaminha à classe-base para a combinação adaptativa dos resultados em uma previsão final.
index++; if(!cConcatResults.Init(0, index, OpenCL, 3 * variables * time_steps_out, optimization, iBatch)) return false; cConcatResults.SetActivationFunction(None); //--- return true; }
O método Init, portanto, constrói uma arquitetura dinâmica e flexível, na qual cada módulo desempenha uma função especializada e, ao mesmo tempo, permanece integrado ao sistema como um todo. O modelo consegue acompanhar simultaneamente as dependências temporais e espaciais, adaptar-se à volatilidade do mercado e produzir previsões precisas mesmo em condições complexas. Essa estrutura pode ser comparada a uma equipe de analistas altamente qualificada, na qual cada integrante cumpre sua função e todos colaboram para construir a representação mais precisa possível do mercado.
Os algoritmos de propagação para frente e propagação reversa do novo módulo são integralmente herdados da classe-base. Isso permite concentrar o desenvolvimento nas particularidades da arquitetura, sem reimplementar os mecanismos de propagação dos sinais, cálculo dos gradientes e atualização dos pesos. Essa abordagem proporciona máxima confiabilidade e coerência ao funcionamento do modelo, pois todos os procedimentos computacionais já foram testados. Graças à herança, os métodos feedForward, calcInputGradients e updateInputWeights não precisam ser implementados separadamente no novo objeto.
O código completo da classe e de todos os seus métodos está disponível no anexo. Ele permite examinar detalhadamente a arquitetura e a sequência de inicialização de cada elemento interno, além de avaliar como todos os blocos interagem dentro de um único modelo.
Arquitetura do modelo
Após implementar todos os componentes necessários, podemos descrever a arquitetura do modelo. Cabe destacar um ponto fundamental. Nosso objetivo vai além da simples previsão das séries de preços. Como antes, estamos construindo um robô de negociação completo: um agente inteligente e autônomo, capaz de analisar o mercado, tomar decisões, abrir e fechar posições e se adaptar às constantes mudanças das condições de mercado. A previsão de preços é apenas uma ferramenta, um Codificador do estado do mercado, um sensor que transforma dados financeiros caóticos em uma representação estruturada e compreensível para o modelo.
A abordagem implementada se baseia no conceito Ator-Crítico, que permite separar a geração das ações da avaliação de sua qualidade. Construímos três modelos funcionais: Codificador, Ator e Crítico. O Codificador analisa os dados históricos e identifica padrões; o Ator toma decisões com base no estado atual do mercado; e o Crítico avalia os resultados das ações e ajusta a estratégia, reduzindo as perdas financeiras e aumentando a eficiência da negociação.
O método CreateDescriptions define a arquitetura dos modelos e monta os arrays com as descrições das camadas de 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; }
No Codificador, os dados analisados são recebidos por uma camada totalmente conectada básica, que atua apenas como buffer 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; } //--- 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; }
Em seguida, é utilizada uma camada de Batch Normalization com a adição de ruído aleatório para realizar data augmentation adicional. Ela estabiliza o sinal e, ao mesmo tempo, introduz uma flutuação controlada que simula a incerteza do mercado.
A camada seguinte, ConcatDiff, desempenha uma função essencial na preparação dos dados para uma análise aprofundada. Ela amplia o espaço de atributos calculando as primeiras diferenças entre passos temporais consecutivos. Esse recurso permite que o modelo capture a velocidade e a intensidade das variações de preço, em vez de considerar apenas seus valores absolutos. Isso é particularmente importante nas séries temporais financeiras, nas quais a velocidade e a direção das mudanças costumam ser mais relevantes do que sua magnitude isolada.
//--- 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 ;
Essa camada pode ser comparada a um trader experiente que não observa apenas o preço atual, mas também acompanha a velocidade e a aceleração de seu movimento. As primeiras diferenças fornecem ao modelo sinais de tendências e reversões, ajudando-o a identificar padrões na evolução multidimensional do mercado. Como resultado, o ConcatDiff gera um conjunto estruturado e informativo de atributos, que é encaminhado às camadas seguintes e oferece uma base sólida para análises e previsões complexas, incluindo o uso da atenção em grafos e dos módulos de atenção global-local.
A camada ExtralongerGraph ocupa uma posição central. Ela é o núcleo do modelo e reúne embeddings temporais e espaciais, atenção em grafos e atenção global-local. Nela são definidos os parâmetros que definem os passos temporais do histórico e do horizonte de previsão, aos períodos e timeframes de análise, ao número de especialistas e à dimensão dos embeddings. A camada transforma os dados históricos em uma representação multidimensional do mercado, permitindo considerar simultaneamente as oscilações de curto prazo e as tendências de longo prazo. É como uma equipe de analistas trabalhando em conjunto: cada integrante acompanha um aspecto específico do comportamento do mercado e, ao final, todos os resultados são combinados em uma única previsão.
//--- layer 3 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronExtralongerGraph; { 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 }; if(ArrayCopy(descr.windows, temp) < (int)temp.Size()) return false; } descr.layers=2; descr.step=NExperts; //Experts descr.probability=0.3f; descr.optimization=ADAM; descr.batch=BatchSize; descr.activation = None; if(!encoder.Add(descr)) { delete descr; return false; } uint window=descr.window; uint count=prev_count;
Uma camada convolucional e a desnormalização concluem o Codificador, que atuam como uma espécie de filtro e mecanismo de ajuste dos atributos obtidos. Nessa etapa, já temos a previsão gerada pelo módulo ExtralongerGraph, mas é importante lembrar que anteriormente ampliamos o espaço de atributos com a inclusão das primeiras diferenças. Esses atributos adicionais permitem que o modelo acompanhe não apenas os valores absolutos do preço, mas também sua velocidade de variação, o que é fundamental para identificar tendências e reversões no mercado financeiro.
A camada convolucional reduz a dimensionalidade dos atributos, simplificando o processamento e direcionando a atenção do modelo para os padrões mais relevantes. A função de ativação TANH limita a amplitude dos sinais, suaviza oscilações bruscas e evita uma sensibilidade excessiva ao ruído, algo especialmente importante em períodos de alta volatilidade. Na saída do objeto, obtemos um sinal no intervalo de -1 a 1, característico de dados normalizados. O objeto RevInDenorm, por sua vez, restitui os valores à escala dos dados originais e, ao mesmo tempo, incorpora os parâmetros estatísticos das séries analisadas, extraídos durante a normalização inicial. Assim, a saída do modelo apresenta os valores previstos no contexto da escala original do mercado, incluindo média, variância e outras características que permitem comparar os valores previstos com os valores reais.
Como resultado, o Codificador produz uma representação rica, multidimensional e estabilizada do mercado, pronta para ser encaminhada aos blocos do Ator e do Crítico, nos quais será utilizada para a tomada de decisões de negociação e a avaliação de sua qualidade.
As arquiteturas dos modelos do Ator e do Crítico foram reutilizadas dos trabalhos anteriores sem alterações. Por isso, não analisaremos suas estruturas em detalhes, já que o foco principal está no funcionamento do Codificador e na preparação dos atributos. O código completo com a descrição da arquitetura de todos os modelos está disponível no anexo, permitindo consultar sua estrutura interna quando necessário.
Testes
Antes de confiar dinheiro real ao modelo, testamos cuidadosamente a estratégia com dados históricos e a submetemos a diferentes cenários de mercado, como se treinássemos o modelo para avaliar riscos e tomar decisões nas mais diversas condições nas mais diversas condições.
A primeira etapa, o treinamento offline, foi realizada com dados históricos do par de moedas EURUSD no timeframe H1, entre janeiro de 2024 e junho de 2025. Esse intervalo se tornou um verdadeiro campo de treinamento para o modelo. A diversidade das condições permitiu que ele desenvolvesse a capacidade de reconhecer sinais importantes, produzir decisões de negociação robustas e manter a coerência de suas decisões mesmo nas situações mais caóticas. Nessa etapa, o modelo aprendeu não apenas a reproduzir padrões históricos, mas também a compreender os padrões do mercado, adaptando-se à velocidade das variações de preço e ao volume de negociação. Nessa etapa, o modelo atua como um trader experiente que analisa o mercado combinando intuição e estratégia.
Concluído o treinamento offline, iniciamos a segunda etapa: o ajuste fino online no Testador de Estratégia do MetaTrader 5. Nessa fase, os dados eram recebidos em tempo real, vela a vela, e o modelo aprendia a processar o fluxo do mercado vela a vela, acompanhando sua cadência em tempo real. Ele aprendia a acompanhar a velocidade e a intensidade reais das variações de preço, aprendia a manter a estabilidade diante do ruído do mercado, ajustar suas ações em períodos de baixa liquidez e reagir imediatamente a picos abruptos de preço. Essa etapa funcionou como um refinamento da estratégia. A estrutura básica, construída com os dados históricos, permaneceu inalterada, mas o modelo aprendeu a se adaptar às condições atuais do mercado, reduzindo o risco de sobreajuste e aprimorando sua capacidade de tomar decisões em um ambiente imprevisível.
A avaliação final foi realizada com os dados de julho e agosto de 2025, totalmente novos e não utilizados anteriormente. Todos os parâmetros obtidos nas etapas anteriores foram carregados sem alterações, o que permitiu avaliar de forma imparcial a capacidade de generalização do modelo. Os resultados dos testes são apresentados a seguir.


Os resultados demonstram a eficácia do treinamento e a capacidade do modelo de se adaptar às condições reais do mercado. O gráfico de Balance e Equity mostra que o modelo mantém um crescimento consistente do capital, apesar da volatilidade. As linhas de Balance e Equity praticamente coincidem, o que indica um drawdown reduzido e uma gestão de risco rigorosa.
A análise das estatísticas das operações confirma a eficácia da estratégia. O modelo apresentou um lucro líquido total de 79,72 USD, com um depósito inicial de 100,00 USD. O lucro bruto, Gross Profit, foi de 315,47 USD, enquanto a perda bruta, Gross Loss, totalizou 235,75 USD, resultando em um Profit Factor de 1,34. Isso significa que, para cada 1,00 USD de perda bruta, a estratégia gerou 1,34 USD de lucro bruto, superando as perdas em aproximadamente 0,34 USD, um resultado satisfatório para uma estratégia em tempo real.
Também é importante observar que a estratégia apresentou drawdowns moderados: o drawdown máximo do Balance foi de 30,05%, enquanto o da Equity chegou a 43,01%. Ao mesmo tempo, o Recovery Factor foi de 1,07 e o Sharpe Ratio alcançou 2,59. Esses indicadores apontam para uma boa relação entre retorno e risco.
O modelo apresentou um desempenho equilibrado em posições vendidas e compradas: foram 17 operações vendidas, com uma taxa de acerto de 64,71%, e 18 operações compradas, com uma taxa de acerto de 55,56%. Ao todo, foram realizadas 35 operações, correspondentes a 69 execuções; 21 operações foram lucrativas, equivalentes a 60%, e 14 foram não lucrativas, equivalentes a 40%.
De modo geral, os testes confirmam que a implementação das abordagens propostas e o treinamento com dados históricos, seguidos pelo ajuste fino em tempo real, permitiram que o modelo respondesse de forma eficiente às oscilações do mercado e apresentasse um comportamento estável e previsível da curva de capital.
Conclusão
Concluímos a implementação das abordagens propostas pelos autores do framework ST-Expert e as integramos com sucesso à estrutura do framework Extralonger. A arquitetura resultante permite analisar simultaneamente as representações temporais e espaciais dos dados, combinando tendências globais com anomalias locais, o que aumenta significativamente a precisão das previsões e a robustez das estratégias.
Foi dada atenção especial à reprodutibilidade e ao determinismo em cada fase do ciclo de vida dos módulos: inicialização da arquitetura, execução da propagação para frente, cálculo dos gradientes na propagação reversa e atualização dos pesos. Essa separação garante um comportamento estável do sistema em diferentes modos de operação e facilita a depuração de encadeamentos complexos de componentes.
Os testes realizados com dados históricos reais demonstraram a eficácia do modelo e sua capacidade de adaptação a diferentes condições de mercado. Os resultados confirmam que a combinação da atenção global e local em grafos com embeddings temporais e espaciais constitui uma base sólida para aplicações práticas no mercado. Essa abordagem permite que os modelos se adaptem às mudanças nas condições de mercado, mantendo alta precisão nas previsões e uma carga computacional controlada.
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 testar o modelo |
| 4 | Trajectory.mqh | Biblioteca de classe | Estrutura que descreve o estado do sistema e a 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/19663
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.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Equação simbólica para prever o preço com SymPy
Está chegando o novo MetaTrader 5 e MQL5
Redes neurais em trading: sinais de negociação robustos em qualquer regime de mercado (módulos de atenção)
- 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
Alguém poderia me dizer como resolver isso aqui —
'Math' não é uma classe, estrutura ou união VAE.mqh 93 8
'MathRandomNormal' — algum operador esperado VAE.mqh 93 14
A conversão do tipo de parâmetro 'int[1]' para 'const uint[] &' não é permitida VAE.mqh 135 55
contagem incorreta de parâmetros: 4 foram passados, mas são necessários 5 VAE.mqh 135 15
pode ser uma das 2 funções VAE.mqh 135 15
bool COpenCL::Execute(const int, const int, const uint&[], const uint&[]) OpenCL.mqh 82 22
bool COpenCL::Execute(const int, const int, const uint&[], const uint&[], const uint&[]) OpenCL.mqh 83 22
???
Alguém poderia me dar uma dica de como lidar com isso aqui? -
'Math' não é uma classe, estrutura ou união VAE.mqh 93 8
'MathRandomNormal' — algum operador esperado VAE.mqh 93 14
A conversão do tipo de parâmetro 'int[1]' para 'const uint[] &' não é permitida VAE.mqh 135 55
contagem incorreta de parâmetros: 4 foram passados, mas são necessários 5 VAE.mqh 135 15
pode ser uma das 2 funções VAE.mqh 135 15
bool COpenCL::Execute(const int, const int, const uint&[], const uint&[]) OpenCL.mqh 82 22
bool COpenCL::Execute(const int, const int, const uint&[], const uint&[], const uint&[]) OpenCL.mqh 83 22
???