Automatizando Estratégias de Trading em MQL5 (Parte 5): Desenvolvendo a Estratégia Adaptive Crossover RSI Trading Suite
Introdução
No artigo anterior (Parte 4 da série), apresentamos o Sistema de Recuperação por Zonas em Múltiplos Níveis, demonstrando como estender os princípios de Zone Recovery para gerenciar múltiplas configurações de trades independentes simultaneamente em MetaQuotes Language 5 (MQL5). Neste artigo (Parte 5), seguimos uma nova direção com a Estratégia Adaptive Crossover RSI Trading Suite, um sistema abrangente projetado para identificar e atuar em oportunidades de trading com alta probabilidade. Essa estratégia combina duas ferramentas críticas de análise técnica — cruzamentos de Média Móvel Adaptativa (14 e 50 períodos) como principal gerador de sinais e um Índice de Força Relativa (RSI) de 14 períodos como filtro de confirmação.
Além disso, ela emprega um filtro de dias de negociação para excluir sessões de trading com baixa probabilidade, garantindo melhor precisão e desempenho. Para aprimorar a usabilidade, o sistema visualiza os sinais de trade confirmados diretamente no gráfico, desenhando setas e anotando-as com descrições claras dos sinais. Um painel também é incluído para fornecer um resumo em tempo real do status da estratégia, métricas principais e atividade dos sinais, oferecendo aos traders uma visão completa de forma imediata. Este artigo irá guiá-lo pelo processo passo a passo de desenvolvimento dessa estratégia, desde a definição do blueprint até sua implementação em MQL5, o backtesting de seu desempenho e a análise dos resultados. Estruturaremos isso por meio dos seguintes tópicos:
- Plano Estratégico
- Implementação em MQL5
- Backtesting
- Conclusão
Ao final, você terá uma compreensão prática de como criar um sistema de trading adaptativo baseado em filtros e refiná-lo para um desempenho robusto em diferentes condições de mercado. Vamos começar então!
Plano Estratégico
A Estratégia Adaptive Crossover RSI Trading Suite é construída sobre uma base de cruzamentos de médias móveis e confirmação de momentum, criando uma abordagem equilibrada para o trading. Os sinais principais serão derivados da interação entre uma média móvel rápida de 14 períodos e uma média móvel lenta de 50 períodos. Um sinal de compra ocorrerá quando a média móvel rápida cruzar acima da média móvel lenta, sugerindo uma tendência de alta, enquanto um sinal de venda será gerado quando a média móvel rápida cruzar abaixo da média móvel lenta, indicando uma tendência de baixa.
Para aumentar a precisão desses sinais, um Índice de Força Relativa (RSI) de 14 períodos será empregado como filtro de confirmação. O RSI garantirá que as operações estejam alinhadas com o momentum predominante do mercado, reduzindo a probabilidade de entrada em trades em condições de sobrecompra ou sobrevenda. Por exemplo, um sinal de compra só será validado se o RSI estiver acima de um limite de 50, enquanto um sinal de venda exigirá que o RSI esteja abaixo de seu limite correspondente. A estratégia também incorporará um filtro de dias de negociação para otimizar o desempenho, evitando trades em dias com volatilidade historicamente baixa ou desempenho insatisfatório. Esse filtro garantirá que o sistema se concentre apenas em oportunidades de trading com alta probabilidade. Em resumo, o plano da estratégia é o seguinte.
Plano de Confirmação de Trade de Venda:

Plano de Confirmação de Trade de Compra:

Além disso, uma vez que um trade seja confirmado, o sistema marcará o gráfico com setas de sinal e anotações, identificando claramente os pontos de entrada. Um painel fornecerá atualizações em tempo real, oferecendo uma visão geral da atividade dos sinais, métricas principais e do status geral do sistema. Essa abordagem estruturada e adaptativa garantirá que a estratégia seja robusta e fácil de usar. A visão final será representada conforme a visualização abaixo.

Implementação em MQL5
Após aprender todas as teorias sobre a estratégia Adaptive Crossover RSI Trading Suite, vamos então automatizar a teoria e criar um Expert Advisor (EA) em MetaQuotes Language 5 (MQL5) para o MetaTrader 5.
Para criar um expert advisor (EA), no seu terminal MetaTrader 5, clique na aba Tools e selecione MetaQuotes Language Editor, ou simplesmente pressione F4 no seu teclado. Alternativamente, você pode clicar no ícone do IDE (Integrated Development Environment) na barra de ferramentas. Isso abrirá o ambiente do MetaQuotes Language Editor, que permite a criação de robôs de negociação, indicadores técnicos, scripts e bibliotecas de funções. Após o MetaEditor ser aberto, na barra de ferramentas, navegue até a aba File e selecione New File, ou simplesmente pressione CTRL + N, para criar um novo documento. Alternativamente, você pode clicar no ícone New na barra de ferramentas. Isso resultará na abertura de um pop-up do MQL Wizard.
No assistente que será exibido, marque Expert Advisor (template) e clique em Next. Nas propriedades gerais do Expert Advisor, na seção de nome, informe o nome do arquivo do seu expert. Observe que, para especificar ou criar uma pasta caso ela não exista, você usa a barra invertida antes do nome do EA. Por exemplo, aqui temos “Experts\” por padrão. Isso significa que o nosso EA será criado na pasta Experts e poderemos encontrá-lo lá. As demais seções são bastante diretas, mas você pode seguir o link na parte inferior do Wizard para saber exatamente como conduzir o processo.

Após fornecer o nome desejado para o arquivo do Expert Advisor, clique em Next, clique em Next novamente e, em seguida, clique em Finish. Depois de concluir todas essas etapas, estaremos prontos para codificar e programar nossa estratégia.
Primeiro, começamos definindo alguns metadados sobre o Expert Advisor (EA). Isso inclui o nome do EA, as informações de copyright e um link para o site da MetaQuotes. Também especificamos a versão do EA, que está definida como “1.00”.
//+------------------------------------------------------------------+ //| Adaptive Crossover RSI Trading Suite.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "EA that trades based on MA Crossover, RSI + Day Filter" #property strict
Isso exibirá os metadados do sistema ao carregar o programa. Em seguida, podemos avançar para a adição de algumas variáveis globais que utilizaremos dentro do programa. Primeiramente, incluímos uma instância de trade utilizando #include no início do código-fonte. Isso nos dá acesso à classe “CTrade”, que usaremos para criar um objeto de trade. Isso é crucial, pois precisamos dela para abrir operações.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os colchetes angulares indicam que o arquivo Trade.mqh será obtido a partir do diretório padrão (normalmente terminal_installation_directory\MQL5\Include). O diretório atual não é incluído na busca. A linha pode ser colocada em qualquer lugar do programa, mas, geralmente, todas as inclusões são posicionadas no início do código-fonte, para uma melhor estrutura e referência mais fácil. A declaração do objeto “obj_Trade” da classe CTrade nos dará acesso de forma simples aos métodos contidos nessa classe, graças aos desenvolvedores do MQL5.

