Criando um EA gradador multiplataforma

18 abril 2019, 16:00
Roman Klymenko
0
1 021

Introdução

Eu acho que não é segredo para ninguém neste site que a linguagem MQL5 é a melhor opção para criar EAs customizados. Porém, nem todas as corretoras permitem que você crie contas para o MetaTrader 5. E mesmo que você opere com uma que o permita, é bem possível que no futuro você tenha que mudar para uma corretora que só trabalhe com o MetaTrader 4. Se for assim, o que fazer com todos os EA que você já criou em MQL5? Desperdiçar tempo convertendo em MQL4? Não seria melhor escrever um EA que possa trabalhar tanto no MetaTrader 5 quanto no MetaTrader 4?

Neste artigo, tentaremos implementar esse tipo de EA e, ao mesmo tempo, verificar se o sistema de negociação baseado na criação de uma grade de ordens pode funcionar.

Algumas palavras sobre compilação condicional

A compilação condicional irá nos assistir para criar um EA que funcione tanto no MetaTrader 4 quanto no MetaTrader 5. A sintaxe que usaremos será a seguinte:

   #ifdef __MQL5__ 
      // código em linguagem MQL5
   #else 
      // código em linguagem MQL4
   #endif 

A compilação condicional permite especificar que certo bloco de código deve ser compilado somente se isso for feito usando um EA para linguagem MQL5. Ao compilar em MQL4 e outras versões da linguagem, este bloco de código simplesmente será descartado, e, em vez dele, será usado o bloco de código após a instrução #else (se definido).

Assim, se certa funcionalidade for implementada de forma diferente em MQL4 e em MQL5, nós a elaboraremos das duas maneiras: a compilação condicional permite usar aquela opção que é necessária para uma linguagem em particular.

Em outros casos, usaremos uma sintaxe que funciona igualmente em MQL4 e em MQL5.

Sobre sistemas de negociação de grade

Antes de começar a desenvolver nosso EA, falaremos sobre o que são estratégias de negociação de grade, para que no futuro você e eu entendamos isso da mesma maneira.

Um gradador é um Expert Advisor cujo trabalho fundamental consiste em colocar simultaneamente e na mesma quantidade ordens limitadas tanto acima como abaixo do preço atual.

As ordens limitadas não são colocadas a um preço, mas, sim, com um determinado passo, quer dizer, a primeira ordem limitada é colocada - para cima - a uma certa distância do preço atual. Em seguida, uma segunda ordem limitada é posicionada à mesma distância da ordem colocada, e assim por diante. O número de ordens e de passos varia.

Acima do preço atual são colocadas as ordens num mesmo sentido, enquanto abaixo do preço atual, na direção oposta. Acredita-se que:

  • durante a tendência, é necessário posicionar ordens Long acima do preço atual, enquanto — Short abaixo do preço em curso;
  • durante uma faixa, é necessário posicionar ordens Short acima do preço atual, enquanto — Long abaixo do preço em curso;

Pode-se trabalhar com ou sem stop-loss e take-profit.

Se você não definir stop-loss e take-profit, todas as posições abertas (lucrativas e não lucrativas) existirão até que o lucro em todas elas atinja um determinado nível. Depois disso, todas as posições abertas, bem como as ordens limitadas não alcançadas pelo preço serão fechadas, e será exibida uma nova grade.

Nesta captura de tela, temos uma dessas grades:

Exemplo da grade exibida

Assim, em teoria, os sistemas de negociação de grade permitem que você obtenha lucro em qualquer mercado sem esperar pontos de entrada exclusivos, bem como sem usar indicadores.

Se forem usados stop-loss e take-profit, o lucro será obtido graças a que a perda numa posição será coberta pelo lucro total nas restantes caso o preço se mova num só sentido.

Quando não são usados stop-loss e take-profit, o lucro é obtido ao abrir um inúmeras ordens na direção certa. Mesmo se, a princípio, o preço tocar as posições numa direção e, depois, se virar, novas posições na direção certa cobriram a perda das anteriormente abertas, pois serão mais.

Funcionamento do gradador

Acima foi descrito o princípio de funcionamento do gradador. Além dele, você pode criar suas próprias versões de grades, variando a direção de abertura da ordem, adicionando abertura de ordens ao mesmo preço, acrescentando indicadores, etc.

Neste artigo, tentaremos primeiro implementar a versão mais simples de gradador sem stop-loss, porque a ideia sobre a qual ela funciona é muito tentadora.

De fato, parece ser muito sensata a ideia de que o preço se movendo numa só direção, mais cedo ou mais tarde, traz lucro, mesmo que as posições sejam inicialmente abertas na direção errada. Suponhamos, em primeiro lugar, que o preço fosse corrigido e tocasse 2 ordens. Depois, o preço começasse a se mover na direção oposta, ou seja, na direção da tendência principal. Neste caso, mais cedo ou mais tarde, mais de 2 ordens são abertas na direção certa, e mantendo a mesma direção após algum tempo, nossa perda inicial se transforma em lucro. Por que esse sistema de negociação não funciona?

Parece que a única situação em que este sistema de negociação pode causar uma perda é quando o preço toca primeiro uma ordem, retrocede e toca a ordem oposta, muda de direção e toca outra ordem, e novamente muda de direção. Assim, muda constantemente de direção, tocando mais e mais ordens distantes. Mas será que esse comportamento do preço é bem possível?

Modelo do EA

Comecemos o desenvolvimento do EA a partir do modelo que será usado. Veremos imediatamente quais funções MQL padrão estarão envolvidas.

#property copyright "Klymenko Roman (needtome@icloud.com)"
#property link      "https://www.mql5.com/pt/users/needtome"
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
  }

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
   {
   }

Ele difere do modelo padrão gerado ao criar EAs usando o assistente MQL5 somente na string #property strict. Nós a adicionamos para que nosso EA também trabalhe em MQL4.

Precisamos da função OnChartEvent() para poder reagir quando pressionado um botão. Depois, no artigo, implementaremos o botão Fechar tudo, assim, fecharemos manualmente todas as posições e ordens do instrumento de uma só vez, se atingirmos o patrimônio desejado ou apenas desejarmos encerrar o EA.

