Programamos um EA multiplataforma para definir o Stop-Loss e o Take-Profit de acordo com nossos riscos

Roman Klymenko | 9 agosto, 2019

Introdução

Como você provavelmente sabe, em qualquer negociação é altamente recomendável seguir as regras de gerenciamento de capital, ou seja, não entrar numa transação em que você pode perder mais de N% do seu depósito.

Você escolhe sozinho o que será N. Mas, a fim de cumprir esta regra, você deve calcular corretamente o lote para entrar no mercado.

Aqueles que aconselham fazer isso em suas master classes normalmente mostram um arquivo Excel no qual eles já possuem as fórmulas de cálculo de lote necessárias para cada símbolo. Eles "simplesmente" precisam inserir o tamanho do stop-loss para obter o tamanho do lote necessário.

Mas será que é assim tão "simplesmente"? Esta operação pode demorar um minuto ou mais do seu tempo. Quando você finalmente decide definir o tamanho do lote, o preço pode ir longe do ponto de entrada pretendido. Isso sem mencionar o trabalho extra que você faz. E, pior ainda, o trabalho manual adicional sempre aumenta a probabilidade de um erro ser cometido.

Por essa razão, vamos tentar simplificar esse processo. Para fazer isso, criaremos um EA que permitirá definir visualmente o preço de abertura e o stop-loss. Assim, com base nesses parâmetros, bem como no nível de risco, será selecionado o tamanho do lote desejado e será aberta a posição na direção certa.

Definição de metas

Bem, já definimos a primeira tarefa.

Outra tarefa que o nosso EA irá resolver será definir um take-profit baseado nas preferências de você relativamente à proporção take-profit/stop-loss.

Gerchik e outros traders bem-sucedidos recomendam que definir sempre um take-profit que seja pelo menos 3 vezes o seu stop-loss. Quer dizer, se você colocar um stop-loss de 40 pontos, o take-profit deve ser de pelo menos 120 pontos. Se as chances de que o preço atinja esse nível sejam poucas, é melhor não entrar no mercado.

Do ponto de vista do cálculo das estatísticas, é melhor usar sempre a mesma proporção stop-loss/take-profit. Por exemplo, sempre entrar em transações com uma proporção de take-profit/stop-loss de 3 para 1, 4 para 1, etc. Você decide por conta própria qual proporção a escolher, com base no desempenho da sua negociação.

Mas permanece a questão: como podemos definir o nível de take-profit sem gastar preciosos minutos do nosso tempo com isso? Usar o Excel novamente? Ou esperar que o método 'a olho nu' seja bastante útil?

Para não pensar nisso, vamos também colocar esse recurso nas configurações do EA. Nós teremos um parâmetro especial que permite que nós definamos a proporção de stop-loss/take-profit. Por exemplo, um valor de 4 para esse parâmetro significaria que a proporção deveria ser de 4 para 1. E, em seguida, o EA definirá automaticamente um nível de take-profit 4 vezes maior do que o tamanho do stop-loss que você selecionou.

Formato de trabalho do EA. A última tarefa que precisamos resolver antes de iniciar o desenvolvimento é determinar como funcionará nosso EA. E, de fato, esta é provavelmente a tarefa mais difícil.

Eu tenho usado um Expert Advisor similar ao que estamos criando agora há mais de um ano. Desde o seu nascimento, ele tem passado por mudanças globais.

Inicialmente, ao iniciar o EA, era exibida uma caixa de diálogo permitindo que nós alterássemos todas as configurações de abertura de nova posição. Essas configurações eram salvas automaticamente e usadas na próxima vez que o EA era iniciado. Esta era a principal vantagem deste método. Afinal, as configurações, normalmente, são definidas apenas uma vez (segundo necessário) e não mudam mais.

No entanto, a caixa de diálogo de configurações tem uma enorme desvantagem: ela ocupa quase toda a janela do gráfico. Por esse motivo, o movimento de preços atrás praticamente não é visível. Como não usamos essa caixa de diálogo após a primeira alteração de configurações, ela fecha o gráfico sem fornecer nenhuma vantagem.

Por exemplo, é assim que a janela do gráfico se vê com uma caixa de diálogo de 640 pixels de largura:

Versão do Expert Advisor com caixa de diálogo

Fig. 1. Versão do Expert Advisor com caixa de diálogo

Como se pode ver, a janela nem cabe na tela.

Para resolver este problema, ao longo do tempo, apareceram duas versões deste EA.

