Русский
preview
Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (HimNet)

Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (HimNet)

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

Introdução

O surgimento de um amplo conjunto de ferramentas para coleta de dados em fluxo levou ao acúmulo massivo de séries espaço-temporais nas mais diversas áreas da atividade humana. Em sistemas de negociação, formam-se fluxos de cotações, ordens e negócios em diferentes plataformas, tickers e tipos de instrumentos. À medida que o volume de dados cresce, a previsão precisa dessas séries se torna uma ferramenta para reduzir custos, gerir riscos e melhorar a execução de estratégias de trading. Um erro na previsão da liquidez local ou da volatilidade se transforma diretamente em slippage, lucro perdido e distorção dos indicadores da estratégia de trading. Por isso, um bom modelo deve enxergar não apenas a tendência global, mas também as diferenças locais e contextuais: onde e quando o mercado é mais rígido e onde é mais frouxo em termos de liquidez.

A principal dificuldade está na heterogeneidade espaço-temporal dos dados. No mercado, a heterogeneidade espacial aparece quando diferentes plataformas de negociação ou ativos exibem padrões distintos ao mesmo tempo. Por exemplo, algumas bolsas podem ter liquidez profunda, enquanto outras apresentam distorções temporárias de arbitragem, sobretudo em períodos de alta volatilidade. Nessas condições, tentar promediar o comportamento de todas as plataformas suaviza efeitos locais importantes e reduz a precisão.

A heterogeneidade temporal se manifesta com mais força do que parece à primeira vista. O comportamento de um mesmo instrumento financeiro muda radicalmente conforme o horário do dia e as datas do calendário. A abertura e o fechamento do pregão são dois exemplos claros: nos primeiros minutos após a abertura, observa-se um salto no volume e na volatilidade; já no fim da noite, ocorre o oposto, com baixa atividade. Notícias e anúncios macroeconômicos criam regimes curtos de volatilidade extrema. Feriados e fins de semana levam ao arrefecimento da atividade. Em conjunto, isso significa que o mesmo padrão não pode ser considerado estacionário no tempo: sua estatística muda, e o modelo precisa levar isso em conta.

Abordagens propostas anteriormente tentaram resolver o problema por caminhos diferentes, mas cada uma delas tem pontos fracos do ponto de vista da aplicação em trading. Métodos baseados em grafos usavam topologia e métricas de similaridade previamente calculadas entre pontos de dados. Isso se parece com um trader que constrói sua estratégia inteira a partir de um mapa de liquidez desenhado de antemão. No mundo real, esse mapa envelhece rapidamente: a composição dos market makers muda, surgem novas plataformas e aparecem lacunas temporárias de liquidez.

Métodos de meta-aprendizado traziam a ideia de armazenar vários conjuntos de parâmetros para diferentes regimes, mas muitas vezes exigiam informações externas. Para finanças, isso significa que seriam necessários metadados confiáveis e disponíveis sobre cada plataforma e instrumento. Mas nem sempre é possível obtê-la em tempo real. Além disso, muitas dessas abordagens exigem recursos excessivos de memória e processamento.

Abordagens baseadas em representações (Representation Learning) aprenderam a extrair embeddings informativos a partir de dados brutos. Isso é poderoso, mas muitas vezes, na sequência, os embeddings passam por um processamento simplificado, e o valor de uma boa representação se perde na etapa seguinte. O aprendizado self-supervised adiciona tarefas auxiliares para tornar os embeddings mais ricos. Isso é útil, mas nem sempre permite treinar o modelo de forma ideal para a tarefa-alvo de previsão em um único ciclo.

É exatamente por isso que os autores do trabalho "Heterogeneity-Informed Meta-Parameter Learning for Spatiotemporal Time Series Forecasting" propuseram uma abordagem diferente: o framework HimNet. A solução deles combina dois componentes principais. Primeiro, a identificação implícita da heterogeneidade por meio de embeddings espaciais e temporais treináveis; depois, o uso dessa informação para o meta-aprendizado dos parâmetros do modelo. Em termos simples, os criadores do framework não buscam apenas identificar as diferenças entre segmentos específicos dos dados, mas também treinam o modelo para escolher e gerar parâmetros únicos para cada contexto.

Imagine que, no pool de informações de trading, existam:

  1. ações de um grande emissor na bolsa principal durante o horário regular;
  2. as mesmas ações em mercados de balcão após um pico local de notícias;
  3. papéis menores com baixa liquidez;
  4. mercados de criptomoedas com diferentes profundidades de livro de ofertas.

Os embeddings espaciais separarão esses grupos em clusters distintos. Em seguida, o sistema extrai do pool de metaparâmetros conjuntos únicos de parâmetros para cada cluster. Para o mercado principal, parâmetros agressivos ajustados a condições de baixo slippage. Para as negociações de balcão, parâmetros conservadores, que ampliam a margem de segurança da execução. Papéis menores recebem parâmetros com regularização mais ampla, para não se sobreajustarem ao ruído. Já as criptos recebem um conjunto que considera o alto nível de ruído e as frequentes ineficiências de arbitragem. Tudo isso é treinado em um único ciclo, de modo que o modelo aprende simultaneamente a distinguir contextos e a escolher os melhores parâmetros para cada um deles.

Os exemplos temporais são igualmente importantes. As primeiras horas após a abertura costumam exigir reação rápida e previsões de volume de curto prazo. Os períodos posteriores à divulgação de dados macroeconômicos relevantes correspondem a um regime de volatilidade elevada. Os períodos mais avançados do dia correspondem a um regime de baixa atividade. O mecanismo de metaparâmetros proposto pelos autores do framework pode separar conjuntos específicos de parâmetros para regimes temporais. Isso se parece com a forma como um trader experiente muda seu estilo de negociação conforme o horário do dia e o calendário.