Função de abertura de posição

Provavelmente, a funcionalidade mais importante de um EA é o posicionamento de ordens. É neste momento que começamos a ter problemas. Em MQL5 e em MQL4, as ordens são colocadas de forma bem diferente. Para unificar essa funcionalidade de alguma forma, temos que desenvolver nossa própria função de posicionamento de ordens.

Cada ordem tem seu próprio tipo: ordem de compra, ordem de venda, ordem limitada de compra ou ordem limitada de venda. A variável em que é definido esse tipo também é diferente em MQL5 e em MQL4.

Em MQL4, o tipo de ordem é especificado por uma variável do tipo int, enquanto, em MQL5, é usada uma enumeração especial ENUM_ORDER_TYPE. Além disso, em MQL4, não existe tal enumeração, daí que, para combinar os dois métodos, criaremos nossa própria enumeração para especificar o tipo de ordem. Graças a isso, a função que criaremos no futuro não dependerá da versão da MQL:

enum TypeOfPos{
   MY_BUY,
   MY_SELL,
   MY_BUYSTOP,
   MY_BUYLIMIT,
   MY_SELLSTOP,
   MY_SELLLIMIT,
}; 

Agora, podemos criar nossa própria função para posicionar ordens - pdxSendOrder(). Transferimos para ela tudo o que precisamos para posicionar uma ordem: tipo de ordem, preço de abertura, stop-loss (0 se não definido), take-profit (0 se não definido), volume, ticket da posição aberta (se necessário modificar uma posição aberta em MQL5), comentário e símbolo (se necessário abrir uma ordem de um símbolo diferente daquele aberto no gráfico atual):

// função de envio de ordem
bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym=""){
   // verificamos os valores transferidos
   if( !StringLen(sym) ){
      sym=_Symbol;
   }
   int curDigits=(int) SymbolInfoInteger(sym, SYMBOL_DIGITS);
   if(sl>0){
      sl=NormalizeDouble(sl,curDigits);
   }
   if(tp>0){
      tp=NormalizeDouble(tp,curDigits);
   }
   if(price>0){
      price=NormalizeDouble(price,curDigits);
   }
   
   #ifdef __MQL5__ 
      ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL;
      ENUM_ORDER_TYPE type=ORDER_TYPE_BUY;
      switch(mytype){
         case MY_BUY:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_STOP;
            break;
         case MY_BUYLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_LIMIT;
            break;
         case MY_SELL:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_STOP;
            break;
         case MY_SELLLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_LIMIT;
            break;
      }
      
      MqlTradeRequest mrequest;
      MqlTradeResult mresult;
      ZeroMemory(mrequest);
      
      mrequest.action = action;
      mrequest.sl = sl;
      mrequest.tp = tp;
      mrequest.symbol = sym;
      if(position>0){
         mrequest.position = position;
      }
      if(StringLen(comment)){
         mrequest.comment=comment;
      }
      if(action!=TRADE_ACTION_SLTP){
         if(price>0){
            mrequest.price = price;
         }
         if(volume>0){
            mrequest.volume = volume;
         }
         mrequest.type = type;
         mrequest.magic = EA_Magic;
         switch(useORDER_FILLING_RETURN){
            case FOK:
               mrequest.type_filling = ORDER_FILLING_FOK;
               break;
            case RETURN:
               mrequest.type_filling = ORDER_FILLING_RETURN;
               break;
            case IOC:
               mrequest.type_filling = ORDER_FILLING_IOC;
               break;
         }
         mrequest.deviation=100;
      }
      if(OrderSend(mrequest,mresult)){
         if(mresult.retcode==10009 || mresult.retcode==10008){
            return true;
         }else{
            msgErr(GetLastError(), mresult.retcode);
         }
      }
   #else 
      int type=OP_BUY;
      switch(mytype){
         case MY_BUY:
            type=OP_BUY;
            break;
         case MY_BUYSTOP:
            type=OP_BUYSTOP;
            break;
         case MY_BUYLIMIT:
            type=OP_BUYLIMIT;
            break;
         case MY_SELL:
            type=OP_SELL;
            break;
         case MY_SELLSTOP:
            type=OP_SELLSTOP;
            break;
         case MY_SELLLIMIT:
            type=OP_SELLLIMIT;
            break;
      }
      if(OrderSend(sym, type, volume, price, 100, sl, tp, comment, EA_Magic, 0)<0){
         msgErr(GetLastError());
      }else{
         return true;
      }
   
   #endif 
   return false;
}

Primeiro, verificamos os valores passados para a função e normalizamos os preços.

Parâmetros de entrada: em seguida, usando a compilação condicional, determinamos a versão atual da MQL e posicionamos uma ordem segundo as regras. Além disso, para MQL5, é usado um parâmetro de entrada adicional useORDER_FILLING_RETURN. Com ele, configuramos o modo de execução de ordens de acordo com os modos suportados pela nossa corretora. Como o parâmetro de entrada useORDER_FILLING_RETURN é necessário apenas para a versão em MQL5 do EA, novamente usamos a compilação condicional para incluí-lo:

#ifdef __MQL5__ 
   enum TypeOfFilling //Filling Mode
     {
      FOK,//ORDER_FILLING_FOK
      RETURN,// ORDER_FILLING_RETURN
      IOC,//ORDER_FILLING_IOC
     }; 
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; //Filling Mode
#endif 

Adicionalmente, ao colocar uma ordem, é usado o parâmetro de entrada EA_Magic contendo o número mágico de nosso EA.

Se esse parâmetro não for especificado nas configurações do EA, ele considerará como próprias as posições do símbolo em que iniciado e decidirá quando fechá-las e o que fazer com elas.

Se o número mágico estiver definido, o EA levará em consideração apenas as posições com este número.

Mapeamento de erros: se a ordem for colocada com sucesso, será retornado o valor true. Caso contrário, o código de erro é passado para a função msgErr(), ela analisa e exibe uma mensagem de erro. Esta função exibe uma mensagem localizada com uma descrição detalhada do erro ocorrido. Não faz muito sentido mostrar aqui todo seu código, portanto, eis apenas uma parte:

void msgErr(int err, int retcode=0){
   string curErr="";
   switch(err){
      case 1:
         curErr=langs.err1;
         break;
//      case N:
//         curErr=langs.errN;
//         break;
      default:
         curErr=langs.err0+": "+(string) err;
   }
   if(retcode>0){
      curErr+=" ";
      switch(retcode){
         case 10004:
            curErr+=langs.retcode10004;
            break;
//         case N:
//            curErr+=langs.retcodeN;
//            break;
      }
   }
   
   Alert(curErr);
}

Mais informações sobre localização são discutidas na próxima seção deste artigo.

Localização do EA

Antes de continuar com o desenvolvimento do EA, tomemos conta dos futuros usuários que não sabem russo (ou pelo menos daqueles que sabem inglês). Adicionemos a funcionalidade para escolher em qual idioma o EA exibe todas as suas mensagens. Teremos dois idiomas: russo e inglês.

Criamos uma enumeração com possíveis opções de idioma e adicionamos um parâmetro de entrada para selecionar o idioma desejado:

enum TypeOfLang{
   MY_ENG, // English
   MY_RUS, // Russo
}; 

input TypeOfLang  LANG=MY_RUS; // Idioma

Criamos uma estrutura para armazenar todas as strings de texto usadas no EA. Depois disso, declaramos uma variável do tipo que criamos:

struct translate{
   string err1;
   string err2;
//   ... outras strings
};
translate langs;

Variável contendo as strings que já temos. Mas ela ainda não contêm as strings em si. Criemos uma função que para preenchê-la com strings no idioma selecionado, no parâmetro de entrada Idioma. Chamemos essa função init_lang(). Parte de seu código é mostrada abaixo:

void init_lang(){
   switch(LANG){
      case MY_ENG:
         langs.err1="No error, but unknown result. (1)";
         langs.err2="General error (2)";
         langs.err3="Incorrect parameters (3)";
//         ... outras strings
         break;
      case MY_RUS:
         langs.err0="Ocorreu um erro ao executar a solicitação";
         langs.err1="Não há erro, mas o resultado é desconhecido (1)";
         langs.err2="Erro geral (2)";
         langs.err3="Parâmetros incorretos (3)";
//         ... outras strings
         break;
   }
}

A única coisa que resta fazer é chamar a função init_lang(), para que as strings sejam preenchidas com os valores que precisamos. O lugar ideal para chamá-la é a função padrão OnInit(), porque ela é chamada logo após a inicialização do EA. Isto é o que precisamos.

Parâmetros de entrada principais

É hora de adicionar os principais parâmetros de entrada ao nosso EA. Além dos já considerados EA_Magic e LANG, eles são:

input double      Lot=0.01;     //Tamanho do lote
input uint        maxLimits=7;  //Número de ordens limitadas na grade num só sentido
input int         Step=10;      //Passo da grade, em pontos
input double      takeProfit=1; //Fechar trades se houver lucro, $

Isto é, abrimos maxLimits ordens numa só direção e a mesma quantidade na direção oposta. A primeira ordem está em Step pontos a partir do preço atual. A segunda — em Step pontos a partir da primeira ordem. e assim por diante.

Fixamos o lucro assim que ela atingir takeProfit dólares. Nesse caso, todas as posições abertas são fechadas, assim como todas as ordens colocadas são canceladas. Depois disso, o EA coloca novamente sua grade.

Não consideramos a possibilidade de perder, portanto, o take-profit é a única condição para fechar posições.

Preenchemos a função OnInit

Como já mencionado, a função OnInit() é chamada uma vez, ao iniciar nosso EA. Nós já adicionamos a chamada de nossa função init_lang() a ela. Vamos preenchê-lo até o fim, para não voltar mais a ele.

No que diz respeito ao nosso EA, o verdadeiro propósito da função OnInit() é ajustar o parâmetro de entrada Step, se o preço tiver 3 ou 5 casas decimais, ou seja, se nossa corretora para este instrumento usar um símbolo adicional após a vírgula:

   ST=Step;
   if(_Digits==5 || _Digits==3){
      ST*=10;
   }

Assim, no próprio EA, em vez do parâmetro de entrada Step, usamos o parâmetro ajustado ST. Nos o declaramos antes de chamar qualquer função, especificando o tipo double.

Como, no futuro, para a formação da grade, não precisamos de uma distância em pontos, mas, sim, no preço do instrumento, imediatamente realizarmos a conversão:

   ST*=SymbolInfoDouble(_Symbol, SYMBOL_POINT);

Adicionalmente, nesta função, podemos verificar se a negociação é permitida para nosso EA. Afinal, se a negociação é proibida, é melhor informar imediatamente o usuário sobre isso para que ele possa remediar a situação.

A verificação pode ser feita usando este pequeno código:

   if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){
      Alert(langs.noBuy+" ("+(string) EA_Magic+")");
      ExpertRemove();
   }   

Se a negociação for proibida, nós informamos sobre isso no idioma escolhido pelo usuário e depois encerramos o EA.

Como resultado, função OnInit() fica assim:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   init_lang();
   
   if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){
      Alert(langs.noBuy+" ("+(string) EA_Magic+")");
      ExpertRemove();
   }

   ST=Step;
   if(_Digits==5 || _Digits==3){
      ST*=10;
   }
   ST*=SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   return(INIT_SUCCEEDED);
  }

Adicionamos o botão Fechar tudo

Trabalhar facilmente como o EA é tão importante quanto seu desempenho ao acompanhar a estratégia de negociação escolhida.

No nosso caso, isto quer dizer que devemos facilmente ver quantas posições Long e Short já estão abertas, e também qual é o lucro total para todas elas.

Além disso, devemos poder fechar rapidamente todas as ordens abertas e posições colocadas, se já estamos satisfeitos com o lucro atingido ou algo está dando errado.

Sendo assim, adicionemos um botão que exiba todas as informações requeridas e, quando clicarmos nele, feche todas as posições e ordens.