No primeiro, este problema foi resolvido escondendo a janela de configurações padrão e exibindo-a quando pressionado o botão Configurações. Este EA ainda pode ser comprado no Mercado para MetaTrader 5.

Já a segunda versão do EA geralmente dispensa a caixa de diálogo de configurações. Todas as configurações do EA são definidas usando os parâmetros de entrada. O que nos poupa o esforço de ter que usar uma caixa de diálogo, mas nos leva a uma dos seguintes tormentos:

Como todos nos consideramos programadores, espero que você escolha a terceira opção. Afinal, é precisamente o clone simplificado da segunda versão do EA que vamos criar agora. Ele ficará assim no gráfico:

Versão do EA sem caixa de diálogo

Fig. 2. Versão do EA sem caixa de diálogo

Parâmetros de entrada

Para entender melhor todo o contexto de trabalho, vamos primeiro olhar para os parâmetros de entrada do nosso EA:

Parâmetros de entrada do EA

Fig. 3. Parâmetros de entrada do EA

Toda nossa negociação será construída a partir do stop-loss, conforme aconselhado pelo guru de trading. Por essa razão, antes de tudo, preste atenção nos dois primeiros parâmetros: " Tipo de stop-loss"e"Tamanho do stop-loss em $ ou %"

Por padrão, stop-loss é definido em dólares, conforme indicado pelo parâmetro "Tipo de stop-loss" Além disso, o stop-loss pode ser definido como uma porcentagem do seu saldo. Além disso, se o stop-loss for selecionado como uma porcentagem, o valor definido não poderá exceder 5% do depósito. Isso é feito para evitar erros ao definir os parâmetros do EA.

O parâmetro "Tamanho do stop-loss em $ ou %" determina a quantia que você está pronto para perder quando ocorrer o stop-loss.

Tamanho do stop-loss em centavos (teclas 7 e 8). Além disso, mais um parâmetro pode ser atribuído à tarefa do stop-loss: "Tamanho do stop-loss em centavos (teclas 7 e 8)"

Além disso, no EA, vamos implementar um conjunto de teclas de atalho que permitem definir o stop-loss do tamanho que precisamos com um clique. Por exemplo, se você sempre usar um stop-loss de 7 centavos ao negociar, basta pressionar a tecla 7 no teclado para que o stop-loss seja definido a essa distância do preço atual. Mas vamos falar sobre isso novamente.

Deve ser entendido que esse parâmetro determina não a quantidade que você perde com o stop-loss, mas a distância do preço de abertura até o preço no qual será ativado o stop-loss.

Não entrar na transação, se com o min. lote, o risco é maior que o especificado. Como o tamanho do lote com o qual você precisa entrar numa transação é calculado automaticamente com base no parâmetro "Tamanho do stop-loss em $ ou %", pode surgir uma situação que, mesmo com o lote mínimo possível para sua corretora, o risco na transação será maior do que o especificado por você.

Nesse caso, você pode inserir a transação com um lote mínimo, ignorando seu risco ou até cancelar a abertura da transação. Esse parâmetro é responsável por escolher o comportamento adequado.

Por padrão, o EA não permitirá que você insira a transação se o risco for excedido.

Cancelar ordem limitada após, horas. Além da entrada no mercado, o EA permite criar ordens limitadas a partir do preço que você especificar. Além disso, você pode usar esse parâmetro para limitar o tempo de vida de tal ordem limitada.

A duração é definida em horas. Por exemplo, se o tempo de vida é de 2 horas, e após 2 horas a partir do momento em que colocada a ordem limitada, ela não é ativada e não é convertida numa posição aberta, então essa ordem limitada é excluída.

Multiplicador para o take-profit. Se você negociar com uma certa proporção de stop-loss/take-profit, então, usando esse parâmetro, você pode definir que o take-profit seja definido automaticamente de acordo com sua regra.

Por padrão, o valor do parâmetro é 4. Ou seja, take-profit será fixado a um preço tal que o lucro na ocorrência de take-profit é igual ao tamanho de 4 de suas perdas.

Os parâmetros restantes. Você também pode:

Função de abertura de posição

Como estamos escrevendo um EA multiplataforma, ele deve funcionar tanto no MetaTrader 4 quanto no MetaTrader 5. No entanto, a funcionalidade para abrir posições nessas versões é diferente. Para o nosso código funcionar imediatamente nas duas versões da plataforma, usaremos a compilação condicional.

Eu tenho repetidamente dito em meus artigos o que é isso. Por exemplo, no artigo Criando um EA gradador multiplataforma.