Tecnicamente, a ideia central é a clusterização dos embeddings e a quantização dinâmica dos parâmetros por meio de um pool pequeno e computacionalmente econômico de metaparâmetros. Isso resolve vários problemas das abordagens anteriores ao mesmo tempo. Em primeiro lugar, não há dependência rígida de atributos auxiliares externos: o próprio modelo identifica os contextos relevantes. Em segundo lugar, o pool de metaparâmetros é compacto, portanto os custos de memória e recursos computacionais permanecem gerenciáveis, o que é importante para sistemas com baixa latência de execução. Em terceiro lugar, os autores do framework passam da identificação passiva das heterogeneidades para seu uso ativo: os clusters determinam diretamente quais parâmetros são aplicados. Isso aumenta a precisão das previsões e melhora a qualidade da execução.

Interpretabilidade merece uma menção à parte. Os metaparâmetros e os clusters podem ser visualizados: o mapa de clusters mostrará quais plataformas e quais janelas temporais pertencem a um mesmo regime; o mapa de calor dos metaparâmetros demonstrará como as configurações diferem entre regimes de baixa e alta volatilidade. Para os profissionais, isso não é apenas um gráfico bonito, mas também uma ferramenta de controle: se um cluster incluir inesperadamente plataformas essencialmente diferentes, isso será um sinal para uma verificação adicional dos dados ou para uma revisão da etapa de pré-processamento.

Por fim, robustez e capacidade de transferência entre domínios. Uma propriedade importante do framework HimNet é sua flexibilidade entre domínios. Os metaparâmetros e os embeddings são treinados de modo a serem aplicáveis também a outros instrumentos e plataformas com ajuste fino mínimo.

Assim, o framework HimNet oferece um caminho prático e eficiente para identificar regimes espaço-temporais, mantém um pool compacto de metaparâmetros e treina o modelo para usar as informações obtidas. Isso dá a traders e desenvolvedores de sistemas de previsão uma ferramenta que, ao mesmo tempo, melhora a precisão, preserva a eficiência computacional e aumenta a interpretabilidade.



Algoritmo HimNet

O ponto central na modelagem da heterogeneidade espaço-temporal é identificar corretamente e separar o contexto dos dados de entrada no tempo e no espaço. Em vez de usar dados externos de referência, os autores do framework HimNet criam embeddings treináveis, que associam a cada contexto espaço-temporal uma representação única. Isso dá ao modelo a flexibilidade e a adaptabilidade necessárias para resolver problemas financeiros reais.

Para a dimensão temporal, os autores do framework criam dois dicionários de embeddings:

  • dicionário de hora do dia Dtod ∈ RNd * dtod;
  • dicionário de dias da semana Ddow ∈ RNw * ddow.

Aqui, dtod e ddow são as dimensões dos respectivos vetores, N d é o número de passos dentro do pregão, e N w é o número de dias na semana.

Para um minibatch dos dados de origem х Xb ∈ RB*T*N com comprimento histórico T, os autores do framework propõem usar a marca de tempo do último passo de cada amostra para selecionar, nos dicionários, os embeddings temporais Etod ∈ RB*dtod e Edow ∈ RB*ddow.. Em seguida, eles os combinam por concatenação em um embedding temporal comum.

O sentido prático dessa separação é simples. A hora do dia captura a periodicidade em alta resolução: picos matinais na abertura, calmaria no horário de almoço, sessão noturna. O dia da semana captura ciclos mais longos: comportamento em dias úteis e fins de semana, além de padrões recorrentes em torno de datas de balanços e divulgações econômicas. Juntos, esses dicionários permitem que o modelo reconheça regimes temporais em várias escalas e escolha para eles os parâmetros de previsão adequados.

Para a dimensão espacial, os autores do framework usam a matriz de embeddings espaciais Es ∈ RN*ds, em que N é o número de séries temporais ou nós espaciais, e ds é a dimensão do vetor para cada localização. Diferentemente dos dicionários temporais, aqui cada espaço está diretamente associado ao seu próprio vetor treinável, inicializado aleatoriamente. O objetivo é capturar diferenças funcionais entre plataformas que influenciam os padrões das séries: profundidade do livro de ofertas, velocidade de execução, perfil dos participantes, tipo de instrumento e características de infraestrutura. Isso permite levar em conta a heterogeneidade espacial sem o uso explícito de atributos auxiliares.

Esse treinamento dos embeddings, na prática, realiza uma clusterização dinâmica. Durante o treinamento, as representações nas matrizes gradualmente se afastam ou se aproximam em função dos dados de entrada. Embeddings associados a contextos espaço-temporais com comportamento semelhante se aproximam no espaço latente. Já aqueles com dinâmicas distintas se afastam. Como resultado, formam-se clusters naturais, que representam regimes típicos de comportamento. Isso se parece com o que ocorre em modelos de linguagem, nos quais rei e rainha ficam próximos em termos semânticos. Só que, aqui, a proximidade reflete a semelhança entre regimes de mercado. O importante é que essa clusterização surge de forma orgânica, sem restrições externas rígidas nem marcação manual. O próprio modelo aprende quais contextos são semelhantes e quais não são.