Prefixo de objetos gráficos: cada objeto gráfico no MetaTrader deve ter um nome. Além disso, os nomes dos objetos criados por um EA não devem coincidir com os nomes dos objetos criados no gráfico quer manualmente quer usando EAs. Portanto, primeiro, definimos o prefixo que adicionaremos aos nomes de todos os objetos gráficos criados:

string prefix_graph="grider_";

Calculamos a posição e o lucro: agora podemos criar uma função que calcula o número de posições abertas em Long e Short, bem como seu lucro total, e, em seguida, exibir um botão com as informações recebidas ou atualizar o texto, se esse botão já existir. Chamemos a função de getmeinfo_btn():

void getmeinfo_btn(string symname){
   double posPlus=0;
   double posMinus=0;
   double profit=0;
   double positionExist=false;

   // calculamos o número de posições Long e Short abertas,
   // e seu lucro total
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=symname) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
         
         positionExist=true;
         
         profit+=PositionGetDouble(POSITION_PROFIT);
         profit+=PositionGetDouble(POSITION_SWAP);
         
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY){
            posPlus+=PositionGetDouble(POSITION_VOLUME);
         }else{
            posMinus+=PositionGetDouble(POSITION_VOLUME);
         }
      }
   #else
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if( OrderType()==OP_BUY || OrderType()==OP_SELL ){}else{ continue; }
            if(OrderSymbol()!=symname) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            
            positionExist=true;
            
            profit+=OrderCommission();
            profit+=OrderProfit();
            profit+=OrderSwap();
            
            if(OrderType()==OP_BUY){
               posPlus+=OrderLots();
            }else{
               posMinus+=OrderLots();
            }
         }
      }
   #endif 
   
   // se houver posições abertas,
   // adicionamos um botão para fechá-las
   if(positionExist){
      createObject(prefix_graph+"delall", 233, langs.closeAll+" ("+DoubleToString(profit, 2)+") L: "+(string) posPlus+" S: "+(string) posMinus);
   }else{
      // caso contrário, removemos o botão de fechamento de posição
      if(ObjectFind(0, prefix_graph+"delall")>0){
         ObjectDelete(0, prefix_graph+"delall");
      }
   }
   
   // atualizamos o gráfico atual para fazer
   // alterações feitas
   ChartRedraw(0);
}

Aqui usamos a compilação condicional pela segunda vez, já que a funcionalidade para trabalhar com posições abertas é diferente em MQL5 e MQL4. Pela mesma razão, mais adiante no artigo, usamos compilação condicional mais de uma vez.

Exibimos o botão: observe também que usamos nossa própria função para exibir o botão no gráfico createObject(). Esta função verifica se existe um botão no gráfico com o nome passado como o primeiro argumento da função.

Se o botão já tiver sido criado, basta atualizar o texto neste botão de acordo com o texto passado no terceiro argumento da função.

Se não houver nenhum botão, nós o criamos no canto superior direito do gráfico. Além disso, a segundo argumento da função define a largura do botão criado:

void createObject(string name, int weight, string title){
   // se o botão com o nome name não estiver no gráfico, crie-o
   if(ObjectFind(0, name)<0){
      // determinamos o deslocamento em relação ao canto direito do gráfico, onde exibido o botão
      long offset= ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-87;
      long offsetY=0;
      for(int ti=0; ti<ObjectsTotal((long) 0); ti++){
         string objName= ObjectName(0, ti);
         if( StringFind(objName, prefix_graph)<0 ){
            continue;
         }
         long tmpOffset=ObjectGetInteger(0, objName, OBJPROP_YDISTANCE);
         if( tmpOffset>offsetY){
            offsetY=tmpOffset;
         }
      }
      
      for(int ti=0; ti<ObjectsTotal((long) 0); ti++){
         string objName= ObjectName(0, ti);
         if( StringFind(objName, prefix_graph)<0 ){
            continue;
         }
         long tmpOffset=ObjectGetInteger(0, objName, OBJPROP_YDISTANCE);
         if( tmpOffset!=offsetY ){
            continue;
         }
         
         tmpOffset=ObjectGetInteger(0, objName, OBJPROP_XDISTANCE);
         if( tmpOffset>0 && tmpOffset<offset){
            offset=tmpOffset;
         }
      }
      offset-=(weight+1);
      if(offset<0){
         offset=ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-87;
         offsetY+=25;
         offset-=(weight+1);
      }
  
     ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
     ObjectSetInteger(0,name,OBJPROP_XDISTANCE,offset); 
     ObjectSetInteger(0,name,OBJPROP_YDISTANCE,offsetY); 
     ObjectSetString(0,name,OBJPROP_TEXT, title); 
     ObjectSetInteger(0,name,OBJPROP_XSIZE,weight); 
     ObjectSetInteger(0,name,OBJPROP_FONTSIZE, 8);
     ObjectSetInteger(0,name,OBJPROP_COLOR, clrBlack);
     ObjectSetInteger(0,name,OBJPROP_YSIZE,25); 
     ObjectSetInteger(0,name,OBJPROP_BGCOLOR, clrLightGray);
     ChartRedraw(0);
  }else{
     ObjectSetString(0,name,OBJPROP_TEXT, title);
  }
}

Reação ao pressionar um botão: agora, se chamarmos a função getmeinfo_btn(), no gráfico aparece o botão Fechar tudo… (se tivermos posições abertas). No entanto, ao clicar nesse botão, nada deverá acontecer.

Para adicionar uma resposta ao pressionar nosso botão, é preciso interceptar esse clique na função padrão OnChartEvent(). Como esta é a única coisa para a qual precisamos função OnChartEvent(), podemos apresentar o código final:

void OnChartEvent(const int id,         // event ID   
                  const long& lparam,   // event parameter of the long type 
                  const double& dparam, // event parameter of the double type 
                  const string& sparam) // event parameter of the string type 
{
   string text="";
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // se o nome do botão pressionado for prefix_graph+"delall",
         if (sparam==prefix_graph+"delall"){
            closeAllPos();
         }
         break;
   }
}