Em suma, o código de compilação condicional é assim:

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

Ao longo do artigo, usaremos os recursos de compilação condicional apenas 3 vezes, duas das quais relacionadas à função para abrir uma posição. Todos os outros códigos funcionarão da mesma forma no MetaTrader 4 e no MetaTrader 5.

Nós também já desenvolvemos a função para abrir uma posição como parte do artigo Criando um EA gradador multiplataforma. Por isso, vamos apenas dar a partir daí. A única coisa é que vamos modificá-lo um pouco para que ele defina o tempo de vida das ordens limitadas abertas:

// tipos possíveis de ordens para a função de abertura de posições
enum TypeOfPos
  {
   MY_BUY,
   MY_SELL,
   MY_BUYSTOP,
   MY_BUYLIMIT,
   MY_SELLSTOP,
   MY_SELLLIMIT,
   MY_BUYSLTP,
   MY_SELLSLTP,
  }; 

// seleção do tipo de execução da transação para MT5
#ifdef __MQL5__ 
   enum TypeOfFilling //tipo de execução da transação
     {
      FOK,//ORDER_FILLING_FOK
      RETURN,// ORDER_FILLING_RETURN
      IOC,//ORDER_FILLING_IOC
     }; 
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; //modo de execução da ordem
#endif 


/*
função para abrir uma posição ou colocar uma ordem limitada
*/
bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym="", datetime expiration=0){
      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);
      }else{
         #ifdef __MQL5__ 
         #else
            MqlTick latest_price;
            SymbolInfoTick(sym,latest_price);
            if( mytype == MY_SELL ){
               price=latest_price.ask;
            }else if( mytype == MY_BUY ){
               price=latest_price.bid;
            }
         #endif 
      }
   #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_BUYSLTP:
            action=TRADE_ACTION_SLTP;
            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_SELLSLTP:
            action=TRADE_ACTION_SLTP;
            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(expiration>0){
         mrequest.type_time = ORDER_TIME_SPECIFIED_DAY;
         mrequest.expiration = expiration;
      }
      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){
            if(action!=TRADE_ACTION_SLTP){
               switch(type){
                  case ORDER_TYPE_BUY:
//                     Alert("Order Buy #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
                  case ORDER_TYPE_SELL:
//                     Alert("Order Sell #:",mresult.order," sl",sl," tp",tp," p",price," !!");
                     break;
               }
            }else{
//               Alert("Order Modify SL #:",mresult.order," sl",sl," tp",tp," !!");
            }
            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, expiration)<0){
            msgErr(GetLastError());
      }else{
         switch(type){
            case OP_BUY:
               Alert("Order Buy sl",sl," tp",tp," p",price," !!");
               break;
            case OP_SELL:
               Alert("Order Sell sl",sl," tp",tp," p",price," !!");
               break;
            }
            return true;
      }
   
   #endif 
   return false;
}

Ao abrir uma posição no MetaTrader 5, devemos selecionar o tipo de execução. Por isso, para o MetaTrader 5, adicionamos mais um parâmetro de entrada: " Modo de execução de ordens"

Corretoras diferentes suportam diferentes tipos de execução de ordens. Provavelmente, o mais popular é ORDER_FILLING_FOK. Por esse motivo, ele é selecionado por padrão. No entanto, se sua empresa de corretagem não suportar esse modo, você poderá escolher facilmente aquele que ela suporta.

Localização do EA

Outro mecanismo, que foi retirado do artigo Criando um EA gradador multiplataforma, é a possibilidade de localizar mensagens de texto do EA. Portanto, novamente, não vamos considerar isso. Se você estiver interessado no seu formato de trabalho, leia o artigo mencionado.

Programando a interface do EA

De um modo geral, neste artigo, não consideraremos o desenvolvimento de um EA a partir do zero. Além disso, supõe-se que você já conheça a linguagem MQL pelo menos num nível básico.

Neste artigo, vamos considerar como são implementadas as partes principais do EA. Isso ajudará se você for usá-lo ou, de repente, quiser refiná-lo, implementando alguma funcionalidade adicional.

Comecemos com a interface do nosso EA.

Iniciado o EA, serão criados os seguintes elementos da interface:

Assim, a fim de criar uma ordem com os parâmetros que precisamos (definidos usando os parâmetros do EA), precisamos mover a linha vermelha para o preço no qual deve ocorrer o stop-loss.

Se colocarmos uma linha vermelha acima do preço atual, será aberta uma posição Short. Se a linha vermelha estiver abaixo do preço atual, será aberta uma posição Long.