Vejamos exemplos financeiros em mais detalhes para mostrar de forma mais concreta como isso funciona na prática. Considere alguns contextos típicos:

  • Abertura da sessão principal do mercado acionário. Alta atividade, grandes volumes, oscilações rápidas de preço. Para essas combinações temporais e espaciais, o modelo atribuirá um embedding específico e escolherá parâmetros voltados para horizontes curtos e respostas rápidas. Isso é importante para algoritmos de execução rápida e para reduzir o slippage.
  • Negociação de balcão dos mesmos papéis no período noturno. Volumes baixos, negócios raros, risco elevado de saltos bruscos causados por ordens isoladas. Aqui, os embeddings temporais serão diferentes, e os metaparâmetros levarão o modelo a ser mais conservador, aumentando a robustez e reduzindo a probabilidade de sobreajuste ao ruído.
  • Bolsas de criptomoedas com diferentes profundidades de livro de ofertas. Uma bolsa tem liquidez profunda; outra, liquidez pouco profunda e distorções de arbitragem. Os embeddings espaciais separarão essas plataformas, e o modelo aplicará estratégias diferentes de previsão de volume e preço, o que é útil para sistemas de arbitragem.
  • Dias de grandes divulgações macroeconômicas ou balanços trimestrais. Os embeddings temporais de dia da semana, em conjunto com a hora do dia, destacarão essas janelas; o modelo alternará para um regime com maior sensibilidade a sinais de curto prazo e intervalos de confiança mais amplos.

A ideia central é simples e, ao mesmo tempo, prática. Nossa tarefa não é apenas registrar a heterogeneidade, mas aprender a gerenciá-la e torná-la efetivamente útil para previsões e execução nos mercados. Em vez de tentar manter um conjunto separado de parâmetros para cada minuto e para cada bolsa, o que é inviável econômica e tecnicamente, os autores do framework expandem o espaço de parâmetros do modelo ao longo das dimensões temporal e espacial e, em seguida, representam essa expansão de forma compacta por meio de pequenos pools de metaparâmetros. Para um vetor de consulta espaço-temporal específico, os parâmetros finais são gerados como uma combinação ponderada dos candidatos do pool: Θ = Q · P. Esse esquema permite trabalhar dentro de um conjunto compacto, em vez de otimizar uma quantidade enorme de parâmetros únicos, reduzindo drasticamente os requisitos de memória e processamento. Em vez de escalar com todas as séries temporais e nós espaciais, pagamos pela adaptação apenas com um pequeno número de candidatos.

Na prática, isso significa manter separadamente um pequeno pool temporal, do qual extraímos o embedding temporal Et e obtemos os metaparâmetros temporais. De modo análogo, o embedding espacial Es consulta o pool espacial e fornece metaparâmetros para cada sequência unitária. Ao mesmo tempo, não se pode esquecer das assinaturas espaço-temporais mistas: construir diretamente um pool conjunto completo seria ineficiente, por isso os autores do framework codificam os dados de entrada Xt em um embedding espaço-temporal Est = Fenc(Xt)e o usam como consulta a um pool relativamente pequeno para gerar metaparâmetros ST. Graças a essa separação, o modelo ganha flexibilidade: ele consegue selecionar parâmetros para um horário específico, para uma plataforma específica e para uma combinação simultânea específica de eventos, sem perder a capacidade de controle nem a eficiência operacional.

Esse esquema também traz vantagens práticas para o negócio. O tamanho reduzido dos pools torna a geração de metaparâmetros barata em termos de tempo de resposta, algo crítico em tarefas com requisitos rígidos de latência. O treinamento conjunto dos pools e do modelo principal permite que os gradientes passem pelas consultas aos pools, de modo que embeddings e parâmetros sejam otimizados de forma coordenada. Ao mesmo tempo, é preciso monitorar a estabilidade do treinamento e controlar a distribuição dos pesos de consulta por meio de restrição de entropia ou suavização por temperatura, para que um único candidato não capture todo o fluxo e leve a um modelo excessivamente complexo, com baixa capacidade de generalização.

A escolha do tamanho do pool é um compromisso entre adaptabilidade expressiva e risco de sobreajuste; na prática, ela é definida por validação, tomando como referência o número de regimes típicos observados nos mercados-alvo.

A arquitetura do framework HimNet se apoia na combinação de convoluções em grafos e regras recorrentes: esses blocos conseguem, ao mesmo tempo, processar o histórico dos dados de entrada e considerar as conexões entre sequências unitárias individuais. Imagine um trader que observa simultaneamente a fita de negócios e o mapa de inter-relações entre mercados, e toma decisões com base nessas duas leituras. É exatamente esse o papel da célula básica do modelo. Ela agrega sinais vizinhos no grafo e atualiza o estado oculto levando o tempo em conta.

Do ponto de vista da implementação técnica, o bloco básico é uma Graph Convolutional Recurrent Unit (GCRU) modificada. Por dentro, estão os já conhecidos gates de reset e update, o estado candidato e a combinação entre a representação oculta antiga e a nova. Mas há um detalhe importante: os parâmetros da convolução em grafos aqui não são fixos. Eles são gerados dinamicamente por meio de mecanismos metaadaptativos. Isso significa que os padrões de peso usados na agregação dos vizinhos, isto é, como uma sequência influencia outra, mudam conforme o contexto. Para o mercado financeiro, isso é crítico: as inter-relações são dinâmicas. Em períodos de reação generalizada a uma grande bolsa, essas inter-relações se tornam mais fortes. Em outro intervalo temporal, quando uma plataforma sai temporariamente da negociação, essas inter-relações enfraquecem.

