English Русский 中文 Español Deutsch 日本語
preview
Recursos do Assistente MQL5 que você precisa conhecer (Parte 64): Uso de padrões dos canais DeMarker e Envelopes com kernel de ruído branco

Recursos do Assistente MQL5 que você precisa conhecer (Parte 64): Uso de padrões dos canais DeMarker e Envelopes com kernel de ruído branco

MetaTrader 5Integração |
30 0
Stephen Njuki
Stephen Njuki

Introdução

Dando continuidade ao artigo anterior, no qual combinamos o indicador DeMarker, baseado em momentum, com o Envelopes atuando como indicador de suporte/resistência, veremos como os sinais desses indicadores podem ser utilizados em machine learning. Em artigos recentes, analisamos abordagens semelhantes para combinar indicadores. Em essência, implementamos os indicadores do MQL5 em Python utilizando dados de preços importados por meio do módulo Python para MetaTrader 5. Esse módulo permite estabelecer conexão com o servidor da corretora e obter dados de preços e informações sobre os símbolos.


Indicadores em Python

O Python dispõe de diversas bibliotecas de análise técnica que podem ser facilmente importadas e utilizadas para implementar diferentes indicadores. O problema está na diversidade de padrões adotados e na ausência de determinados indicadores em algumas dessas bibliotecas. Por exemplo, a biblioteca pandas-ta não inclui o oscilador DeMarker, embora ele esteja disponível no módulo "ta", voltado para análise técnica. Por outro lado, o Envelopes está disponível no pandas-ta, mas não existe em sua forma original na biblioteca ta. Em seu lugar, a ta oferece indicadores relacionados, como as Bandas de Bollinger e o Canal de Donchian.

Curiosamente, desenvolver um indicador próprio "do zero" atualmente exige aproximadamente o mesmo esforço, se não menos, do que instalar uma dessas bibliotecas apenas para utilizar suas funções prontas. Por isso, implementamos nossas próprias funções para o oscilador DeMarker e o Envelopes. Em teoria, isso também deve permitir que o código Python seja executado um pouco mais rapidamente, graças ao menor número de referências a módulos.


Indicador DeMarker

O DeMarker é um oscilador que acompanha as condições de sobrecompra e sobrevenda de um ativo ao longo de determinado período. Como mencionado no artigo anterior, seus valores variam de 0 a 1, sendo que valores acima de 0,7 indicam sobrecompra e valores abaixo de 0,3 indicam sobrevenda. Sua implementação como função personalizada em Python é a seguinte:

def DeMarker(df, period=14):
    """
    Calculate DeMarker indicator for a DataFrame with OHLC prices
    
    Args:
        df: Pandas DataFrame with columns ['open', 'high', 'low', 'close']
        period: Lookback period (default 14)
        
    Returns:
        Pandas Series with DeMarker values
    """
    # Calculate DeMax and DeMin
    demax = df['high'].diff().clip(lower=0)
    demin = -df['low'].diff().clip(upper=0)
    
    # Smooth the values using SMA
    demax_sma = demax.rolling(window=period).mean()
    demin_sma = demin.rolling(window=period).mean()
    
    # Calculate DeMarker
    demarker = demax_sma / (demax_sma + demin_sma)
    return pd.DataFrame({'main': demarker})

As entradas da nossa função são df e period. df é um DataFrame do pandas que contém os dados de abertura, máxima, mínima e fechamento, enquanto period define o período utilizado nos cálculos do oscilador DeMarker. A função encapsula a lógica do oscilador DeMarker, garantindo modularidade e reutilização.

O cálculo de DeMax começa pela diferença entre máximas consecutivas (df['high'].diff()). Se a diferença nesse buffer for positiva, indicando que a máxima atual é superior à anterior, o valor é mantido; caso contrário, ele é definido como zero. Essa operação é realizada pela função clip (clip(lower=0)). De forma semelhante, quando a diferença entre as mínimas é negativa, ou seja, quando a mínima atual está abaixo da mínima anterior, seu valor absoluto é armazenado no buffer DeMin; caso contrário, o valor também é definido como zero por meio de clip (clip(upper=0)).

O acompanhamento dessas variações absolutas é importante porque o DeMax mede o impulso de alta ao registrar o avanço das máximas, enquanto o DeMin mede o impulso de baixa ao registrar a queda das mínimas. Essas duas medidas são fundamentais para o oscilador DeMarker, pois mostram a intensidade das variações do preço em relação aos períodos anteriores. Em Python, ao utilizar a função diff(), é essencial garantir que o DataFrame de entrada contenha um número suficiente de observações e lembrar que a primeira linha sempre apresentará um valor NaN.