O volume da posição aberta será calculado automaticamente para que, quando o stop-loss ocorrer, você perca a quantia mais próxima daquela especificada nas configurações do EA. Assim, tudo que você precisa fazer é clicar no botão para abrir uma posição. Como resultado, será aberta uma posição a preço de mercado.

Se quiser entrar no mercado com uma ordem limitada, você também precisa clicar no botão "Mostrar linha de preço de abertura (0)" e mover a linha verde aparada para o preço no qual você deseja abrir uma ordem limitada. O EA determinará automaticamente a direção e o tipo da ordem limitada com base na localização da linha de stop-loss e na linha de take-profit (stop-loss é maior que o preço de abertura ou menor).

Em princípio, não é preciso clicar no botão "Mostrar linha de preço de abertura (0)". Depois de mover a linha vermelha de stop-loss até o preço desejado, este botão será pressionado automaticamente e aparecerá a linha verde do preço de abertura. Se você movê-la, será aberta uma ordem limitada. Se você não tocar, será aberta uma posição de mercado.

Bem, já acabamos com a parte do princípio de trabalho. Agora podemos proceder à programação.

Trabalho com comentário do gráfico. Para trabalhar com comentários no gráfico, é usada a função padrão Comment. Portanto, só precisamos preparar a linha que a função Comment exibirá no gráfico. Para fazer isso, vamos criar nossa própria função getmespread:

/*
   Exibimos informações sobre o spread e a hora de fechamento da sessão num comentário no gráfico
*/
void getmespread(){
   string msg="";
   
   //obtemos o spread na moeda do instrumento
   curSpread=lastme.ask-lastme.bid;
   
   // se o pregão não estiver encerrado, exibimos informações sobre o spread
   if( !isClosed ){
      if(curSpread>0){
         StringAdd(msg, langs.Label1_spread+": "+(string) DoubleToString(curSpread, (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))+" "+currencyS+" ("+DoubleToString(curSpread/curPoint, 0)+langs.lbl_point+")");
         StringAdd(msg, "; "+DoubleToString(((curSpread)/lastme.bid)*100, 3)+"%");
      }else{
         StringAdd(msg, langs.Label1_spread+": "+langs.lblNo);
      }
      StringAdd(msg, "; ");
   }
   
   // exibimos a hora de fechamento do pregão, se anteriormente tivéssemos podido determiná-lo
   if(StringLen(time_info)){
      StringAdd(msg, "   "+time_info);
   }
      
   Comment(msg);
}

Vamos chamar a função getmespread quando iniciado o EA (Oninit) e a cada novo tick (Ontick)

Na função getmespread usamos cinco variáveis globais do nosso EA: lastme, isClosed, time_info, currencyS, curPoint.

Na variável lastme, são armazenadas informações sobre Ask e Bid do último preço recebido. Seu conteúdo é atualizado nas funções Oninit e Ontick com o comando:

SymbolInfoTick(_Symbol,lastme);

As variáveis restantes são inicializadas na função OnInit. isClosed e time_info são inicializados da seguinte maneira:

  isClosed=false;
  // obtemos a data atual
  TimeToStruct(TimeCurrent(), curDay);
  // obtemos o horário de negociação do instrumento para hoje
  if(SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){
      time_info="";
      TimeToStruct(dto, curEndTime);
      TimeToStruct(dfrom, curStartTime);
         
         isEndTime=true;
         string tmpmsg="";
         tmp_val=curEndTime.hour;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val+":");
         tmp_val=curEndTime.min;
         if(tmp_val<10){
            StringAdd(tmpmsg, "0");
         }
         StringAdd(tmpmsg, (string) tmp_val);
         if(curEndTime.hour==curDay.hour){
            if(tmp_val>curDay.min){
            }else{
               isClosed=true;
            }
         }else{
            if(curEndTime.hour==0){
            }else{
               if( curEndTime.hour>1 && (curDay.hour>curEndTime.hour || curDay.hour==0)){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isClosed=true;
               }else if(curDay.hour<curStartTime.hour ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }else if(curDay.hour==curStartTime.hour && curDay.min<curStartTime.min ){
                  StringAdd(time_info, " ("+langs.lbl_close+")");
                  isEndTime=false;
                  isClosed=true;
               }
            }
         }

         if(isEndTime){
            StringAdd(time_info, langs.lblshow_TIME+": "+tmpmsg+time_info);
         }else{
            StringAdd(time_info, langs.lblshow_TIME2+": "+tmpmsg+time_info);
         }
  }