O modelo é construído segundo o esquema clássico Codificador-Decodificador. O codificador no framework HimNet opera em dois fluxos paralelos, que depois são somados. Um fluxo se adapta ao tempo. Ele recebe embeddings temporais e, com base neles, gera os metaparâmetros temporais. O outro fluxo se adapta ao espaço: usando os embeddings espaciais das sequências, gera metaparâmetros para cada ponto. Essa abordagem de dois canais permite que o modelo considere simultaneamente o que está acontecendo agora e onde isso acontece. Mas os autores do framework não param por aí. Para levar em conta os efeitos espaço-temporais conjuntos, o decodificador recebe o estado latente e o projeta em um embedding espaço-temporal, que serve como vetor de consulta para o pool de metaparâmetros ST. O resultado é um conjunto de pesos recorrentes específicos para a combinação atual de tempo, lugar e dinâmica.

A matriz de adjacência adaptativa é outra parte importante. Em vez de manter uma matriz de conexões estática, o modelo a gera a partir dos embeddings espaciais: os produtos escalares dos embeddings passam por ReLU e SoftMax. O resultado é uma matriz que reflete as inter-relações atuais do mercado. O efeito prático é que, quando começa um pico em uma bolsa, o modelo reforça automaticamente a influência dessa bolsa sobre os nós vizinhos. Quando uma plataforma perde liquidez, sua influência enfraquece. Isso torna o comportamento do modelo sensível ao estado real do mercado, e não a um mapa estático que envelhece rapidamente.

O decodificador no HimNet gera iterativamente previsões para os próximos passos. Ele recebe o estado oculto inicial, aplica a célula GCRU com metaparâmetros ST e produz uma previsão para cada passo futuro. Essa abordagem permite não apenas gerar uma previsão média para todas as sequências, mas formar previsões que consideram regimes locais e janelas temporais.

O treinamento é implementado de ponta a ponta. Os autores do framework usam uma métrica simples, mas clara: MAE em todos os passos e plataformas. Isso é conveniente: a perda é medida nas mesmas unidades da variável prevista e fica mais fácil de interpretar pelo negócio. Durante o treinamento, é importante controlar a estabilidade: é útil introduzir regularização L2 para os pools de metaparâmetros, suavização por temperatura na geração dos pesos e limitação da velocidade de atualização. O tamanho dos pools é definido como um compromisso entre:

  • pequeno demais, e o modelo não cobrirá todos os regimes;
  • grande demais, e surgirá risco de especialização excessiva e aumento da carga computacional.

Do ponto de vista da aplicação prática, o HimNet traz benefícios tangíveis. Uma estimativa local precisa da liquidez reduz o slippage na execução de grandes blocos. Para sistemas de arbitragem, o HimNet fornece contexto para filtrar falsas discrepâncias e identificar divergências persistentes entre bolsas.

Por fim, os detalhes de engenharia tornam o sistema adequado para implantação em produção. A geração de metaparâmetros consiste em multiplicações de pequenas matrizes, o que não provoca crescimento explosivo do consumo de memória. Os pools são compactos e permitem manter a baixa latência necessária para tarefas com requisitos temporais rígidos. Tudo isso simplifica a integração do HimNet ao pipeline de execução de ordens e ao sistema de controle de risco.

A visualização do framework HimNet apresentada pelos autores está abaixo.


Implementação em MQL5

Após a análise detalhada dos fundamentos teóricos do framework HimNet, o passo lógico é mostrar como implementar essas ideias em código MQL5 funcional. A implementação prática não é uma transposição seca de fórmulas, mas a tradução da arquitetura para as restrições e possibilidades de uma plataforma de trading real. Vamos proceder de forma sistemática e cuidadosa: dividir a arquitetura maior em objetos independentes e bem descritos, e montar o modelo passo a passo, como um mestre que monta um mecanismo de relojoaria: com detalhe, previsibilidade e respeito por cada engrenagem. Essa abordagem facilita os testes, acelera a depuração e permite escalar a solução sem dor para diferentes mercados e timeframes.

No nível da infraestrutura MQL5, isso significa que os blocos de pré-processamento, embeddings, metaparâmetros e células recorrentes funcionam como componentes separados. Fluxos de candles e valores dos indicadores analisados são convertidos em janelas de dados bem estruturadas e de comprimento fixo. Embeddings de tempo e de sequências unitárias alimentam os módulos de geração de pesos. A célula GCRU recebe como entrada a estrutura em grafo do mercado e retorna o estado oculto atualizado. Tudo parece coerente, mas há um ponto sem o qual o HimNet perde parte de sua eficácia: a forma de processar rapidamente e com estabilidade os vizinhos no grafo a vários saltos de distância no grafo. Em termos financeiros, trata-se de capturar não apenas as influências diretas entre sequências, mas também efeitos de segunda e terceira ordem, quando um pico de liquidez em um nó se propaga pela rede e chega ao instrumento analisado com atraso e atenuação.

É aqui que entra em cena o polinômio de Chebyshev. Ele permite construir um filtro local K-hop sobre o grafo sem uma decomposição espectral pesada. Em vez de calcular autovetores, os autores do framework usam uma recursão simples que, a partir da matriz de conexões, gera uma base de matrizes T0, T1, …, TK-1. Cada uma dessas matrizes corresponde a uma camada de vizinhança: do próprio nó até a vizinhança de ordem K. Como resultado, uma operação de convolução se transforma em uma combinação linear bem estruturada de várias transformações previamente preparadas. Para trading, isso é crítico: menos atraso, menor consumo de memória, funcionamento numérico estável em séries longas e com N grande. Quando o mercado fica ruidoso, esse filtro ajuda a destacar conexões persistentes e a não reagir a picos aleatórios.