Depois disso, precisamos declarar diversas variáveis de entrada importantes que permitirão ao usuário alterar os valores de negociação conforme desejado, sem modificar o próprio código. Para isso, organizamos as entradas em grupos para maior clareza, ou seja, configurações gerais, de indicadores e de filtros.
sinput group "GENERAL SETTINGS" sinput double inpLots = 0.01; // LotSize input int inpSLPts = 300; // Stoploss Points input double inpR2R = 1.0; // Risk to Reward Ratio sinput ulong inpMagicNo = 1234567; // Magic Number input bool inpisAllowTrailingStop = true; // Apply Trailing Stop? input int inpTrailPts = 50; // Trailing Stop Points input int inpMinTrailPts = 50; // Minimum Trailing Stop Points sinput group "INDICATOR SETTINGS" input int inpMA_Fast_Period = 14; // Fast MA Period input ENUM_MA_METHOD inpMA_Fast_Method = MODE_EMA; // Fast MA Method input int inpMA_Slow_Period = 50; // Slow MA Period input ENUM_MA_METHOD inpMA_Slow_Method = MODE_EMA; // Slow MA Method sinput group "FILTER SETTINGS" input ENUM_TIMEFRAMES inpRSI_Tf = PERIOD_CURRENT; // RSI Timeframe input int inpRSI_Period = 14; // RSI Period input ENUM_APPLIED_PRICE inpRSI_Applied_Price = PRICE_CLOSE; // RSI Application Price input double inpRsiBUYThreshold = 50; // BUY Signal Threshold input double inpRsiSELLThreshold = 50; // SELL Signal Threshold input bool Sunday = false; // Trade on Sunday? input bool Monday = false; // Trade on Monday? input bool Tuesday = true; // Trade on Tuesday? input bool Wednesday = true; // Trade on Wednesday? input bool Thursday = true; // Trade on Thursday? input bool Friday = false; // Trade on Friday? input bool Saturday = false; // Trade on Saturday?
Aqui, definimos os parâmetros centrais e as configurações do programa Adaptive Crossover RSI Trading Suite, permitindo um controle preciso sobre seu comportamento. Dividimos essas configurações em três grupos principais: “GENERAL SETTINGS”, “INDICATOR SETTINGS” e “FILTER SETTINGS”, juntamente com controles específicos para os dias de negociação. O uso de tipos de variáveis e enumerações aumenta a flexibilidade e a clareza no design do sistema.
No grupo “GENERAL SETTINGS”, definimos os parâmetros de gerenciamento de trades. Utilizamos a palavra-chave input para parâmetros otimizáveis e sinput para parâmetros do tipo string ou não otimizáveis. A variável “inpLots” especifica o tamanho do lote da operação, enquanto “inpSLPts” define o nível de stop-loss em pontos, garantindo que o risco seja controlado em cada trade. A variável “inpR2R” estabelece a relação risco-retorno desejada, mantendo um equilíbrio favorável entre risco e potencial de recompensa. Um identificador único de trade é atribuído por meio de “inpMagicNo”, que o programa utiliza para diferenciar suas ordens. A funcionalidade de trailing stop é gerenciada por meio de “inpisAllowTrailingStop”, permitindo que os usuários a ativem ou desativem. As variáveis “inpTrailPts” e “inpMinTrailPts” especificam, respectivamente, a distância do trailing stop e o limite mínimo de ativação, garantindo que os trailing stops estejam alinhados às condições de mercado.
No grupo “INDICATOR SETTINGS”, configuramos os parâmetros das médias móveis, que formam a espinha dorsal da geração de sinais. O período da média móvel rápida é definido por “inpMA_Fast_Period”, e seu método de cálculo é escolhido utilizando a enumeração ENUM_MA_METHOD com a variável “inpMA_Fast_Method”, que suporta opções como MODE_SMA, MODE_EMA, MODE_SMMA e MODE_LWMA. Da mesma forma, a média móvel lenta é configurada com “inpMA_Slow_Period”, enquanto seu método é determinado usando “inpMA_Slow_Method”. Essas enumerações garantem que os usuários possam personalizar a estratégia com seus tipos de médias móveis preferidos para diferentes condições de mercado.
O grupo “FILTER SETTINGS” concentra-se no indicador RSI, que atua como um filtro de momentum. A variável “inpRSI_Tf”, definida utilizando a enumeração ENUM_TIMEFRAMES, permite que os usuários selecionem o timeframe do RSI, como PERIOD_M1, “PERIOD_H1” ou “PERIOD_D1”. O período do RSI é especificado com “inpRSI_Period”, enquanto “inpRSI_Applied_Price”, uma enumeração ENUM_APPLIED_PRICE, determina os dados de preço (por exemplo, “PRICE_CLOSE”, “PRICE_OPEN” ou “PRICE_MEDIAN”) usados nos cálculos. Os limites para validação dos sinais de compra e venda são definidos usando “inpRsiBUYThreshold” e “inpRsiSELLThreshold”, garantindo que o RSI esteja alinhado com o momentum do mercado antes da execução das operações.
Por fim, implementamos um filtro de dias de negociação utilizando variáveis booleanas, como “Sunday”, “Monday” e assim por diante, permitindo o controle da atividade do EA em dias específicos. Ao desativar o trading em dias menos favoráveis, o sistema evita exposição desnecessária a condições potencialmente não lucrativas. Depois disso, precisamos definir os handles dos indicadores que iremos utilizar.
int handleMAFast = INVALID_HANDLE; int handleMASlow = INVALID_HANDLE; int handleRSIFilter = INVALID_HANDLE;
Inicializamos três variáveis principais — “handleMAFast”, “handleMASlow” e “handleRSIFilter” — e as definimos como INVALID_HANDLE. Ao fazer isso, garantimos que o nosso EA inicie em um estado limpo e controlado, evitando possíveis problemas decorrentes de handles de indicadores não inicializados ou inválidos. Utilizamos “handleMAFast” para gerenciar o indicador de média móvel rápida, que configuramos para capturar tendências de preço de curto prazo com base nos parâmetros definidos.
Da mesma forma, “handleMASlow” é designado para gerenciar o indicador de média móvel lenta, permitindo acompanhar tendências de preço de longo prazo. Esses handles são vitais para recuperar e processar dinamicamente os valores das médias móveis necessários para a nossa estratégia. Com “handleRSIFilter”, preparamos a conexão com o indicador RSI, que utilizamos como filtro de momentum para confirmar nossos sinais. Em seguida, precisamos definir os arrays de armazenamento nos quais iremos guardar os dados recuperados dos indicadores. Isso também exigirá três arrays.
double bufferMAFast[]; double bufferMASlow[]; double bufferRSIFilter[];
Aqui, declaramos três arrays dinâmicos: “bufferMAFast[]”, “bufferMASlow[]” e “bufferRSIFilter[]”. Esses arrays servirão como contêineres de armazenamento onde coletaremos e gerenciaremos os valores calculados dos indicadores usados em nossa estratégia. Ao organizar os dados dessa forma, garantimos que o nosso EA tenha acesso direto e eficiente aos resultados dos indicadores durante sua execução. A partir daqui, precisaremos ir para a função de inicialização e criar os handles dos indicadores.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE); handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE); handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price); //---- }
Aqui, inicializamos os handles dos indicadores que utilizaremos na estratégia: a média móvel rápida, a média móvel lenta e o filtro RSI. Começamos inicializando a média móvel rápida usando a função iMA. Essa função requer vários parâmetros. O primeiro, _Symbol, informa à função que calcule a média móvel para o instrumento de negociação atual. O segundo, _Period, especifica o timeframe do gráfico (como 1 minuto, 1 hora).
Também passamos o período da média móvel rápida (“inpMA_Fast_Period”), que determina quantas barras são usadas para calcular a média móvel. O parâmetro “0” refere-se ao “shift” da média móvel, onde “0” significa sem deslocamento. O método da média móvel (“inpMA_Fast_Method”) especifica se ela é uma média móvel Exponencial ou Simples, e “PRICE_CLOSE” indica que estamos utilizando os preços de fechamento de cada barra para calcular a média.
O resultado dessa função é atribuído a “handleMAFast”, permitindo que acessemos o valor da média móvel rápida para cálculos futuros.
Em seguida, inicializamos a média móvel lenta da mesma forma, chamando a função iMA. Aqui, utilizamos o mesmo _Symbol, _Period e o período da média móvel lenta (“inpMA_Slow_Period”). Novamente, especificamos o método e o preço (“PRICE_CLOSE”) usados para calcular essa média móvel. Esse valor é armazenado em “handleMASlow” para uso futuro. Por fim, inicializamos o filtro RSI utilizando a função iRSI. Fornecemos o _Symbol para especificar o instrumento, o timeframe do RSI (“inpRSI_Tf”), o período do RSI (“inpRSI_Period”) e o preço aplicado (“inpRSI_Applied_Price”). O resultado da função é armazenado em “handleRSIFilter”, o que nos permitirá utilizar o valor do RSI para confirmar os sinais de trade na estratégia.
Como esses handles são a espinha dorsal da nossa estratégia, precisamos garantir que eles sejam inicializados corretamente; caso contrário, claramente não há sentido em continuar executando o programa.
if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){ Print("ERROR! Unable to create the indicator handles. Reveting Now!"); return (INIT_FAILED); }
Aqui, verificamos se a inicialização dos handles dos indicadores foi bem-sucedida. Avaliamos se algum dos handles (“handleMAFast”, “handleMASlow” ou “handleRSIFilter”) é igual a INVALID_HANDLE, o que indicaria uma falha na criação dos indicadores correspondentes. Se algum dos handles falhar, utilizamos a função “Print” para exibir uma mensagem de erro no terminal, alertando-nos sobre o problema. Por fim, retornamos INIT_FAILED, o que interrompe a execução do EA caso qualquer um dos handles de indicador seja inválido, garantindo que o EA não continue rodando em condições incorretas.
Outra falha também pode ocorrer quando o usuário fornece períodos irreais, tecnicamente menores ou iguais a zero. Assim, precisamos verificar os valores de entrada definidos pelo usuário para os períodos da média móvel rápida, da média móvel lenta e do RSI, garantindo que os períodos (“inpMA_Fast_Period”, “inpMA_Slow_Period”, “inpRSI_Period”) sejam maiores que zero.
if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){ Print("ERROR! Os períodos não podem ser <= a 0. Reverting Now!"); return (INIT_PARAMETERS_INCORRECT); }
Aqui, se os valores de entrada do usuário não forem maiores que zero, encerramos o programa retornando INIT_PARAMETERS_INCORRECT. Se passarmos por essa verificação, então teremos os handles dos indicadores prontos e poderemos definir os arrays de armazenamento como séries temporais.
ArraySetAsSeries(bufferMAFast,true); ArraySetAsSeries(bufferMASlow,true); ArraySetAsSeries(bufferRSIFilter,true); obj_Trade.SetExpertMagicNumber(inpMagicNo); Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode());
Por fim, realizamos algumas ações-chave para finalizar o processo de inicialização. Primeiro, utilizamos a função ArraySetAsSeries para definir os arrays (“bufferMAFast”, “bufferMASlow” e “bufferRSIFilter”) como séries temporais. Isso é importante porque garante que os dados dentro desses arrays sejam armazenados de forma compatível com a maneira como o MetaTrader lida com dados de séries temporais — armazenando os dados mais recentes no índice 0. Ao definir cada um desses arrays como séries, garantimos que os indicadores sejam acessados na ordem correta durante o trading.
Em seguida, chamamos o método “SetExpertMagicNumber” no objeto “obj_Trade”, passando o valor de “inpMagicNo” como número mágico. O número mágico é um identificador exclusivo para as operações do EA, garantindo que elas possam ser diferenciadas de outras operações realizadas manualmente ou por outros EAs. Por fim, utilizamos a função Print para exibir uma mensagem de sucesso no terminal, confirmando que o processo de inicialização foi concluído. A mensagem inclui o tipo de conta, que é obtido por meio da função “trading_Account_Mode” — indicando se a conta é demo ou real. A função responsável por isso é a seguinte.
string trading_Account_Mode(){ string account_mode; switch ((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)){ case ACCOUNT_TRADE_MODE_DEMO: account_mode = "DEMO"; break; case ACCOUNT_TRADE_MODE_CONTEST: account_mode = "COMPETITION"; break; case ACCOUNT_TRADE_MODE_REAL: account_mode = "REAL"; break; } return account_mode; }
Aqui, definimos uma função do tipo string chamada “trading_Account_Mode” para determinar o tipo de conta de negociação (se é uma conta demo, de competição ou real) com base no valor do parâmetro ACCOUNT_TRADE_MODE. Começamos declarando uma variável “account_mode” para armazenar o tipo de conta como uma string. Em seguida, utilizamos uma instrução “switch” para avaliar o modo de negociação da conta, que é obtido chamando a função “AccountInfoInteger” com o parâmetro ACCOUNT_TRADE_MODE. Essa função retorna o modo de negociação da conta como um valor inteiro. A instrução switch verifica o valor desse inteiro e o compara com os possíveis modos de conta:
- Se o modo da conta for ACCOUNT_TRADE_MODE_DEMO, definimos “account_mode” como “DEMO”.
- Se o modo da conta for ACCOUNT_TRADE_MODE_CONTEST, definimos “account_mode” como “COMPETITION”.
- Se o modo da conta for ACCOUNT_TRADE_MODE_REAL, definimos “account_mode” como “REAL”.
Por fim, a função retorna “account_mode” como uma string, que indica o tipo de conta à qual o EA está conectado. Portanto, a função final de inicialização é a seguinte:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE); handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE); handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price); if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){ Print("ERROR! Unable to create the indicator handles. Reveting Now!"); return (INIT_FAILED); } if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){ Print("ERROR! Os períodos não podem ser <= a 0. Reverting Now!"); return (INIT_PARAMETERS_INCORRECT); } ArraySetAsSeries(bufferMAFast,true); ArraySetAsSeries(bufferMASlow,true); ArraySetAsSeries(bufferRSIFilter,true); obj_Trade.SetExpertMagicNumber(inpMagicNo); Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode()); //--- return(INIT_SUCCEEDED); }
Agora avançamos para o manipulador de eventos OnDeinit, onde precisaremos liberar os handles dos indicadores, já que não precisaremos mais deles.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- IndicatorRelease(handleMAFast); IndicatorRelease(handleMASlow); IndicatorRelease(handleRSIFilter); }
Para liberar os recursos alocados aos handles dos indicadores, começamos chamando a função IndicatorRelease para cada um dos handles dos indicadores: “handleMAFast”, “handleMASlow” e “handleRSIFilter”. O objetivo dessa função é liberar a memória e os recursos associados aos handles dos indicadores que foram inicializados durante a execução do EA. Isso garante que os recursos da plataforma não fiquem desnecessariamente ocupados por indicadores que não estão mais em uso. Em seguida, avançamos para o manipulador de eventos OnTick, que é onde a maior parte da nossa lógica de trading será tratada. Primeiro, precisaremos recuperar os dados dos indicadores a partir dos handles.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Check if data can be retrieved for the fast moving average (MA) if (CopyBuffer(handleMAFast,0,0,3,bufferMAFast) < 3){ //--- Print error message for fast MA data retrieval failure Print("ERROR! Não foi possível recuperar os dados FAST MA solicitados. Reverting."); //--- Exit the function if data retrieval fails return; } //--- Check if data can be retrieved for the slow moving average (MA) if (CopyBuffer(handleMASlow,0,0,3,bufferMASlow) < 3){ //--- Print error message for slow MA data retrieval failure Print("ERROR! Não foi possível recuperar os dados SLOW MA solicitados. Reverting."); //--- Exit the function if data retrieval fails return; } //--- Check if data can be retrieved for the RSI filter if (CopyBuffer(handleRSIFilter,0,0,3,bufferRSIFilter) < 3){ //--- Print error message for RSI data retrieval failure Print("ERROR! Não foi possível recuperar os dados RSI solicitados. Reverting."); //--- Exit the function if data retrieval fails return; } //--- }
Aqui, focamos em recuperar os dados mais recentes dos indicadores da média móvel rápida, da média móvel lenta e do filtro RSI, para garantir que o EA tenha as informações necessárias para tomar decisões de trading. Primeiro, utilizamos a função CopyBuffer para o handle da média móvel rápida (“handleMAFast”). A função extrai os valores do indicador para o buffer correspondente (“bufferMAFast”) para processamento. Especificamente, solicitamos 3 pontos de dados a partir do índice 0, que representa os dados mais recentes no gráfico. Se o número de valores recuperados for menor que 3, isso indica uma falha no acesso aos dados necessários. Nesse caso, exibimos uma mensagem de erro utilizando a função Print e encerramos a função antecipadamente com o operador return.
Em seguida, repetimos um processo semelhante para o handle da média móvel lenta (“handleMASlow”) e seu buffer (“bufferMASlow”). Novamente, se a função CopyBuffer não conseguir recuperar pelo menos 3 pontos de dados, exibimos uma mensagem de erro e encerramos a função para evitar a execução posterior. Por fim, utilizamos a mesma função para o handle do filtro RSI (“handleRSIFilter”) e seu buffer (“bufferRSIFilter”). Como antes, garantimos que os pontos de dados solicitados sejam recuperados com sucesso; caso contrário, uma mensagem de erro é exibida e a função é encerrada. Se não retornarmos até este ponto, temos os dados necessários e podemos continuar a gerar sinais. No entanto, queremos gerar sinais a cada nova barra, e não a cada tick. Assim, precisaremos de uma função para detectar a geração de novas barras.
//+------------------------------------------------------------------+ //| Function to detect if a new bar is formed | //+------------------------------------------------------------------+ bool isNewBar(){ //--- Static variable to store the last bar count static int lastBarCount = 0; //--- Get the current bar count int currentBarCount = iBars(_Symbol,_Period); //--- Check if the bar count has increased if (currentBarCount > lastBarCount){ //--- Update the last bar count lastBarCount = currentBarCount; //--- Return true if a new bar is detected return true; } //--- Return false if no new bar is detected return false; }
Aqui, definimos a função “isNewBar”, que foi projetada para detectar o surgimento de uma nova barra no gráfico. Essa função será crucial para garantir que nossas operações sejam executadas apenas uma vez por barra, em vez de repetidamente a cada tick. Começamos declarando uma variável estática “lastBarCount” e inicializando-a com 0. Uma variável estática mantém seu valor entre chamadas da função, permitindo que comparemos o estado atual com o estado anterior. Em seguida, recuperamos o número total de barras no gráfico utilizando a função iBars, passando _Symbol (o instrumento de negociação atual) e _Period (o timeframe atual). O resultado é armazenado em “currentBarCount”.
Em seguida, comparamos “currentBarCount” com “lastBarCount”. Se “currentBarCount” for maior, isso indica que uma nova barra foi formada no gráfico. Nesse caso, atualizamos “lastBarCount” para corresponder a “currentBarCount” e retornamos true, sinalizando a presença de uma nova barra. Se nenhuma nova barra for detectada, a função retorna false. Agora podemos usar essa função no manipulador de eventos de tick.
//--- Check if a new bar has formed if (isNewBar()){ //--- Print debug message for a new tick //Print("THIS IS A NEW TICK"); //--- Identify if a buy crossover has occurred bool isMACrossOverBuy = bufferMAFast[1] > bufferMASlow[1] && bufferMAFast[2] <= bufferMASlow[2]; //--- Identify if a sell crossover has occurred bool isMACrossOverSell = bufferMAFast[1] < bufferMASlow[1] && bufferMAFast[2] >= bufferMASlow[2]; //--- Check if the RSI confirms a buy signal bool isRSIConfirmBuy = bufferRSIFilter[1] >= inpRsiBUYThreshold; //--- Check if the RSI confirms a sell signal bool isRSIConfirmSell = bufferRSIFilter[1] <= inpRsiSELLThreshold; //--- }
Aqui, implementamos a lógica central para detectar sinais específicos de trading com base na relação entre as médias móveis e as confirmações do RSI. O processo começa verificando se uma nova barra foi formada usando a função “isNewBar”. Isso garante que a lógica subsequente seja executada apenas uma vez por barra, evitando avaliações repetidas dentro da mesma barra.
Se uma nova barra for detectada, primeiro nos preparamos para identificar um cruzamento de compra avaliando a relação entre as médias móveis rápida e lenta. Especificamente, verificamos se o valor da média móvel rápida da barra anterior (“bufferMAFast[1]”) é maior que o valor da média móvel lenta da mesma barra (“bufferMASlow[1]”), enquanto, ao mesmo tempo, o valor da média móvel rápida de duas barras atrás (“bufferMAFast[2]”) era menor ou igual ao valor da média móvel lenta daquela barra (“bufferMASlow[2]”). Se ambas as condições forem verdadeiras, definimos a variável booleana “isMACrossOverBuy” como true, indicando um cruzamento de compra.
Da mesma forma, identificamos um cruzamento de venda verificando se o valor da média móvel rápida da barra anterior (“bufferMAFast[1]”) é menor que o valor da média móvel lenta da mesma barra (“bufferMASlow[1]”), enquanto o valor da média móvel rápida de duas barras atrás (“bufferMAFast[2]”) era maior ou igual ao valor da média móvel lenta daquela barra (“bufferMASlow[2]”). Se essas condições forem atendidas, definimos a variável booleana “isMACrossOverSell” como true, indicando um cruzamento de venda.
Em seguida, incorporamos o RSI como filtro de confirmação para os cruzamentos detectados. Para uma confirmação de compra, verificamos se o valor do RSI da barra anterior (“bufferRSIFilter[1]”) é maior ou igual ao limite de compra (“inpRsiBUYThreshold”). Se verdadeiro, definimos a variável booleana “isRSIConfirmBuy” como true. Da mesma forma, para uma confirmação de venda, verificamos se o valor do RSI da barra anterior (“bufferRSIFilter[1]”) é menor ou igual ao limite de venda (“inpRsiSELLThreshold”). Se verdadeiro, definimos a variável booleana “isRSIConfirmSell” como true. Agora podemos usar essas variáveis para tomar decisões de trading.
//--- Handle buy signal conditions if (isMACrossOverBuy){ if (isRSIConfirmBuy){ //--- Print buy signal message Print("BUY SIGNAL"); //--- }
Aqui, verificamos se há um cruzamento das médias móveis e se o RSI confirma o sinal; se todas as condições forem verdadeiras, imprimimos um sinal de compra. No entanto, antes de abrir uma posição de compra, precisamos verificar a aderência ao filtro de dias de negociação. Portanto, precisamos de uma função para manter tudo modularizado.
//+------------------------------------------------------------------+ //| Function to check trading days filter | //+------------------------------------------------------------------+ bool isCheckTradingDaysFilter(){ //--- Structure to store the current date and time MqlDateTime dateTIME; //--- Convert the current time into structured format TimeToStruct(TimeCurrent(),dateTIME); //--- Variable to store the day of the week string today = "DAY OF WEEK"; //--- Assign the day of the week based on the numeric value if (dateTIME.day_of_week == 0){today = "SUNDAY";} if (dateTIME.day_of_week == 1){today = "MONDAY";} if (dateTIME.day_of_week == 2){today = "TUESDAY";} if (dateTIME.day_of_week == 3){today = "WEDNESDAY";} if (dateTIME.day_of_week == 4){today = "THURSDAY";} if (dateTIME.day_of_week == 5){today = "FRIDAY";} if (dateTIME.day_of_week == 6){today = "SATURDAY";} //--- Check if trading is allowed based on the input parameters if ( (dateTIME.day_of_week == 0 && Sunday == true) || (dateTIME.day_of_week == 1 && Monday == true) || (dateTIME.day_of_week == 2 && Tuesday == true) || (dateTIME.day_of_week == 3 && Wednesday == true) || (dateTIME.day_of_week == 4 && Thursday == true) || (dateTIME.day_of_week == 5 && Friday == true) || (dateTIME.day_of_week == 6 && Saturday == true) ){ //--- Print acceptance message for trading Print("Today is on ",today,". Trade ACCEPTED."); //--- Return true if trading is allowed return true; } else { //--- Print rejection message for trading Print("Today is on ",today,". Trade REJECTED."); //--- Return false if trading is not allowed return false; } }
Aqui, criamos uma função “isCheckTradingDaysFilter” para determinar se o trading é permitido no dia atual com base nas configurações de entrada do usuário. Isso garante que as operações sejam executadas apenas nos dias de negociação permitidos, aumentando a precisão e evitando operações não intencionais em dias restritos. Primeiro, definimos um objeto estruturado “MqlDateTime dateTIME” para armazenar a data e hora atuais. Utilizando a função TimeToStruct, convertemos o horário atual do servidor (TimeCurrent) na estrutura “dateTIME”, permitindo acesso fácil a componentes como o dia da semana.
Em seguida, definimos uma variável “today” e atribuímos a ela uma string de preenchimento “DAY OF WEEK”. Essa variável armazenará posteriormente o nome do dia atual em um formato legível. Usando uma série de condições if, mapeamos o valor numérico de “day_of_week” (variando de 0 para domingo até 6 para sábado) para o nome do dia correspondente, atualizando a variável “today” com o dia correto.
Depois disso, verificamos se o trading é permitido no dia atual comparando “dateTIME.day_of_week” com as variáveis booleanas de entrada correspondentes (“Sunday”, “Monday” etc.). Se o dia atual corresponder a um dos dias de negociação habilitados, uma mensagem é exibida usando “Print” para indicar que o trading é permitido, incluindo o nome do dia, e a função retorna true. Por outro lado, se o trading não for permitido, uma mensagem é exibida indicando que o trading foi rejeitado, e a função retorna false. Tecnicamente, essa função atua como um guardião, garantindo que as operações de trading estejam alinhadas às preferências do usuário para dias específicos. Podemos utilizá-la para aplicar o filtro de dias de negociação.
//--- Verify trading days filter before placing a trade if (isCheckTradingDaysFilter()){ //--- Retrieve the current ask price double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Retrieve the current bid price double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Set the open price to the ask price double openPrice = Ask; //--- Calculate the stop-loss price double stoploss = Bid - inpSLPts*_Point; //--- Calculate the take-profit price double takeprofit = Bid + (inpSLPts*inpR2R)*_Point; //--- Define the trade comment string comment = "BUY TRADE"; //--- Execute a buy trade obj_Trade.Buy(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment); //--- Initialize the ticket variable ulong ticket = 0; //--- Retrieve the order result ticket ticket = obj_Trade.ResultOrder(); //--- Print success message if the trade is opened if (ticket > 0){ Print("SUCCESS. Opened the BUY position with ticket # ",ticket); } //--- Print error message if the trade fails to open else {Print("ERROR! Failed to open the BUY position.");} }
Aqui, verificamos se o trading é permitido no dia atual chamando a função “isCheckTradingDaysFilter”. Se a função retornar true, prosseguimos para coletar os dados de mercado e executar uma operação, garantindo que o trading esteja em conformidade com os filtros de dias definidos pelo usuário. Primeiro, recuperamos os preços atuais de mercado usando a função SymbolInfoDouble. Os parâmetros SYMBOL_ASK e SYMBOL_BID são usados para obter os preços atuais de compra (ask) e venda (bid) do símbolo de negociação ativo (_Symbol). Esses valores são armazenados nas variáveis “Ask” e “Bid”, respectivamente, servindo como base para cálculos adicionais.
Em seguida, calculamos os níveis de preço necessários para a operação. O preço “Ask” é definido como o “openPrice”, representando o preço de entrada para uma posição de compra. Calculamos o preço de stop-loss subtraindo “inpSLPts” (os pontos de stop-loss definidos na entrada) multiplicados por _Point do preço “Bid”. Da mesma forma, o preço de take-profit é determinado adicionando ao preço “Bid” o produto de “inpSLPts”, da relação risco-retorno (“inpR2R”) e de _Point. Esses cálculos definem os limites de risco e recompensa da operação.
Em seguida, definimos um comentário de trade (“BUY TRADE”) para identificar a operação para referência futura. Depois disso, executamos a operação de compra usando o método “obj_Trade.Buy”, passando como parâmetros o tamanho do lote (“inpLots”), o símbolo de negociação, o preço de entrada, o preço de stop-loss, o preço de take-profit e o comentário. Essa função envia a ordem de trade ao mercado. Após a execução da operação, inicializamos a variável “ticket” com 0 e atribuímos a ela o ticket da ordem retornado pelo método “obj_Trade.ResultOrder”. Se o ticket for maior que 0, isso indica que a operação foi aberta com sucesso, e uma mensagem de sucesso é exibida com o número do ticket. Se o ticket permanecer 0, isso indica falha na operação e uma mensagem de erro é exibida. Para uma posição de venda, seguimos o mesmo procedimento, porém com condições inversas. O trecho de código correspondente é o seguinte:
//--- Handle sell signal conditions else if (isMACrossOverSell){ if (isRSIConfirmSell){ //--- Print sell signal message Print("SELL SIGNAL"); //--- Verify trading days filter before placing a trade if (isCheckTradingDaysFilter()){ //--- Retrieve the current ask price double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); //--- Retrieve the current bid price double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- Set the open price to the bid price double openPrice = Bid; //--- Calculate the stop-loss price double stoploss = Ask + inpSLPts*_Point; //--- Calculate the take-profit price double takeprofit = Ask - (inpSLPts*inpR2R)*_Point; //--- Define the trade comment string comment = "SELL TRADE"; //--- Execute a sell trade obj_Trade.Sell(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment); //--- Initialize the ticket variable ulong ticket = 0; //--- Retrieve the order result ticket ticket = obj_Trade.ResultOrder(); //--- Print success message if the trade is opened if (ticket > 0){ Print("SUCCESS. Opened the SELL position with ticket # ",ticket); } //--- Print error message if the trade fails to open else {Print("ERROR! Failed to open the SELL position.");} } } }
Ao executar o programa, temos o seguinte resultado.

A partir da imagem, podemos ver que confirmamos os trades. No entanto, seria uma boa ideia visualizar os sinais no gráfico para maior clareza. Assim, precisamos de uma função para desenhar setas com anotações.
//+------------------------------------------------------------------+ //| Create signal text function | //+------------------------------------------------------------------+ void createSignalText(datetime time,double price,int arrowcode, int direction,color clr,double angle,string txt ){ //--- Generate a unique name for the signal object string objName = " "; StringConcatenate(objName, "Signal @ ",time," at Price ",DoubleToString(price,_Digits)); //--- Create the arrow object at the specified time and price if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)){ //--- Set arrow properties ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode); ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP); if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM); } //--- Generate a unique name for the description text object string objNameDesc = objName+txt; //--- Create the text object at the specified time and price if (ObjectCreate(0,objNameDesc,OBJ_TEXT,0,time,price)){ //--- Set text properties ObjectSetInteger(0,objNameDesc,OBJPROP_COLOR,clr); ObjectSetDouble(0,objNameDesc,OBJPROP_ANGLE,angle); if (direction > 0){ ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_LEFT); ObjectSetString(0,objNameDesc,OBJPROP_TEXT," "+txt); } if (direction < 0){ ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_BOTTOM); ObjectSetString(0,objNameDesc,OBJPROP_TEXT," "+txt); } } }
Aqui, definimos a função “createSignalText” para representar visualmente os sinais de trading no gráfico por meio de setas e textos descritivos. Essa função melhora a clareza do gráfico ao marcar eventos significativos, como sinais de compra ou venda. Primeiro, geramos um nome exclusivo para o objeto de seta utilizando a função StringConcatenate. O nome inclui a palavra “Signal”, o horário especificado e o preço do sinal. Essa nomenclatura exclusiva garante que não haja sobreposição com outros objetos no gráfico.
Em seguida, criamos um objeto de seta no gráfico no horário e preço especificados usando a função ObjectCreate. Se a criação for bem-sucedida, prosseguimos para personalizar suas propriedades. O parâmetro “arrowcode” determina o tipo de seta a ser exibida, enquanto o parâmetro “clr” especifica a cor da seta. Com base na direção do sinal, o ponto de ancoragem da seta é definido no topo (ANCHOR_TOP) para sinais de alta ou na parte inferior (ANCHOR_BOTTOM) para sinais de baixa. Isso garante que a posição da seta esteja corretamente alinhada com o contexto do sinal.
Em seguida, criamos um objeto de texto descritivo para acompanhar a seta. Um nome exclusivo para o objeto de texto é gerado adicionando a descrição “txt” ao nome da seta. O objeto de texto é posicionado nas mesmas coordenadas de tempo e preço da seta. As propriedades do objeto de texto são configuradas para melhorar sua aparência e alinhamento. O parâmetro “clr” define a cor do texto, e o parâmetro de ângulo determina sua rotação. Para sinais de alta, a âncora é alinhada à esquerda (ANCHOR_LEFT), e o texto recebe espaços no início para ajustar o espaçamento. Da mesma forma, para sinais de baixa, a âncora é alinhada à parte inferior (ANCHOR_BOTTOM), com o mesmo ajuste de espaçamento.
Agora podemos usar essa função para criar as setas com as respectivas anotações.
//--- FOR A BUY SIGNAL //--- Retrieve the time of the signal datetime textTime = iTime(_Symbol,_Period,1); //--- Retrieve the price of the signal double textPrice = iLow(_Symbol,_Period,1); //--- Create a visual signal on the chart for a buy createSignalText(textTime,textPrice,221,1,clrBlue,-90,"Buy Signal"); //... //--- FOR A SELL SIGNAL //--- Retrieve the time of the signal datetime textTime = iTime(_Symbol,_Period,1); //--- Retrieve the price of the signal double textPrice = iHigh(_Symbol,_Period,1); //--- Create a visual signal on the chart for a sell createSignalText(textTime,textPrice,222,-1,clrRed,90,"Sell Signal");
Aqui, criamos marcadores visuais no gráfico para representar sinais de Compra e Venda. Esses marcadores consistem em setas e textos descritivos para melhorar a clareza do gráfico e auxiliar na tomada de decisões.
Para um Sinal de Compra:
- Recuperar o Horário do Sinal:
Utilizando a função iTime, obtemos o datetime da segunda última barra concluída (índice 1) no gráfico para o símbolo e timeframe atuais (_Symbol e _Period). Isso garante que o sinal corresponda a uma barra confirmada.
- Recuperar o Preço do Sinal:
Utilizamos a função iLow para obter o menor preço da mesma barra (1). Isso serve como a posição onde desejamos colocar o marcador.
- Criar um Sinal Visual:
A função “createSignalText” é chamada com os valores recuperados de “textTime” e “textPrice”, juntamente com parâmetros adicionais:
- “221”: O código da seta para um tipo específico de seta que representa um sinal de compra.
- “1”: Direção do sinal, indicando movimento ascendente.
- “clrBlue”: Cor da seta e do texto, representando um sinal positivo.
- “-90”: Ângulo do texto para alinhamento adequado.
- “Buy Signal”: O texto descritivo é exibido próximo à seta. Isso marca visualmente o sinal de compra no gráfico.
Para um Sinal de Venda:
- Recuperar o Horário do Sinal:
Assim como no sinal de compra, utilizamos iTime para obter o datetime da barra no índice 1.
- Recuperar o Preço do Sinal:
A função iHigh é utilizada para obter o maior preço da mesma barra. Isso representa a posição de colocação do marcador do sinal de venda.
- Criar um Sinal Visual:
A função “createSignalText” é chamada com:
- “222”: O código da seta que representa um sinal de venda.
- “-1”: Direção do sinal, indicando movimento descendente.
- “clrRed”: Cor da seta e do texto, indicando um sinal negativo.
- “90”: Ângulo do texto para alinhamento.
- “Sell Signal”: O texto descritivo exibido próximo à seta. Isso adiciona um marcador claro para o sinal de venda no gráfico.
Ao rodar o programa, temos a seguinte saída:

A partir da imagem, podemos ver que, uma vez que temos um sinal confirmado, a seta e sua respectiva anotação aparecem no gráfico para maior clareza. Isso adiciona um toque profissional ao gráfico, tornando mais fácil interpretar os sinais de trading por meio de indicações visuais claras. Agora podemos avançar para a adição de um recurso de trailing stop ao código, para que possamos proteger parte dos lucros ao atingir determinados níveis predefinidos. Por simplicidade, utilizaremos uma função.
//+------------------------------------------------------------------+ //| Trailing stop function | //+------------------------------------------------------------------+ void applyTrailingStop(int slpoints, CTrade &trade_object,ulong magicno=0,int minProfitPts=0){ //--- Calculate the stop loss price for buy positions double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID) - slpoints*_Point,_Digits); //--- Calculate the stop loss price for sell positions double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK) + slpoints*_Point,_Digits); //--- Loop through all positions in the account for (int i=PositionsTotal()-1; i>=0; i--){ //--- Get the ticket of the position ulong ticket = PositionGetTicket(i); //--- Ensure the ticket is valid if (ticket > 0){ //--- Select the position by ticket if (PositionSelectByTicket(ticket)){ //--- Check if the position matches the symbol and magic number (if provided) if (PositionGetSymbol(POSITION_SYMBOL) == _Symbol && (magicno == 0 || PositionGetInteger(POSITION_MAGIC) == magicno) ){ //--- Retrieve the open price and current stop loss of the position double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); double positionSl = PositionGetDouble(POSITION_SL); //--- Handle trailing stop for buy positions if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ //--- Calculate the minimum profit price for the trailing stop double minProfitPrice = NormalizeDouble((positionOpenPrice+minProfitPts*_Point),_Digits); //--- Apply trailing stop only if conditions are met if (buySl > minProfitPrice && buySl > positionOpenPrice && (positionSl == 0 || buySl > positionSl) ){ //--- Modify the position's stop loss trade_object.PositionModify(ticket,buySl,PositionGetDouble(POSITION_TP)); } } //--- Handle trailing stop for sell positions else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ //--- Calculate the minimum profit price for the trailing stop double minProfitPrice = NormalizeDouble((positionOpenPrice-minProfitPts*_Point),_Digits); //--- Apply trailing stop only if conditions are met if (sellSl < minProfitPrice && sellSl < positionOpenPrice && (positionSl == 0 || sellSl < positionSl) ){ //--- Modify the position's stop loss trade_object.PositionModify(ticket,sellSl,PositionGetDouble(POSITION_TP)); } } } } } } }
Aqui, implementamos um mecanismo de trailing stop na função “applyTrailingStop” para ajustar dinamicamente os níveis de stop-loss das posições de trading ativas. Isso garante que, à medida que o mercado se move de forma favorável, os lucros sejam protegidos enquanto os riscos são minimizados. A função opera com base na seguinte lógica. Primeiro, calculamos os níveis de stop-loss tanto para posições de compra quanto de venda. Utilizando a função SymbolInfoDouble, recuperamos o preço SYMBOL_BID para determinar o nível “buySl”, subtraindo os “slpoints” especificados (distância do stop-loss em pontos) e normalizando o valor para o número correto de casas decimais usando a função NormalizeDouble. Da mesma forma, calculamos o nível “sellSl” adicionando os “slpoints” ao preço SYMBOL_ASK e normalizando o resultado.
Em seguida, iteramos por todas as posições ativas na conta de trading utilizando um loop for reverso (“for (int i=PositionsTotal()-1; i>=0; i--)”). Para cada posição, recuperamos seu “ticket” usando a função PositionGetTicket. Se o “ticket” for válido, selecionamos a posição correspondente utilizando a função PositionSelectByTicket. Dentro do loop, verificamos se a posição corresponde ao “symbol” atual e ao “magicno” fornecido (número mágico). Se “magicno” for 0, incluímos todas as posições, independentemente do número mágico. Para as posições elegíveis, recuperamos seus valores de POSITION_PRICE_OPEN (preço de abertura) e POSITION_SL (nível atual de stop-loss).
Para posições de compra, calculamos o “minProfitPrice” adicionando os “minProfitPts” (lucro mínimo em pontos) ao preço de abertura e normalizando o valor. Aplicamos o trailing stop somente se o nível “buySl” atender a todas as condições:
- “buySl” excede o “minProfitPrice”.
- “buySl” é maior que o preço de abertura.
- “buySl” é maior que o stop-loss atual ou não há stop-loss definido (“positionSl == 0”).
Se essas condições forem satisfeitas, modificamos o stop-loss da posição utilizando o método “PositionModify” do objeto “CTrade”. Para posições de venda, calculamos o “minProfitPrice” subtraindo os “minProfitPts” do preço de abertura e normalizando o valor. Da mesma forma, aplicamos o trailing stop se o nível “sellSl” atender às seguintes condições:
- “sellSl” é menor que o “minProfitPrice”.
- “sellSl” é menor que o preço de abertura.
- “sellSl” é menor que o stop-loss atual ou não há stop-loss definido.
Se essas condições forem atendidas, também modificamos o stop-loss da posição utilizando o método “PositionModify”. Em seguida, podemos chamar essas funções a cada tick para aplicar a lógica de trailing stop às posições abertas, conforme segue.
//--- Apply trailing stop if allowed in the input parameters if (inpisAllowTrailingStop){ applyTrailingStop(inpTrailPts,obj_Trade,inpMagicNo,inpMinTrailPts); }
Aqui, chamamos a função de trailing stop e, ao executar o programa, obtemos o seguinte resultado.

A partir da imagem, podemos ver que o objetivo do trailing stop foi alcançado com sucesso. Agora precisamos visualizar os dados no gráfico. Para isso, será necessário um dashboard com uma base principal e rótulos. Para a base, precisaremos de um rótulo retangular. Aqui está a implementação da função.
//+------------------------------------------------------------------+ //| Create Rectangle label function | //+------------------------------------------------------------------+ bool createRecLabel(string objNAME,int xD,int yD,int xS,int yS, color clrBg,int widthBorder,color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT,ENUM_LINE_STYLE borderStyle = STYLE_SOLID ){ //--- Reset the last error code ResetLastError(); //--- Attempt to create the rectangle label object if (!ObjectCreate(0,objNAME,OBJ_RECTANGLE_LABEL,0,0,0)){ //--- Log the error if creation fails Print(__FUNCTION__,": Failed to create the REC LABEL. Error Code = ",_LastError); return (false); } //--- Set rectangle label properties ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD); ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD); ObjectSetInteger(0, objNAME,OBJPROP_XSIZE, xS); ObjectSetInteger(0, objNAME,OBJPROP_YSIZE, yS); ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, objNAME,OBJPROP_BGCOLOR, clrBg); ObjectSetInteger(0, objNAME,OBJPROP_BORDER_TYPE, borderType); ObjectSetInteger(0, objNAME,OBJPROP_STYLE, borderStyle); ObjectSetInteger(0, objNAME,OBJPROP_WIDTH, widthBorder); ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrBorder); ObjectSetInteger(0, objNAME,OBJPROP_BACK, false); ObjectSetInteger(0, objNAME,OBJPROP_STATE, false); ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false); //--- Redraw the chart to reflect changes ChartRedraw(0); return (true); }
Na função booleana “createRecLabel” que definimos, criamos um rótulo retangular personalizável no gráfico seguindo uma série de etapas. Primeiro, redefinimos quaisquer códigos de erro anteriores utilizando a função ResetLastError. Em seguida, tentamos criar o objeto de rótulo retangular utilizando a função ObjectCreate. Se essa criação falhar, exibimos uma mensagem de erro com o motivo da falha e retornamos “false”. Se a criação for bem-sucedida, prosseguimos para definir várias propriedades do rótulo retangular utilizando a função ObjectSetInteger.
Essas propriedades nos permitem definir a posição, o tamanho, a cor de fundo, o estilo da borda e outros aspectos visuais do retângulo. Atribuímos os parâmetros “xD”, “yD”, “xS” e “yS” para determinar a posição e o tamanho do rótulo retangular, utilizando OBJPROP_XDISTANCE, “OBJPROP_YDISTANCE”, “OBJPROP_XSIZE” e “OBJPROP_YSIZE”. Além disso, definimos a cor de fundo, o tipo de borda e o estilo da borda por meio de OBJPROP_BGCOLOR, “OBJPROP_BORDER_TYPE” e “OBJPROP_STYLE”, respectivamente.
Por fim, para garantir que a representação visual do rótulo seja atualizada, chamamos a função ChartRedraw para atualizar o gráfico. Se o rótulo retangular for criado com sucesso e todas as propriedades forem definidas corretamente, a função retorna “true”. Dessa forma, podemos anotar visualmente o gráfico com rótulos retangulares personalizados com base nos parâmetros fornecidos. Fazemos o mesmo para uma função de rótulo.
//+------------------------------------------------------------------+ //| Create label function | //+------------------------------------------------------------------+ bool createLabel(string objNAME,int xD,int yD,string txt, color clrTxt = clrBlack,int fontSize = 12, string font = "Arial Rounded MT Bold" ){ //--- Reset the last error code ResetLastError(); //--- Attempt to create the label object if (!ObjectCreate(0,objNAME,OBJ_LABEL,0,0,0)){ //--- Log the error if creation fails Print(__FUNCTION__,": Failed to create the LABEL. Error Code = ",_LastError); return (false); } //--- Set label properties ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD); ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD); ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(0, objNAME,OBJPROP_TEXT, txt); ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrTxt); ObjectSetString(0, objNAME,OBJPROP_FONT, font); ObjectSetInteger(0, objNAME,OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, objNAME,OBJPROP_BACK, false); ObjectSetInteger(0, objNAME,OBJPROP_STATE, false); ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false); //--- Redraw the chart to reflect changes ChartRedraw(0); return (true); }
Munidos dessas funções, agora podemos criar uma função para gerenciar a criação do dashboard sempre que necessário.
//+------------------------------------------------------------------+ //| Create dashboard function | //+------------------------------------------------------------------+ void createDashboard(){ //--- }
Aqui, criamos a função void chamada “createDashboard”, que podemos utilizar para abrigar a lógica de criação do dashboard. Para acompanhar efetivamente as mudanças, podemos chamar a função no manipulador de eventos OnInit primeiro, antes de definir seu corpo, conforme abaixo.
//--- createDashboard(); //---
Após chamar a função, podemos definir o corpo da função. A primeira coisa que fazemos é definir o corpo do dashboard, e precisaremos definir seu nome como uma constante global.
//+------------------------------------------------------------------+ //| Global constants for dashboard object names | //+------------------------------------------------------------------+ const string DASH_MAIN = "MAIN";
Aqui, definimos uma string constante, const, o que significa que ela não será alterada ao longo do programa. Agora utilizamos essa constante para a criação do rótulo da seguinte forma.
//+------------------------------------------------------------------+ //| Create dashboard function | //+------------------------------------------------------------------+ void createDashboard(){ //--- Create the main dashboard rectangle createRecLabel(DASH_MAIN,10,50+30,200,120,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID); //--- }
Na função “createDashboard”, iniciamos o processo de criação de um dashboard visual no gráfico. Para isso, chamamos a função “createRecLabel”, que é responsável por desenhar um retângulo no gráfico para servir como base do dashboard. A função recebe parâmetros específicos para definir a aparência e o posicionamento desse retângulo. Primeiro, especificamos o nome do retângulo como “DASH_MAIN”, o que nos permitirá identificar esse objeto posteriormente. Em seguida, definimos a posição do retângulo configurando seu canto superior esquerdo nas coordenadas (10, 50+30) no gráfico, utilizando os parâmetros “xD” e “yD”. A largura e a altura do retângulo são definidas como 200 e 120 pixels, respectivamente, por meio dos parâmetros “xS” e “yS”, mas podem ser ajustadas posteriormente.
Em seguida, definimos a aparência visual do retângulo. A cor de fundo do retângulo é definida como “clrBlack”, e escolhemos a cor azul (“clrBlue”) para a borda. A borda possui uma largura de 2 pixels e um estilo de linha sólida (STYLE_SOLID), e o tipo de borda é definido como plano (BORDER_FLAT). Essas configurações garantem que o retângulo tenha uma aparência clara e bem definida. Esse retângulo serve como o elemento base do dashboard, e outros elementos, como textos ou componentes interativos, podem ser adicionados a ele em etapas subsequentes. No entanto, vamos executar o marco atual e observar o resultado.

A partir da imagem, podemos ver que a base do dashboard está conforme o esperado. Em seguida, podemos criar os outros elementos do dashboard utilizando a função de rótulo e seguindo o mesmo procedimento. Assim, definimos o restante dos objetos da seguinte forma.
//+------------------------------------------------------------------+ //| Global constants for dashboard object names | //+------------------------------------------------------------------+ const string DASH_MAIN = "MAIN"; const string DASH_HEAD = "HEAD"; const string DASH_ICON1 = "ICON 1"; const string DASH_ICON2 = "ICON 2"; const string DASH_NAME = "NAME"; const string DASH_OS = "OS"; const string DASH_COMPANY = "COMPANY"; const string DASH_PERIOD = "PERIOD"; const string DASH_POSITIONS = "POSITIONS"; const string DASH_PROFIT = "PROFIT";
Aqui, apenas definimos o restante dos objetos. Novamente, utilizamos a função de rótulo para criar o rótulo de cabeçalho, conforme abaixo.
//+------------------------------------------------------------------+ //| Create dashboard function | //+------------------------------------------------------------------+ void createDashboard(){ //--- Create the main dashboard rectangle createRecLabel(DASH_MAIN,10,50+30,200,120+30,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID); //--- Add icons and text labels to the dashboard createLabel(DASH_ICON1,13,53+30,CharToString(40),clrRed,17,"Wingdings"); createLabel(DASH_ICON2,180,53+30,"@",clrWhite,17,"Webdings"); createLabel(DASH_HEAD,65,53+30,"Dashboard",clrWhite,14,"Impact"); }
Aqui, aprimoramos o dashboard adicionando ícones e um título usando a função “createLabel”. Essa função é chamada várias vezes para posicionar elementos de texto em locais específicos do gráfico, permitindo construir uma interface visualmente atraente e informativa. Primeiro, criamos um ícone rotulado como “DASH_ICON1”, que é posicionado nas coordenadas (13, 53+30) em relação ao gráfico. O ícone é representado pelo código de caractere 40, convertido em string usando a função “CharToString(40)”. Esse ícone é exibido na cor vermelha (“clrRed”) com tamanho de fonte 17, e o estilo da fonte é definido como “Wingdings” para renderizar o caractere como um símbolo gráfico.
Em seguida, adicionamos outro ícone rotulado como “DASH_ICON2”, posicionado nas coordenadas (180, 53+30). Esse ícone utiliza o caractere “@”, exibido na cor branca (“clrWhite”) com tamanho de fonte 17. O estilo da fonte é “Webdings”, garantindo que o caractere “@” apareça de forma decorativa e estilizada. Aqui está a representação.

Por fim, incluímos um título de texto rotulado como “DASH_HEAD” na posição (65, 53+30). O título exibe o texto “Dashboard” na cor branca (“clrWhite”) com tamanho de fonte 14. O estilo da fonte é definido como “Impact”, o que confere ao título uma aparência forte e distinta. Em seguida, podemos definir o restante dos rótulos.
createLabel(DASH_NAME,20,90+30,"EA Name: Crossover RSI Suite",clrWhite,10,"Calibri"); createLabel(DASH_COMPANY,20,90+30+15,"LTD: "+AccountInfoString(ACCOUNT_COMPANY),clrWhite,10,"Calibri"); createLabel(DASH_OS,20,90+30+15+15,"OS: "+TerminalInfoString(TERMINAL_OS_VERSION),clrWhite,10,"Calibri"); createLabel(DASH_PERIOD,20,90+30+15+15+15,"Period: "+EnumToString(Period()),clrWhite,10,"Calibri"); createLabel(DASH_POSITIONS,20,90+30+15+15+15+30,"Positions: "+IntegerToString(PositionsTotal()),clrWhite,10,"Calibri"); createLabel(DASH_PROFIT,20,90+30+15+15+15+30+15,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY),clrWhite,10,"Calibri");
Aqui, preenchemos o dashboard com rótulos informativos importantes utilizando a função “createLabel”. Primeiro, criamos o rótulo “DASH_NAME”, posicionado em (20, 90+30). Esse rótulo exibe o texto “EA Name: Crossover RSI Suite” na cor branca (“clrWhite”) com tamanho de fonte 10, e o estilo da fonte é “Calibri”. Esse rótulo serve como o nome do Expert Advisor, fornecendo uma identificação clara ao usuário.
Em seguida, adicionamos o rótulo “DASH_COMPANY” em (20, 90+30+15). Ele exibe o texto “LTD: ”, seguido das informações da empresa da conta, que são obtidas utilizando a função AccountInfoString com o parâmetro ACCOUNT_COMPANY. O rótulo é estilizado na cor branca, com tamanho de fonte 10, e utiliza a fonte “Calibri”. Logo após, o rótulo “DASH_OS” é posicionado em (20, 90+30+15+15). Ele mostra a versão do sistema operacional, com o texto “OS: ”, combinado com o resultado da função TerminalInfoString com o parâmetro TERMINAL_OS_VERSION. Esse rótulo ajuda o usuário a identificar o sistema operacional do terminal, também estilizado em branco, com tamanho de fonte 10 e fonte “Calibri”.
Em seguida, incluímos o rótulo “DASH_PERIOD” em (20, 90+30+15+15+15). Esse rótulo exibe o timeframe atual do gráfico com o texto “Period: ”, acrescido do resultado da função EnumToString aplicada ao período. O texto branco, o tamanho de fonte reduzido e a fonte “Calibri” mantêm a consistência com o design geral do dashboard. Além disso, adicionamos o rótulo “DASH_POSITIONS” em (20, 90+30+15+15+15+30). Esse rótulo mostra o número total de posições atualmente abertas na conta, com o texto “Positions: ”, seguido do total de posições. Essa informação é essencial para acompanhar as operações ativas.
Por fim, o rótulo “DASH_PROFIT” é posicionado em (20, 90+30+15+15+15+30+15). Ele exibe o lucro atual da conta com o texto “Profit: ”, seguido do resultado da função de lucro da conta, representando o valor com duas casas decimais, juntamente com a moeda da conta obtida por meio da função AccountInfoString.
Por fim, precisamos excluir o dashboard ao final, quando o programa for removido. Assim, precisamos de uma função para excluir o dashboard.
//+------------------------------------------------------------------+ //| Delete dashboard function | //+------------------------------------------------------------------+ void deleteDashboard(){ //--- Delete all objects related to the dashboard ObjectDelete(0,DASH_MAIN); ObjectDelete(0,DASH_ICON1); ObjectDelete(0,DASH_ICON2); ObjectDelete(0,DASH_HEAD); ObjectDelete(0,DASH_NAME); ObjectDelete(0,DASH_COMPANY); ObjectDelete(0,DASH_OS); ObjectDelete(0,DASH_PERIOD); ObjectDelete(0,DASH_POSITIONS); ObjectDelete(0,DASH_PROFIT); //--- Redraw the chart to reflect changes ChartRedraw(); }
Aqui, criamos uma função void “deleteDashboard”, chamamos a função ObjectDelete com todos os nomes dos objetos e, por último, redesenhamos o gráfico utilizando a função ChartRedraw para que as alterações entrem em vigor. Em seguida, chamamos essa função na função de desinicialização. Além disso, precisamos atualizar o dashboard sempre que houver posições, para exibir corretamente as posições e o lucro. Aqui está a lógica que utilizamos.
if (PositionsTotal() > 0){ ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal())); ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY)); }
Aqui, verificamos se o número de posições é maior que 0; se houver uma posição, podemos atualizar suas propriedades. Aqui está o resultado.