Agora, quando pressionamos o botão de fechar a posição, chamamos a função closeAllPos(). Este recurso ainda não foi criado. Mas na próxima seção do artigo corrigimos essa falha.

Ações adicionais: Nós já temos a função getmeinfo_btn() que calcula os dados que precisamos e exibe um botão para fechar as posições. Além disso, já implementamos a ação que é desencadeada ao clicar nesse botão. No entanto, a função getmeinfo_btn() não é chamada em nenhum lugar do EA. Portanto, ela ainda não é exibida no gráfico.

Nós usamos a função getmeinfo_btn(), mais tarde, quando tratemos do código da função padrão OnTick().

Por enquanto, podemos recordar sobre a função OnDeInit(). Como o nosso EA cria um objeto gráfico, é preciso tomar cuidado para que, ao fechar o EA, todos os objetos gráficos criados por ele sejam excluídos. Para isso, precisamos da função OnDeInit(). Afinal, ela é automaticamente chamada assim que o EA é fechado.

Como resultado, o corpo da função OnDeInit() toma a forma:

void OnDeinit(const int reason)
  {
      ObjectsDeleteAll(0, prefix_graph);
  }

Com a ajuda desta linha, ao fechar o EA, removemos do gráfico todos os objetos gráficos cujo nome contém nosso prefixo. Até agora, temos apenas um desses objetos.

Implementamos a função para fechar todas as posições

Implementemos o código da função closeAllPos().

A função closeAllPos() fecha todas as posições atualmente abertas, bem como exclui todas as ordens posicionadas.

Mas nem tudo é tão simples. Esta função não apaga apenas todas as posições atualmente abertas. Se tivermos uma posição aberta em Long e a mesma posição aberta em Short, tentaremos fechar uma delas com a posição oposta da segunda posição. Se sua corretora suportar esta operação para o instrumento atual, então, desta forma, você pode devolver o spread que foi pago pela abertura das duas posições. Graças a isso, a rentabilidade do nosso EA cresce. Quando todas as posições são fechadas pelo take-profit, na verdade, temos um lucro um pouco maior do que o especificado no parâmetro de entrada takeProfit.

Assim, a primeira linha da função closeAllPos() é a chamada de mais uma função: closeByPos().

A função closeByPos() tenta fechar as posições com a ajuda das posições opostas abertas. Depois que todas as opostas estiverem fechadas, a função closeAllPos() fecha as posições restantes de maneira usual. Em seguida, fecha as ordens colocadas.

Acontece que para fechar posições no MQL5 eu uso o objeto CTrade. Portanto, antes de implementar nossas duas funções, vamos conectar essa classe e criar imediatamente um objeto dela:

#ifdef __MQL5__ 
   #include <Trade\Trade.mqh>
   CTrade Trade;
#endif 

Agora, pode-se começar a escrever a função que fecha todas as posições com as opostas:

void closeByPos(){
   bool repeatOpen=false;
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=_Symbol) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
         
         if( PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY ){
            long closefirst=PositionGetInteger(POSITION_TICKET);
            double closeLots=PositionGetDouble(POSITION_VOLUME);
            
            for(int ti2=cntMyPos-1; ti2>=0; ti2--){
               if(PositionGetSymbol(ti2)!=_Symbol) continue;
               if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
               if( PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_SELL ) continue;
               if( PositionGetDouble(POSITION_VOLUME)!=closeLots ) continue;
               
               MqlTradeRequest request;
               MqlTradeResult  result;
               ZeroMemory(request);
               ZeroMemory(result);
               request.action=TRADE_ACTION_CLOSE_BY;
               request.position=closefirst;
               request.position_by=PositionGetInteger(POSITION_TICKET);
               if(EA_Magic>0) request.magic=EA_Magic;
               if(OrderSend(request,result)){
                  repeatOpen=true;
                  break;
               }
            }
            if(repeatOpen){
               break;
            }
         }
      }
   #else
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue; 
            if( OrderSymbol()!=_Symbol ) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            
            if( OrderType()==OP_BUY ){
               int closefirst=OrderTicket();
               double closeLots=OrderLots();
               
               for(int ti2=cntMyPos-1; ti2>=0; ti2--){
                  if(OrderSelect(ti2,SELECT_BY_POS,MODE_TRADES)==false) continue; 
                  if( OrderSymbol()!=_Symbol ) continue;
                  if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
                  if( OrderType()!=OP_SELL ) continue;
                  if( OrderLots()<closeLots ) continue;
                  
                  if( OrderCloseBy(closefirst, OrderTicket()) ){
                     repeatOpen=true;
                     break;
                  }
               }
               if(repeatOpen){
                  break;
               }
            }
                        
         }
      }
   #endif 
   // se fecharmos qualquer posição usando uma oposta,
   // executamos a função closeByPos novamente
   if(repeatOpen){
      closeByPos();
   }
}

Caso seja possível fechar uma posição usando a oposta, essa função se chama a si mesma. Isto é necessário, uma vez que os volumes das posições podem diferir, e nem sempre é o fechamento de duas posições que realmente leva ao seu fechamento. Quer dizer, se os volumes forem diferentes, uma das posições - em vez de fechar - diminuirá em volume e, na próxima vez que iniciada a função, será possível fechá-la com uma nova oposta.

Depois de todas as posições opostas terem sido fechadas, a função closeAllPos() fecha as restantes:

void closeAllPos(){
   closeByPos();
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=_Symbol) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;

         Trade.PositionClose(PositionGetInteger(POSITION_TICKET));
      }
      int cntMyPosO=OrdersTotal();
      for(int ti=cntMyPosO-1; ti>=0; ti--){
         ulong orderTicket=OrderGetTicket(ti);
         if(OrderGetString(ORDER_SYMBOL)!=_Symbol) continue;
         if(EA_Magic>0 && OrderGetInteger(ORDER_MAGIC)!=EA_Magic) continue;
         
         Trade.OrderDelete(orderTicket);
      }
   #else
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue; 
            if( OrderSymbol()!=_Symbol ) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            
            if( OrderType()==OP_BUY ){
               MqlTick latest_price;
               if(!SymbolInfoTick(OrderSymbol(),latest_price)){
                  Alert(GetLastError());
                  return;
               }
               if(!OrderClose(OrderTicket(), OrderLots(),latest_price.bid,100)){
               }
            }else if(OrderType()==OP_SELL){
               MqlTick latest_price;
               if(!SymbolInfoTick(OrderSymbol(),latest_price)){
                  Alert(GetLastError());
                  return;
               }
               if(!OrderClose(OrderTicket(), OrderLots(),latest_price.ask,100)){
               }
            }else{
               if(!OrderDelete(OrderTicket())){
               }
            }
                        
         }
      }
   #endif 
   // removemos o botão de fechamento de posição
   if(ObjectFind(0, prefix_graph+"delall")>0){
      ObjectDelete(0, prefix_graph+"delall");
   }

}

Implementemos a função OnTick

Já implementamos quase todas as funcionalidades do EA. Falta o mais importante, colocar a grade de ordens.

A função padrão OnTick() é chamada após cada tick do instrumento ser recebido. Ela verifica se a nossa grade é de ordens, para, caso não seja assim, criar uma.

Verificando o início da barra: verificar cada tick é redundante. Basta verificar a presença da grade, por exemplo, uma vez a cada 5 minutos. Para fazer isso, adicionamos à função OnTick() o código que verifica o início da barra. Se este não for o primeiro tick do início da barra, encerramos a função sem fazer nada:

   if( !pdxIsNewBar() ){
      return;
   }

Função pdxIsNewBar():

bool pdxIsNewBar(){
   static datetime Old_Time;
   datetime New_Time[1];

   if(CopyTime(_Symbol,_Period,0,1,New_Time)>0){
      if(Old_Time!=New_Time[0]){
         Old_Time=New_Time[0];
         return true;
      }
   }
   return false;
}

Para que o EA verifique as nossas condições uma vez a cada 5 minutos, ele deve ser iniciado no timeframe M5.

Verificando o take-profit: antes de verificar a presença de uma grade, precisamos verificar se o lucro já foi atingido para todas as posições atualmente abertas na grade que já abrimos. Se houver, chamamos a já conhecida função closeAllPos().

   if(checkTakeProfit()){
      closeAllPos();
   }

Para verificar se há lucro, chamamos a função checkTakeProfit(). Ela calcula o lucro de todas as posições atualmente abertas e compara-o com o valor do parâmetro de entrada takeProfit:

bool checkTakeProfit(){
   if( takeProfit<=0 ) return false;
   double curProfit=0;
   double profit=0;
   
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=_Symbol) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
         
         profit+=PositionGetDouble(POSITION_PROFIT);
         profit+=PositionGetDouble(POSITION_SWAP);
      }
   #else
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if( OrderType()==OP_BUY || OrderType()==OP_SELL ){}else{ continue; }
            if(OrderSymbol()!=_Symbol) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            
            profit+=OrderCommission();
            profit+=OrderProfit();
            profit+=OrderSwap();
         }
      }
   #endif 
   if(profit>takeProfit){
      return true;
   }
   return false;
}

Exibição do botão Fechar tudo...: ainda não acabamos, não podemos esquecer o botão Fechar tudo..., que implementamos, mas nunca apresentamos. Chegou a hora de adicionar a chamada de sua função,

getmeinfo_btn(_Symbol);

ficando assim:

Botão Fechar tudo...

Colocando a grade: finalmente, chegamos à parte mais importante do nosso EA. Parece bem simples, já que todo o código está mais uma vez escondido atrás das funções:

   // se o instrumento tiver posições abertas ou ordens, então
   if( existLimits() ){
   }else{
   // caso contrário, colocamos a grade
      initLimits();
   }

A função existLimits() retorna true se houver posições abertas para este instrumento ou se colocadas ordens:

bool existLimits(){
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=_Symbol) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
         return true;
      }
      int cntMyPosO=OrdersTotal();
      for(int ti=cntMyPosO-1; ti>=0; ti--){
         ulong orderTicket=OrderGetTicket(ti);
         if(OrderGetString(ORDER_SYMBOL)!=_Symbol) continue;
         if(EA_Magic>0 && OrderGetInteger(ORDER_MAGIC)!=EA_Magic) continue;
         return true;
      }
   #else 
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol()!=_Symbol) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            return true;
         }
      }
   #endif 
   
   return false;
}

Se esta função retornar true, não fazemos nada. Caso contrário, definimos uma nova grade de ordens usando a função initLimits():

void initLimits(){
   // preço para colocar as ordens da grade
   double curPrice;
   // preço atual do instrumento
   MqlTick lastme;
   SymbolInfoTick(_Symbol, lastme);
   // se o preço atual não pôde ser obtido, cancelamos a grade
   if( lastme.bid==0 ){
      return;
   }

   // distância mínima em relação ao preço à qual se pode colocar stop loss e
   // provavelmente colocar ordens pendentes
   double minStop=SymbolInfoDouble(_Symbol, SYMBOL_POINT)*SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   
   // colocamos ordens em Long
   curPrice=lastme.bid;
   for(uint i=0; i<maxLimits; i++){
      curPrice+=ST;

      if( curPrice-lastme.ask < minStop ) continue;
      if(!pdxSendOrder(MY_BUYSTOP, curPrice, 0, 0, Lot, 0, "", _Symbol)){
      }
   }
   // colocamos ordens em Short
   curPrice=lastme.ask;
   for(uint i=0; i<maxLimits; i++){
      curPrice-=ST;
      if( lastme.bid-curPrice < minStop ) continue;
      if(!pdxSendOrder(MY_SELLSTOP, curPrice, 0, 0, Lot, 0, "", _Symbol)){
      }
   }
}

Testando o Expert Advisor

Parabéns, nosso EA está pronto. Chegou a hora de testá-lo e tirar conclusões sobre o desempenho da estratégia de negociação inventada por nós.

Como nosso EA trabalha tanto no MetaTrader 4 quanto no MetaTrader 5, podemos escolher qual versão do terminal testar. Embora aqui, provavelmente, a escolha seja óbvia. O testador de estratégias do MetaTrader 5 dá uma melhor visualização e, como dizem, é melhor.