Para que essa ideia funcione em produção, precisamos de um objeto separado que construa rapidamente, a partir da matriz de adjacência atual, as matrizes do polinômio de Chebyshev da ordem necessária. Ele se tornará o motor silencioso de toda a camada em grafo: recebe como entrada a matriz de suporte adaptativa, construído a partir dos embeddings, e retorna um conjunto compacto de Tk, pronto para uso na GCRU.

O primeiro passo do nosso trabalho prático é fazer alterações no programa OpenCL. A GPU é ideal para essa tarefa: o multithreading e as operações vetoriais permitem processar grandes matrizes de adjacência muito mais rápido do que na CPU. Isso é especialmente crítico para dados financeiros, em que o tamanho do grafo pode ser bastante grande e conter uma estrutura de conexões ramificada.

O algoritmo do kernel de propagação para frente ChebStep é uma espécie de coração do mecanismo de geração dos polinômios de Chebyshev, executado diretamente no núcleo do OpenCL. Ele se parece muito com o funcionamento coordenado de um matching engine de bolsa, no qual cada tick e cada ordem se encaixam em uma lógica única.

__kernel void ChebStep(__global const float* support,
                       __global float* outputs,
                       const int step
                      )
  {
   const size_t l = get_local_id(0);
   const size_t r = get_global_id(1);
   const size_t c = get_global_id(2);
   const size_t total_l = get_local_size(0);
   const size_t total_r = get_global_size(1);
   const size_t total_c = get_global_size(2);

O programa começa obtendo as coordenadas no espaço de execução. Os identificadores locais e globais das threads são nossos traders de bolsa, cada um responsável por uma parte específica da matriz. Eles trabalham em paralelo para que todo o array de dados seja processado não de forma sequencial, como nas antigas negociações manuais, mas de maneira síncrona, como em um sistema automatizado de trading de alta frequência. O buffer local Temp desempenha o papel de um centro de compensação temporário: é nele que são acumuladas as somas intermediárias, que depois são sincronizadas e formam o resultado final.

   __local float Temp[LOCAL_ARRAY_SIZE];
//---
   if(step <= 0 || total_r != total_c)
      return;

O algoritmo impõe imediatamente uma restrição importante: se o passo for menor ou igual a zero, ou se a matriz não for quadrada, não há sentido em continuar os cálculos. É o mesmo caso de um trader que recusa uma operação quando as condições de mercado não atendem às regras da estratégia.

Em seguida, começa a hierarquia sequencial de construção das potências dos polinômios de Chebyshev. No primeiro passo, tudo é simples: forma-se a matriz identidade, com a diagonal preenchida por uns. É como o capital inicial: a base a partir da qual tudo começa.

if(step <= 3)
  {
   const float diag = (r == c ? 1.0f : 0.0f);
   if(l == 0)
      outputs[RCtoFlat(r, c, total_r, total_c, 0)] = diag;

No segundo passo, entra em jogo a matriz de adjacência original, o mapa de conexões entre os vértices do grafo, refletindo a estrutura real do mercado. Ela é adicionada ao array de saída e, já aqui, começa a aproximação do movimento real das cotações.

if(step < 2)
   return;
if(l == 0)
  {
   const float s = IsNaNOrInf(support[RCtoFlat(r, c, total_r, total_c, 0)], 0);
   outputs[RCtoFlat(r, c, total_r, total_c, 1)] = s;
  }

No terceiro passo, forma-se a segunda potência: o quadrado da matriz. O código multiplica linhas e colunas em um laço, somando cuidadosamente os produtos. Isso se parece com a análise de correlações cruzadas entre ativos, quando buscamos não apenas a dependência direta, mas também conexões indiretas por meio de terceiros elos.

A matriz resultante reflete interações mais complexas, nas quais a influência não se limita às conexões diretas, mas se propaga pelos vizinhos. Após a soma, aplica-se a normalização e o ajuste para remover redundâncias, como um trader que filtra o ruído das cotações e preserva apenas os sinais relevantes.

 if(step < 3)
    return;
 float out = 0;
 for(int t = 0; t < total_c; t += total_l)
   {
    const float s1 = IsNaNOrInf(support[RCtoFlat(r, t + l, total_r, total_c, 0)], 0);
    const float s2 = IsNaNOrInf(support[RCtoFlat(t + l, c, total_r, total_c, 0)], 0);
    out += IsNaNOrInf(s1 * s2, 0);
   }
 out = 2 * LocalSum(out, 0, Temp);
 if(l == 0)
   {
    out -= diag;
    outputs[RCtoFlat(r, c, total_r, total_c, 2)] = IsNaNOrInf(out, 0);
   }
 return;
}

Se o passo for maior que três, o algoritmo passa para o nível da fórmula recorrente geral dos polinômios de Chebyshev. Aqui, cada nova matriz não é construída do zero, mas a partir de uma combinação das potências anteriores. O código toma a matriz de adjacência, multiplica-a pelo resultado do passo anterior e ajusta o resultado subtraindo uma matriz ainda mais antiga. Esse processo lembra a construção de indicadores de trading complexos: por exemplo, médias móveis exponenciais levam em conta o estado anterior, suavizando oscilações bruscas, mas sem esquecer o histórico.

   float out = 0;
   for(int t = 0; t < total_c; t += total_l)
     {
      if((t + l) >= total_c)
         continue;
      const float s1 = IsNaNOrInf(support[RCtoFlat(r, t + l, total_r, total_c, 0)], 0);
      const float s2 = IsNaNOrInf(outputs[RCtoFlat(t + l, c, total_r, total_c, step - 2)], 0);
      out += IsNaNOrInf(s1 * s2, 0);
     }
   out = 2 * LocalSum(out, 0, Temp);
   if(l == 0)
     {
      out -= IsNaNOrInf(outputs[RCtoFlat(r, c, total_r, total_c, step - 3)], 0);
      outputs[RCtoFlat(r, c, total_r, total_c, step - 1)] = IsNaNOrInf(out, 0);
     }
   return;
  }

