Técnicas do MQL5 Wizard que você deve conhecer (Parte 57): Aprendizado Supervisionado com Média Móvel e Oscilador Estocástico
Introdução
Estamos continuando nossa análise de padrões simples que podem ser implementados com Expert Advisors montados pelo MQL wizard. O principal objetivo disso é sempre testar ou experimentar ideias. A implementação final e o uso em conta real podem utilizar Expert Advisors montados manualmente após testes por períodos mais longos, mas os Experts montados pelo wizard permitem testes rápidos com menos código inicial.
O Aprendizado de Máquina está em alta no momento, e abordamos alguns aspectos específicos dele em artigos anteriores desta série. Continuaremos a abordar algumas dessas características mais técnicas neste e em artigos futuros, no entanto, elas servirão como pano de fundo, pois estaremos mais focados em padrões de indicadores mais conhecidos e estabelecidos.
Além disso, no contexto do aprendizado de máquina, nossos artigos cobrirão os 3 principais ramos de aprendizado em artigos separados, em um ciclo. Para começar, analisaremos a supervisão ou aprendizado supervisionado, e nossos padrões de indicadores serão provenientes da combinação de um indicador de tendência e um indicador de momentum. Estaremos analisando a Média Móvel e o Oscilador Estocástico
No aprendizado supervisionado, buscaremos implementar cada padrão em uma rede neural separada. Estas, como argumentado em artigos recentes, são melhor codificadas e treinadas em python do que em MQL5. Os ganhos de eficiência são extremamente elevados. O Python também permite facilmente testar validação cruzada após uma sessão de treinamento, e, portanto, realizaremos esses testes para cada padrão.
Enquanto a validação cruzada é realizada em Python comparando o valor de perda da execução de teste com o valor de perda da última época de treinamento, isso por si só, embora importante, tende a ser insuficiente para avaliar a validação cruzada dos pesos e vieses atuais da rede.
Portanto, realizaremos execuções forward walk no testador de estratégias do MetaTrader 5, com as redes ONNX exportadas. Para este artigo, os dados de preço ou conjuntos de dados x e y enviados ao python a partir do MetaTrader 5 para iniciar o treinamento serão do ano de 2023, para o par EUR JPY. Assim, o forward walk será para o mesmo símbolo, porém para o ano de 2024. Estamos realizando nossa análise no timeframe Diário.
Combinar a Média Móvel (MA) com o Oscilador Estocástico pode gerar uma variedade de sinais de negociação. Para nossos propósitos de teste e exploração, consideraremos apenas os 10 principais padrões de sinais que os traders podem utilizar quando esses indicadores são combinados.
Abordagem com Tipos de Aprendizado de Máquina
Um pipeline de aprendizado de máquina pode ser visto como estas 3 fases interconectadas: Supervisão, Reforço e Inferência. Vamos recapitular as definições de cada uma.
Aprendizado Supervisionado (também conhecido como Fase de Treinamento) – é quando o modelo aprende padrões históricos com base em dados rotulados (dados independentes ou de entrada frequentemente rotulados como x, e dados dependentes a serem previstos também frequentemente rotulados como y).
Enquanto isso, Aprendizado por Reforço (que pode ser visto como a fase de aprendizado em uso e otimização) – pode ser considerado como uma etapa em que o modelo se refina por meio da interação com seu ambiente e da otimização de suas ações para recompensas de longo prazo (quase como backpropagation durante um forward walk).
E, por fim, Aprendizado por Inferência (também conhecido como fase de Aprendizado Não Supervisionado) – ocorre quando o modelo generaliza a partir do aprendizado passado e aplica o que foi aprendido a novos dados e novos problemas.
Puramente para ilustração, vamos observar como essas 3 fases podem ser conectadas ao lidar com diferentes conjuntos de problemas. Consideraremos casos de sistemas de informações meteorológicas e previsão de séries temporais financeiras.
Supervisão:
O objetivo do aprendizado supervisionado é gerar um modelo preditivo a partir de dados rotulados. Esse processo envolve a coleta e o pré-processamento de dados históricos. Em seguida, ocorre a extração de características, que pode ser entendida como uma forma de normalizar os dados históricos para uma faixa ou formato que possa ser inserido na rede neural ou modelo de treinamento. Depois disso, o modelo é treinado em LSTMs, ou XGBoost ou transformers usando dados rotulados. Rotular significa marcar os dados independentes (dados precursores conhecidos e usados para previsão) e os dados dependentes (os dados que queremos prever) com rótulos distintos. O objetivo inicial aqui é a minimização da perda, fazendo com que a diferença entre o previsto e o real seja a menor possível utilizando gradient descent.
No aprendizado supervisionado, a validação cruzada pode estar envolvida, e seus resultados podem sustentar a decisão de implantar o modelo para uso completo. No entanto, com as 3 etapas que estamos explorando aqui, o estágio final do aprendizado supervisionado seria a Seleção do Modelo e o ajuste de hiperparâmetros, onde essencialmente a taxa de aprendizado ideal e o tamanho do lote de treinamento são escolhidos com base no que funcionou melhor. Além disso, a arquitetura em relação aos tipos de ativação e até mesmo os tamanhos das camadas é definida, caso essas opções tenham sido testadas nesta fase de treinamento.
Para ilustrar isso, em um sistema de previsão do tempo: os dados seriam temperatura passada, umidade ou pressão; o modelo poderia ser uma floresta aleatória ou um modelo baseado em CNN; com o resultado sendo um sistema para prever tendências de temperatura. Com séries temporais financeiras, os dados de previsão seriam preços históricos com engenharia de características para gerar valores de indicadores; o modelo poderia ser LSTM; a função de perda MSE; e o resultado sendo um modelo treinado que prevê o próximo preço com base em observações passadas.
Reforço:
Tendo realizado o aprendizado supervisionado, que se baseia principalmente em dados históricos rotulados, a questão passa a ser se o modelo será capaz de funcionar com dados ao vivo ou futuros. Ou até mesmo se conseguirá se adaptar caso as condições futuras mudem em relação ao que foi treinado. Para responder a isso, utiliza-se o Aprendizado por Reforço. Modelos estáticos geralmente têm dificuldade em ambientes dinâmicos, razão pela qual o Aprendizado por Reforço ajuda a otimizar o processo de tomada de decisão no futuro.
O objetivo do RL, portanto, é melhorar as previsões otimizando a tomada de decisão com base em feedback. Em outras palavras, uma vez que temos, por exemplo, uma rede neural que prevê mudanças de preço, passamos então a desenvolver rede(s) de política e de valor capazes de utilizar essas mudanças de preço como estados. Assim, passamos a separar as mudanças de preço das ações do trader, com a introdução de estados e ações.
Esses exemplos são para previsão de séries temporais financeiras; se fosse previsão do tempo, como ilustrado acima, então os estados e ações poderiam ser a quantidade de chuva e quanto plantar de uma cultura principal. Um resultado mais tangível e desejado da previsão de séries temporais financeiras geralmente são os lucros/prejuízos decorrentes das ações tomadas ao seguir as previsões da rede. Para o exemplo meteorológico, isso poderia ser a produtividade da cultura principal.
O processo de RL, como abordado anteriormente, não envolve apenas o treinamento e atualização da rede crítica para antecipar melhor nossa posição de lucro/prejuízo, mas também fornece deltas que refinam ainda mais os pesos e vieses da rede de política.
O processo de RL, para recapitular, envolve: configuração Agente-Ambiente, onde estados, ações e recompensas são definidos. Como mencionado, estes seriam derivados ou determinados a partir do modelo treinado na fase de supervisão (como mostrado nas ilustrações acima). Também envolve aprendizado de política, onde uma rede ator ou algoritmo equivalente é otimizado para mapear estados para suas ações correspondentes. Além disso, há um mecanismo de otimização baseado em recompensa, que pode assumir a forma de um algoritmo de valor ou rede crítica que ajusta previsões com base na lucratividade de longo prazo. E, por fim, um agente que equilibra exploração (testar diferentes configurações de política com o objetivo de aprender algo novo) e exploração prática (manter o que funcionou bem no passado).
Se utilizarmos exemplos para demonstrar o que ocorre aqui com previsão do tempo e previsão financeira como na fase de supervisão: na previsão do tempo, o modelo inicial prevê chuva com base em temperatura, umidade etc., e gera saídas de precipitação projetada que buscamos. Como essa chuva é um fator determinante para decidir se devemos plantar nossa cultura, o RL introduz uma camada de decisão de interpretação. A previsão de chuva passa a ser tratada como estados. Se esses estados indicarem diferentes níveis de precipitação em diferentes regiões ou momentos, sua interpretação para decidir plantar pode ser otimizada visando uma recompensa, que neste caso seria a produtividade da cultura. O resultado disso seria um modelo de previsão que se aprimora em resposta às mudanças reais do clima. Assim, se a fase de supervisão fosse a escola, o reforço seria o treinamento no trabalho.
Para previsão financeira, teríamos as mudanças de preço previstas pelo modelo na fase de supervisão, atuando como nossos estados. Essas mudanças ainda podem ser um vetor multidimensional se, por exemplo, abrangerem mais de um timeframe. As ações seriam aquelas tomadas pelo trader ou Expert Advisor, que são comprar, vender ou permanecer neutro. Com base nas ações tomadas para cada estado (mudanças de preço), mapearíamos, por meio de uma rede crítica ou algoritmo, os estados e ações para os lucros esperados de cada um. Uma vez feito isso, a política (mapeamento de estados para ações) pode ser atualizada gradualmente, equilibrando exploração e exploração prática, para melhor interpretar as saídas do modelo da fase de supervisão (que tratamos como estados).
Esse treinamento em ambiente real, porém, significa que ele ocorre muito lentamente por padrão, pois os dados ao vivo são gerados de forma gradual e limitada. Assim, se o treinamento supervisionado cobriu um período histórico de 10 anos, o treinamento por reforço por um período de 1 ano, teoricamente também levaria 1 ano. Isso, portanto, torna necessária a utilização de simulação ou de uma classe de ambiente mais elaborada.
Ao simular condições de dados ao vivo em um conjunto de dados históricos, não apenas aceleramos o ritmo do aprendizado por reforço, mas também abrangemos muito mais dados, o que deve nos proporcionar modelos mais robustos.
Assim, para previsão de séries financeiras, isso significa que podemos realizar testes com os mesmos dados usados na supervisão, mas agora otimizando as redes de RL para lucro, com base nas ações tomadas dadas as previsões de estado (ou mudanças de preço) feitas pela rede na fase de supervisão.
Inferência:
Tendo dois modelos, um para realizar as previsões básicas (no aprendizado supervisionado) e outro para interpretar e aplicar melhor essas previsões (no reforço), chegamos então à terceira fase, a inferência, onde essencialmente aplicamos esse conhecimento e esses modelos a um campo diferente ou, no nosso caso, a um símbolo de negociação diferente.
Isso também é conhecido como generalização em dados não vistos. É a aplicação do conhecimento aprendido em ambientes do mundo real, refinando-o ao longo do tempo e detectando padrões não vistos sem utilizar rótulos nos dados. Grafos e autoencoders desempenham papéis muito importantes nesta fase, assim como o clustering.
Nessas 3 fases, a cada progressão para a próxima, as cargas computacionais são reduzidas. O que, em essência, justifica essas fases. Métodos que utilizam aprendizado não supervisionado já foram analisados anteriormente nesta série, no entanto, posso revisitá-los em breve com foco em como eles podem “continuar” a partir do aprendizado por reforço. Embora o RL possa ser usado de forma autônoma em previsões, essas fases apresentadas buscam demonstrar que pode ser mais eficiente utilizá-lo em conjunto com outros modos de aprendizado.
Combinar a Média Móvel (MA) com o Oscilador Estocástico pode gerar uma variedade de sinais de negociação. Examinamos os 10 principais padrões de sinais que são gerados a partir da combinação desses indicadores que os traders podem utilizar.
Cruzamento de Média Móvel + Estocástico em Sobrecompra/Sobrevenda
Esse padrão proveniente da combinação de um indicador seguidor de tendência (MA) e um oscilador de momentum (o estocástico) tende a fornecer configurações de alta probabilidade. O sinal de compra é um cruzamento de alta (quando a MA rápida cruza acima da MA lenta) para confirmar uma possível tendência de alta, enquanto o Oscilador Estocástico está abaixo de 20. Isso geralmente sugere que o ativo está sobrevendido e o preço está pronto para uma reversão. O sinal de venda, por outro lado, é um cruzamento de baixa. Isso indica a MA rápida cruzando a MA lenta de cima para baixo, o que por si só já é um possível indicativo de tendência de baixa. Como isso também deve ser respaldado pelo Estocástico acima do nível 80, sinal de sobrecompra, forma-se um forte sinal de venda.
Com nosso formato de classe de sinal ligeiramente modificado, onde usamos leituras de indicadores como características para as redes, agora temos uma função de padrão individual que codificamos da seguinte forma:
//+------------------------------------------------------------------+ //| Check for Pattern. | //+------------------------------------------------------------------+ double CSignal_MA_STO::IsPattern(int Index, ENUM_POSITION_TYPE T) { vectorf _x = Get(Index, m_time.GetData(X()), m_close, m_ma, m_ma_lag, m_sto); vectorf _y(1); _y.Fill(0.0); ResetLastError(); if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, _x, _y)) { printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError()); return(double(_y[0])); } //printf(__FUNCSIG__+" y: "+DoubleToString(_y[0],2)); if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } return(double(_y[0])); }
Incluímos um arquivo ‘57_X.mqh’ que possui uma função para recuperar valores tanto da média móvel quanto do estocástico, que são entradas para nossas redes. Estamos considerando até 10 padrões diferentes. Isso significa que utilizaremos até 10 redes diferentes. Nossa função neste arquivo incluído, ‘Get’, portanto, também retornará até 10 conjuntos de dados diferentes, um para cada rede.
‘Get’ retorna um vetor de ponto flutuante (vectorf) que podemos facilmente inserir em uma rede ONNX. Essa mesma função pode ser usada como alternativa à biblioteca de importação do MetaTrader 5 em Python, que exigiria a normalização do preço para um formato semelhante ao fornecido aqui antes que a rede possa utilizá-lo como entrada. Nossa rede em Python será direta e pode ser codificada da seguinte forma:
class SimpleNeuralNetwork(nn.Module): def __init__(self): super(SimpleNeuralNetwork, self).__init__() self.fc1 = nn.Linear(feature_size, 256) # Input layer to hidden layer 1 self.fc2 = nn.Linear(256, 256) # Hidden layer 1 to hidden layer 2 self.fc3 = nn.Linear(256, state_size) # Hidden layer 2 to output layer self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.sigmoid(self.fc1(x)) # Activation for hidden layer 1 x = self.sigmoid(self.fc2(x)) # Activation for hidden layer 2 x = self.fc3(x) # Output layer (no activation) return x
Os parâmetros ‘feature_size’ e ‘state_size’ são os tamanhos de entrada e saída da nossa rede. A saída será padrão em todos os 10 padrões, pois teremos como alvo um valor no intervalo de 0.0 a 1.0. Qualquer valor abaixo de 0.5 será interpretado como uma previsão de variação negativa, qualquer valor acima de 0.5 será positivo e 0.5 será neutro.
A definição das características, no entanto, não envolverá tamanhos de vetor padrão para entrada, já que cada padrão pode ter um número diferente de condições. Para este padrão, o primeiro, são 4. Portanto, atribuimos o vetor de entrada à rede dentro da função ‘Get’, da seguinte forma.
if(Index == 0) { if(CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(M_LAG.Handle(), 0, T, 2, _ma_lag) >= 2 && CopyBuffer(S.Handle(), 0, T, 1, _sto_k) >= 1) { _v[0] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[0] <= 20.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[0] >= 80.0) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[1] < _ma[1] && _ma_lag[0] > _ma[0]) ? 1.0f : 0.0f); } }
Para detalhar isso, as condições para o padrão de alta são 2, uma para cada indicador, e o mesmo se aplica ao padrão de baixa. Ao definir o vetor de entrada, simplesmente verificamos se cada uma das condições é atendida. Dada a natureza espelhada ou booleana dessas condições, o máximo que pode ser atendido a qualquer momento é 2.
Ainda assim, todas são descritas no vetor, pois isso pode ser informativo para a rede. Além disso, essas 4 condições, no caso do padrão-0, poderiam ser ainda divididas em 6, já que a primeira condição utiliza 2 argumentos, assim como a 4ª. O leitor pode experimentar isso, pois o código-fonte está anexado ao final do artigo.
Uma execução de treinamento e teste em Python registra os seguintes valores de perda para ambas as execuções:
Epoch 10/10, Train Loss: 0.2498
Test Loss: 0.2593 O valor de perda de teste é menor que o valor de perda inicial da 1ª época (não indicado), mas ainda maior que o valor de perda da 10ª época. Os dados de preço usados para treinamento e validação foram inteiramente do ano de 2023. Portanto, tentamos um forward walk para o ano de 2024 após otimizar limites adequados de abertura/fechamento e padrões para o padrão-0 em 2023. Isso nos fornece o seguinte relatório.