Nas variáveis currencyS, vamos armazenar o símbolo da moeda que é usado para calcular o lucro do instrumento atual. Podemos obtê-lo com o comando:

currencyS=SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);

O tamanho do ponto do instrumento será armazenado na variável curPoint:

curPoint=SymbolInfoDouble(_Symbol, SYMBOL_POINT);

Linha de stop-loss. Imediatamente após o início do EA, vemos apenas uma linha: a linha vermelha para definir o stop-loss.

Vamos exibir essa linha na função Oninit, bem como os botões. Mas antes de desenhar uma linha, precisamos verificar se já existe essa linha no gráfico. Se houver uma linha, não criaremos uma nova linha e outros elementos da interface. Em vez disso, nas variáveis globais colocaremos o preço no qual está localizada essa linha, bem como o preço no qual está localizada a linha para abrir a transação, se essa linha existir no gráfico:

  // se no gráfico houver linhas de stop-loss e preços de abertura, então
  // nas variáveis colocamos preços nos quais elas estão localizadas
  if(ObjectFind(0, exprefix+"_stop")>=0){
      draw_stop=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
      if(ObjectFind(0, exprefix+"_open")>=0){
         draw_open=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
      }
  // caso contrário, criamos a interface gráfica do EA
  }else{
      draw_open=lastme.bid;
      draw_stop=draw_open-(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)*curPoint);
      ObjectCreate(0, exprefix+"_stop", OBJ_HLINE, 0, 0, draw_stop);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_stop",OBJPROP_ANCHOR,ANCHOR_TOP);

      // outros elementos da interface
  }

Essa situação pode surgir quando já existem elementos de interface do Expert Advisor que está sendo executado no gráfico?

Se você não implementou um código que, ao fechar o EA, irá apagar todos os elementos de interface que ele cria, então não apenas ele pode, mas certamente irá acontecer. Mas mesmo que tal código seja implementado, de qualquer forma, pode haver algum tipo de erro no EA, por causa do qual ele será fechado, e os elementos de interface criados por ele permanecerão no gráfico. Portanto, é melhor sempre verificar se existe um elemento desse tipo no gráfico antes de criar qualquer elemento gráfico.

Bem, criamos uma linha vermelha. Além disso, fizemos isso selecionado por padrão. Portanto, você não precisa clicar duas vezes numa linha para ativá-la. Basta movê-la imediatamente para o preço desejado. No entanto, se você agora mover a linha vermelha, nada acontecerá. Afinal de contas, o código que executará determinadas ações após a linha ser movida ainda não foi implementado.

Qualquer interação com elementos da interface gráfica é realizada na função padrão OnchartEvent. O deslocamento de elementos da interface gera um evento com ID CHARTEVENT_OBJECT_DRAG. Assim, para executar algo depois de mover a linha no gráfico, precisamos, na função OnchartEvent, interceptar este evento, verificar o elemento com o nome acionado, e se este for o nosso elemento, poderemos executar o código que precisamos:

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 
  { 
   switch(id){
      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }
         break;
   }
}

Depois de mover a linha vermelha, será iniciada a função setstopbyline, que "se lembra" do nível de stop-loss para uma ordem futura:

/*
"lembra" o nível de stop-loss para uma ordem futura
*/
void setstopbyline(){
   // obtemos o preço no qual está localizada a linha de stop-loss
   double curprice=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE);
   // se o preço for diferente daquele em que foi colocada a linha de stop-loss durante a inicialização do EA, então
   if(  curprice>0 && curprice != draw_stop ){
      double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
      if( tmp_double>0 && tmp_double!=1 ){
         if(tmp_double<1){
            resval=DoubleToString(curprice/tmp_double, 8);
            if( StringFind(resval, ".00000000")>0 ){}else{
               curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
            }
         }else{
            if( MathMod(curprice,tmp_double) ){
               curprice= MathFloor(curprice/tmp_double)*tmp_double;
            }
         }
      }
      draw_stop=STOPLOSS_PRICE=curprice;
                  
      updatebuttontext();
      ChartRedraw(0);
   }
}

Além da função setstopbyline, o deslocamento da linha vermelha causa a exibição do preço de abertura no gráfico (função showOpenLine) e provoca a alteração do estado do botão " Mostrar linha de preço de abertura (0)"