Assim, passo a passo, o modelo constrói aproximações cada vez mais profundas das propriedades espectrais do grafo. 

Cada iteração no kernel termina com a sincronização dos dados e uma verificação de consistência. O código utiliza uma função de proteção contra NaN e infinitos, um lembrete de que o mercado não perdoa erros. Assim como a gestão de risco controla os limites de perda, aqui qualquer anomalia é filtrada logo no início, para não distorcer o resultado geral.

Como resultado dessa lógica passo a passo, obtemos uma sequência de polinômios de Chebyshev, que se tornam o material de construção dos filtros espectrais em redes neurais baseadas em grafos. Na prática, isso significa que o algoritmo não apenas observa o mercado pela ótica das conexões atuais, mas constrói uma perspectiva multinível, capaz de capturar dependências locais e distantes entre instrumentos. É algo parecido com a capacidade de um analista experiente de enxergar, no movimento do ouro, um sinal indireto para o mercado cambial, ou na dinâmica dos títulos, um prenúncio de mudanças nas ações.

Depois de analisarmos o algoritmo de construção direta dos polinômios de Chebyshev e vermos como sua estrutura computacional é montada passo a passo, é natural passar ao mecanismo de retropropagação do erro. Aqui entra em cena um kernel especial, responsável por distribuir cuidadosamente os gradientes pelos polinômios de Chebyshev e até a matriz de adjacência, garantindo o treinamento correto de todo o modelo.

__kernel void ChebStepGrad(__global const float* support,
                           __global float* support_g,
                           __global const float* outputs,
                           __global float* outputs_g,
                           const int step
                          )
  {
   const size_t l = get_local_id(0);
   const size_t r = get_global_id(1);
   const size_t c = get_global_id(2);
   const size_t total_l = get_local_size(0);
   const size_t total_r = get_global_size(1);
   const size_t total_c = get_global_size(2);
//---
   __local float Temp[LOCAL_ARRAY_SIZE];
//---
   if(step < 1 || total_r!=total_c)
      return;

No início da execução, o algoritmo também inicializa os identificadores locais e globais que, como cotações de bolsa, determinam a posição de cada unidade computacional na enorme grade de threads paralelas. A memória local é alocada para um array temporário, a fim de acelerar as reduções, enquanto as primeiras checagens garantem que o passo esteja correto e que a matriz tenha formato quadrado. Assim como um trader nunca analisaria um livro de ofertas incompleto, aqui o programa interrompe imediatamente a execução em condições inválidas.

Quando o passo de obtenção dos gradientes do erro é maior ou igual a 2, começa a parte mais interessante. Primeiro, o gradiente do passo seguinte é utilizado e subtraído diretamente do nível anterior das saídas. Esse momento pode ser comparado a uma correção da previsão: se, no último tick, a estratégia gerou um erro, sua sombra é projetada sobre o passo anterior.

if(step >= 2)
  {
   float grad = IsNaNOrInf(outputs_g[RCtoFlat(r, c, total_r, total_c, step)], 0);
   if(l == 0)
      outputs_g[RCtoFlat(r, c, total_r, total_c, step - 2)] -= grad;

Em seguida, o algoritmo calcula os gradientes em relação à matriz de adjacência. Cada thread pega sua parte dos dados e multiplica o erro das saídas pelos valores correspondentes do passo anterior, somando tudo cuidadosamente por meio da memória local. No fim, forma-se um sinal de correção, parecido com o reajuste dos pesos em uma carteira: quando alguns ativos ficam sobrecarregados, sua influência é redistribuída gradualmente.

//--- support grad
grad = 0;
for(int t = 0; t < total_c; t += total_l)
  {
   if((t + l) >= total_c)
      continue;
   const float s2 = IsNaNOrInf(outputs[RCtoFlat(c, t + l, total_r, total_c, step - 2)], 0);
   grad += IsNaNOrInf(outputs_g[RCtoFlat(r, t + l, total_r, total_c, step)] * s2, 0);
  }
grad = LocalSum(grad, 0, Temp);
if(l == 0)
   outputs_g[RCtoFlat(r, c, total_r, total_c, 1)] += grad;
BarrierLoc;

Após a sincronização das threads, chega o momento da etapa seguinte: o cálculo do gradiente em relação ao polinômio Tk-1. Aqui, tudo funciona por analogia: cada thread processa uma parte da matriz de adjacência, multiplica-a pelos erros do passo seguinte e adiciona cuidadosamente sua contribuição à soma total. Como resultado, forma-se uma correção para os gradientes dos polinômios no nível k-1. Esse processo se parece com o momento em que um analista volta um passo em seu modelo e verifica se houve superestimação do risco, acrescentando o ajuste correspondente.

 //--- T(k-1) grad
 grad = 0;
 for(int t = 0; t < total_c; t += total_l)
   {
    if((t + l) >= total_c)
       continue;
    const float s2 = IsNaNOrInf(support[RCtoFlat(t + l, r, total_r, total_c, 0)], 0);
    grad += IsNaNOrInf(outputs_g[RCtoFlat(t + l, c, total_r, total_c, step)] * s2, 0);
   }
 grad = LocalSum(grad, 0, Temp);
 if(l == 0)
    outputs_g[RCtoFlat(r, c, total_r, total_c, step - 1)] += grad;
}

Observe: todos os gradientes do erro são cuidadosamente reunidos em um tensor correspondente aos polinômios de Chebyshev. Para ele também vão os gradientes intermediários da matriz de adjacência, calculados em cada etapa da propagação reversa. É importante lembrar que a própria matriz de adjacência fica armazenada dentro do polinômio de primeira ordem, o que torna esse nível o principal ponto de convergência dos fluxos de dados. E só ao chegar a ele transferimos o gradiente do erro acumulado para o buffer da matriz de adjacência, como se fixássemos o saldo final após uma série de ajustes intermediários.

Assim, esse kernel assume o papel de uma espécie de "auditor" dos cálculos, impedindo que o erro se perca ou se dilua no caminho de volta. Cada valor passa por um sistema de somas locais, é ajustado e encaminhado para onde pode alterar os pesos em favor de uma previsão mais precisa. E, assim como no mercado cada décimo de ponto percentual extra pode ter papel decisivo, aqui a distribuição cuidadosa do gradiente ao longo dos passos permite que o modelo aprenda de forma estável e não saia do rumo.

Agora, com a base pronta, isto é, a geração das matrizes e a distribuição dos gradientes, surge uma pergunta natural: quem vai gerenciar todo esse processo no programa principal? É preciso um objeto que assuma o papel de uma espécie de despachante, encapsulando cuidadosamente os kernels OpenCL de baixo nível e fornecendo uma interface conveniente para interação com os demais módulos do modelo. Assim surge a classe CChebPolinom, que herda as interfaces básicas do objeto de camada totalmente conectada CNeuronBaseOCL.

class CChebPolinom   :  public CNeuronBaseOCL
  {
protected:
   uint              iDimension;
   uint              iSteps;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { return true; }
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CChebPolinom(void)   {};
                    ~CChebPolinom(void)   {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension,
                          uint steps, ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual bool      Save(const int file_handle) override;
   virtual bool      Load(const int file_handle) override;
   //---
   virtual int       Type(void) override const  {  return defChebPolinom; }
   virtual uint      GetDimension(void) const { return iDimension; }
   virtual uint      GetSteps(void) const { return iSteps; }
  };

Internamente, ela armazena os parâmetros principais: a dimensão do espaço de representação e o número de passos necessários para a decomposição pelos polinômios de Chebyshev. Esses parâmetros definem os limites dos cálculos e a profundidade que o modelo pode alcançar ao operar com a estrutura em grafo.

A inicialização do objeto é implementada no método Init, que permite definir todos os parâmetros necessários.

bool CChebPolinom::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint dimension,
                        uint steps, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, dimension * dimension * steps,
                                                              optimization_type, batch))
      return false;