Em seguida, suavizamos os valores de cada buffer. A suavização reduz o ruído em cada série de dados e torna o indicador menos sensível às oscilações de curto prazo. O método rolling(window=period).mean() calcula a média ao longo do período especificado, introduzindo um atraso coerente com o objetivo do indicador de identificar tendências de maior prazo. Os primeiros period - 1 valores serão NaN devido à quantidade insuficiente de dados para preencher a janela móvel e devem ser tratados, seja por remoção, seja pelo preenchimento com valores normalizados. A escolha do período também afeta a sensibilidade do oscilador DeMarker: quanto menor o período, maior a sensibilidade.

Depois disso, calculamos o valor do oscilador dividindo a média de DeMax pela soma da média de DeMax com a média de DeMin. Isso normaliza o indicador para o intervalo de 0 a 1, facilitando sua interpretação. Em essência, essa razão mede a força relativa dos movimentos de alta em relação à força combinada dos movimentos de alta e de baixa. Valores próximos de 1 indicam um forte impulso de alta, enquanto valores próximos de 0 indicam um impulso de baixa mais forte.

Na implementação, é fundamental evitar uma divisão por zero nessa etapa. No entanto, isso ocorre raramente, pois os preços estão em constante variação, especialmente nas máximas e mínimas. Por fim, a função retorna os valores do oscilador DeMarker como um DataFrame do pandas com uma única coluna, que optamos por chamar de main. O formato DataFrame mantém a compatibilidade com outras ferramentas analíticas baseadas em pandas e também facilita a integração com um sistema de negociação mais amplo.


Envelopes

O envelope de preços (price envelope) é um indicador de suporte/resistência que traça duas bandas, uma superior e outra inferior, em torno de uma média móvel do preço. As bandas são deslocadas por um percentual fixo de desvio, o que pode ajudar os traders a estimar as regiões de suporte e resistência quando o preço toca essas bandas. Implementação em Python:

def Envelopes(df, period=14, deviation=0.1):
    """
    Calculate Price Envelopes for a DataFrame with OHLC prices
    
    Args:
        df: Pandas DataFrame with columns ['open', 'high', 'low', 'close']
        period: MA period (default 20)
        deviation: Percentage deviation from MA (default 0.05 for 5%)
        
    Returns:
        DataFrame with columns ['upper', 'ma', 'lower']
    """
    # Calculate moving average (typically using close prices)
    ma = df['close'].rolling(window=period).mean()
    
    # Calculate upper and lower envelopes
    upper = ma * (1 + deviation)
    lower = ma * (1 - deviation)
    
    return pd.DataFrame({'upper': upper, 'ma': ma, 'lower': lower})

O código acima oferece uma forma flexível de calcular o Envelopes, com parâmetros ajustáveis de período e desvio para diferentes estratégias. A primeira etapa do cálculo consiste em obter a média móvel. Utilizamos uma média móvel simples dos preços de fechamento ao longo do período informado. Essa média móvel atua como linha central do Envelopes e representa o preço médio da tendência. O cálculo rolling(window=period).mean() retorna um vetor/buffer com os valores médios, mas, como esperado, os primeiros period - 1 valores serão NaN. Eles precisam ser tratados adequadamente.

Em seguida, calculamos os valores dos buffers das bandas superior e inferior. A banda superior do Envelopes é obtida multiplicando a média móvel por (1 + desvio). Por exemplo, se o desvio for de 10%, a banda superior corresponderá a 110% da média móvel. Como o valor padrão do desvio é 0,1, isso equivale a 10%. A banda inferior é calculada multiplicando a média móvel por (1 - desvio), o que, neste caso, corresponde a 90%.

As bandas superior e inferior definem uma faixa de preços dentro da qual se espera que o preço oscile em condições "normais". Quando o preço toca uma dessas bandas, pode surgir uma condição de rompimento ou reversão, já que essas bandas são tratadas como níveis de suporte e resistência. Essas duas situações podem indicar continuidade da tendência ou oscilação do mercado em regiões de sobrecompra ou sobrevenda.

O parâmetro de desvio controla a largura do Envelopes. Um percentual de desvio maior gera bandas mais largas, o que pode ser adequado para ativos voláteis, enquanto um desvio menor produz bandas mais estreitas para ativos mais estáveis. O desvio deve ser positivo para evitar a formação de bandas inválidas. As bandas do Envelopes são simétricas em relação à média móvel, desde que a volatilidade seja semelhante acima e abaixo da tendência. Para construir bandas assimétricas, podem ser utilizadas as Bandas de Bollinger.

O DataFrame do pandas retornado contém três colunas: upper (superior), lower (inferior) e ma (média móvel). Esse formato facilita o acesso aos três componentes e permite utilizá-los em visualizações, geração de sinais ou análises posteriores.


Atributos em Python