Botão e linha de preço de abertura. O botão "Mostrar linha de preço de abertura (0)"também é criado quando é inicializado o EA:

      if(ObjectFind(0, exprefix+"_openbtn")<0){
         ObjectCreate(0, exprefix+"_openbtn", OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XDISTANCE,0); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YDISTANCE,33); 
         ObjectSetString(0,exprefix+"_openbtn",OBJPROP_TEXT, langs.btnShowOpenLine); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XSIZE,333); 
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_FONTSIZE, 8);
         ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YSIZE,25); 
      }

Como mencionado acima, qualquer interação com elementos de interface é processada dentro da função padrão OnchartEvent, incluindo o botão pressionado. O evento com ID CHARTEVENT_OBJECT_CLICK é responsável por isso. Só podemos interceptá-lo, verificar a origem do evento e executar as ações necessárias. Para fazer isso, adicionamos um case adicional ao operador switch da função OnchartEvent:

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

Função updateOpenLine, que é chamada quando você clica no botão "Mostrar linha de preço de abertura (0)"é um pequeno embrulho para chamar a função principal showOpenLine. Isso, por sua vez, simplesmente exibe o preço de abertura no gráfico:

void showOpenLine(){
   if(ObjectFind(0, exprefix+"_open")<0){
      draw_open=lastme.bid;
      ObjectCreate(0, exprefix+"_open", OBJ_HLINE, 0, 0, draw_open);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTABLE,1);
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTED,1); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_STYLE,STYLE_DASHDOTDOT); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_ANCHOR,ANCHOR_TOP); 
      ObjectSetInteger(0,exprefix+"_open",OBJPROP_COLOR,clrGreen);
   }
}

Nós só podemos reescrever o manipulador de eventos CHARTEVENT_OBJECT_DRAG de modo que ele reaja ao movimento da linha de stop-loss e da linha de preço de abertura:

      case CHARTEVENT_OBJECT_DRAG:
         if(sparam==exprefix+"_stop"){
            setstopbyline();
            showOpenLine();
            ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true);
         }else if(sparam==exprefix+"_open"){
               curprice=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE);
               if( curprice>0 && curprice != draw_open ){
                  double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                  if( tmp_double>0 && tmp_double!=1 ){
                     if(tmp_double<1){
                        resval=DoubleToString(curprice/tmp_double, 8);
                        if( StringFind(resval, ".00000000")>0 ){}else{
                           curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double;
                        }
                     }else{
                        if( MathMod(curprice,tmp_double) ){
                           curprice= MathFloor(curprice/tmp_double)*tmp_double;
                        }
                     }
                  }
                  draw_open=open=OPEN_PRICE=curprice;
                  
                  updatebuttontext();
                  ObjectSetString(0,exprefix+"Edit3",OBJPROP_TEXT,0, (string) NormalizeDouble(draw_open, _Digits));
                  ChartRedraw(0);
               }
         }
         break;

Linha de take-profit. Além das linhas vermelha e verde, ainda temos que realizar mais uma, a tracejada. Ela aparecerá depois que você mover a linha vermelha de stop-loss para o preço desejado. E mostrará o preço pelo qual o take-profit na transação é atingido:

Linhas de stop-loss, de take-profit e de preços de abertura

Fig. 4. Linhas de stop-loss, de take-profit e de preços de abertura

Botão para abertura de posição. O botão para abrir uma posição é exibido exatamente da mesma maneira que o botão "Mostrar linha de preço de abertura (0)"

Este botão pressionado também irá gerar um evento CHARTEVENT_OBJECT_CLICK. Como já foi considerado trabalhar com este evento. Em geral, como resultado, quando você clica no botão de abertura de posição, será executada a função startPosition:

      case CHARTEVENT_OBJECT_CLICK:
         if (sparam==exprefix+"_send"){
            startPosition();
         }else if (sparam==exprefix+"_openbtn"){
            updateOpenLine();
         }
         break;

Exclusão de elementos da interface no encerramento do EA. Não se esqueça da remover corretamente os elementos da interface após a conclusão do EA. Afinal, se não cuidarmos disso, todos os elementos permanecerão no gráfico.

Para executar quaisquer comandos no encerramento do EA, basta escrevê-los dentro da função padrão Ondeinit:

void OnDeinit(const int reason)
  {

     if(reason!=REASON_CHARTCHANGE){
        ObjectsDeleteAll(0, exprefix);
        Comment("");
     }
      
  }