//---
   iDimension = dimension;
   iSteps = steps;
//---
   return true;
  }

O algoritmo do método é bastante simples. Primeiro, passamos o controle para o método homônimo da classe pai, indicando uma dimensionalidade do objeto suficiente para armazenar o tensor concatenado de todos os polinômios de Chebyshev necessários. Em seguida, o objeto armazena internamente dois parâmetros principais: a dimensionalidade iDimension e o número de passos iSteps. Esses valores se tornam uma espécie de ficha de identificação da classe, que servirá de referência para todos os demais métodos. Pode-se dizer que, aqui, formamos definitivamente o espaço de trabalho: definimos tanto a largura da tela quanto o número de camadas pelas quais a informação irá passar.

O retorno de true no fim do método confirma que o objeto foi preparado com sucesso para a operação. E, embora a implementação pareça concisa, é justamente nessa etapa que se resolve uma questão essencial: se o nosso algoritmo conseguirá implementar com eficiência a aproximação polinomial na GPU.

O método de propagação para frente feedForward, em essência, desempenha o papel de um despachante, apenas encapsulando e estruturando a chamada do kernel correspondente do programa OpenCL. Sua estrutura é bastante reconhecível: verificação dos dados de entrada, preparação dos parâmetros de execução e chamada sequencial do kernel. No entanto, a especificidade da tarefa, ou seja, a geração e a aplicação dos polinômios de Chebyshev, introduz aqui suas particularidades.

bool CChebPolinom::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL || NeuronOCL.Neurons() != (iDimension * iDimension))
      return false;

Primeiro, o método verifica se um objeto válido foi fornecido como entrada e se o número de neurônios coincide com o tamanho da matriz de adjacência iDimension * iDimension. Se essa condição não for satisfeita, a execução posterior simplesmente deixa de fazer sentido.

Em seguida, vem a preparação dos intervalos de trabalho: as dimensões globais da grade de cálculo são definidas de modo a considerar a restrição real da GPU quanto ao tamanho do grupo local, além de refletir a estrutura matricial da tarefa. Na prática, as três dimensões distribuem a grade de cálculo pelos eixos da matriz.

   uint global_work_offset[3] = { 0 };
   uint global_work_size[3] = { MathMin(iDimension, uint(OpenCL.GetMaxLocalSize(0))),
                                iDimension, iDimension
                              };
   uint local_work_size[3] = { global_work_size[0], 1, 1 };

O elemento central aqui é o laço pelos passos. É ele que reflete a natureza recursiva da construção dos polinômios de Chebyshev: para obter o valor do polinômio no nível k, é preciso se apoiar nos valores anteriores. A partir do menor passo válido, normalmente o segundo, já que os dois primeiros são definidos pelas condições de base, o método fornece sequencialmente o número do passo ao kernel e, em seguida, inicia a execução. Assim, a cada iteração, a GPU recebe uma nova tarefa: construir o próximo nível da aproximação polinomial.

