English Русский 中文 Español Deutsch 日本語
preview
Como simplificar o teste manual de estratégias com MQL5: construindo seu próprio conjunto de ferramentas

Como simplificar o teste manual de estratégias com MQL5: construindo seu próprio conjunto de ferramentas

MetaTrader 5Testador |
17 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

O teste de estratégias de trading em dados históricos é um dos pilares do trading bem-sucedido, mas automatizar cada ideia que surge pode transmitir uma sensação de limitação, enquanto o teste manual muitas vezes carece de estrutura e precisão. E se você pudesse unir o controle do trading manual à potência do Testador de Estratégias do MetaTrader 5? Neste artigo, apresentamos um EA personalizado em MetaQuotes Language 5 (MQL5) que transforma o teste manual baseado em dados históricos em um processo intuitivo e eficiente, oferecendo a você um conjunto de ferramentas para testar estratégias nas condições que você definir. Vamos seguir estas etapas:

  1. Plano: Desenvolvimento de um conjunto de ferramentas para teste manual em dados históricos
  2. Implementação em MQL5: Apresentação do conjunto de ferramentas ao público
  3. Teste em dados históricos em ação: Uso do conjunto de ferramentas
  4. Conclusão

Ao final deste trabalho, você terá uma solução prática para testar em dados históricos com rapidez e confiança, além de aprimorar suas ideias de trading no Testador de Estratégias.


Plano: Desenvolvimento de um conjunto de ferramentas para teste manual em dados históricos

Nosso objetivo é criar um conjunto de ferramentas que combine o controle manual com a alta velocidade do teste em dados históricos no Testador de Estratégias do MetaTrader 5 e permita contornar a lentidão do avanço tick a tick em tempo real do teste manual tradicional. Desenvolveremos um programa com botões no gráfico para abrir operações de compra ou venda, ajustar o tamanho do lote, definir os níveis de stop loss (SL) e take profit (TP), além de um botão de emergência para fechar todas as posições. O programa se integrará totalmente a qualquer estratégia, desde indicadores e padrões de velas japonesas até price action, e tudo isso funcionará no ritmo acelerado do Testador. Essa configuração flexível nos permitirá testar com rapidez e precisão qualquer abordagem de trading em modo interativo, o que simplifica o refinamento da estratégia em um ambiente simulado. Em resumo, aqui está uma visualização do que buscamos:

IMAGEM DO PLANO


Implementação em MQL5: Apresentação do conjunto de ferramentas ao público

Para criar o programa em MQL5, precisaremos definir os metadados do programa, depois alguns parâmetros de entrada do usuário e, por fim, incluir os arquivos de biblioteca que permitirão executar operações de trading.

//+------------------------------------------------------------------+
//|                       Manual backtest toolkit in Strategy Tester |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA Enables manual backtest in the strategy tester"
#property strict //--- Enforce strict coding rules to catch errors early

#define BTN_BUY "BTN BUY" //--- Define the name for the Buy button
#define BTN_SELL "BTN SELL" //--- Define the name for the Sell button
#define BTN_P "BTN P" //--- Define the name for the button that increase lot size
#define BTN_M "BTN M" //--- Define the name for the button that decrease lot size
#define BTN_LOT "BTN LOT" //--- Define the name for the lot size display button
#define BTN_CLOSE "BTN CLOSE" //--- Define the name for the button that close all positions
#define BTN_SL "BTN SL" //--- Define the name for the Stop Loss display button
#define BTN_SL1M "BTN SL1M" //--- Define the name for the button that slightly lower Stop Loss
#define BTN_SL2M "BTN SL2M" //--- Define the name for the button that greatly lower Stop Loss
#define BTN_SL1P "BTN SL1P" //--- Define the name for the button that slightly raise Stop Loss
#define BTN_SL2P "BTN SL2P" //--- Define the name for the button that greatly raise Stop Loss
#define BTN_TP "BTN TP" //--- Define the name for the Take Profit display button
#define BTN_TP1M "BTN TP1M" //--- Define the name for the button that slightly lower Take Profit
#define BTN_TP2M "BTN TP2M" //--- Define the name for the button that greatly lower Take Profit
#define BTN_TP1P "BTN TP1P" //--- Define the name for the button that slightly raise Take Profit
#define BTN_TP2P "BTN TP2P" //--- Define the name for the button that greatly raise Take Profit
#define BTN_YES "BTN YES" //--- Define the name for the button that confirm a trade
#define BTN_NO "BTN NO" //--- Define the name for the button that cancel a trade
#define BTN_IDLE "BTN IDLE" //--- Define the name for the idle button between Yes and No
#define HL_SL "HL SL" //--- Define the name for the Stop Loss horizontal line
#define HL_TP "HL TP" //--- Define the name for the Take Profit horizontal line

