English Русский 中文 Español Deutsch 日本語
preview
Recursos do Assistente MQL5 que você precisa conhecer (Parte 66): Uso de padrões FrAMA e Force Index com kernel de produto escalar

Recursos do Assistente MQL5 que você precisa conhecer (Parte 66): Uso de padrões FrAMA e Force Index com kernel de produto escalar

MetaTrader 5Sistemas de negociação |
19 0
Stephen Njuki
Stephen Njuki

Introdução

No artigo anterior, em que apresentamos essa combinação de indicadores como fonte de sinais para entrada em posição de um EA, os resultados dos testes forward não foram animadores. Apontamos algumas possíveis razões e também observamos que o treinamento e a otimização foram realizados com apenas um ano de dados, embora seja fundamental testar qualquer padrão da forma mais abrangente possível e com um volume considerável de dados históricos. Dando continuidade a esse trabalho, como de costume, analisaremos os padrões que passaram pelo teste forward e aplicaremos aprendizado de máquina a eles.

Ao implementar algoritmos de aprendizado de máquina em MQL5, o OpenCL é sempre uma opção disponível, mas seu uso frequentemente exige uma unidade de processamento gráfico (GPU). Ao mesmo tempo, o ecossistema de bibliotecas Python tornou-se bastante amplo, permitindo obter várias vantagens usando apenas a CPU. É justamente essa abordagem que exploramos nesta série de artigos. Neste trabalho, assim como em alguns dos anteriores, implementamos nossas redes neurais em Python, pois tanto a programação quanto o treinamento nesse ambiente são bastante eficientes.

Dos dez padrões que otimizamos ou treinamos no artigo anterior, apenas o sexto e o nono passaram pelo teste forward. Por isso, prosseguiremos com os testes usando uma rede neural, como nos artigos anteriores, mas desta vez empregando uma rede neural convolucional (convolutional neural network, CNN). Nessa CNN, implementaremos um kernel de produto escalar (dot-product kernel). Antes disso, porém, como de costume nas implementações em Python, definiremos as funções dos indicadores necessárias para alimentar nossa rede com os sinais.


Função da média móvel adaptativa fractal (FrAMA)

A FrAMA é uma média móvel dinâmica que ajusta seu nível de suavização de acordo com a dimensão fractal dos movimentos de preço. Em geral, isso a torna mais sensível a movimentos significativos do preço e menos suscetível ao ruído. Implementamos essa função em Python da seguinte forma:

def FrAMA(df, period=14, price_col='close'):
    """
    Calculate Fractal Adaptive Moving Average (FrAMA) for a DataFrame with price data.
    
    Args:
        df: Pandas DataFrame with a price column (default 'close').
        period: Lookback period for fractal dimension (default 20).
        price_col: Name of the price column (default 'close').
        
    Returns:
        Pandas DataFrame with a single column 'main' containing FrAMA values.
    """
    prices = df[price_col]
    frama = pd.Series(index=prices.index, dtype=float)
    
    for t in range(period, len(prices)):
        # 1. High-Low range (volatility proxy)
        high = prices.iloc[t-period:t].max()
        low = prices.iloc[t-period:t].min()
        range_hl = high - low
        
        # 2. Fractal Dimension (simplified)
        fd = 1.0 + (np.log(range_hl + 1e-9) / np.log(period))  # Avoid log(0)
        
        # 3. Adaptive EMA smoothing factor
        alpha = 2.0 / (period * fd + 1)
        
        # 4. Update FrAMA (recursive EMA)
        frama.iloc[t] = alpha * prices.iloc[t] + (1 - alpha) * frama.iloc[t-1]
    
    return pd.DataFrame({'main': frama})

A função descrita acima oferece flexibilidade na entrada de dados, pois recebe um DataFrame do pandas e permite configurar tanto a coluna de preços quanto o período. Ela utiliza como indicador de volatilidade a faixa entre as máximas e mínimas para estimar a volatilidade do mercado e também calcula uma dimensão fractal simplificada para medir a complexidade dos movimentos de preço. A FrAMA é atualizada por meio de uma EMA recursiva.

Ao analisarmos o código, a primeira etapa consiste em extrair do DataFrame de entrada a coluna de preços especificada. Esse procedimento permite que a função opere com qualquer coluna de preços escolhida pelo usuário. Assim, evitamos valores definidos de forma fixa no código e mantemos a compatibilidade com diferentes estruturas de dados. Com essa abordagem, pode ser interessante aprimorar a função verificando se a coluna de preços existe no DataFrame de entrada e se os dados nela contidos são válidos, sem valores NaN nem dados ausentes, já que a FrAMA é sensível aos dados de preço usados como entrada.