A partir da visualização, podemos ver que o dashboard não é atualizado quando as posições são encerradas. Assim, precisaremos rastrear quando as posições são fechadas e, quando não houver posições, redefinir os valores do dashboard. Para isso, precisaremos do manipulador de eventos OnTradeTransaction.
//+------------------------------------------------------------------+ //| OnTradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction( const MqlTradeTransaction& trans, // trade transaction structure const MqlTradeRequest& request, // request structure const MqlTradeResult& result // response structure ){ if (trans.type == TRADE_TRANSACTION_DEAL_ADD){ Print("A deal was added. Make updates."); if (PositionsTotal() <= 0){ ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal())); ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY)); } } }
Aqui, configuramos a função OnTradeTransaction, que é acionada sempre que ocorre uma transação relacionada a trades. Essa função processa eventos de negociação e atualiza as informações relevantes no dashboard em resposta a ações específicas. Começamos verificando se o “type” da transação de trade, fornecido pelo parâmetro “trans” do tipo MqlTradeTransaction, é igual a TRADE_TRANSACTION_DEAL_ADD. Essa condição determina se um novo negócio foi adicionado à conta. Quando essa transação é detectada, exibimos a mensagem “A deal was added. Make updates.” no log, para fins de depuração ou informação.<segmento 5895>
Em seguida, verificamos se o número total de posições abertas, obtido por meio da função PositionsTotal, é menor ou igual a 0. Isso garante que as atualizações do dashboard sejam realizadas apenas quando não houver posições ativas restantes na conta. Se a condição for atendida, utilizamos a função ObjectSetString para atualizar dois rótulos no dashboard. Aqui está o resultado.