#include <Trade/Trade.mqh> //--- Bring in the Trade library needed for trading functions
CTrade obj_Trade; //--- Create a trading object to handle trade operations

bool tradeInAction = false; //--- Track whether a trade setup is currently active
bool isHaveTradeLevels = false; //--- Track whether Stop Loss and Take Profit levels are shown

input double init_lot = 0.03;
input int slow_pts = 10;
input int fast_pts = 100;

Aqui começaremos definindo um conjunto de botões interativos, como BTN_BUY e BTN_SELL, usando a diretiva #define, permitindo iniciar operações a qualquer momento, o que nos dá controle direto sobre os pontos de entrada, enquanto BTN_P e BTN_M permitem ajustar o tamanho de init_lot, inicialmente definido em 0.03, para aumentar ou diminuir, de acordo com nossa tolerância ao risco. Além disso, incluímos BTN_CLOSE como uma saída de emergência, uma forma rápida de fechar todas as posições em um instante, e também contamos com tradeInAction para acompanhar se a operação está em configuração, e com isHaveTradeLevels para indicar a atividade dos marcadores visuais de Stop Loss e Take Profit.

Em seguida, usamos a classe CTrade de <Trade/Trade.mqh> para criar o objeto obj_Trade, com o objetivo de executar operações de trading de forma consistente e eficiente. Para garantir ainda mais flexibilidade, adicionamos parâmetros de entrada configuráveis, como slow_pts definido em 10 e fast_pts definido em 100, para que possamos ajustar os níveis de Stop Loss e Take Profit dinamicamente, garantindo que nosso conjunto de ferramentas se adapte a qualquer estratégia que estejamos testando. Agora, como precisamos criar botões no painel, vamos criar uma função com todos os parâmetros necessários para reutilização e ajuste.

//+------------------------------------------------------------------+
//| Create button function                                           |
//+------------------------------------------------------------------+
void CreateBtn(string objName,int xD,int yD,int xS,int yS,string txt,
               int fs=13,color clrTxt=clrWhite,color clrBg=clrBlack,
               color clrBd=clrBlack,string font="Calibri"){
   ObjectCreate(0,objName,OBJ_BUTTON,0,0,0); //--- Create a new button object on the chart
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, xD); //--- Set the button's horizontal position
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, yD); //--- Set the button's vertical position
   ObjectSetInteger(0,objName,OBJPROP_XSIZE, xS); //--- Set the button's width
   ObjectSetInteger(0,objName,OBJPROP_YSIZE, yS); //--- Set the button's height
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Position the button from the top-left corner
   ObjectSetString(0,objName,OBJPROP_TEXT, txt); //--- Set the text displayed on the button
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fs); //--- Set the font size of the button text
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt); //--- Set the color of the button text
   ObjectSetInteger(0,objName,OBJPROP_BGCOLOR, clrBg); //--- Set the background color of the button
   ObjectSetInteger(0,objName,OBJPROP_BORDER_COLOR,clrBd); //--- Set the border color of the button
   ObjectSetString(0,objName,OBJPROP_FONT,font); //--- Set the font style of the button text

   ChartRedraw(0); //--- Refresh the chart to show the new button
}

Aqui definimos a função CreateBtn para criar no gráfico cada botão, como BTN_BUY ou BTN_SELL, recebendo parâmetros como objName, que identifica o botão, xD e yD, que definem suas posições horizontal e vertical, xS e yS para sua largura e altura, além de txt para o rótulo que queremos exibir, como BUY ou SELL. Para isso, usamos a função ObjectCreate para colocar no gráfico um novo objeto OBJ_BUTTON, definindo por simplicidade seu ponto de referência nas coordenadas (0,0,0). Em seguida, posicionamos esse objeto com ObjectSetInteger, ajustando OBJPROP_XDISTANCE para xD e OBJPROP_YDISTANCE para yD, garantindo que ele fique exatamente onde precisamos, e redimensionamos o botão com OBJPROP_XSIZE para xS e OBJPROP_YSIZE para yS, para que ele se encaixe no nosso layout.