Em seguida, inicializamos uma Series vazia do pandas com o mesmo índice. Isso pré-aloca memória para os valores da FrAMA e mantém seu alinhamento com o índice do DataFrame de entrada, facilitando as atualizações iterativas. Como esse método exige uma janela retrospectiva para realizar os cálculos, os primeiros valores da FrAMA são NaN e precisam ser tratados ou substituídos por zero. Também é importante garantir a consistência do índice dos preços, inclusive na correspondência entre preço e tempo, para evitar desalinhamentos.

Depois disso, entramos em um laço for, no qual calculamos a faixa entre a máxima e a mínima do período analisado para estimar a volatilidade. Em cada passo temporal t, obtemos os preços máximo e mínimo do respectivo período e calculamos a diferença entre eles. Essa faixa funciona como um indicador da volatilidade do mercado, algo importante para determinar a dimensão fractal. Uma faixa maior indica volatilidade mais elevada, influenciando o comportamento adaptativo da FrAMA. Em uma implementação prática, um mecanismo de tratamento de erros que monitore valores NaN também pode ajudar, até certo ponto, a lidar com dados ausentes.

Em seguida, calculamos uma dimensão fractal simplificada usando a relação logarítmica da faixa entre a máxima e a mínima. O termo epsilon 1e-9 evita divisões por zero ou o cálculo do logaritmo de zero quando a faixa hl é igual a zero. A dimensão fractal, fd, mede a complexidade ou o grau de tendência dos preços. Um valor de fd mais alto, próximo de 2, indica um mercado mais instável e ruidoso. Por outro lado, um valor de fd mais baixo, próximo de 1, indica um mercado tendencial. Dessa forma, esse parâmetro determina o comportamento adaptativo da FrAMA. O valor 1e-9 garante estabilidade numérica e é suficientemente pequeno para não distorcer os resultados. Além disso, a dimensão fractal usada aqui é uma versão simplificada; em implementações mais sofisticadas, podem ser empregados métodos como a contagem de caixas. Depois disso, calculamos o coeficiente de suavização, alfa.

Esse é o coeficiente de suavização da média móvel exponencial, determinado com base na dimensão fractal e no período. Ele é importante porque o alfa adaptativo define a rapidez com que a FrAMA reage aos novos preços. Um fd mais alto, característico de mercados mais ruidosos, reduz alfa, fazendo com que a EMA responda mais lentamente e filtre melhor o ruído. Já um fd mais baixo, típico de mercados tendenciais, aumenta alfa, tornando a FrAMA mais sensível às variações de preço.

Assim, essa fórmula estabelece um equilíbrio entre capacidade de resposta e suavização. O período pode ser ajustado para controlar a sensibilidade. É importante garantir que o denominador period * fd + 1 permaneça positivo, evitando problemas de divisão.

A linha seguinte do código atualiza o valor da FrAMA no instante t usando a fórmula da EMA, que consiste em uma combinação ponderada entre o preço atual e o valor anterior da FrAMA. Isso é importante porque o cálculo recursivo da EMA constitui a base da FrAMA e gera uma média móvel adaptativa suavizada, cujo comportamento se ajusta às condições do mercado por meio de alfa. Entre os aprimoramentos possíveis dessa implementação estão, em primeiro lugar, a verificação de valores NaN na FrAMA e o tratamento adequado dos casos de limite do buffer da FrAMA.

A última linha da função FrAMA retorna seus valores como um DataFrame do pandas com uma única coluna chamada main. Isso padroniza o formato de saída e facilita a compatibilidade com outras ferramentas de análise técnica ou bibliotecas de visualização. O uso do nome main é uma prática comum em indicadores com um único buffer. Ao integrar a função a outros sistemas em que esse nome seja usado para acessar a saída do indicador, ele pode ser alterado para FrAMA ou para uma denominação mais específica. Também é importante garantir que o DataFrame retornado preserve o mesmo índice do DataFrame de entrada.


Função do oscilador Force Index

O oscilador mede a força dos movimentos de preço combinando as variações de preço com o volume negociado. Uma média móvel exponencial (EMA) é usada para destacar tendências ou reversões. A implementação em Python é a seguinte:

def ForceIndex(df, period=14, price_col='close', volume_col='tick_volume'):
    """
    Calculate Force Index for a DataFrame with price and volume data.
    
    Args:
        df: Pandas DataFrame with columns for price and volume (default 'close' and 'volume').
        period: Smoothing window for EMA (default 13).
        price_col: Name of the price column (default 'close').
        volume_col: Name of the volume column (default 'volume').
        
    Returns:
        Pandas DataFrame with a single column 'main' containing Force Index values.
    """
    closes = df[price_col]
    volumes = df[volume_col]
    
    # 1. Raw Force Index = Price Delta * Volume
    price_delta = closes.diff()
    raw_force = price_delta * volumes
    
    # 2. Smooth with EMA
    alpha = 2.0 / (period + 1)
    force_index = pd.Series(index=closes.index, dtype=float)
    
    for t in range(1, len(raw_force)):
        if pd.isna(raw_force.iloc[t]):
            force_index.iloc[t] = np.nan
        else:
            force_index.iloc[t] = alpha * raw_force.iloc[t] + (1 - alpha) * force_index.iloc[t-1]
    
    return pd.DataFrame({'main': force_index})

Assim como a FrAMA, nossa função oferece flexibilidade na entrada de dados, pois permite especificar as colunas de preço e volume. Ela calcula inicialmente o valor bruto do Force Index, combinando a variação do preço com o volume para estimar a pressão compradora e vendedora. Em seguida, aplica uma suavização por EMA para reduzir o ruído e destacar movimentos mais persistentes. A função também trata valores NaN. Por fim, padroniza a saída retornando um DataFrame, o que simplifica sua integração com outros componentes.

Analisando o código linha a linha, começamos extraindo do DataFrame de entrada as séries do pandas correspondentes ao preço de fechamento e ao volume. Isso é importante porque permite selecionar apenas os dados relevantes para os cálculos, mantendo flexibilidade nos nomes das colunas e eficiência no processamento. Entre os possíveis aprimoramentos estão verificar se as colunas price_col e volume_col existem no DataFrame para evitar erros, além de garantir que os dados de volume não sejam negativos e que os dados de preço sejam válidos, já que o Force Index depende de ambos.

Em seguida, definimos a diferença de preços como a diferença entre preços de fechamento consecutivos. O método usado em Python retorna um buffer com essas diferenças, permitindo medir o movimento do preço. Isso é importante porque a variação do preço representa a direção e a magnitude do movimento do mercado ponderado pelo volume, um componente fundamental do Force Index. Para o primeiro índice, é necessário tratar o valor NaN, por exemplo substituindo-o por zero, além de considerar eventuais dados ausentes.

Depois calculamos o valor bruto do Force Index, dado pelo produto entre a diferença de preços e os volumes. Python simplifica bastante essa implementação, pois permite multiplicar diretamente os buffers ou arrays, de forma semelhante ao que faríamos com valores escalares. Esse produto representa a força do movimento do preço, combinando sua dinâmica com o volume para quantificar a pressão compradora ou vendedora. Uma variação expressiva do preço acompanhada de alto volume costuma indicar maior convicção do mercado. Ao realizar essa multiplicação vetorizada, além do código apresentado acima, é recomendável garantir que os buffers de volume e de variação do preço estejam alinhados pelo índice, evitando erros nos cálculos. Também é importante observar que o Force Index bruto pode apresentar bastante ruído, motivo pelo qual a suavização pode ser aplicada posteriormente.

Em seguida, definimos o valor de alfa. Trata-se do coeficiente de suavização da EMA, calculado a partir do período especificado pela fórmula padrão 2/(N + 1). Esse coeficiente determina o peso atribuído aos dados mais recentes em relação ao histórico no Force Index suavizado. Quanto maior o período, menor tende a ser alfa e, consequentemente, mais suave o resultado. Usamos como padrão o período 14, um dos mais comuns para esse indicador. No entanto, esse valor deve ser ajustado de acordo com o timeframe utilizado. Em geral, períodos mais curtos são adequados ao trading intradiário, enquanto períodos mais longos podem ser empregados em timeframes diários ou superiores. Também é importante garantir que o período seja positivo para evitar erros de divisão por zero.

Depois inicializamos o buffer que armazenará os valores suavizados do Force Index usando o DataFrame de entrada do pandas como referência. Dessa forma, criamos um buffer vazio com o mesmo índice dos dados de preço para armazenar os valores suavizados do indicador. Além de pré-alocar memória, isso preserva o alinhamento dos índices e facilita o cálculo iterativo da EMA. É esperado que os valores iniciais sejam NaN, pois o cálculo do primeiro valor do Force Index depende de um valor anterior.