Realizamos o walk, embora apenas com operações compradas. Treinamento com conjuntos de dados maiores é necessário para verificar se tal walk poderia ocorrer também com operações vendidas. Além disso, esse forward walk utiliza pesos de treinamento de um período de 80% de 2023, não do ano inteiro. No entanto, eles são aplicados em todo o ano de 2024.
Preço cruza a MA + Estocástico confirma a tendência
Nosso próximo padrão utiliza a lógica de cruzamento do preço com a MA e a direção do Estocástico para definir suas condições de compra e venda. O sinal de compra ocorre quando o preço cruza acima da Média Móvel e o Estocástico %K cruza ou está acima do %D. Um sinal de venda ocorre quando o preço cruza abaixo da média móvel, com %K abaixo do %D.
Uma vez que os dados de preço são importados e formatados para serem entradas da nossa rede, o vetor de entrada será semelhante à saída da nossa função ‘Get’, conforme destacado abaixo:
else if(Index == 1) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_c[1] < _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < _sto_d[1] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > _sto_d[1] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); _v[3] = ((_c[1] > _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
Estamos usando tamanho 4, mas há mais de 4 condições no total, pois, como mencionado acima, algumas condições utilizam mais de um argumento. Neste caso, todas as 4 condições utilizam 2 argumentos, o que significa que poderíamos ter usado uma entrada de tamanho 8 para a rede.
O treinamento da rede é realizado por meio da função Train abaixo:
# Train function def Train(model, train_loader, optimizer, loss_fn, epochs): device = T.device('cuda:0' if T.cuda.is_available() else 'cpu') model.to(device) for epoch in range(epochs): model.train() train_loss = 0.0 for batch_idx, (data, target) in enumerate(tqdm(train_loader, desc=f"Epoch {epoch + 1}/{epochs} (Train)")): # Step 1: Ensure proper tensor dimensions data = data.view(-1, 1, feature_size) # Step 2: Verify dimensions expected_shape = [feature_size] actual_shape = list(data.shape[2:]) if actual_shape != (expected_shape): raise RuntimeError(f"Invalid spatial dimensions after reshaping. Got {data.shape[2:]}, expected {[x_data.shape[1]]}") # Step 3: Move to device and forward pass data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) target = target.view(-1, 1, state_size) loss = loss_fn(output, target) # Step 4: Backpropagation loss.backward() optimizer.step() train_loss += loss.item() train_loss /= len(train_loader) print(f"Epoch {epoch + 1}/{epochs}, Train Loss: {train_loss:.4f}")
Execuções de treinamento ao longo do ano de 2023 apresentam os seguintes registros da função de perda:
Epoch 10/10, Train Loss: 0.2491
Test Loss: 0.2592 Um forward walk para o ano de 2024 nos fornece o seguinte relatório:

Obtemos um forward walk promissor, que, no entanto, apresenta um número um pouco reduzido de operações vendidas em comparação com as compradas. Mais treinamento deve resolver isso. Esse padrão captura movimentos iniciais de tendência, pois o objetivo é o cruzamento do preço com a MA, o que pode sinalizar uma mudança de tendência e tende a ter menos atraso do que o cruzamento de duas MAs, como vimos no padrão-0. Também filtra muitos rompimentos falsos, pois o Estocástico é usado como confirmação, ajudando a evitar operações prematuras.
Esse padrão também possui suas fraquezas e limitações. Primeiramente, a natureza atrasada da MA, especialmente quando períodos mais longos são utilizados, pode tornar as entradas nas operações lentas. Usamos um período de 8 no timeframe Diário por padrão, mas este é um parâmetro otimizável para a classe de sinal personalizada, permitindo que os leitores ajustem conforme necessário. Em segundo lugar, o Oscilador Estocástico é conhecido por ser muito volátil, especialmente em mercados laterais. Isso pode levar a muitas confirmações falsas. Além disso, em situações em que um ativo já está em forte tendência, esperar pelo cruzamento do preço com a MA pode resultar na perda de muitas entradas antecipadas.
Quanto à otimização dos períodos dos indicadores, uma MA curta na faixa abaixo de 20 períodos reagirá mais rapidamente às mudanças de preço do que períodos mais longos na faixa de 50 a 200. Assim, os usuários precisam considerar o timeframe utilizado para decidir se desejam reações rápidas ou mais estabilidade e confirmação antes de aceitar mudanças.
Da mesma forma, para o estocástico, (5,3,3) fornece configurações mais rápidas, mas os sinais gerados tendem a ser ruidosos. Configurações mais lentas como (14,5,5) podem ser mais apropriadas, mas isso também deve ser considerado juntamente com o timeframe utilizado. Na classe de sinal personalizada usada nesses testes, utilizamos um período padrão para a média móvel e o Oscilador Estocástico. Esses parâmetros são personalizáveis, mas para ilustrar, se o período da MA for N, então o Oscilador Estocástico será (N,3,3) e a MA lenta será 2 x N.
Picos de volume também podem ser utilizados para confirmar cruzamentos da MA ou a força de um rompimento. Níveis de suporte e resistência também podem ser usados para aumentar a confiabilidade do padrão.
Inclinação da Média Móvel + Confirmação de Tendência pelo Estocástico
Esse padrão foca na direção da tendência por meio da inclinação da MA, enquanto confirma o momentum com o Estocástico. Um sinal de compra ocorre quando a média móvel está inclinada para cima E o Estocástico está acima de 50 e subindo. O sinal de venda é o inverso. Com a média móvel inclinada para baixo, enquanto o Estocástico está abaixo de 50 e caindo. Assim, nossa função ‘Get’ recupera um vetor de entrada que verifica essas condições:
else if(Index == 2) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_ma[1] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < _sto_k[0] && _sto_k[1] >= 50.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > _sto_k[0] && _sto_k[1] <= 50.0) ? 1.0f : 0.0f); _v[3] = ((_ma[1] > _ma[0]) ? 1.0f : 0.0f); } }
Este é o primeiro padrão dos 10 em que utilizamos o segundo buffer do Estocástico, também conhecido como %K. Ele está sempre atrasado em relação ao buffer principal (%D), e seu uso aqui ajuda a confirmar a inclinação desse oscilador. Uma vez que os vetores desses dados, ao longo do ano de 2023 para o EUR JPY, são exportados para o Python, treinamos a rede simples listada acima com a função ‘Train’, também apresentada anteriormente. O teste ou validação cruzada seria realizado por uma função muito semelhante à função ‘Train’ acima, que denominamos função ‘Test’. Sua implementação em Python é a seguinte:
# Test function def Test(model, test_loader, optimizer, loss_fn, epochs): device = T.device('cuda:0' if T.cuda.is_available() else 'cpu') model.to(device) with T.no_grad(): test_loss = 0.0 for batch_idx, (data, target) in enumerate(tqdm(test_loader, desc=f"Loss at {test_loss} in (Test)")): # Step 1: Ensure proper tensor dimensions data = data.view(-1, 1, feature_size) ... # Step 3: Move to device and forward pass data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) target = target.view(-1, 1, 1) #print(f"target: {target.shape}, plus output: {output.shape}") loss = loss_fn(output, target) test_loss += loss.item() test_loss /= len(test_loader) print(f"Test Loss: {test_loss:.4f}")
As pontuações comparativas de perda entre treinamento e teste estão alinhadas com o que foi apresentado acima nos padrões 0 e 1 e, portanto, não serão exibidas aqui. Um forward walk para o ano de 2024 no testador de estratégias do MetaTrader nos apresenta o seguinte relatório:

Nosso padrão é capaz de gerar um walk desequilibrado, já que apenas operações de compra são abertas. Treinamento com conjuntos de dados maiores deve resolver esse problema. O padrão-2, no entanto, é um bom filtro para mercados laterais. A inclinação da MA garante que operações sejam realizadas apenas quando há uma tendência clara. A confirmação da força da tendência é então feita pelo Oscilador Estocástico, já que a posição relativa em relação ao nível 50 valida o momentum na tendência predominante. De forma geral, também há uma redução no atraso, pois não há necessidade de esperar por cruzamentos de MA, como ocorre nos padrões anteriores. Isso significa que este padrão é adequado para aceleração de tendência.
Suas principais fraquezas incluem a reação lenta a reversões de tendência, já que a inclinação de uma média móvel pode levar tempo para mudar após uma reversão. Além disso, o Estocástico nessas situações pode ser propenso a gerar sinais tardios. Se uma tendência já estiver em andamento, esperar pela confirmação do Estocástico pode levar à perda de entradas iniciais. De modo geral, este padrão também não é adequado para mercados laterais, devido à sua forte dependência de tendências.
Combiná-lo com filtros adicionais, como o ADX (Average Directional Index), pode ajudar a confirmar tendências fortes. Além disso, verificar rompimentos ou retrações no preço pode melhorar a precisão. Casos de uso práticos incluem forex (que é o que estamos testando aqui), ações, pois esse padrão pode ajudar a identificar continuidade de tendência após pequenas retrações; ou cripto e commodities, já que é útil para capturar movimentos impulsivos por momentum em ativos como ouro ou Bitcoin.
Rebote na MA com Divergência do Estocástico
Este padrão funciona combinando o suporte/resistência dinâmico do preço na Média Móvel com a divergência de momentum do Oscilador Estocástico para identificar possíveis reversões antes que ocorram. O sinal de compra ocorre quando o preço rebate em uma MA inclinada para cima (que neste caso atua como suporte) e o Oscilador Estocástico apresenta divergência de alta, de modo que o preço forma um fundo mais baixo enquanto o oscilador forma um fundo mais alto.
O sinal de venda ocorre quando o preço rejeita uma MA inclinada para baixo que atua como resistência e o Oscilador Estocástico indica divergência de baixa, onde o preço forma um topo mais alto, mas o Estocástico forma um topo mais baixo. A implementação em MQL5 e a definição da entrada da rede que processa esse padrão são tratadas da seguinte forma:
else if(Index == 3) { _v.Init(6); _v.Fill(0.0); if(C.GetData(T, 3, _c) >= 3 && CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(S.Handle(), 0, T, 3, _sto_k) >= 3) { _v[0] = ((_c[2] > _c[1] && _c[1] < _c[0] && _c[0] < _c[2] && _ma[1] >= _c[1]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[2] > _sto_k[1] && _sto_k[1] < _sto_k[0] && _sto_k[1] >= 40.0) ? 1.0f : 0.0f); _v[2] = ((_ma[2] > _ma[0]) ? 1.0f : 0.0f); _v[3] = ((_ma[2] < _ma[0]) ? 1.0f : 0.0f); _v[4] = ((_sto_k[2] < _sto_k[1] && _sto_k[1] > _sto_k[0] && _sto_k[1] <= 60.0) ? 1.0f : 0.0f); _v[5] = ((_c[2] < _c[1] && _c[1] > _c[0] && _c[0] > _c[2] && _ma[1] <= _c[1]) ? 1.0f : 0.0f); } }
As pontuações da função de perda de validação de treino e teste não são muito diferentes dos registros dos dois primeiros padrões, cujos logs foram apresentados. A função de perda de teste é menor do que a função de perda do treinamento na primeira época, mas ainda menor do que a função de perda na décima época. Um relatório de forward walk para o ano de 2024, após treinamento com dados de 2023, é o seguinte:

Um forward walk positivo é sempre um bom sinal, desde que tanto posições compradas quanto vendidas sejam abertas de forma equilibrada. Não é o caso aqui. No entanto, o padrão-3 é eficaz na detecção antecipada de reversões, pois a divergência do Estocástico geralmente sinaliza reversões antes que o preço reaja. Ele também evita perseguir a tendência, permitindo entradas próximas aos níveis dinâmicos de suporte/resistência em vez de entradas tardias. Também funciona bem em mercados com tendência, já que o rebote na MA confirma a tendência enquanto a divergência alerta para exaustão.
As fraquezas e limitações incluem sinais falsos quando os mercados estão em forte tendência. Isso ocorre porque, nessas situações, a divergência por si só muitas vezes não é suficiente para uma reversão. Além disso, há o efeito de atraso da MA, já que o rebote pode não coincidir perfeitamente com o momento da divergência, e a necessidade de confirmação para melhorar a precisão também é uma desvantagem.
O padrão-3 pode ser otimizado combinando-o com padrões de candlestick (como pin bars ou engulfing bars) no nível da MA para fortalecer a configuração. Picos de volume também podem ser utilizados para confirmar a divergência.
MA atuando como Suporte/Resistência Dinâmico + Cruzamento do Estocástico
Nossa estratégia do padrão-4 é uma combinação de acompanhamento de tendência (usando a MA para direção e também como suporte/resistência) e confirmação de momentum (verificada por meio de cruzamentos do Estocástico) para gerar sinais de compra e venda utilizáveis.
O sinal de compra ocorre quando o preço se mantém acima de uma MA inclinada para cima e o Estocástico %K cruza acima do %D. Esse padrão de alta ajuda a garantir vários pré-requisitos importantes para uma posição comprada. Primeiramente, atua como confirmação de tendência, já que uma MA inclinada para cima garante que as operações estejam alinhadas com a direção predominante do mercado.
Em segundo lugar, o cruzamento do Estocástico fornece uma entrada sincronizada com o impulso. Isso ocorre porque uma mudança no momentum de crescimento geralmente confirma uma renovada pressão de compra. Entradas prematuras em qualquer toque da MA são evitadas, pois o cruzamento do Estocástico garante que há momentum antes da compra. Assim, sinais falsos são reduzidos. Uma possível desvantagem é que pode perder rompimentos iniciais, pois aguarda ambas as condições, resultando em entradas ligeiramente atrasadas.
O sinal de venda ocorre quando o preço permanece abaixo de uma média móvel inclinada para baixo e o Estocástico %K cruza abaixo do %D. Isso, de forma semelhante ao cenário de alta, fornece confirmação de tendência, entrada ou saída baseada em momentum, evita entradas prematuras e filtra sinais fracos. Uma possível desvantagem é que, em mercados voláteis, o preço pode romper a MA antes de retomar a queda. Isso é particularmente relevante porque, para a maioria dos ativos, as quedas tendem a ser mais voláteis do que as tendências de alta. Implementamos isso em MQL5 da seguinte forma:
else if(Index == 4) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_sto_k[1] < _sto_d[1] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[1] = ((_ma[1] < _c[1] && _ma[0] < _c[0] && _ma[1] < _ma[0]) ? 1.0f : 0.0f); _v[2] = ((_ma[1] > _c[1] && _ma[0] > _c[0] && _ma[1] > _ma[0]) ? 1.0f : 0.0f); _v[3] = ((_sto_k[1] > _sto_d[1] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); } }
Agrupamos o vetor de entrada da rede que processa esse padrão em uma dimensão de tamanho 4. Como mostrado nos padrões anteriores, esse vetor pode ser mais detalhado incluindo cada argumento das condições como parte das entradas. Um forward walk para a rede treinada apenas com o padrão-4 nos fornece os seguintes resultados:

Esse padrão deve funcionar bem em mercados com tendência, no entanto, tende a ter dificuldades em cenários de consolidação. Embora tenhamos um forward walk favorável, é importante testar com conjuntos de dados maiores para garantir que possamos executar tanto operações de compra quanto de venda, e não apenas compras, como relatado acima.
Reversão de Sobrecompra/Sobrevenda do Estocástico Próxima à MA
O padrão-5 combina acompanhamento de tendência com a MA atuando como suporte/resistência dinâmico e reversão de momentum com o Estocástico. O sinal de compra ocorre quando o Estocástico cruza acima do nível 20 enquanto o preço é sustentado pela MA. Esse padrão fornece validação de suporte dinâmico, já que a MA atua como zona de suporte, garantindo que o preço esteja reagindo dentro de uma tendência de alta existente. O cruzamento do Estocástico acima de 20 também sinaliza que o momentum de baixa está enfraquecendo e que compradores estão entrando no mercado. O uso de dois indicadores não apenas evita entradas prematuras, como também representa uma configuração de alta probabilidade. Uma possível desvantagem é que, em fortes tendências de baixa, o preço pode temporariamente se sustentar na MA antes de romper para baixo.
O sinal de venda ocorre quando o Estocástico cruza abaixo de 80 enquanto o preço está próximo da resistência de uma média móvel inclinada para baixo. Isso também atua como validação de resistência dinâmica, já que a MA funciona como zona de resistência, comprovando que o preço não conseguiu subir mais. A confirmação de reversão em sobrecompra sinaliza enfraquecimento do momentum de alta e entrada de vendedores. Mais uma vez, o uso combinado dos indicadores evita saídas/entradas prematuras e filtra retrações fracas. Uma possível desvantagem do sinal de baixa do padrão-5 é que, em fortes tendências de baixa, o preço pode consolidar acima da MA antes de cair, o que pode levar a saídas tardias ou perda de pontos.
Implementamos isso em MQL5 da seguinte forma:
else if(Index == 5) { if(C.GetData(T, 3, _c) >= 3 && CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_sto_k[1] < 20.0 && _sto_k[0] > 20.0) ? 1.0f : 0.0f); _v[1] = ((_c[2] > _c[1] && _c[1] < _c[0] && _c[1] >= _ma[1]) ? 1.0f : 0.0f); _v[2] = ((_c[2] < _c[1] && _c[1] > _c[0] && _c[1] <= _ma[1]) ? 1.0f : 0.0f); _v[3] = ((_sto_k[1] > 80.0 && _sto_k[0] < 80.0) ? 1.0f : 0.0f); } }
Nossas implementações de padrões em MQL5 neste artigo têm como objetivo definir um vetor de entrada para uma rede neural. Definimos esses vetores de entrada como uma simples coleção de 0s e 1s, onde 1 indica que uma condição específica de compra ou venda foi atendida. Os índices para condições de compra e venda são separados, o que tecnicamente significa que não é possível ter um vetor de entrada totalmente preenchido com 1s, já que as condições de compra e venda sempre se espelham.
Além disso, a condição em cada índice pode ser detalhada ou expandida, fazendo com que cada argumento da condição ocupe seu próprio índice. Isso resultaria em vetores de entrada muito mais longos, no entanto, não exploramos essa abordagem. Como estamos testando e treinando com dados do ano de 2023, o forward walk é realizado para o ano de 2024. Isso nos fornece o seguinte relatório:

Somos capazes de realizar o forward walk com o padrão-5, com uma ressalva. Apenas operações de compra foram executadas! Isso se deve principalmente ao treinamento em conjuntos de dados limitados/pequenos, fazendo com que as saídas da rede fiquem enviesadas para essa pequena amostra. O treinamento com conjuntos de dados maiores deve corrigir isso.
Golden Cross/Death Cross + Confirmação pelo Estocástico
O padrão-6 utiliza o golden-cross/death-cross, onde a MA de período mais curto cruza a MA mais lenta, e o momentum também é verificado pelo cruzamento do Estocástico no nível 50. O sinal de compra é o Golden Cross, onde a MA de período mais curto cruza de baixo para cima a MA de período mais longo, enquanto o Estocástico está acima de 50 e subindo. O sinal de venda é o death-cross, com a MA de período mais curto cruzando a MA de período mais longo de cima para baixo, enquanto o Estocástico está abaixo de 50 e caindo. Definimos as entradas da rede do padrão-6 da seguinte forma:
else if(Index == 6) { if(CopyBuffer(M.Handle(), 0, T, 2, _ma_lag) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_d) >= 2) { _v[0] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((50.0 <= _sto_d[0] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[2] = ((50.0 >= _sto_d[0] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); } }
No código utilizado, o identificador ‘_ma’ é a média móvel de período mais curto, para a qual, em nosso treinamento, atribuímos o período 8. A média de período mais longo utiliza o identificador ‘_ma_lag’, e seu período é o dobro da mais curta, ou seja, 16. Um forward walk para o ano de 2024, após treinamento no ano de 2023, nos fornece o seguinte relatório:

O padrão-6 falha no forward walk, não apenas em lucratividade, mas também no equilíbrio entre operações de compra e venda. Ainda assim, isso pode ser aprofundado com treinamento e testes mais extensivos antes de ser descartado.
Reversão Extrema do Estocástico + Confirmação de Tendência pela MA
O padrão-7 utiliza a inflexão do Estocástico em níveis extremos juntamente com a posição relativa do preço em relação à MA para definir seus sinais de entrada. O sinal de compra ocorre quando o Estocástico está abaixo de 10 e começa a subir, enquanto o preço está acima de uma média móvel inclinada para cima. O sinal de venda ocorre quando há uma queda a partir de um nível acima de 90, enquanto o preço está abaixo de uma média móvel inclinada para baixo. Mapeamos isso para uma rede a partir do MQL5 da seguinte forma:
else if(Index == 7) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 3, _sto_k) >= 3) { _v[0] = ((_ma[0] > _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[0] > _sto_k[1] && _sto_k[1] < _sto_k[2] && _sto_k[2] <= 10.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[0] < _sto_k[1] && _sto_k[1] > _sto_k[2] && _sto_k[2] >= 90.0) ? 1.0f : 0.0f); _v[3] = ((_ma[0] < _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
Utilizamos estritamente o identificador %K para o Estocástico aqui, e tudo o que fazemos é verificar a formação em formato de “n” para as condições de alta ou reversão em formato de “u” para as condições de baixa em níveis extremos. Um forward walk nos fornece o seguinte relatório:

Com base nos resultados acima, esse padrão também não consegue realizar um forward walk de um ano com base no teste do ano anterior. E ele executa apenas operações de venda. A execução de operações unilaterais em um forward walk pode ser preocupante. Como nossa saída é um valor escalar em ponto flutuante no intervalo de 0.0 a 1.0, isso significa que todas as previsões ficaram abaixo de 0.5. Treinamento/teste com conjuntos de dados maiores é importante para corrigir isso.
Rompimento do Estocástico com Confirmação pela MA
Nosso penúltimo padrão, o padrão-8, combina o nível 50 do Estocástico com cruzamentos entre preço e MA. O sinal de compra ocorre quando há aumento de momentum, indicado pelo Oscilador Estocástico cruzando o nível 50 de baixo para cima, fechando acima dele, enquanto simultaneamente o preço cruza a MA de forma semelhante, também fechando acima. O sinal de venda é o inverso, com o oscilador cruzando abaixo de 50 enquanto o preço rompe o suporte da MA. Esse padrão tende a não gerar muitas operações se aplicado da mesma forma que fizemos em artigos anteriores. No entanto, agora estamos verificando separadamente as condições da MA e do Estocástico para sinais de alta e baixa. Isso é feito da seguinte forma em MQL5:
else if(Index == 8) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_c[1] < _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < 50.0 && _sto_k[0] > 50.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > 50.0 && _sto_k[0] < 50.0) ? 1.0f : 0.0f); _v[3] = ((_c[1] > _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
Nosso vetor de entrada para a rede do padrão-8 agora tem maior probabilidade de registrar pelo menos uma das condições de alta ou baixa, o que auxilia tanto no treinamento quanto na adaptabilidade ao implantar a rede. Isso ocorre porque, além de processar ambos os indicadores, ele fornecerá previsões mesmo que apenas um deles registre os padrões-alvo. Um forward walk nos fornece o seguinte:

Parece que esse padrão consegue realizar o walk, o que é encorajador. Algumas ressalvas importantes devem ser mencionadas. Primeiro, esses testes foram realizados com metas de preço (take-profit) e sem stop loss. É verdade que o stop loss não garante o preço de saída, mas é necessário ter alguma estratégia para lidar com operações malsucedidas. Em segundo lugar, treinamos com dados do ano recente e testamos no ano subsequente, o que representa uma janela de teste relativamente curta. Períodos de teste mais longos, com dados de boa qualidade de corretoras, são geralmente preferíveis.
Compressão de Médias Móveis com Rompimento do Estocástico
Nosso padrão final, o padrão-9, utiliza a compressão das médias móveis para identificar condições de baixa volatilidade, juntamente com o momentum do Estocástico para cronometrar rompimentos dessas condições. O sinal de compra ocorre quando duas MAs, uma de período curto e outra de período longo, permanecem comprimidas por um período prolongado e, em seguida, há um aumento de momentum conforme indicado pelo Estocástico cruzando rapidamente acima de 50. Um sinal de venda também apresenta as mesmas condições, com a diferença de que, no caso de alta, a MA curta está acima da MA longa, enquanto no sinal de venda isso se inverte, e o Estocástico, para venda, cai rapidamente abaixo do nível 50 (em vez de subir).
Recuperamos o vetor de entrada da rede a partir do MQL5 da seguinte forma:
else if(Index == 9) { if(CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(M_LAG.Handle(), 0, T, 3, _ma_lag) >= 3 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_ma_lag[0] < _ma[0] && fabs(fabs(_ma_lag[2] - _ma[2]) - fabs(_ma_lag[0] - _ma[0])) <= fabs(_ma[2] - _ma[0])) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] <= 45.0 && _sto_k[0] >= 55.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] >= 55.0 && _sto_k[0] <= 45.0) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[0] > _ma[0] && fabs(fabs(_ma_lag[2] - _ma[2]) - fabs(_ma_lag[0] - _ma[0])) <= fabs(_ma[2] - _ma[0])) ? 1.0f : 0.0f); } }
E seus resultados de forward walk são os seguintes:

O padrão-9 também parece não ter consistência, apesar de conseguir executar tanto operações de compra quanto de venda. Em uma base de peso igual, não deveria ser estudado mais a fundo, já que temos alguns padrões acima que apresentaram walks favoráveis, mas essa decisão depende do trader e de quanto mais testes ele está disposto a realizar.
Conclusão
Não abordamos a combinação de padrões nem a seleção de diferentes padrões para criar um sistema unificado. Isso tende a ser perigoso, como observamos em artigos anteriores, especialmente o último. Utilizar múltiplos padrões ao mesmo tempo exige que o trader tenha domínio sobre eles, pois podem cancelar as operações uns dos outros. Se números mágicos forem utilizados para rastreamento, isso pode ajudar, mas ainda podem existir desafios relacionados a limites de margem. No próximo artigo, analisaremos como o aprendizado por reforço pode evoluir a partir do que desenvolvemos aqui.
| Arquivo | Descrição |
|---|---|
| 57_0.onnx | rede do padrão-0 |
| 57_1.onnx | padrão-1 |
| 57_2.onnx | padrão-2 |
| 57_3.onnx | padrão-3 |
| 57_4.onnx | padrão-4 |
| 57_5.onnx | padrão-5 |
| 57_6.onnx | padrão-6 |
| 57_7.onnx | padrão-7 |
| 57_8.onnx | padrão-8 |
| 57_9.onnx | padrão-9 |
| SignalWZ_57.mqh | Arquivo da Classe de Sinal |
| 57_X.mqh | Arquivo de Sinal para processamento das entradas da rede |
| wz_57.mq5 | Incluído para mostrar os arquivos utilizados na montagem do Expert Advisor |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17479
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Teoria dos grafos: Algoritmo de Dijkstra no trading
Técnicas de reamostragem para avaliação de previsão e classificação em MQL5
Análise espectral singular em MQL5
Ciência de dados e aprendizado de máquina (Parte 44): Previsão de séries OHLC no Forex pelo método de autorregressão vetorial (VAR)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Oi, um anexo está faltando SignalWZ_57.mqh
Sim, também enfrentei o mesmo problema de falta do arquivo SignalWZ_57.mqh.