Vamos fixá-lo no canto superior esquerdo, definindo OBJPROP_CORNER como CORNER_LEFT_UPPER, o que garante consistência ao layout, e usamos ObjectSetString para atribuir OBJPROP_TEXT como txt, para que a função do botão fique clara. Quanto ao estilo, alteramos OBJPROP_FONTSIZE para fs, com padrão 13, OBJPROP_COLOR para clrTxt, branco por padrão, para o texto, OBJPROP_BGCOLOR para clrBg, preto por padrão, para o fundo, e OBJPROP_BORDER_COLOR para clrBd, preto por padrão, para a borda, enquanto OBJPROP_FONT recebe o valor font, Calibri por padrão, para uma aparência nítida. Por fim, usamos a função ChartRedraw para atualizar o gráfico com ID de janela 0, exibindo imediatamente nosso novo botão, com o qual poderemos interagir no Testador de Estratégias. Agora podemos chamar essa função sempre que quisermos criar um botão, e começaremos fazendo isso no manipulador de eventos OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   CreateBtn(BTN_P,150,45,40,25,CharToString(217),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to increase lot size with an up arrow
   CreateBtn(BTN_LOT,190,45,60,25,string(init_lot),12,clrWhite,clrGray,clrBlack); //--- Make the button showing the current lot size
   CreateBtn(BTN_M,250,45,40,25,CharToString(218),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to decrease lot size with a down arrow
   CreateBtn(BTN_BUY,110,70,110,30,"BUY",15,clrWhite,clrGreen,clrBlack); //--- Make the Buy button with a green background
   CreateBtn(BTN_SELL,220,70,110,30,"SELL",15,clrWhite,clrRed,clrBlack); //--- Make the Sell button with a red background
   CreateBtn(BTN_CLOSE,110,100,220,30,"PANIC BUTTON (X)",15,clrWhite,clrBlack,clrBlack); //--- Make the emergency button to close all trades
         
   return(INIT_SUCCEEDED); //--- Tell the system the EA start up successfully
}

Aqui iniciamos nosso conjunto de ferramentas para teste em dados históricos por meio do manipulador de eventos OnInit, configurando sua interface no Testador de Estratégias. Usamos a função CreateBtn para posicionar BTN_P em xD 150 e yD 45 com uma seta para cima de CharToString(217) em Wingdings, BTN_LOT em xD 190, exibindo init_lot, e BTN_M em xD 250 com uma seta para baixo de CharToString(218), todos compondo o bloco de controle do tamanho do lote. Em seguida, adicionamos BTN_BUY em xD 110 e yD 70 com o texto BUY em clrGreen, BTN_SELL em xD 220 com o texto SELL em clrRed e BTN_CLOSE em xD 110 e yD 100 como botão de pânico PANIC BUTTON (X) em clrBlack, antes de sinalizar a conclusão bem-sucedida com return e INIT_SUCCEEDED. A fonte Wingdings usada nos ícones vem da tabela de símbolos já disponível em MQL5, mostrada abaixo.

WINGDINGS

Ao iniciar o programa, obtemos o seguinte resultado.

INTERFACE DOS BOTÕES

Como já configuramos a interface básica, precisamos ler os estados dos botões para poder usá-los nas operações. Para isso, também precisamos de algumas funções.

int GetState(string Name){return (int)ObjectGetInteger(0,Name,OBJPROP_STATE);} //--- Get whether a button is pressed or not
string GetValue(string Name){return ObjectGetString(0,Name,OBJPROP_TEXT);} //--- Get the text shown on an object
double GetValueHL(string Name){return ObjectGetDouble(0,Name,OBJPROP_PRICE);} //--- Get the price level of a horizontal line

Aqui definiremos a função GetState para verificar os cliques nos botões. Nela, usamos a função ObjectGetInteger com OBJPROP_STATE para retornar true quando o botão Name é pressionado, GetValue para extrair o texto de Name com ObjectGetString e OBJPROP_TEXT, e GetValueHL para obter os níveis de preço do objeto Name com ObjectGetDouble, usando OBJPROP_PRICE para controle preciso das operações. Agora podemos usar essas funções para obter os estados dos botões no manipulador de eventos OnTick, já que não podemos usar diretamente o manipulador de eventos OnChartEvent no Testador de Estratégias. Veja como fazemos isso.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get the current Ask price and adjust it to the right decimal places
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price and adjust it to the right decimal places

   if (GetState(BTN_BUY)==true || GetState(BTN_SELL)){ //--- Check if either the Buy or Sell button is clicked
      tradeInAction = true; //--- Set trade setup to active
   }
}