Para começar, testamos sem otimização. Nosso EA não deve depender particularmente dos valores dos parâmetros de entrada, se usarmos valores razoáveis. Tomamos:

  • símbolo EURUSD;
  • período gráfico M5;
  • período de 1 de agosto de 2018 a 1 de janeiro de 2019;
  • modo de teste OHLC em M1.

Mantemos os parâmetros de entrada por padrão (lote 0,01, passo 10 pontos, 7 ordens na grade, take-profit 1 dólar).

O resultado é mostrado na imagem:

Gráfico de saldo no primeiro teste do EA

Como se pode ver no gráfico, tudo correu bem um mês e uma semana. Conseguimos ganhar quase $ 100 com um rebaixamento de $ 30. Logo, aconteceu algo que, pareceu-nos, não poderia acontecer. Vejamos o visualizador, como foi o preço em setembro:

Resultado de visualização

Tudo começou em 13 de setembro, um pouco depois das 4:15. Primeiro, foi atingida a ordem em Long, a seguir, 2 ordens em Short, logo, mais 2 ordens em Long e, então, o preço pegou as 5 ordens em Short restantes. No total, temos 3 ordens em Long e 7 ordens em Short abertas.

Mais adiante, na imagem não se consegue ver, mas o preço não baixou, e em 20 de setembro o preço voltou ao ponto principal, e atingiu as 4 ordens em Long restantes.

No total, temos as 7 ordens em Short e 7 em Long abertas. Portanto, já não conseguimos atingir o take-profit.

Se olharmos para o futuro movimento dos preços, ele irá para cima em cerca de 80 pontos. Assim, se em nossa cadeia não havia 7 ordens, mas, digamos, 13, é bem possível que nessa situação pudéssemos obter uma vantagem.

Mas mesmo que isso não bastasse, mais tarde o preço cairia em 200 pontos, portanto, com 30 ordens na cadeia, teoricamente, poderíamos alcançar o take-profit. Na verdade, isso tardaria mais de um mês e o rebaixamento não seria o menor.

Testamos o novo número de ordens na grade: verificamos nossas suposições. 13 ordens na grade não mudaram nada. Mas 20 ordens realmente permitiram nos safar dessa:

Testando EURUSD, 20 ordens na grade

Porém, tivemos um rebaixamento de cerca de US $ 300, enquanto o lucro total foi de pouco mais de US $ 100. Portanto, nossa estratégia de negociação pode existir e pode funcionar, mas precisa de grandes melhorias.

Por esse motivo, não há muito sentido em otimizar agora. Mas, apesar disso, tentemos realizá-la.

Realizamos otimização: a otimização é realizada com os seguintes parâmetros:

  • número de ordens na grade: de 4 a 21;
  • passo da grade: de 10 a 20 pontos;
  • take-profit permanece o mesmo, em $ 1.

O melhor foram o passo de 13 pontos e 16 ordens na grade:

Testando EURUSD, 16 ordens na grade, passo de 13 pontos

Este é o resultado do teste no modo Cada tick baseado em dados reais. Apesar de o resultado ser positivo, US $ 119 de lucro de 5 meses com um rebaixamento de US $ 221 não é o melhor resultado. Portanto, nossa estratégia de negociação realmente precisa ser modificada.

Opções para alterar a estratégia de negociação

Parece que não podemos fazer apenas um take-profit para todas as posições. De tempos em tempos, há situações em que o preço atinge todas ou a maioria das ordens em ambas as direções. E então podemos esperar lucros, se não infinitamente, então, por meses.

Pensemos sobre o que pode ser feito para resolver o problema detectado.

Controle manual: naturalmente, a maneira mais fácil de resolver isso é controlar manualmente o EA de vez em quando, assim, se uma situação problemática acontecer, será possível tentar resolvê-la fazendo ordens adicionais ou simplesmente fechando todas as posições.

Colocando uma grade adicional: pode-se tentar colocar outra grade se, por exemplo, 70% das ordens são numa direção e 70% noutra direção estiverem afetadas, esperando que as ordens da grade adicional permitam aumentar rapidamente o número de posições abertas numa direção e, assim, alcançar rapidamente o take-profit.

Além do número de posições abertas, podemos verificar a data de abertura da última posição. Se, por exemplo, passar mais de uma semana desde a abertura da última posição, pomos uma nova grade.

Com ambas as opções, existe o risco de agravar ainda mais a situação, aumentando o já grande rebaixamento.

Fechar a grade inteira e abrir uma nova: além de colocar uma grade adicional, podem-se fechar todas as posições e ordens colocadas na grade atual, reconhecendo a derrota nessa batalha, mas não em toda a gerra.

Além disso, há mais opções quando isso pode ser feito:

  • se mais de N% de ordens forem abertas numa e noutra direção,
  • se desde a abertura da última posição se passaram N dias,
  • se a perda em todas as posições abertas atingir N dólares.

Como exemplo, tentemos implementar o último item dessa lista. Nomeadamente, adicionamos um parâmetro de entrada no qual definimos o tamanho da perda em dólares para indicar o fechamento de posições na grade atual e abrir uma nova. Definimos a perda como um número menor do que 0:

input double      takeLoss=0; //Fechar em caso de perda, $

Agora temos que reescrever a função checkTakeProfit(), para ela retornar o lucro atual de todas as posições:

double checkTakeProfit(){
   double curProfit=0;
   double profit=0;
   
   #ifdef __MQL5__ 
      int cntMyPos=PositionsTotal();
      for(int ti=cntMyPos-1; ti>=0; ti--){
         if(PositionGetSymbol(ti)!=_Symbol) continue;
         if(EA_Magic>0 && PositionGetInteger(POSITION_MAGIC)!=EA_Magic) continue;
         
         profit+=PositionGetDouble(POSITION_PROFIT);
         profit+=PositionGetDouble(POSITION_SWAP);
      }
   #else
      int cntMyPos=OrdersTotal();
      if(cntMyPos>0){
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if( OrderType()==OP_BUY || OrderType()==OP_SELL ){}else{ continue; }
            if(OrderSymbol()!=_Symbol) continue;
            if(EA_Magic>0 && OrderMagicNumber()!=EA_Magic) continue;
            
            profit+=OrderCommission();
            profit+=OrderProfit();
            profit+=OrderSwap();
         }
      }
   #endif 
   return profit;
}