//---
   uint kernel = def_k_ChebStep;
   setBuffer(kernel, def_k_cheb_support, NeuronOCL.getOutputIndex())
   setBuffer(kernel, def_k_cheb_outputs, getOutputIndex())
   for(int step =::MathMin(2, MathMax(int(iSteps) - 1, 0)); step < int(iSteps); step++)
     {
      setArgument(kernel, def_k_cheb_step, step + 1)
      kernelExecuteLoc(kernel, global_work_offset, global_work_size, local_work_size)
     }
//---
   return true;
  }

O resultado é uma estrutura aninhada de cálculos: cada passo depende do anterior, mas toda a execução é paralelizada pelos elementos da matriz. Nos mercados financeiros, essa abordagem lembra uma estratégia de construção de previsões com base em horizontes móveis: cada nova previsão não é formada do zero, mas como continuação da anterior, refinando e aprofundando a leitura do quadro.

É justamente aí que está a força desse método: conciso na forma, ele combina a simplicidade da lógica de despacho com uma recursão matemática profunda.

O método responsável pela distribuição dos gradientes do erro é construído segundo um princípio semelhante. Como na propagação para frente, sua principal tarefa é estruturar corretamente as chamadas do kernel correspondente do programa OpenCL, levando em conta a estrutura recursiva dos cálculos dos polinômios de Chebyshev. Todos os gradientes acumulados são cuidadosamente somados e distribuídos pelos passos correspondentes, com atenção especial à matriz de adjacência e aos polinômios intermediários.

A análise detalhada das linhas desse método pode ser feita separadamente. O código-fonte completo da classe, incluindo todos os seus métodos, está disponível no anexo, o que permite formar uma visão integral do funcionamento desta parte do framework.

O volume de material já é considerável, e agora é um bom momento para fazer uma breve pausa, a fim de sistematizar e organizar todas as informações obtidas. Uma decomposição detalhada ajudará a compreender melhor os princípios de funcionamento e as inter-relações entre os componentes. A continuação da construção dos algoritmos do framework HimNet, incluindo a integração prática de todos os módulos, será abordada no próximo artigo.



Conclusão

Neste artigo, conhecemos os aspectos teóricos do framework HimNet e avançamos para a implementação prática das abordagens propostas com MQL5 e OpenCL. Analisamos em detalhes o conceito de metaparâmetros espaço-temporais. Vimos como os blocos recorrentes com convolução em grafos conseguem considerar dependências temporais e espaciais, além de examinar os algoritmos de geração e aplicação dos polinômios de Chebyshev na GPU para acelerar os cálculos e aumentar a robustez do modelo.

No próximo artigo, continuaremos a desenvolver os algoritmos do framework HimNet.


Referências


Programas usados no artigo

# Nome Tipo Descrição
1 Study.mq5 Expert Advisor EA para treinamento offline de modelos
2 StudyOnline.mq5 Expert Advisor EA para treinamento online de modelos
3 Test.mq5 Expert Advisor EA para teste do modelo
4 Trajectory.mqh Biblioteca de classe Estrutura para descrição do estado do sistema e da arquitetura dos modelos
5 NeuroNet.mqh Biblioteca de classe Biblioteca de classes para criação 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/19233

Arquivos anexados |
MQL5.zip (3011.64 KB)
Análise da variação por hora dos símbolos de negociação e de seus spreads no MetaTrader 5 Análise da variação por hora dos símbolos de negociação e de seus spreads no MetaTrader 5
O indicador de índice de sazonalidade ProSpread com média móvel é uma ferramenta de análise técnica que identifica padrões sazonais de movimento dos preços, analisa o comportamento dos preços em horários específicos de negociação, pode trabalhar tanto com um único instrumento quanto com o spread entre dois ativos e também representa visualmente a probabilidade estatística de movimentos direcionados.
Treinamento de um U-Transformer não linear nos resíduos de um modelo autorregressivo linear Treinamento de um U-Transformer não linear nos resíduos de um modelo autorregressivo linear
O artigo apresenta um sistema híbrido inovador para previsão de taxas de câmbio, que combina um modelo autorregressivo linear com a arquitetura U-Transformer para análise dos resíduos. O sistema alterna automaticamente entre as fontes de sinais conforme a qualidade de cada uma e inclui uma lógica de negociação completa, com estratégias de averaging/pyramiding. A principal vantagem da abordagem está no fato de a rede neural ser treinada nos resíduos do modelo linear, o que simplifica a tarefa e reduz o risco de sobreajuste. A implementação foi feita integralmente em MQL5 e está pronta para uso em negociação real, com adaptação automática às mudanças nas condições de mercado.
Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (Componentes principais) Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (Componentes principais)
Neste artigo, analisamos em detalhes os algoritmos de implementação dos principais componentes do framework HimNet. Mostramos como é possível alcançar alta consistência e capacidade de controle sobre todo o sistema com um número mínimo de componentes treináveis. A implementação apresentada se destaca pela estrutura compacta e transparente, o que facilita sua adaptação a tarefas reais de mercado.
Algoritmo de Otimização por Sonhos: Dream Optimization Algorithm (DOA) Algoritmo de Otimização por Sonhos: Dream Optimization Algorithm (DOA)
Algoritmo populacional de otimização inspirado em um fenômeno controverso e pouco estudado: o mecanismo dos sonhos humanos. Grupos de agentes com diferentes níveis de "memória", modulação cossenoidal do movimento e uma distribuição incomum entre fases na proporção 99/1: descubra como essas características influenciam a eficiência da otimização das suas estratégias de trading.