Também precisamos verificar se o Force Index bruto no instante t é NaN. O tratamento explícito de dados ausentes torna o cálculo mais robusto e evita que valores inválidos sejam propagados pela EMA. Como lacunas são comuns em dados reais, esse aspecto precisa ser considerado. Durante a depuração, também é uma boa prática sinalizar os valores NaN para facilitar sua identificação.

Em seguida, atualizamos o buffer do Force Index em cada instante t usando a fórmula da EMA. Isso combina o valor bruto atual do Force Index com o valor suavizado anterior. Essa etapa é fundamental, pois reduz o ruído do indicador bruto e ajuda a destacar tendências persistentes ou possíveis reversões. Nesse ponto, é importante garantir que force_index[t-1] tenha sido inicializado corretamente, já que as primeiras posições costumam conter valores NaN. Portanto, os casos de limite desse buffer precisam ser tratados de forma adequada.

Depois disso, retornamos os valores suavizados do Force Index como um DataFrame do pandas com uma única coluna chamada main. Como já mencionado no caso da FrAMA, a padronização do formato de saída facilita a integração e a visualização dos dados em outros formatos ou ferramentas, de acordo com as necessidades do usuário. O nome main segue uma convenção bastante comum em indicadores de análise técnica. Para garantir uma integração sem desalinhamentos, é importante que o índice do DataFrame retornado corresponda ao dos dados de entrada.

Agora que definimos em Python as funções dos indicadores, podemos passar aos dois padrões de sinal que foram aprovados no teste forward, conforme descrito no artigo anterior. São os padrões 6 e 9. Vamos representá-los como um vetor simples de 2 bits, que servirá como entrada para nossa rede neural. Esses "bits" representam as condições de alta e de baixa, e cada posição assume apenas os valores 0 ou 1. Em resumo, o vetor de entrada tem dimensão 2. Na primeira posição, registramos 0 quando nem todas as condições de alta são atendidas e 1 quando todas elas são satisfeitas. Da mesma forma, na segunda posição registramos 0 quando nem todas as condições de baixa são atendidas e 1 quando todas elas são satisfeitas.

Anteriormente, também analisamos cenários em que cada componente dos sinais de alta e de baixa era separado, produzindo vetores de entrada mais detalhados para nossas redes neurais. Os resultados obtidos a partir dessa abordagem, porém, não foram tão promissores e acabaram se aproximando mais de uma estratégia que já havíamos testado antes, na qual vários padrões eram combinados em um único EA, como discutido no artigo anterior.

Ainda assim, essa análise pode ser imprecisa. Por isso, os leitores podem usar e modificar o código-fonte fornecido e realizar seus próprios testes para chegar a conclusões independentes. O código anexado destina-se ao uso com o Assistente MQL5. O guia correspondente pode ser consultado aqui.


Função do atributo 6

Nossa implementação em Python da função do padrão 6 gera um array bidimensional de sinais binários, com valores 0 ou 1, conforme descrito acima. Começamos inicializando o array de saída com zeros. O resultado é um array NumPy bidimensional equivalente a uma matriz de duas colunas. O número de linhas dessa "matriz" é definido de acordo com o tamanho, ou comprimento, de um dos DataFrames de entrada, one_df. Essa inicialização prepara a estrutura de saída para armazenar separadamente os sinais de alta e de baixa. Como todos os valores começam em zero, nenhum sinal é gerado quando as condições do padrão não são atendidas. Em outras palavras, começamos com uma espécie de "folha em branco".

Na implementação, é importante garantir que o comprimento do primeiro DataFrame de entrada do pandas seja igual ao do segundo DataFrame, two_df, e também ao do DataFrame de preços, price_df, para evitar desalinhamentos de índice. A estrutura bidimensional é fundamental para distinguir os sinais de baixa e de alta, por isso é importante verificar se sua forma corresponde ao esperado. Nossa implementação é a seguinte:

def feature_6(one_df, two_df, price_df):
    """
    Generate binary signals based on sustained price-FrAMA alignment and Force Index momentum.
    
    Args:
        one_df: DataFrame with FrAMA values ('main' column).
        two_df: DataFrame with Force Index values ('main' column).
        price_df: DataFrame with price data ('close' column).
    
    Returns:
        2D NumPy array with bullish (column 0) and bearish (column 1) signals.
    """
    feature = np.zeros((len(one_df), 2))
    
    feature[:, 0] = ((price_df['close'] > one_df['main']) &
                     (price_df['close'].shift(1) > one_df['main'].shift(1)) &
                     (price_df['close'].shift(2) > one_df['main'].shift(2)) &
                     (two_df['main'] > 0.0) &
                     (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int)
    feature[:, 1] = ((price_df['close'] < one_df['main']) &
                     (price_df['close'].shift(1) < one_df['main'].shift(1)) &
                     (price_df['close'].shift(2) < one_df['main'].shift(2)) &
                     (two_df['main'] < 0.0) &
                     (two_df['main'].shift(1) < two_df['main'].shift(2))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Em seguida, definimos as condições de alta, ou seja, o primeiro elemento de cada linha. Retomando o que vimos no artigo anterior, temos um cenário de alta quando o preço atual está acima da FrAMA, o preço anterior também está acima da FrAMA anterior e a mesma condição era observada dois períodos antes. Além disso, o Force Index atual deve ser positivo, enquanto seus valores de um e dois períodos atrás devem estar abaixo do nível atual.

A primeira coluna representa o cenário de alta e faz com que o sinal seja acionado somente quando o preço permanece consistentemente acima da média móvel adaptativa FrAMA, indicando uma tendência de alta. O Force Index confirma a presença de forte pressão compradora. A verificação em três períodos aumenta a confiabilidade do sinal, enquanto a condição aplicada ao Force Index ajuda a eliminar movimentos fracos. Na implementação, é importante verificar se todos os DataFrames de entrada estão alinhados pelos índices e contêm dados válidos. As operações shift() geram valores NaN nas primeiras linhas, que precisam ser tratados antes que os valores dos indicadores possam ser retornados.

Na segunda coluna, o sinal de baixa é definido por condições equivalentes: o preço atual está abaixo da FrAMA; o preço anterior também está abaixo da FrAMA; a mesma condição era observada dois períodos antes; o Force Index atual é negativo; e o Force Index de um período atrás está acima tanto do valor atual quanto do valor de dois períodos atrás. Esse formato em "n" do Force Index indica aumento da pressão vendedora associada ao volume ou intensificação do sentimento de baixa.

Esse modelo representa uma tendência de alta que indica oportunidades de venda. Ele identifica tendências de baixa persistentes acompanhadas de forte pressão vendedora. A simetria mantém a lógica consistente nos dois cenários. Assim como no padrão de alta, é fundamental garantir a consistência dos dados. Também é necessário realizar testes para determinar se esse padrão ocorre com frequência suficiente. A verificação do momentum do Force Index (shift(1) < shift(2)) pode se comportar de maneira diferente em mercados de baixa e de alta. Por isso, é importante analisar o desempenho histórico para avaliar sua confiabilidade.

Em seguida, definimos como zero os valores das duas primeiras linhas do array de atributos, pois as operações .shift(1) e .shift(2) geram NaN nessas posições, tornando essas condições indefinidas. Isso evita sinais incorretos nas linhas iniciais, onde ainda não há histórico suficiente, e produz uma saída limpa e pronta para uso, sem necessidade de pós-processamento manual. Essa verificação é fundamental para garantir a robustez da implementação. Como esse padrão também pode usar mais de dois deslocamentos, por exemplo quando são comparados períodos além de 2, é importante verificar se zerar apenas as duas primeiras linhas ainda é suficiente. Isso depende do maior deslocamento utilizado. Se forem adicionados outros deslocamentos, esse tratamento deverá ser ajustado de acordo com o maior deslocamento utilizado.

Resumindo o atributo 6, o cenário de alta pressupõe que o preço permaneça acima da FrAMA por pelo menos três períodos, acompanhado de um Force Index positivo que tenha formado recentemente um padrão em "U". Isso confirma a persistência da tendência de alta. O padrão de baixa é o reflexo desse cenário: o preço permanece abaixo da FrAMA por pelo menos três períodos, enquanto o Force Index é negativo e apresenta um padrão recente em forma de "n". Isso confirma a persistência da tendência de baixa.

A principal diferença entre os dois, além da simetria e da direção oposta, está na confirmação do sentimento pelo Force Index, que exige um aumento recente de sua magnitude. Esse comportamento pode se manifestar de maneira diferente em mercados de alta e de baixa devido à assimetria dos movimentos. Em particular, os mercados tendem a cair mais rapidamente do que sobem.


Função do atributo 9

Esta implementação em Python do sinal 9 se concentra no alinhamento, ao longo de um único período, do comportamento do preço, da FrAMA e do Force Index, tornando-o mais sensível a mudanças de curto prazo do que o padrão 6. Começamos da mesma forma que na função 9, inicializando a saída-alvo com zeros. A estrutura é semelhante à usada no padrão 6: o tamanho inicial do array NumPy bidimensional de saída é definido de acordo com o comprimento de um dos DataFrames de entrada, one-df. A implementação em Python é a seguinte:

def feature_9(one_df, two_df, price_df):
    """
    Generate binary signals based on single-period momentum alignment of price, FrAMA, and Force Index.
    
    Args:
        one_df: DataFrame with FrAMA values ('main' column).
        two_df: DataFrame with Force Index values ('main' column).
        price_df: DataFrame with price data ('close' column).
    
    Returns:
        2D NumPy array with bullish (column 0) and bearish (column 1) signals.
    """
    feature = np.zeros((len(one_df), 2))
    
    feature[:, 0] = ((price_df['close'] > price_df['close'].shift(1)) &
                     (one_df['main'] > one_df['main'].shift(1)) &
                     (two_df['main'] > two_df['main'].shift(1))).astype(int)
    feature[:, 1] = ((price_df['close'] < price_df['close'].shift(1)) &
                     (one_df['main'] < one_df['main'].shift(1)) &
                     (two_df['main'] < two_df['main'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

Em ambas as funções de atributo, one-df representa o DataFrame da FrAMA, enquanto two-df corresponde ao DataFrame do Force Index. Assim, como já mencionamos no padrão 6, precisamos garantir que one-df, two-df e o DataFrame de preços tenham o mesmo comprimento para evitar incompatibilidades de forma. Essa matriz, ou array bidimensional, separa os padrões de alta e de baixa, por isso é importante verificar se o tamanho de cada linha está correto.

Em seguida, verificamos primeiro as condições de alta desse padrão. São elas: o preço atual está acima do preço de fechamento do período anterior; a FrAMA atual está acima da FrAMA anterior; e o Force Index atual está acima de seu valor anterior. Isso representa um momentum de alta de curto prazo, no qual preço, FrAMA e Force Index avançam em conjunto, indicando pressão compradora consistente. O padrão também é um pouco mais sensível do que o padrão 6, pois considera apenas um período. Isso pode torná-lo mais adequado para scalping ou operações de curto prazo.

Na utilização prática, é importante garantir a consistência dos dados em todas as entradas para evitar comparações entre elementos desalinhados. Como o padrão é sensível a variações de apenas um período, também precisamos avaliar a frequência com que o sinal é gerado, evitando excesso de operações quando os dados de mercado apresentarem muito ruído. A visualização dos sinais sobre o gráfico de preços também pode ajudar a confirmar se eles correspondem ao sentimento de mercado esperado com base no volume.

Definimos o padrão de baixa do atributo 9 pelas seguintes condições: o preço atual está abaixo do preço anterior; a FrAMA atual está abaixo da FrAMA anterior; e o Force Index atual está abaixo do Force Index anterior. Isso representa um sentimento de baixa de curto prazo, no qual os três indicadores recuam simultaneamente, indicando pressão vendedora consistente entre os três indicadores. Como a análise se concentra em apenas um período, o padrão reage a recuos de curto prazo.

Assim como no padrão de alta, é fundamental verificar a integridade dos dados e testar a frequência de geração dos sinais. Em mercados voláteis, os sinais de baixa podem ser acionados com maior frequência, por isso a análise do desempenho histórico é importante ao ajustar essa estratégia.

Em seguida, zeramos as primeiras linhas do array de saída devido ao uso do operador shift, como já mencionado, e retornamos o array como resultado da função.


CNN com kernel de produto escalar

Para ampliar os modelos baseados em FrAMA e Force Index, escolhemos como algoritmo de aprendizado de máquina uma rede neural convolucional que utiliza um kernel de produto escalar. Para isso, usamos a classe DotProductConv1D. Essa classe é um módulo de rede neural do PyTorch que combina convoluções unidimensionais com um mecanismo de atenção baseado em produto escalar, inspirado na arquitetura Transformer.

Ela processa os dados de entrada no formato [batch, channels, width] e produz um único valor para cada amostra no intervalo [0, 1]. Valores mais próximos de 0 indicam uma previsão de baixa, enquanto valores mais próximos de 1 indicam uma previsão de alta. O mecanismo de atenção baseado em produto escalar permite que a rede se concentre nas partes relevantes da sequência de entrada, enquanto as camadas convolucionais projetam os dados em um espaço adequado ao cálculo da atenção.

O uso desse kernel em uma rede neural convolucional oferece várias vantagens. Em primeiro lugar, permite direcionar seletivamente a atenção para os atributos mais relevantes. O mecanismo de atenção baseado em produto escalar calcula medidas de similaridade entre um vetor de consulta e um vetor-chave, permitindo que a rede atribua maior importância aos passos temporais ou atributos mais relevantes. Isso pode ser útil em determinadas séries temporais, nas quais alguns períodos carregam mais informação do que outros, como períodos de alta volatilidade em comparação com períodos de baixa volatilidade. Em segundo lugar, essa abordagem favorece uma compreensão global do contexto.

Ao contrário das redes neurais convolucionais tradicionais, que utilizam kernels de tamanho fixo e campos receptivos locais, o mecanismo de atenção baseado em produto escalar permite que cada passo temporal considere todos os demais passos da sequência. Dessa forma, a rede consegue capturar dependências de longo alcance sem aumentar o tamanho do kernel. Em terceiro lugar, a ponderação dinâmica, na qual os pesos de atenção são calculados dinamicamente a partir dos dados de entrada, torna o modelo adaptável a padrões que mudam ao longo dos dados, como diferentes condições de mercado dentro de uma série temporal.

Por fim, a combinação com redes CNN oferece vantagens adicionais: elas são adequadas para extrair atributos locais, enquanto o kernel de produto escalar permite identificar relações globais. O mecanismo de atenção baseado em produto escalar também apresenta boa eficiência computacional para sequências de comprimento moderado, enquanto a rede neural convolucional unidimensional reduz a dimensionalidade dos dados de entrada, tornando o modelo como um todo mais leve do que modelos Transformer completos. A implementação da classe da rede é a seguinte:

class DotProductConv1D(nn.Module):
    def __init__(self, in_channels=1, out_channels=1, kernel_size=3):
        super().__init__()
        self.kernel_size = kernel_size
        self.padding = kernel_size // 2
        
        # Projections for dot product attention (1D convolution)
        self.query = nn.Conv1d(in_channels, out_channels, kernel_size=1)
        self.key = nn.Conv1d(in_channels, out_channels, kernel_size=1)
        self.value = nn.Conv1d(in_channels, out_channels, kernel_size=1)
        
        # Output projection to produce a single value per sample
        self.proj = nn.Sequential(
            nn.Conv1d(out_channels, 1, kernel_size=1),
            nn.AdaptiveAvgPool1d(1),  # Reduce width to 1 (global average pooling)
            nn.Sigmoid()  # Ensures output in [0, 1]
        )

    def forward(self, x):
        B, C, W = x.shape  # [Batch, Channels, Width]
        
        # Compute Q/K/V (all [B, out_channels, W])
        q = self.query(x)
        k = self.key(x)
        v = self.value(x)
        
        # Dot product attention
        attn = torch.bmm(q.transpose(1, 2), k)  # [B, W, W]
        attn = F.softmax(attn / (W ** 0.5), dim=-1)  # Scaled softmax
        
        # Apply attention to values
        out = torch.bmm(v, attn.transpose(1, 2))  # [B, out_channels, W]
        
        # Project to [B, 1, 1] and squeeze to [B, 1]
        out = self.proj(out)  # [B, 1, 1]
        return out.squeeze(-1)  # [B, 1]

Primeiro, como mostrado acima, definimos essa classe como uma subclasse de "nn.module" do PyTorch. Isso permite utilizá-la como um módulo de rede neural, com gerenciamento automático dos parâmetros e suporte a GPU. Em seguida, inicializamos o módulo com parâmetros que definem os canais de entrada, os canais de saída das projeções de atenção e o tamanho do kernel usado no cálculo do preenchimento. Esses parâmetros determinam a capacidade da rede e sua compatibilidade com os dados de entrada. Depois calculamos o tamanho do preenchimento para preservar o comprimento da sequência de entrada após a convolução, usando divisão inteira para obter um preenchimento simétrico.

Em seguida, definimos três camadas convolucionais unidimensionais que serão usadas para projetar os dados de entrada nos tensores de consulta, chave e valor do mecanismo de atenção. Cada uma delas utiliza um kernel de tamanho 1 e atua como uma transformação linear em cada passo temporal. Depois definimos o pipeline de projeção da saída. Ele reduz para 1 o número de canais da saída do mecanismo de atenção. Em seguida, aplica pooling médio global para reduzir a dimensão temporal a um único valor. Por fim, converte a saída em um escalar no intervalo [0, 1].

Depois de definir essa estrutura inicial da classe, passamos à função forward. Na propagação para frente, primeiro aplicamos as camadas convolucionais de consulta, chave e valor ao tensor de entrada "x", cuja forma é "[B, C, W]". Como resultado, obtemos três tensores com a forma [B, out_channels, W]. Em seguida, calculamos os escores de atenção por produto escalar realizando uma multiplicação de matrizes em lote ("bmm") entre o tensor de consulta transposto ([B, W, out_channels]) e o tensor de chave ([B, out_channels, W]), produzindo uma matriz de atenção com a forma [B, W, W].

Depois aplicamos SoftMax escalado aos escores de atenção, dividindo-os pela raiz quadrada do comprimento da sequência para estabilizar os gradientes. Por fim, realizamos a normalização ao longo da última dimensão para obter os pesos de atenção, cuja soma é igual a 1. A saída da função forward é então calculada por meio de uma multiplicação de matrizes em lote entre "v" ([B, out_channels, W]) e a matriz de atenção transposta ([B, W, W]), resultando em uma saída com a forma "[B, out_channels, W]".

Em seguida, passamos a saída do mecanismo de atenção pelo pipeline de projeção ("self.proj"), obtendo um tensor com a forma "[B, 1, 1]". Depois, simplesmente removemos a última dimensão, de modo que o resultado retornado tenha a forma "[B, 1]".


Testes

Os resultados dos testes para os padrões dos atributos 6 e 9 são apresentados abaixo. Ambos parecem apresentar evolução positiva, mas, como sempre, nosso treinamento foi realizado com um conjunto de dados bastante limitado. Por isso, recomendamos que os leitores façam testes mais abrangentes e aprofundados antes de tirar qualquer conclusão de longo prazo.

img altr6

img altc6

Para o atributo 6.

img altr9

img altc9

Para o atributo 9.


Conclusão

Demonstramos como o aprendizado de máquina com um kernel de produto escalar pode ser aplicado para ampliar e, possivelmente, aproveitar o potencial preliminar dos sinais fornecidos pelo indicador FrAMA e pelo oscilador Force Index. Testamos apenas dois padrões, 6 e 9, o que representa uma amostra bastante limitada. No entanto, além dos outros oito padrões que não analisamos, existem várias outras formas de combinar apenas esses dois indicadores que também podem ser estudadas.

Arquivo Descrição
wz_66.mq5 EA gerado pelo Assistente MQL5, cujo cabeçalho mostra os arquivos incluídos
SignalWZ_66.mqh Arquivo da classe de sinais personalizada
66_6.0nnx Arquivo ONNX do padrão 6
66_9.onnx Arquivo ONNX do padrão 9

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18188

Arquivos anexados |
wz_66.mq5 (6.69 KB)
SignalWZ_66.mqh (11.84 KB)
66_6.onnx (29.43 KB)
66_9.onnx (29.43 KB)
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Machine Learning e Data Science (Parte 41): YOLOv8v para detectar padrões nos mercados Forex e de ações Machine Learning e Data Science (Parte 41): YOLOv8v para detectar padrões nos mercados Forex e de ações
A identificação de padrões gráficos nos mercados financeiros é uma tarefa complexa, pois exige a análise dos dados exibidos no gráfico, algo difícil de realizar em MQL5 devido às limitações relacionadas ao processamento de imagens. Neste artigo, veremos um modelo bastante interessante em Python que permite detectar padrões no gráfico com o mínimo de esforço.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 65): uso de padrões com FrAMA e Índice de Força Recursos do Assistente MQL5 que você precisa conhecer (Parte 65): uso de padrões com FrAMA e Índice de Força
A média móvel adaptativa fractal (FrAMA) e o oscilador Índice de Força (Force Index Oscillator) são mais um par de indicadores que podem ser usados em conjunto em um EA desenvolvido em MQL5. Esses dois indicadores se complementam até certo ponto, pois a FrAMA é um indicador de acompanhamento de tendência, enquanto o Índice de Força é um oscilador baseado em volume. Como sempre, usaremos o Assistente MQL5 para avaliar rapidamente o potencial da combinação dessas duas ferramentas.