O que mudamos é destacado a amarelo.

Agora, podemos modificar a função OnTick(), para verificar não apenas o take-profit, mas, também, o stop-loss de todas as posições:

   if(takeProfit>0 && checkTakeProfit()>takeProfit){
      closeAllPos();
   }else if(takeLoss<0 && checkTakeProfit()<takeLoss){
      closeAllPos();
   }

Teste adicional

Vejamos se funcionou.

Otimizamos apenas o stop-loss em dólares, na faixa de -5 a -100 dólares. Deixamos os parâmetros restantes como foram selecionados durante o último teste (passo de 13 pontos, 16 ordens na grade).

Como resultado, obtemos mais lucro quando o stop-loss é de -56 dólares. O lucro de 5 meses é de 156 dólares, com um rebaixamento máximo de 83 dólares:

Testando EURUSD, stop-loss em -56 dólares

Olhando para o gráfico, pode-se ver que o stop-loss de 5 meses funcionou apenas uma vez. Relativamente à relação entre o lucro e o rebaixamento, o resultado é, obviamente, melhor.

No entanto, antes de fazer conclusões finais, verificamos se o nosso EA com os parâmetros selecionados pode trazer pelo menos algum lucro a longo prazo, pelo menos para os últimos 5 anos:

Testando EURUSD com stop-loss, 5 anos

A imagem é deprimente. Talvez a otimização adicional possa melhorá-la. Mas, em qualquer caso, o uso desta estratégia de grade requer uma melhoria radical. Simplesmente, a ideia de que mais cedo ou mais tarde posições abertas adicionais nos trarão o lucro não pode ser chamada de verdadeira em termos de negociações automatizadas de longo prazo.

Adicionamos o stop-loss e o take-profit das ordens

Infelizmente, as outras opções para aprimorar o EA que foram listadas acima também não levam a melhores resultados, mas quanto ao stop-loss para trades individuais? Como sem stop-loss não foi possível fazer um EA para negociação automática de longo prazo, será que a adição de stop-loss poderia mudar tudo?

A otimização com base no histórico de 5 anos mostrou melhores resultados do que os apresentados acima.

Os melhores resultados foram o stop-loss em 140 pontos e take-profit em 50 pontos. Além disso, não houve nenhuma posição aberta na grade atual durante 30 dias, portanto, ela foi encerrada e, logo, aberta uma nova grade.

Resultado final:

Usando stop-loss e take-profit para ordens

Lucro $ 351 com um rebaixamento de $ 226. Isso é melhor do que os resultados da negociação sem stop-loss. No entanto, é uma pena que todos os resultados ao encerrar a grade atual antes de passados 30 dias após o último trade não sejam rentáveis. Adicionalmente, passados mais de 30 dias, também não são lucrativos. Esse resultado é mais uma coincidência do que uma regra.

Fim do artigo

O principal objetivo deste artigo foi tentar escrever um EA funcionando tanto no MetaTrader 4 quanto no MetaTrader 5. Nós realmente conseguimos fazer isso.

Além disso, mais uma vez, vimos que testar um EA com base em vários meses de histórico não é suficiente se você não ajustar seus parâmetros a cada semana.

Infelizmente, ideias baseadas em gradadores simples não são viáveis. Claro, talvez tenhamos esquecido alguma coisa. Se você sabe como fazer uma grade baseada em princípios básicos que geram lucro, por favor escreva sobre isso nos comentários.

No entanto, você também não deve pensar que as estratégias de negociação de grade não podem trazer lucro, eis alguns sinais de exemplo:

Trata-se do mesmo gradador, mais complexo que o descrito neste artigo. Ele pode realmente trazer até 100% do lucro para o depósito mensal. Mas falaremos mais sobre isso no próximo artigo sobre esse tópico.

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/5596

Arquivos anexados |
griderEA.ex5 (112.85 KB)
griderEA.mq5 (78.29 KB)
griderEA.ex4 (36.54 KB)
griderEA.mq4 (78.29 KB)
Colorindo os resultados da otimização de estratégias de negociação Colorindo os resultados da otimização de estratégias de negociação

Neste artigo nós vamos realizar um experimento: nós vamos colorir os resultados da otimização. A cor é determinada por três parâmetros: os níveis de vermelho, verde e azul (RGB). Existem outros métodos de codificação de cores, que também usam três parâmetros. Assim, três parâmetros de teste podem ser convertidos em uma cor, que representa visualmente os valores. Leia este artigo para descobrir se essa representação pode ser útil.

Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C# Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C#

Uma maneira simples e rápida de criar janelas gráficas usando o editor do Visual Studio, e integração no código MQL do EA. O artigo é destinado para um vasto público de leitores e não requer conhecimentos de C# e .Net.

Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte I). Conceito, gerenciamento de dados e primeiros resultados Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte I). Conceito, gerenciamento de dados e primeiros resultados

Ao analisar um grande número de estratégias de negociação, pedidos de desenvolvimento de aplicativos para os terminais MetaTrader 5 e MetaTrader 4 e vários sites sobre MetaTrader, eu cheguei à conclusão de que toda essa diversidade é baseada principalmente nas mesmas funções elementares, ações e valores que aparecem regularmente em diferentes programas. Isso resultou na biblioteca multi-plataforma DoEasy para o desenvolvimento fácil e rápido de aplicativos para a МetaТrader 5 e МetaТrader 4.

Extraindo dados estruturados de páginas HTML através de seletores CSS Extraindo dados estruturados de páginas HTML através de seletores CSS

O artigo descreve um método universal para analisar e converter dados de documentos HTML com base em seletores CSS. Em MQL estão disponíveis relatórios de negociação e de teste, calendários econômicos, sinais públicos e monitoramento de contas, fontes de cotações on-line adicionais.