Aqui usamos o manipulador de eventos OnTick para gerenciar as ações do nosso conjunto de ferramentas em tempo real no Testador de Estratégias. Aplicamos a função NormalizeDouble com SymbolInfoDouble para atribuir a Ask o preço atual de SYMBOL_ASK e a Bid o preço de SYMBOL_BID, ambos ajustados para _Digits para garantir precisão, e, se GetState indicar que BTN_BUY ou BTN_SELL for true, definimos tradeInAction como true para começar a definir a operação. É nesse momento que precisamos de níveis adicionais de trading para definir e ajustar esses níveis dinamicamente. Para isso, vamos criar a função correspondente.

//+------------------------------------------------------------------+
//| Create high low function                                         |
//+------------------------------------------------------------------+
void createHL(string objName,datetime time1,double price1,color clr){
   if (ObjectFind(0,objName) < 0){ //--- Check if the horizontal line doesn’t already exist
      ObjectCreate(0,objName,OBJ_HLINE,0,time1,price1); //--- Create a new horizontal line at the specified price
      ObjectSetInteger(0,objName,OBJPROP_TIME,time1); //--- Set the time property (though not critical for HLINE)
      ObjectSetDouble(0,objName,OBJPROP_PRICE,price1); //--- Set the price level of the horizontal line
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); //--- Set the color of the line (red for SL, green for TP)
      ObjectSetInteger(0,objName,OBJPROP_STYLE,STYLE_DASHDOTDOT); //--- Set the line style to dash-dot-dot
      ChartRedraw(0); //--- Refresh the chart to display the new line
   }
}

Primeiro, definimos a função createHL para desenhar linhas horizontais no gráfico para o nosso conjunto de ferramentas no Testador de Estratégias. Nela, usamos a função ObjectFind para verificar se objName existe. Se o valor retornado for menor que 0, usamos a função ObjectCreate para criar um OBJ_HLINE em time1 e price1, usamos a função ObjectSetInteger para definir OBJPROP_TIME como time1 e OBJPROP_COLOR como clr, enquanto OBJPROP_STYLE recebe STYLE_DASHDOTDOT, usamos a função ObjectSetDouble para definir OBJPROP_PRICE como price1, e usamos a função ChartRedraw, para atualizar o gráfico com 0 e exibi-lo. Em seguida, integramos essa função a outra função para criar os níveis de trading de forma direta, como mostrado abaixo.