A variável reason contém informações sobre o motivo pelo qual o trabalho do EA foi concluído. No momento, a única razão importante para nós é a mudança no timeframe ( REASON_CHARTCHANGE) Acontece que, por padrão, uma mudança no timeframe causa o encerramento e reinicialização do EA. Para nós este não é o comportamento mais aceitável. De fato, neste caso, ao mudar o timeframe, perdemos os preços de stop-loss e de aberturas, que já podemos ter definido.

Portanto, na função Ondeinit, primeiro, verificamos se o motivo da remoção do EA é uma alteração no EA. E somente se a razão para o fechamento for diferente, removemos todos os elementos da interface do EA, bem como limpamos os comentários no gráfico.

Implementamos as teclas de controle rápido do EA

Claro, o posicionamento de ordens com o mouse é ótima ideia. Mas às vezes pode ser útil poder trabalhar mais rapidamente com o EA pressionando teclas.

Pressionar as teclas do teclado também se refere à interação com os elementos da interface do EA, ou seja, à interação com o gráfico inteiro em que iniciado o EA. Assim, precisamos interceptar as teclas na função OnchartEvent.

Ao pressionar qualquer tecla, ocorre o evento CHARTEVENT_KEYDOWN. Neste caso, código da tecla pressionada é colocado no parâmetro sparam. Esse conhecimento é suficiente para começar a processar as teclas pressionadas:

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="";
   double curprice=0;
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // pressionando botões no gráfico
         break;
      case CHARTEVENT_OBJECT_DRAG:
         // deslocamento das linhas
         break;
      case CHARTEVENT_KEYDOWN:
         switch((int) sparam){
            // encerrar o EA sem colocar ordens
            case 45: //x
               closeNotSave();
               break;
            // colocar ordem e encerrar o EA
            case 31: //s
               startPosition();
               break;
            // definir stop-loss o mais baixo possível para abrir uma posição Long
            case 22: //u
               setMinStopBuy();
               break;
            // definir stop-loss o mais baixo possível para abrir uma posição Sell
            case 38: //l
               setMinStopSell();
               break;
            // cancelar o preço de abertura definido
            case 44: //z
               setZero();
               ChartRedraw();
               break;
            // definir stop-loss a 0,2% do preço atual para abrir uma posição Long
            case 3: //2
               set02StopBuy();
               break;
            // definir stop-loss a 0,2% do preço atual para abrir uma posição Short
            case 4: //3
               set02StopSell();
               break;
            // definir stop-loss a 7 centavos do preço atual (parâmetro CENT_STOP)
            // para abrir posição Long
            case 8: //7
               set7StopBuy();
               break;
            // definir stop-loss a 7 centavos do preço atual (parâmetro CENT_STOP)
            // para abrir posição Short
            case 9: //8
               set7StopSell();
               break;
         }
         break;
   }
}

Assim, se você definir uma stop-loss fixo igual ao mínimo possível, 0,2% do preço ou em centavos do preço, então você não precisará nem usar o mouse. Imaginemos que iniciemos o EA, apertemos " 2"para definir um stop-loss igual a 0,2% do preço em Long, pressionemos a tecla "S"e seja aberta a posição.

Se você usar o MetaTrader 5, poderá até mesmo iniciar o EA a partir do teclado, atribuindo teclas de acesso a ele. Se alguém não souber sobre esse recurso do MetaTrader 5, na janela Navegador chame o menu de contexto do EA desejado, selecione o item Atribuir tecla de atalho e sua vida pode ser mais fácil:

Atribuindo teclas de atalho para EAs

Fig. 5. Atribuindo teclas de atalho para EAs

Cálculo do volume de transações requerido

Nós só precisamos considerar a função para abrir posição (startPosition). No entanto, não há praticamente nada de interessante nela. Basta verificar a disponibilidade de todos os dados de que precisamos: preços de stop-loss, preços de abertura de posição, configurações de EA. Depois disso, é calculado o tamanho do lote que deve ser incluído na transação para atender aos seus riscos. E é chamada a função pdxSendOrder, já considerada no começo do artigo.

O mais interessante é seu mecanismo para calcular o volume de transação.

Primeiro, precisamos calcular quanto vamos perder em caso de stop-loss nos volumes mínimos possíveis. A implementação dessa funcionalidade no MQL5 é diferente da do MQL4.

MQL5 tem uma função especial OrderCalcProfit, que permite calcular o montante de lucro que você receberá quando o preço de um instrumento se move para um nível especificado. Com sua ajuda, é fácil calcular o lucro possível quanto as perdas possíveis com um stop-loss específico.

MQL4 usa uma fórmula mais complexa para calcular perdas.

Como resultado, para calcular perdas, obtivemos a seguinte função:

double getMyProfit(double fPrice, double fSL, double fLot, bool forLong=true){
   double fProfit=0;
   
   fPrice=NormalizeDouble(fPrice,_Digits);
   fSL=NormalizeDouble(fSL,_Digits);
   #ifdef __MQL5__ 
      if( forLong ){
         if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }else{
         if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){};
      }
   #else
      if( forLong ){
         fProfit=(fPrice-fSL)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }else{
         fProfit=(fSL-fPrice)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE);
      }
   #endif 
   if( fProfit!=0 ){
      fProfit=MathAbs(fProfit);
   }
   
   return fProfit;
}

Obtido o tamanho das perdas no volume mínimo, resta determinar o volume da transação em que as perdas não excederão o risco especificado nas configurações do EA:

      profit=getMyProfit(open, STOPLOSS_PRICE, lot);
      if( profit!=0 ){
         // se, no volume mínimo, o tamanho das perdas for menor que seus riscos,
         // calcularemos o volume de transação desejado
         if( profit<stopin_value ){
            // obteremos o volume de transação desejado
            lot*=(stopin_value/profit);
            // ajustaremos o volume se não corresponder ao mínimo passo possível
            // de acordo com o instrumento
            if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.01 ){
               lot=(floor(lot*100))/100;
            }else if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.1 ){
               lot=(floor(lot*10))/10;
            }else{
               lot=floor(lot);
            }
         // se, no volume mínimo, o tamanho das perdas for maior que seus riscos,
         // cancelaremos a abertura da posição, se especificado nas configurações do EA
         }else if( profit>stopin_value && EXIT_IF_MORE ){
            Alert(langs.wrnEXIT_IF_MORE1+": "+(string) lot+" "+langs.wrnEXIT_IF_MORE2+": "+(string) profit+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(string) stopin_value+" "+AccountInfoString(ACCOUNT_CURRENCY)+")!");
            return;
         }
      }

Restrições de entrada

No processo, o EA verifica uma série de condições para que você não perca seu tempo tentando abrir uma transação onde é proibido.

Por exemplo, ao inicializar um EA, é verificado o tamanho mínimo permitido do lote para o instrumento atual. Se esse valor for 0, o EA não será iniciado. Como regra, você não pode abrir uma posição para instrumentos com tais configurações:

   if(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)==0){
      Alert(langs.wrnMinVolume);
      ExpertRemove();
   }

Uma verificação também é executada no modo de acesso à negociação do instrumento. Se a negociação no instrumento for proibida ou somente for permitido o fechamento de transações previamente abertas, o EA também não será iniciado:

   if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
      Alert(langs.wrnOnlyClose);
      ExpertRemove();
   }

Imediatamente após a abertura de uma posição, é verificado se o um determinado preço de abertura e stop-loss loss é correto. Por exemplo, se a passo mínimo do preço for 0,25 e o stop-loss estiver definido como 23,29, a corretora simplesmente não aceitará sua ordem. Em geral, em tais casos, a corretora automaticamente traz o preço para o valor correto (o preço de stop-loss não será 23,29, mas, sim, 23,25 ou 23,5). Você simplesmente não pode definir o preço "errado". Mas, em qualquer caso, é executada apenas uma verificação adicional:

   if( SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)>0 && SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)!=1 ){
      resval=DoubleToString(STOPLOSS_PRICE/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), 8);
      if( StringFind(resval, ".00000000")>0 ){}else{
         Alert(langs.wrnSYMBOL_TRADE_TICK_SIZE+" "+(string) SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)+"! "+langs.wrnSYMBOL_TRADE_TICK_SIZE_end);
         return;
      }
   }

Fim do artigo

Naturalmente, criamos apenas recursos básicos para colocar ordens. Mas mesmo esses recursos simplificam muito a vida daqueles que negociam usando níveis de Gerchik ou em qualquer outro nível.

Espero que você não use mais as planilhas do Excel para outras finalidades. Graças ao isso, a velocidade e precisão da sua negociação aumentará, assim, também o lucro crescerá.

Quaisquer melhorias no EA são permitidas e bem-vindas.

Se a programação não é sua habilidade, mas você realmente precisa implementar algum tipo de funcionalidade que não esteja dentro da estrutura deste EA, mande uma mensagem privada. No entanto, provavelmente iso não será de graça 😊

Ou você pode ver a funcionalidade das versões avançadas deste EA localizadas no Mercado:

Talvez entre essa funcionalidade esteja o que você precisa.