Por atributos, entendemos as representações vetorizadas dos sinais dos dois indicadores: o oscilador DeMarker e o Envelopes. Esses atributos servem como dados de entrada para nosso modelo de machine learning, uma rede neural recorrente que utiliza um kernel de ruído branco (white noise kernel). Esse kernel será abordado mais adiante. Essa implementação de machine learning em Python se baseia nos atributos apresentados pela primeira vez no artigo anterior, em um total de 10. Desses 10, apenas 6 passaram no teste forward: 0, 1, 5, 6, 7 e 8. São esses atributos que testaremos com nossa rede neural recorrente (Recurrent Neural Network, RNN). Portanto, precisamos implementá-los manualmente em Python antes de passá-los para a rede neural recorrente.


Atributo 0

Como vimos no artigo anterior, o atributo 0, ou padrão 0, gera sinais com base no cruzamento dos níveis de sobrecompra ou sobrevenda pelo indicador DeMarker, combinado ao cruzamento das bandas superior ou inferior do Envelopes pelo preço. Em Python, implementamos esse comportamento da seguinte forma:

def feature_0(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:
    feature[:, 0] = ((dem_df['main'] <= 0.3) &
                     (price_df['close'] > env_df['lower']) &
                     (price_df['close'].shift(1) <= env_df['lower'].shift(1)) &
                     (price_df['close'].shift(2) >= env_df['lower'].shift(2))).astype(int)
    feature[:, 1] = ((dem_df['main'] >= 0.7) &
                     (price_df['close'] < env_df['upper']) &
                     (price_df['close'].shift(1) >= env_df['upper'].shift(1)) &
                     (price_df['close'].shift(2) <= env_df['upper'].shift(2))).astype(int)
    
    # Set first 2 rows to 0 (no previous values to compare)
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

A primeira linha de código da função cria um array NumPy preenchido com zeros, de dimensão (len(dem_df), 2), destinado a armazenar os sinais de alta e de baixa. A função inicializa esse array garantindo que, por padrão, todas as linhas contenham zero. O tamanho do array deve corresponder ao comprimento do DataFrame para evitar desalinhamento entre os índices.

O índice usado para a verificação do sinal de alta é 0, portanto essa condição é avaliada primeiro. Conforme descrito no artigo anterior, em nossa implementação o sinal de alta é gerado quando o oscilador DeMarker está na zona de sobrevenda, normalmente abaixo de 0,3; o preço de fechamento atual está acima da banda inferior do Envelopes; o fechamento anterior estava no nível dessa banda ou abaixo dela; e, dois períodos antes, o fechamento estava no nível da banda inferior ou acima dela.

Esse sinal de alta combina a condição de sobrevenda indicada pelo oscilador DeMarker com o retorno do preço para acima da banda inferior, sugerindo uma possível continuidade ou reversão da tendência. As condições shift-1 e shift-2 garantem que o preço tenha interagido com a banda inferior do Envelopes conforme definido no artigo anterior. Também podem ser acrescentados filtros adicionais. Entre eles, verificações em múltiplos períodos podem reduzir falsos sinais ao exigir a presença de um determinado padrão de preço. Além disso, verificar env_df['lower'] e price_df['close'] quanto à presença de NaN evita comparações inválidas.

A coluna de baixa representa o sinal gerado quando o oscilador DeMarker está na zona de sobrecompra; o preço de fechamento atual está abaixo da banda superior do Envelopes; o fechamento anterior estava no nível dessa banda ou acima dela; e, dois períodos antes, o fechamento estava no nível da banda superior ou abaixo dela. Esse padrão procura identificar possíveis reversões ou recuos após uma condição de sobrecompra. Para utilizá-lo corretamente, é necessário garantir que haja histórico suficiente para realizar as comparações com shift.

Por esse motivo, definimos como zero os valores das duas primeiras linhas. Isso é necessário porque utilizamos os valores shift-1 e shift-2.

A instrução return devolve o array NumPy que contém nossos padrões de sinal. Esse formato é adequado para alimentar a RNN. A interação do preço com o Envelopes ao longo de vários períodos, por meio de shift(1) e shift(2), acrescenta uma camada de confirmação temporal e funciona, na prática, como um filtro adicional, diferenciando esse padrão de sinais simples de cruzamento.


Atributo 1

O padrão gera sinais quando o indicador DeMarker está em zonas extremas de sobrecompra ou sobrevenda e o preço permanece acima ou abaixo das bandas do Envelopes por vários períodos consecutivos. Implementação em Python:

def feature_1(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:
    feature[:, 0] = ((dem_df['main'] > 0.7) &
                     (price_df['close'] > env_df['upper']) &
                     (price_df['close'].shift(1) > env_df['upper'].shift(1))).astype(int)
    feature[:, 1] = ((dem_df['main'] < 0.3) &
                     (price_df['close'] < env_df['lower']) &
                     (price_df['close'].shift(1) < env_df['lower'].shift(1))).astype(int)
    
    # Set first row to 0 (no previous values to compare)
    feature[0, :] = 0
    
    return feature

Assim como no padrão 0, a primeira etapa consiste em definir o tamanho do array e inicializá-lo com zeros. Dessa forma, por padrão, não há sinais, o que é preferível a manter valores NaN. Em seguida, definimos os valores de cada índice. No primeiro índice/coluna, verificamos as condições de alta. O código verifica se o indicador DeMarker está na zona de sobrecompra (>0,7), se o preço de fechamento atual está acima da banda superior do Envelopes e se o fechamento anterior também estava acima dessa banda.

Isso é importante porque permite identificar um forte impulso de alta em situações nas quais o preço permanece acima da banda superior do Envelopes apesar da condição de sobrecompra indicada pelo DeMarker. Como vimos no artigo anterior, esse comportamento sinaliza continuidade da tendência, e não reversão. Em seguida, definimos o valor do segundo índice, que verifica as condições de baixa. O código verifica se o indicador DeMarker está na zona de sobrevenda (<0,3), se o preço de fechamento atual está abaixo da banda inferior do Envelopes e se o fechamento anterior também estava abaixo dessa banda.

Esse é o equivalente de baixa do sinal anterior. Ele representa um forte impulso de baixa, com o preço permanecendo abaixo da banda inferior do Envelopes. Como já observado no artigo anterior, isso indica continuidade da tendência de baixa. Como utilizamos comparações com shift, precisamos definir os valores da primeira linha como 0. Neste caso, apenas a primeira linha precisa ser ajustada, pois a comparação utiliza somente um período anterior. A instrução return devolve o array de sinais no formato NumPy.

Essa função identifica rompimentos sustentados além das bandas do Envelopes, confirmados por valores extremos do indicador DeMarker, o que fornece um forte sinal de continuidade da tendência. Em essência, trata-se de um sinal para entrar a favor de uma tendência já existente, e não para antecipar uma reversão. O atributo 1 se diferencia do atributo 0 por se concentrar em períodos consecutivos fora das bandas, sem exigir um padrão de cruzamento.


Atributo 5

O atributo 5 é o próximo entre os padrões que passaram no teste forward. Assim como nas duas funções descritas acima, ele utiliza sinais derivados de valores extremos do indicador DeMarker, mas considera a direção das bandas do Envelopes em vez de se concentrar na interação do preço com essas bandas. Implementação em Python:

def feature_5(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:
    feature[:, 0] = ((dem_df['main'] > 0.7) &
                     (env_df['upper'] > env_df['upper'].shift(1))).astype(int)
    feature[:, 1] = ((dem_df['main'] < 0.3) &
                     (env_df['lower'] < env_df['lower'].shift(1))).astype(int)
    
    # Set first row to 0 (no previous values to compare)
    feature[0, :] = 0
    
    return feature

Assim como nas duas funções anteriores, primeiro precisamos inicializar o array de saída com zeros e definir sua segunda dimensão como 2. Isso corresponde à primeira linha de código da função apresentada acima. Em seguida, definimos o primeiro índice, que, como nos casos anteriores, verifica a condição de alta. A regra é relativamente simples: se o DeMarker estiver em condição de sobrecompra (>0,7) e a banda superior do Envelopes estiver subindo, temos um sinal de alta. Depois, definimos o segundo índice, responsável por verificar a condição de baixa. Como esperado, um valor do DeMarker abaixo de 0,3 combinado à queda da banda inferior do Envelopes indica um sinal de baixa. Por fim, definimos os valores da primeira linha como zero para evitar comparações inválidas causadas pelos valores NaN gerados pelas operações de deslocamento.


Atributo 6

O atributo 6, ou padrão 6, conforme descrito no artigo anterior, gera sinais com base nas mudanças de momentum indicadas pelo DeMarker e em formações ondulatórias do preço nas bandas do Envelopes. Implementação em Python:

def feature_6(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:
    feature[:, 0] = ((dem_df['main'] > dem_df['main'].shift(1)) &
                     (price_df['low'].shift(1) <= env_df['lower'].shift(1)) &
                     (price_df['low'].shift(2) >= env_df['lower'].shift(2)) &
                     (price_df['low'].shift(3) <= env_df['lower'].shift(3)) &
                     (price_df['low'].shift(4) >= env_df['lower'].shift(4))).astype(int)
    feature[:, 1] = ((dem_df['main'] < dem_df['main'].shift(1)) &
                     (price_df['high'].shift(1) >= env_df['upper'].shift(1)) &
                     (price_df['high'].shift(2) <= env_df['upper'].shift(2)) &
                     (price_df['high'].shift(3) >= env_df['upper'].shift(3)) &
                     (price_df['high'].shift(4) <= env_df['upper'].shift(4))).astype(int)
    
    # Set first 4 rows to 0 (no previous values to compare)
    feature[0, :] = 0
    feature[1, :] = 0
    feature[2, :] = 0
    feature[3, :] = 0
    
    return feature

A inicialização segue a mesma lógica já apresentada anteriormente, com as diferenças esperadas nos valores atribuídos ao array. Para o primeiro índice, responsável por verificar a condição de alta, atribuímos o valor 1, equivalente a true, quando a condição para uma posição comprada é satisfeita. Essa condição combina uma queda progressiva do valor do indicador DeMarker com um padrão de fechamento do preço em relação à banda inferior do Envelopes ao longo dos quatro períodos anteriores, alternando entre valores abaixo e acima da banda. O segundo índice verifica a condição de baixa, acompanhando a queda do DeMarker e, de forma semelhante ao caso de alta, confirmando a formação de um padrão de fechamento em "M" junto à banda superior.

Em seguida, definimos como zero os valores iniciais das linhas, o que é necessário para evitar comparações inválidas entre os indicadores. Sem essa atribuição, ocorreriam comparações incorretas, pois os valores correspondentes seriam NaN devido ao uso de operações shift. Assim, atribuímos zero às quatro primeiras linhas, de 0 a 3, já que utilizamos shifts de 1 a 4 períodos.


Atributo 7

Como vimos no artigo anterior, esse atributo gera sinais com base nos valores do DeMarker em diferentes defasagens temporais e nos cruzamentos do preço com as bandas do Envelopes. Com isso, o foco passa para as mudanças de momentum. Implementação em Python:

def feature_7(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:DEM(X()) >= 0.5 && DEM(X() + 2) <= 0.3 && Close(X()) > ENV_UP(X()) && Close(X() + 1) <= ENV_UP(X() + 1)
    feature[:, 0] = ((dem_df['main'] >= 0.5) &
                     (dem_df['main'].shift(2) <= 0.3) &
                     (price_df['close'] >= env_df['upper']) &
                     (price_df['close'].shift(1) <= env_df['upper'].shift(1))).astype(int)
    feature[:, 1] = ((dem_df['main'] <= 0.5) &
                     (dem_df['main'].shift(2) >= 0.8) &
                     (price_df['close'] <= env_df['lower']) &
                     (price_df['close'].shift(1) >= env_df['lower'].shift(1))).astype(int)
    
    # Set first row to 0 (no previous values to compare)
    feature[0, :] = 0
    
    return feature

O array é inicializado com uma segunda dimensão igual a 2 e preenchido com zeros, sendo o primeiro índice utilizado para verificar o sinal de compra. Nosso código considera a condição de alta como true quando o DeMarker atual está em uma condição neutra ou de alta (>=0,5), o DeMarker de dois períodos atrás estava em sobrevenda (<=0,3) e o preço de fechamento atual está no nível da banda superior do Envelopes ou abaixo dela. Esse conjunto de condições procura capturar uma mudança de momentum, saindo da sobrevenda para uma condição neutra ou de alta, confirmada por um rompimento acima da banda superior do Envelopes. Isso normalmente indica uma forte reversão ou o início de uma tendência. A condição shift(2) introduz uma defasagem e exige que tenha ocorrido recentemente uma condição de sobrevenda.

A condição de baixa no índice 1 é satisfeita quando o DeMarker atual está em uma condição neutra ou de baixa (<=0,5), o DeMarker de dois períodos atrás estava em sobrecompra (>=0,8), o preço de fechamento atual está no nível da banda inferior do Envelopes ou abaixo dela e, por fim, o fechamento anterior estava no nível dessa banda ou acima dela. Para concluir, definimos como zero apenas a primeira linha, pois utilizamos apenas uma defasagem com shift.


Atributo 8

Nosso último atributo gera sinais quando o indicador DeMarker está em zonas extremas e a mínima ou a máxima do preço fica significativamente fora das bandas do Envelopes, indicando movimentos extremos do preço. Implementação em Python:

def feature_8(dem_df, env_df, price_df):
    """
    """
    # Initialize empty array with 2 dimensions and same length as input
    feature = np.zeros((len(dem_df), 2))
    
    # Dimension 1:DEM(X()) > 0.7 && Low(X()) > ENV_UP(X())
    feature[:, 0] = ((dem_df['main'] > 0.7) &
                     (price_df['low'] > env_df['upper'])).astype(int)
    feature[:, 1] = ((dem_df['main'] < 0.3) &
                     (price_df['high'] < env_df['lower'])).astype(int)
    
    # Set first row to 0 (no previous values to compare)
    # feature[0, :] = 0
    
    return feature

Começamos inicializando com zeros o array NumPy de saída, cuja segunda dimensão é igual a 2, e então definimos o índice 0. Assim como nos demais padrões descritos acima, esse índice representa a condição de alta. Neste caso, o sinal de alta ocorre quando o indicador DeMarker está em sobrecompra (>0,7) e a mínima atual do preço está acima da banda superior do Envelopes. Esse padrão normalmente indica um movimento de alta muito forte, pois até mesmo a mínima do período permanece acima da banda superior. Isso sinaliza um impulso de alta significativo.

A condição de baixa, verificada no índice 1, é satisfeita quando o indicador DeMarker está na zona de sobrevenda (<0,3) e a máxima atual do preço permanece abaixo da banda inferior do Envelopes. Assim como no cenário de alta descrito acima, ambas as situações são raras. No entanto, nos testes realizados no artigo anterior, conseguimos identificar algumas operações desse tipo. Em condições reais de mercado, justamente devido à baixa frequência desses sinais, pode ser conveniente acrescentar um filtro de confirmação para reduzir a ocorrência de falsos sinais.


RNN em Python

Nossa rede neural recorrente (RNN) recebe as saídas vetorizadas dos indicadores correspondentes aos atributos 0, 1, 5, 6, 7 e 8. Trata-se de um módulo de rede neural em PyTorch que combina uma RNN padrão com um mecanismo de injeção de ruído para aumentar a robustez ou modelar processos estocásticos. A arquitetura foi desenvolvida para tarefas de regressão e produz um único valor de saída para cada sequência de entrada. O ruído é aplicado aos estados ocultos da rede neural recorrente, e sua influência é modulada por uma camada de projeção e por uma função de ativação sigmoid.

A entrada dessa rede neural recorrente é um tensor com formato (batch_size, input_size). A arquitetura inclui uma camada RNN, seguida por uma projeção linear para atenuar o ruído e, por fim, uma camada totalmente conectada responsável pela saída. A injeção de ruído (noise injection) nos estados ocultos da rede neural recorrente é realizada por meio de um kernel de ruído branco. Esse ruído pode ser gerado durante a execução ou fornecido como um tensor previamente calculado. Como resultado, a rede produz um único valor de regressão para cada sequência, com formato (batch_size,). Implementação em Python:

# Define the network as a class
class WhiteNoiseRNN(nn.Module):
    def __init__(self, input_size=5, hidden_size=64, num_layers=1):
        super().__init__()
        self.hidden_size = hidden_size
        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
        self.noise_proj = nn.Linear(hidden_size, hidden_size)  # Projects noise to hidden space
        self.fc = nn.Linear(hidden_size, 1)  # Single output for regression
        
    def forward(self, x, noise_std=0.05):
        """
        Args:
            x: Input tensor of shape (batch_size, seq_len, input_size)
            noise_std: Either:
                - float: Standard deviation for generated noise
                - tensor: Pre-computed noise (must match rnn_out shape: batch_size × seq_len × hidden_size)
        """
        # Ensure proper input dimensions
        if x.dim() == 2:
            x = x.unsqueeze(1)  # (batch, features) → (batch, 1, features)
            
        # RNN processing
        rnn_out, _ = self.rnn(x)  # (batch_size, seq_len, hidden_size)
        
        # Noise injection
        if isinstance(noise_std, torch.Tensor):
            noise_std = noise_std.float()
            if noise_std.dim() == 1:
                noise_std = noise_std.view(-1, 1, 1)  # (batch,) → (batch, 1, 1)
            noise = noise_std.expand_as(rnn_out)
        elif noise_std > 0:
            noise = torch.randn_like(rnn_out) * noise_std
        else:
            noise = 0
            
        if isinstance(noise, torch.Tensor):
            projected_noise = self.noise_proj(noise)
            rnn_out = rnn_out + torch.sigmoid(rnn_out) * projected_noise
            
        return self.fc(rnn_out[:, -1, :])  # (batch_size,)

Como podemos observar no exemplo acima, a classe WhiteNoiseRNN é definida como uma subclasse de nn.Module e inicializada com três parâmetros de entrada: dimensão da entrada, dimensão do estado oculto e número de camadas. É nessa etapa que são definidos a arquitetura da rede e seus hiperparâmetros.

Graças à herança de nn.Module, podemos utilizar facilmente os recursos de cálculo automático de gradientes e gerenciamento de modelos do PyTorch, por exemplo, durante o treinamento e a avaliação. A dimensão da entrada é definida pela dimensionalidade dos atributos fornecidos ao modelo. No nosso caso, todos os atributos têm duas dimensões, portanto esse valor será igual a 2.

A dimensão do estado oculto determina a capacidade do modelo. Valores maiores aumentam seu poder de representação, mas também elevam o risco de sobreajuste e o custo computacional. Para a maioria das tarefas simples, uma única camada costuma ser suficiente, enquanto o aumento do número de camadas pode agravar problemas de gradientes evanescentes.

Chamamos o método de inicialização super() para acessar a classe pai, nn.Module, garantindo a configuração correta dos recursos do módulo PyTorch, como o rastreamento de parâmetros e o gerenciamento de dispositivos. Incluir essa chamada como parte do procedimento padrão ajuda a evitar diversos erros de inicialização. O parâmetro hidden-size é armazenado em uma variável de instância para possível uso em outros métodos.

Inicializamos a variável de classe rnn como uma camada RNN com a dimensão de entrada, a dimensão do estado oculto e o número de camadas especificados. Definir o parâmetro batch-first como true garante que os tensores de entrada e saída utilizem o tamanho do lote como primeira dimensão. Em seguida, definimos uma camada de projeção do ruído, que projeta o ruído para a mesma dimensionalidade dos estados ocultos da RNN. Isso permite transformar o ruído, por exemplo, por escalonamento ou rotação, antes de adicioná-lo aos estados ocultos, tornando a injeção de ruído um processo treinável. Com isso, o modelo pode se adaptar melhor ao efeito do ruído durante o treinamento. Como essa camada linear adiciona novos parâmetros, é necessário treinamento suficiente para ajustá-los adequadamente.

Depois, definimos uma camada totalmente conectada que mapeia o estado oculto final da RNN para um único valor de saída. O resultado é uma saída de regressão na qual a dimensionalidade do estado oculto é reduzida a um único valor escalar. Isso é essencial para o funcionamento do modelo, especialmente quando precisamos prever um valor contínuo, como neste caso.

O método forward define a propagação para frente da rede, processando os dados de entrada x e aplicando ruído com desvio-padrão noise_std. Essa função implementa as principais operações computacionais, incluindo a combinação dos dados, o processamento pela RNN, a injeção de ruído e a geração da previsão de saída. É sempre fundamental garantir que a variável x tenha o formato correto e tenha sido devidamente pré-processada antes de alimentar o modelo. O valor padrão de noise_std, igual a 0,05, é um hiperparâmetro que deve ser ajustado de acordo com o nível de ruído desejado. Ao utilizar ruído pré-calculado, também é necessário verificar seu formato e tipo para evitar erros durante a execução.

A primeira operação executada no método forward é alterar o formato de x adicionando uma dimensão de sequência com comprimento 1. Isso garante compatibilidade com a RNN, que espera uma dimensão de sequência mesmo quando os dados de entrada correspondem a um único passo temporal. Essa abordagem também torna o modelo flexível para trabalhar tanto com entradas de um único passo quanto com sequências de vários passos.

Em seguida, processamos x pela RNN para obter os estados ocultos de cada passo temporal. Essa operação produz dois resultados. Primeiro, obtemos rnn_out, um tensor que contém os estados ocultos; segundo, obtemos _, que representa o estado oculto da última camada e é ignorado neste caso, pois não precisamos dele. Esse processamento é fundamental porque a RNN captura as dependências temporais presentes na sequência de entrada, formando a base do processamento sequencial do modelo. Esses estados ocultos são utilizados posteriormente tanto para a injeção de ruído quanto para a geração da previsão.

Depois disso, passamos ao processamento do ruído pré-calculado, cuja presença é verificada pelo primeiro bloco condicional if. Para manter a consistência, convertemos o tensor para um tipo de ponto flutuante, ajustamos o formato dos tensores unidimensionais e expandimos o ruído para compatibilizá-lo com a forma de rnn_out. Como alternativa, quando o ruído é gerado durante a execução, introduzimos aleatoriedade nos estados ocultos, tornando a rede mais robusta a efeitos estocásticos durante a modelagem. A amplitude do ruído é controlada pelo parâmetro noise_std. Se nenhum ruído for fornecido e noise_std for igual ou inferior a zero, o ruído também é definido como zero. Dessa forma, o modelo pode operar sem ruído, o que pode ser útil para previsões determinísticas ou durante a depuração.

Depois de definir o ruído, nós o projetamos pela camada linear noise_proj para alinhá-lo ao espaço dos estados ocultos. Isso permite modular seu efeito usando a função sigmoid aplicada à saída da rede neural recorrente e, em seguida, adicioná-lo aos estados ocultos. Como mencionado anteriormente, essa projeção linear torna a injeção de ruído um processo treinável, permitindo que o modelo ajuste a influência do ruído durante o treinamento. torch.sigmoid(rnn_out) limita o fator de modulação ao intervalo de 0 a 1, evitando que o ruído exerça influência excessiva sobre os estados ocultos. A ideia é que essa abordagem aumente a robustez ao introduzir um componente estocástico controlado, o que pode ajudar a reduzir o sobreajuste.

Em seguida, selecionamos o estado oculto correspondente ao último passo temporal e o passamos pela camada totalmente conectada para obter um único valor de saída para cada sequência. O estado oculto do último passo temporal resume as informações acumuladas ao longo da sequência, o que o torna adequado para tarefas de regressão. A camada fc mapeia esse estado oculto para o valor de saída desejado.


Testes

Depois de definir os parâmetros da nossa rede, realizaremos testes com os seis padrões utilizados no artigo anterior: 0, 1, 5, 6, 7 e 8. Antes da publicação do artigo anterior, também havíamos experimentado entradas mais longas para analisar o comportamento da rede, pois nelas os sinais dos indicadores não estavam associados diretamente a uma condição de alta ou de baixa.

Neste artigo, não adotamos essa abordagem, pois todos os padrões possuem índices de verificação claramente definidos para condições de alta e de baixa. Isso pode explicar por que, no artigo mais recente, a proporção de testes forward aprovados foi maior do que no artigo anterior a ele, no qual essa distinção não havia sido considerada. Os testes são realizados no GBPUSD. Os testes foram realizados com dados de 2023, no timeframe de 4 horas, utilizando Python. O modelo treinado em Python fornece recomendações sobre a abertura de posições compradas ou vendidas.

Como o modelo ONNX importado para o MetaTrader 5 é utilizado posteriormente em um EA criado pelo Assistente, que aplica condições ponderadas para posições compradas e vendidas, esses valores também precisam ser otimizados. Essa otimização também é realizada com dados de 2023.

Após o treinamento e a otimização, executamos testes no período de 01.01.2023 a 01.01.2025 e obtivemos os seguintes resultados para os seis padrões/atributos:

r0

c0

O atributo 0 não passou no teste forward!

r1

c1

O atributo 1 não passou no teste forward.

r5

c5

O atributo 5 passou no teste forward.

r6

c6

O atributo 6 não passou no teste forward.

r7

c7

O atributo 7 passou no teste forward.

r8

c8

O atributo 8 passou no teste forward.


Conclusão

Analisamos os padrões obtidos pela combinação do indicador DeMarker com o Envelopes. Esse par complementar, formado por um oscilador de momentum e um indicador de suporte/resistência, foi testado pela primeira vez no artigo anterior, no qual as operações eram realizadas com base diretamente nos padrões originais. Neste artigo, processamos esses padrões com uma rede neural recorrente que utiliza um kernel de ruído branco durante o treinamento para processar os mesmos padrões gerados pelos indicadores. Ainda precisamos analisar uma rede neural que combine todos os padrões. Esse tema poderá ser abordado em artigos futuros.

Nome Descrição
wz64.mq5 EA criado no Assistente, cujo cabeçalho exibe os arquivos utilizados
SignalWZ_64.mqh Arquivo da classe de sinais personalizados
64_0.onnx Modelo ONNX exportado para o atributo 0
64_1.onnx Modelo ONNX exportado para o atributo 1
64_5.onnx " para o atributo 5
64_6.onnx " para o atributo 6
64_7.onnx " para o atributo 7
64_8.onnx " para o atributo 8

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

Arquivos anexados |
wz64.mq5 (7.56 KB)
SignalWZ_64.mqh (18.51 KB)
64_0.onnx (35.81 KB)
64_1.onnx (35.81 KB)
64_5.onnx (35.81 KB)
64_6.onnx (35.81 KB)
64_7.onnx (35.81 KB)
64_8.onnx (35.81 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.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 63): Uso de padrões dos canais DeMarker e Envelopes Recursos do Assistente MQL5 que você precisa conhecer (Parte 63): Uso de padrões dos canais DeMarker e Envelopes
O oscilador DeMarker (DeMarker Oscillator) e o Envelopes são ferramentas que identificam o momentum e os níveis de suporte/resistência e podem ser combinadas no desenvolvimento de um EA. Por isso, analisamos cada padrão separadamente para determinar o que pode ser útil e o que, potencialmente, deve ser evitado. Como sempre, usamos um EA criado no Assistente, juntamente com as funções de padrões integradas à classe de sinais do EA (Expert Signal).
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.
Ferramentas de negociação em MQL5 (Parte 2): Aprimorando o assistente de negociação interativo com visualização dinâmican Ferramentas de negociação em MQL5 (Parte 2): Aprimorando o assistente de negociação interativo com visualização dinâmican
Neste artigo, vamos atualizar nossa ferramenta Trade Assistant, adicionando um painel arrastável e efeitos ao passar o cursor do mouse para tornar a interface mais intuitiva e responsiva. Também vamos aprimorar a validação das configurações das ordens em tempo real, garantindo que os parâmetros de negociação sejam definidos com precisão em relação aos preços de mercado. Além disso, testaremos essas melhorias com dados históricos para confirmar sua confiabilidade.