//+------------------------------------------------------------------+
//| Create trade levels function                                     |
//+------------------------------------------------------------------+
void CreateTradeLevels(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get unnoticed the current Ask price, adjusted for digits
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price, adjusted for digits
   
   string level_SL,level_TP; //--- Declare variables to hold SL and TP levels as strings
   if (GetState(BTN_BUY)==true){ //--- Check if the Buy button is active
      level_SL = string(Bid-100*_Point); //--- Set initial Stop Loss 100 points below Bid for Buy
      level_TP = string(Bid+100*_Point); //--- Set initial Take Profit 100 points above Bid for Buy
   }
   else if (GetState(BTN_SELL)==true){ //--- Check if the Sell button is active
      level_SL = string(Ask+100*_Point); //--- Set initial Stop Loss 100 points above Ask for Sell
      level_TP = string(Ask-100*_Point); //--- Set initial Take Profit 100 points below Ask for Sell
   }
   
   createHL(HL_SL,0,double(level_SL),clrRed); //--- Create a red Stop Loss line at the calculated level
   createHL(HL_TP,0,double(level_TP),clrGreen); //--- Create a green Take Profit line at the calculated level

   CreateBtn(BTN_SL,110,135,110,23,"SL: "+GetValue(HL_SL),13,clrRed,clrWhite,clrRed); //--- Make a button showing the Stop Loss level
   CreateBtn(BTN_TP,220,135,110,23,"TP: "+GetValue(HL_TP),13,clrGreen,clrWhite,clrGreen); //--- Make a button showing the Take Profit level
   
   CreateBtn(BTN_SL1M,110,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Stop Loss
   CreateBtn(BTN_SL2M,137,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Stop Loss
   CreateBtn(BTN_SL2P,164,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Stop Loss
   CreateBtn(BTN_SL1P,191,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Stop Loss
   
   CreateBtn(BTN_TP1P,222,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Take Profit
   CreateBtn(BTN_TP2P,249,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Take Profit
   CreateBtn(BTN_TP2M,276,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Take Profit
   CreateBtn(BTN_TP1M,303,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Take Profit
   
   CreateBtn(BTN_YES,110,178,70,30,CharToString(254),20,clrWhite,clrDarkGreen,clrWhite,"Wingdings"); //--- Make a green checkmark button to confirm the trade
   CreateBtn(BTN_NO,260,178,70,30,CharToString(253),20,clrWhite,clrDarkRed,clrWhite,"Wingdings"); //--- Make a red X button to cancel the trade
   CreateBtn(BTN_IDLE,180,183,80,25,CharToString(40),20,clrWhite,clrBlack,clrWhite,"Wingdings"); //--- Make a neutral button between Yes and No
}

Aqui definimos a função CreateTradeLevels para configurar nossos níveis de trading, em que usamos a função NormalizeDouble com SymbolInfoDouble para atribuir a Ask o valor de SYMBOL_ASK e a Bid o valor de SYMBOL_BID, ambos ajustados para _Digits, e declaramos level_SL e level_TP como variáveis do tipo string. Se GetState indicar BTN_BUY como true, definimos level_SL como Bid-100_Point e level_TP como Bid+100_Point, mas, se BTN_SELL estiver como true, definimos level_SL como Ask+100_Point e level_TP como Ask-100_Point.

Usamos a função createHL para desenhar HL_SL no nível double(level_SL) em clrRed e HL_TP no nível double(level_TP) em clrGreen. Em seguida, usamos a função CreateBtn para criar botões como BTN_SL com o texto GetValue(HL_SL), BTN_TP com o texto GetValue(HL_TP), além dos botões de ajuste BTN_SL1M, BTN_SL2M, BTN_SL2P, BTN_SL1P, BTN_TP1P, BTN_TP2P, BTN_TP2M e BTN_TP1M com símbolos como - e +, além de BTN_YES, BTN_NO e BTN_IDLE com CharToString para as opções de confirmação, cancelamento e estado neutro em Wingdings. Com essa função, podemos chamá-la ao pressionar os botões de compra ou venda para iniciar a definição dos níveis de trading.

if (!isHaveTradeLevels){ //--- Check if trade levels aren't already on the chart
   CreateTradeLevels(); //--- Add Stop Loss and Take Profit levels and controls to the chart
   isHaveTradeLevels = true; //--- Mark that trade levels are now present
}

Aqui verificamos se isHaveTradeLevels é false com !isHaveTradeLevels e, se esse for o caso, usamos a função CreateTradeLevels para colocar no gráfico os controles de ajuste de Stop Loss e Take Profit, e depois atualizamos isHaveTradeLevels para true para indicar que eles estão ativos. Após a compilação, teremos o seguinte resultado.

NÍVEIS DE TRADING

Em seguida, precisamos fazer os botões dos níveis passarem a responder aos comandos e executar as funções necessárias. Veja como fazemos isso.

if (tradeInAction){ //--- Continue if a trade setup is active

   // SL SLOW/FAST BUTTONS
   if (GetState(BTN_SL1M)){ //--- Check if the small Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-slow_pts*_Point); //--- Move the Stop Loss down by a small amount
      ObjectSetInteger(0,BTN_SL1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2M)){ //--- Check if the large Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-fast_pts*_Point); //--- Move the Stop Loss down by a large amount
      ObjectSetInteger(0,BTN_SL2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL1P)){ //--- Check if the small Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+slow_pts*_Point); //--- Move the Stop Loss up by a small amount
      ObjectSetInteger(0,BTN_SL1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2P)){ //--- Check if the large Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+fast_pts*_Point); //--- Move the Stop Loss up by a large amount
      ObjectSetInteger(0,BTN_SL2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   
   // TP SLOW/FAST BUTTONS
   if (GetState(BTN_TP1M)){ //--- Check if the small Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-slow_pts*_Point); //--- Move the Take Profit down by a small amount
      ObjectSetInteger(0,BTN_TP1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2M)){ //--- Check if the large Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-fast_pts*_Point); //--- Move the Take Profit down by a large amount
      ObjectSetInteger(0,BTN_TP2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP1P)){ //--- Check if the small Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+slow_pts*_Point); //--- Move the Take Profit up by a small amount
      ObjectSetInteger(0,BTN_TP1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2P)){ //--- Check if the large Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+fast_pts*_Point); //--- Move the Take Profit up by a large amount
      ObjectSetInteger(0,BTN_TP2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }

}

Aqui gerenciamos as configurações dos níveis de Stop Loss e Take Profit no nosso conjunto de ferramentas quando tradeInAction for true. Para isso, usamos a função GetState para verificar se botões como BTN_SL1M, BTN_SL2M, BTN_SL1P ou BTN_SL2P foram pressionados e ajustamos HL_SL por slow_pts_Point ou fast_pts_Point com a função ObjectSetDouble, usando OBJPROP_PRICE e GetValueHL. Depois, usamos a função ObjectSetInteger para redefinir OBJPROP_STATE como false e a função ChartRedraw para atualizar o gráfico. Aplicamos o mesmo tratamento a BTN_TP1M, BTN_TP2M, BTN_TP1P ou BTN_TP2P para os ajustes de HL_TP. Por fim, assim que os níveis forem definidos, poderemos confirmar a ordem e abrir as posições correspondentes, e então limpar a configuração dos níveis de trading, mas antes precisaremos da função para remover o painel de configuração dos níveis.

//+------------------------------------------------------------------+
//| Delete objects function                                          |
//+------------------------------------------------------------------+
void DeleteObjects_SLTP(){
   ObjectDelete(0,HL_SL); //--- Remove the Stop Loss line from the chart
   ObjectDelete(0,HL_TP); //--- Remove the Take Profit line from the chart
   ObjectDelete(0,BTN_SL); //--- Remove the Stop Loss display button
   ObjectDelete(0,BTN_TP); //--- Remove the Take Profit display button
   ObjectDelete(0,BTN_SL1M); //--- Remove the small Stop Loss decrease button
   ObjectDelete(0,BTN_SL2M); //--- Remove the large Stop Loss decrease button
   ObjectDelete(0,BTN_SL1P); //--- Remove the small Stop Loss increase button
   ObjectDelete(0,BTN_SL2P); //--- Remove the large Stop Loss increase button
   ObjectDelete(0,BTN_TP1P); //--- Remove the small Take Profit increase button
   ObjectDelete(0,BTN_TP2P); //--- Remove the large Take Profit increase button
   ObjectDelete(0,BTN_TP2M); //--- Remove the large Take Profit decrease button
   ObjectDelete(0,BTN_TP1M); //--- Remove the small Take Profit decrease button
   ObjectDelete(0,BTN_YES); //--- Remove the confirm trade button
   ObjectDelete(0,BTN_NO); //--- Remove the cancel trade button
   ObjectDelete(0,BTN_IDLE); //--- Remove the idle button
   
   ChartRedraw(0); //--- Refresh the chart to show all objects removed
}

Aqui limpamos os elementos do nosso conjunto de ferramentas com a função DeleteObjects_SLTP, em que usamos a função ObjectDelete para remover do gráfico HL_SL, HL_TP, BTN_SL, BTN_TP, BTN_SL1M, BTN_SL2M, BTN_SL1P, BTN_SL2P, BTN_TP1P, BTN_TP2P, BTN_TP2M, BTN_TP1M, BTN_YES, BTN_NO e BTN_IDLE, e depois usamos a função ChartRedraw com o valor 0 para atualizar e exibir todos os objetos removidos. Agora podemos usar essa função em nossa lógica de envio de ordens.

// BUY ORDER PLACEMENT
if (GetState(BTN_BUY) && GetState(BTN_YES)){ //--- Check if both Buy and Yes buttons are clicked
   obj_Trade.Buy(double(GetValue(BTN_LOT)),_Symbol,Ask,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Buy order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
// SELL ORDER PLACEMENT
else if (GetState(BTN_SELL) && GetState(BTN_YES)){ //--- Check if both Sell and Yes buttons are clicked
   obj_Trade.Sell(double(GetValue(BTN_LOT)),_Symbol,Bid,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Sell order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
else if (GetState(BTN_NO)){ //--- Check if the No button is clicked to cancel
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_NO,OBJPROP_STATE,false); //--- Turn off the No button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as canceled
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}

Realizamos operações com nosso conjunto de ferramentas no Testador de Estratégias, onde usamos a função GetState para verificar se BTN_BUY e BTN_YES forem true e, em seguida, usamos o método obj_Trade.Buy com double(GetValue(BTN_LOT)), _Symbol, Ask, GetValueHL(HL_SL) e GetValueHL(HL_TP) para enviar uma ordem de compra ou, se BTN_SELL e BTN_YES forem true, usamos obj_Trade.Sell usando Bid em vez de Ask, e, em qualquer um dos casos, usamos a função DeleteObjects_SLTP para limpar os objetos, definimos isHaveTradeLevels e tradeInAction como false, usamos a função ObjectSetInteger para redefinir OBJPROP_STATE de BTN_YES, BTN_BUY ou BTN_SELL como false e usamos a função ChartRedraw para atualizar o gráfico, mas, se BTN_NO for true, cancelamos a operação removendo os objetos e redefinindo os estados da mesma forma. Da mesma forma, tratamos os botões de aumento e redução do volume de trading assim.

if (GetState(BTN_P)==true){ //--- Check if the lot size increase button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot += lotStep; //--- Increase the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot > 0.1 ? lotStep : newLot; //--- Ensure lot size doesn't exceed 0.1, otherwise reset to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_P,OBJPROP_STATE,false); //--- Turn off the increase button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}
if (GetState(BTN_M)==true){ //--- Check if the lot size decrease button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot -= lotStep; //--- Decrease the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot < lotStep ? lotStep : newLot; //--- Ensure lot size doesn't go below minimum, otherwise set to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_M,OBJPROP_STATE,false); //--- Turn off the decrease button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}

Aqui ajustamos o tamanho do lote, começando pelo aumento, em que usamos a função GetState para verificar se BTN_P for true, depois usamos GetValue para ler o valor de BTN_LOT em newLot, usamos a função SymbolInfoDouble para obter de SYMBOL_VOLUME_STEP o valor de lotStep, somamos lotStep a newLot e usamos NormalizeDouble para arredondar para 2 casas decimais, limitando o resultado a lotStep quando necessário, antes de usar a função ObjectSetString para atualizar OBJPROP_TEXT, usar ObjectSetInteger para redefinir OBJPROP_STATE de BTN_P como false, e então ChartRedraw para atualizar.

Para reduzir, usamos GetState para verificar BTN_M, subtraímos lotStep de newLot depois de obter o valor da mesma forma, garantimos que esse valor não fique abaixo de lotStep e executamos as mesmas etapas com as funções ObjectSetString, ObjectSetInteger e ChartRedraw para atualizar o valor de BTN_LOT e redefinir o estado de BTN_M. Quanto ao botão de pânico, precisamos definir uma função para fechar todas as posições abertas quando ele for pressionado.

//+------------------------------------------------------------------+
//| Close all positions function                                     |
//+------------------------------------------------------------------+
void closeAllPositions(){
   for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through all open positions, starting from the last one
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position is for the current chart symbol
               obj_Trade.PositionClose(ticket); //--- Close the selected position
            }
         }
      }
   }
}

Tratamos o fechamento de todas as posições com a função closeAllPositions, em que usamos a função PositionsTotal para criar um loop de i partindo da última posição menos 1 até 0, usamos a função PositionGetTicket para obter o valor de ticket de cada índice i, e, se o ticket for válido, usamos PositionSelectByTicket para selecioná-lo e, em seguida, usamos PositionGetString para verificar se POSITION_SYMBOL corresponde a _Symbol, antes de usar o método obj_Trade.PositionClose para fechar a posição pelo ticket. Depois, podemos chamar essa função ao pressionar o botão de pânico para fechar todas as posições.

if (GetState(BTN_CLOSE)==true){ //--- Check if the close all positions button is clicked
   closeAllPositions(); //--- Close all open trades
   ObjectSetInteger(0,BTN_CLOSE,OBJPROP_STATE,false); //--- Turn off the close button press state
   ChartRedraw(0); //--- Refresh the chart to reflect closed positions
}

Para controlar o fechamento de todas as operações, usamos a função GetState para verificar se BTN_CLOSE for true, e, se esse for o caso, usamos a função closeAllPositions para fechar todas as posições abertas, depois usamos a função ObjectSetInteger para definir OBJPROP_STATE de BTN_CLOSE como false e a função ChartRedraw com 0 para atualizar o gráfico. Após compilar e executar o programa, obtivemos o seguinte resultado.

RESULTADO FINAL

Na imagem, vemos que ajustamos os níveis operacionais e podemos abrir posições dinamicamente, alcançando nosso objetivo. Agora só falta testar o programa de forma criteriosa, e é disso que trata a próxima seção.


Teste em dados históricos em ação: Uso do conjunto de ferramentas

Testamos nosso conjunto de ferramentas no Testador de Estratégias do MetaTrader 5 carregando o programa, escolhendo nossas configurações e executando-o. Veja no GIF abaixo os botões Buy (compra), Sell (venda) e Adjustment (ajuste dos níveis) em ação, com grande rapidez. Clique em Buy ou Sell, ajuste os níveis de Stop Loss, Take Profit e o tamanho do lote e, em seguida, confirme clicando em Yes ou cancele com No, e o botão de pânico (Panic) permitirá fechar rapidamente todas as operações. Aqui está.

GIF DE VERIFICAÇÃO DO TESTADOR EM DADOS HISTÓRICOS


Conclusão

Em conclusão, criamos um conjunto de ferramentas para teste manual em dados históricos que combina controle prático com a velocidade do Testador de Estratégias, usando MQL5, simplificando o teste de ideias de trading. Mostramos como projetá-lo, escrever seu código e usá-lo para ajustar as operações por meio de botões, tudo desenvolvido especificamente para uma simulação rápida e precisa. Você pode adaptar esse conjunto de ferramentas às suas necessidades e, com ele, aprimorar o teste em dados históricos.

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

Últimos Comentários | Ir para discussão (6)
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 abr. 2025 em 14:31
Mogulh Chilyalya Kiti #:
E quanto aos diferentes períodos de tempo?

Olá. No momento, apenas um período de tempo. Talvez você possa tentar isso em um futuro próximo.

Blessing Dumbura
Blessing Dumbura | 16 abr. 2025 em 15:47
Obrigado, é uma ferramenta útil
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 abr. 2025 em 18:26
Blessing Dumbura #:
Obrigado, é uma ferramenta útil

Claro, seja bem-vindo e obrigado também pelo feedback.

Dontrace
Dontrace | 7 mai. 2025 em 11:08

Gostaria de saber como converter o indicador mq4 em mq5

zigooo
zigooo | 17 jun. 2025 em 17:16
Tentei modificá-lo para que ele possa abrir ordens rapidamente com tamanho de lote e tp/sl, de acordo com os parâmetros de entrada do EA. Funciona no mercado real, mas não funciona no modo backtest. Qual é a solução?
Estratégias de trading de rompimento: análise dos principais métodos Estratégias de trading de rompimento: análise dos principais métodos
As estratégias de rompimento da faixa de abertura (Opening Range Breakout, ORB) partem da ideia de que a faixa inicial de negociação, formada logo após a abertura do mercado, reflete níveis de preço relevantes, quando compradores e vendedores chegam a um acordo sobre o valor. Ao identificar rompimentos de uma determinada faixa para cima ou para baixo, os traders podem aproveitar o momentum que costuma surgir quando a direção do mercado fica mais clara. Neste artigo, vamos analisar três estratégias ORB adaptadas a partir de materiais da Concretum Group.
Uma Nova Abordagem para Critérios Personalizados em Otimizações (Parte 1): Exemplos de Funções de Ativação Uma Nova Abordagem para Critérios Personalizados em Otimizações (Parte 1): Exemplos de Funções de Ativação
O primeiro de uma série de artigos que analisam a matemática dos Critérios Personalizados com foco específico em funções não lineares usadas em Redes Neurais, código MQL5 para implementação e o uso de offsets direcionados e corretivos.
De Iniciante a Especialista: Indicador de Força de Suporte e Resistência (SRSI) De Iniciante a Especialista: Indicador de Força de Suporte e Resistência (SRSI)
Neste artigo, compartilharemos insights sobre como utilizar a programação em MQL5 para identificar níveis de mercado — diferenciando entre níveis de preço mais fracos e mais fortes. Desenvolveremos completamente um indicador funcional de Força de Suporte e Resistência (SRSI).
Introdução às curvas ROC (Receiver Operating Characteristic) Introdução às curvas ROC (Receiver Operating Characteristic)
As curvas ROC são representações gráficas utilizadas para avaliar o desempenho de classificadores. Apesar de os gráficos ROC serem relativamente simples, existem equívocos e armadilhas comuns ao utilizá-los na prática. Este artigo tem como objetivo fornecer uma introdução aos gráficos ROC como uma ferramenta para profissionais que buscam compreender a avaliação de desempenho de classificadores.