A partir da imagem, podemos ver que as atualizações realmente entram em vigor a cada operação realizada, atingindo nosso objetivo, e o que resta agora é realizar o backtest do programa e analisar seu desempenho. Isso é tratado na seção seguinte.
Backtesting
Após um backtest completo, obtivemos os seguintes resultados.
Gráfico de backtest:

Relatório de backtest:

Aqui também há um vídeo demonstrando todo o backtest da estratégia em um período de 1 ano, 2024.
Conclusão
Em conclusão, demonstramos como desenvolver um Expert Advisor (EA) robusto em MQL5 que integra indicadores técnicos, gerenciamento automatizado de trades e um dashboard interativo. Ao combinar ferramentas como cruzamentos de Médias Móveis, Índice de Força Relativa (RSI) e trailing stops com recursos como atualizações dinâmicas de trades, trailing stops e uma interface amigável, criamos um EA capaz de gerar sinais, gerenciar operações e fornecer insights em tempo real para uma tomada de decisão eficaz.
Aviso legal: Este artigo é apenas para fins educacionais. O trading envolve riscos financeiros significativos, e as condições de mercado podem ser imprevisíveis. Embora as estratégias discutidas forneçam uma estrutura organizada, o desempenho passado não garante resultados futuros. Testes completos e um gerenciamento de risco adequado são essenciais antes da utilização em ambiente real.
Ao aplicar esses conceitos, você pode construir sistemas de trading mais adaptativos e aprimorar suas estratégias de trading algorítmico. Feliz codificação e negociações bem-sucedidas!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17040
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Ciência de Dados e ML (Parte 33): Dataframe do Pandas em MQL5, Coleta de Dados para Uso em ML facilitada
Está chegando o novo MetaTrader 5 e MQL5
Algoritmo do camelo — Camel Algorithm (CA)
- 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
Olá. O vídeo mostra tudo, desde a compilação, o período de teste e os parâmetros de entrada usados.