English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Construindo um Expert Advisor de arrastar e soltar semiautomático interativo com base no risco predefinido e proporção R/R

Construindo um Expert Advisor de arrastar e soltar semiautomático interativo com base no risco predefinido e proporção R/R

MetaTrader 5Exemplos | 7 fevereiro 2014, 09:13
1 808 0
investeo
investeo

Introdução

Alguns operadores executam todas suas negociações automaticamente, e alguns misturam negociações automáticas e manuais, com base na saída de diversos indicadores. Sendo um membro do último grupo, precisei de uma ferramenta interativa para avaliar risco dinamicamente e obter níveis de preço diretamente do gráfico.

Tendo declarado o risco máximo em meu patrimônio, quis calcular parâmetros em tempo real com base no nível de Stop Loss que coloquei no gráfico e precisei executar minha negociação diretamente a partir do EA, com base em níveis SL e TP calculados.

Este artigo apresentará uma maneira de implementar um Expert Advisor interativo semiautomático, com risco de equidade predefinido e proporção R/R. O risco do Expert Advisor, R/R e parâmetros de tamanho de lote podem ser alterados durante o tempo de execução no painel do EA.


1. Requisitos

Os requisitos para o EA foram os seguintes:

  • Habilidade para predefinir o nível de risco no início e para alterá-lo durante o tempo de execução, para ver como ele afeta o tamanho da posição.
  • Habilidade para predefinir a proporção risco para recompensa e alterá-la durante o tempo de execução.
  • Habilidade para calcular o tamanho máximo de lote em tempo real para determinados níveis de risco e stop loss.
  • Habilidade para alterar o tamanho do lote em tempo real para ver como ele afeta a equidade de risco e recompensa.
  • Habilidade para executar ordem de mercado de compra e venda diretamente a partir do EA.
  • Interface de arrastar e soltar para definir stop loss e ver o nível de preço para um nível de risco para recompensa predefinido.


2. Projeto

Devido aos requisitos para o EA de exibir e alterar parâmetros durante o tempo de execução, decidi usar classes CChartObject e seus descendentes para exibir GUI na janela de gráfico e manipular eventos de entrada no gráfico para interação com usuário. E, portanto, a interface de usuário necessária ao EA com rótulos, botões e campos de edição.

No início, quis usar o objetos CChartObjectPanel para agrupar outros objetos no painel, mas decidi tentar uma abordagem diferente, projetei uma classe que mantém rótulos, campos de edição e botões e os exibe em uma imagem de fundo. A imagem de fundo da interface foi feita usando o software GIMP. Objetos gerados pelo MQL5 são campos de edição, rótulos vermelhos atualizados em tempo real e botões.

Eu simplesmente coloquei objetos de rótulo no gráfico e registrei suas posições e construí uma classe CRRDialog que manipula todas as funções de exibição de saída calculada, recebendo parâmetros dos campos CChartObjectEdit e registrando estados de botão. Os retângulos coloridos de risco e recompensa são objetos da classe CChartObjectRectangle e o apontador de stop loss arrastável é um objeto bitmap da classe CChartObjectBitmap.


Figura 1. Captura de tela do EA visual

Figura 1. Captura de tela do EA visual


3. Implementação da classe de diálogo do EA

A classe CRRDialog manipula toda a interface de usuário do EA. Ela contém diversas variáveis que são exibidas, objetos que são usados para exibir as variáveis e métodos para obter/definir valores de variáveis e atualizar a caixa de diálogo.

Estou usando o objeto CChartObjectBmpLabel para o plano de fundo, objetos CChartObjectEdit para campos de edição, objetos CChartObjectLabel para exibir rótulos e objetos CChartObjectButton para botões:

class CRRDialog
  {
private:

   int               m_baseX;
   int               m_baseY;
   int               m_fontSize;
   
   string            m_font;
   string            m_dialogName;
   string            m_bgFileName;

   double            m_RRRatio;
   double            m_riskPercent;
   double            m_orderLots;
   double            m_SL;
   double            m_TP;
   double            m_maxAllowedLots;
   double            m_maxTicksLoss;
   double            m_orderEquityRisk;
   double            m_orderEquityReward;
   ENUM_ORDER_TYPE   m_orderType;

   CChartObjectBmpLabel m_bgDialog;

   CChartObjectEdit  m_riskRatioEdit;
   CChartObjectEdit  m_riskValueEdit;
   CChartObjectEdit  m_orderLotsEdit;

   CChartObjectLabel m_symbolNameLabel;
   CChartObjectLabel m_tickSizeLabel;
   CChartObjectLabel m_maxEquityLossLabel;
   CChartObjectLabel m_equityLabel;
   CChartObjectLabel m_profitValueLabel;
   CChartObjectLabel m_askLabel;
   CChartObjectLabel m_bidLabel;
   CChartObjectLabel m_tpLabel;
   CChartObjectLabel m_slLabel;
   CChartObjectLabel m_maxAllowedLotsLabel;
   CChartObjectLabel m_maxTicksLossLabel;
   CChartObjectLabel m_orderEquityRiskLabel;
   CChartObjectLabel m_orderEquityRewardLabel;
   CChartObjectLabel m_orderTypeLabel;

   CChartObjectButton m_switchOrderTypeButton;
   CChartObjectButton m_placeOrderButton;
   CChartObjectButton m_quitEAButton;

public:

   void              CRRDialog(); // CRRDialog constructor
   void             ~CRRDialog(); // CRRDialog destructor

   bool              CreateCRRDialog(int topX,int leftY);
   int               DeleteCRRDialog();
   void              Refresh();
   void              SetRRRatio(double RRRatio);
   void              SetRiskPercent(double riskPercent);
   double            GetRiskPercent();
   double            GetRRRRatio();
   void              SetSL(double sl);
   void              SetTP(double tp);
   double            GetSL();
   double            GetTP();
   void              SetMaxAllowedLots(double lots);
   void              SetMaxTicksLoss(double ticks);
   void              SetOrderType(ENUM_ORDER_TYPE);
   void              SwitchOrderType();
   void              ResetButtons();
   ENUM_ORDER_TYPE   GetOrderType();
   void              SetOrderLots(double orderLots);
   double            GetOrderLots();
   void              SetOrderEquityRisk(double equityRisk);
   void              SetOrderEquityReward(double equityReward);
  };

Uma vez que métodos de variáveis get/set são simples, me concentrarei nos métodos CreateCRRDialog() e Refresh(). O método CreateCRRDialog() inicializa a imagem de fundo, rótulos, botões e campos de edição.

Para inicializar rótulos e campos de edição, eu uso: O método Create() com parâmetros coordenados para localizar o objeto no gráfico, métodos Font() e FontSize() para configurar a fonte e o método Description() para colocar texto no rótulo.

Para botões: Parâmetros adicionais ao método Create() especificam o tamanho do botão e o método BackColor() especifica a cor de fundo do botão.

bool CRRDialog::CreateCRRDialog(int topX,int leftY)
  {
   bool isCreated=false;

   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_baseX = topX;
   m_baseY = leftY;

   m_bgDialog.Create(0, m_dialogName, 0, topX, leftY);
   m_bgDialog.BmpFileOn(m_bgFileName);

   m_symbolNameLabel.Create(0, "symbolNameLabel", 0, m_baseX + 120, m_baseY + 40);
   m_symbolNameLabel.Font("Verdana");
   m_symbolNameLabel.FontSize(8);
   m_symbolNameLabel.Description(Symbol());

   m_tickSizeLabel.Create(0, "tickSizeLabel", 0, m_baseX + 120, m_baseY + 57);
   m_tickSizeLabel.Font("Verdana");
   m_tickSizeLabel.FontSize(8);
   m_tickSizeLabel.Description(DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()));

   m_riskRatioEdit.Create(0, "riskRatioEdit", 0, m_baseX + 120, m_baseY + 72, 35, 15);
   m_riskRatioEdit.Font("Verdana");
   m_riskRatioEdit.FontSize(8);
   m_riskRatioEdit.Description(DoubleToString(m_RRRatio, 2));

   m_riskValueEdit.Create(0, "riskValueEdit", 0, m_baseX + 120, m_baseY + 90, 35, 15);
   m_riskValueEdit.Font("Verdana");
   m_riskValueEdit.FontSize(8);
   m_riskValueEdit.Description(DoubleToString(m_riskPercent, 2));

   m_equityLabel.Create(0, "equityLabel", 0, m_baseX + 120, m_baseY + 107);
   m_equityLabel.Font("Verdana");
   m_equityLabel.FontSize(8);
   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));

   m_maxEquityLossLabel.Create(0, "maxEquityLossLabel", 0, m_baseX + 120, m_baseY + 122);
   m_maxEquityLossLabel.Font("Verdana");
   m_maxEquityLossLabel.FontSize(8);
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/100.0,2));

   m_askLabel.Create(0, "askLabel", 0, m_baseX + 120, m_baseY + 145);
   m_askLabel.Font("Verdana");
   m_askLabel.FontSize(8);
   m_askLabel.Description("");

   m_bidLabel.Create(0, "bidLabel", 0, m_baseX + 120, m_baseY + 160);
   m_bidLabel.Font("Verdana");
   m_bidLabel.FontSize(8);
   m_bidLabel.Description("");

   m_slLabel.Create(0, "slLabel", 0, m_baseX + 120, m_baseY + 176);
   m_slLabel.Font("Verdana");
   m_slLabel.FontSize(8);
   m_slLabel.Description("");

   m_tpLabel.Create(0, "tpLabel", 0, m_baseX + 120, m_baseY + 191);
   m_tpLabel.Font("Verdana");
   m_tpLabel.FontSize(8);
   m_tpLabel.Description("");

   m_maxAllowedLotsLabel.Create(0, "maxAllowedLotsLabel", 0, m_baseX + 120, m_baseY + 208);
   m_maxAllowedLotsLabel.Font("Verdana");
   m_maxAllowedLotsLabel.FontSize(8);
   m_maxAllowedLotsLabel.Description("");

   m_maxTicksLossLabel.Create(0, "maxTicksLossLabel", 0, m_baseX + 120, m_baseY + 223);
   m_maxTicksLossLabel.Font("Verdana");
   m_maxTicksLossLabel.FontSize(8);
   m_maxTicksLossLabel.Description("");

   m_orderLotsEdit.Create(0, "orderLotsEdit", 0, m_baseX + 120, m_baseY + 238, 35, 15);
   m_orderLotsEdit.Font("Verdana");
   m_orderLotsEdit.FontSize(8);
   m_orderLotsEdit.Description("");

   m_orderEquityRiskLabel.Create(0, "orderEquityRiskLabel", 0, m_baseX + 120, m_baseY + 255);
   m_orderEquityRiskLabel.Font("Verdana");
   m_orderEquityRiskLabel.FontSize(8);
   m_orderEquityRiskLabel.Description("");

   m_orderEquityRewardLabel.Create(0, "orderEquityRewardLabel", 0, m_baseX + 120, m_baseY + 270);
   m_orderEquityRewardLabel.Font("Verdana");
   m_orderEquityRewardLabel.FontSize(8);
   m_orderEquityRewardLabel.Description("");

   m_switchOrderTypeButton.Create(0, "switchOrderTypeButton", 0, m_baseX + 20, m_baseY + 314, 160, 20);
   m_switchOrderTypeButton.Font("Verdana");
   m_switchOrderTypeButton.FontSize(8);
   m_switchOrderTypeButton.BackColor(LightBlue);

   m_placeOrderButton.Create(0, "placeOrderButton", 0, m_baseX + 20, m_baseY + 334, 160, 20);
   m_placeOrderButton.Font("Verdana");
   m_placeOrderButton.FontSize(8);
   m_placeOrderButton.BackColor(LightBlue);
   m_placeOrderButton.Description("Place Market Order");

   m_quitEAButton.Create(0, "quitEAButton", 0, m_baseX + 20, m_baseY + 354, 160, 20);
   m_quitEAButton.Font("Verdana");
   m_quitEAButton.FontSize(8);
   m_quitEAButton.BackColor(LightBlue);
   m_quitEAButton.Description("Quit");

   return isCreated;
  }

O método Refresh() atualiza todos os rótulos e descrição de botões com as variáveis CRRDialog e níveis atuais de compra e venda, equidade de contas e valores de risco de equidade:

void CRRDialog::Refresh()
  {
   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*
                                         StringToDouble(m_riskValueEdit.Description())/100.0,2));
   m_askLabel.Description(DoubleToString(current_tick.ask, Digits()));
   m_bidLabel.Description(DoubleToString(current_tick.bid, Digits()));
   m_slLabel.Description(DoubleToString(m_SL, Digits()));
   m_tpLabel.Description(DoubleToString(m_TP, Digits()));
   m_maxAllowedLotsLabel.Description(DoubleToString(m_maxAllowedLots,2));
   m_maxTicksLossLabel.Description(DoubleToString(m_maxTicksLoss,0));
   m_orderEquityRiskLabel.Description(DoubleToString(m_orderEquityRisk,2));
   m_orderEquityRewardLabel.Description(DoubleToString(m_orderEquityReward,2));

   if(m_orderType==ORDER_TYPE_BUY) m_switchOrderTypeButton.Description("Order Type: BUY");
   else if(m_orderType==ORDER_TYPE_SELL) m_switchOrderTypeButton.Description("Order Type: SELL");
  }


4. Eventos de gráfico

Uma vez que o EA foi projetado para ser interativo, ele deve manipular eventos de gráfico.

Os eventos que são manipulados incluem:

  • arrastar o apontador de S/L (Objetos SL_arrow da classe CChartObjectBitmap) no gráfico - isto permitirá coletar o nível de S/L e calcular o nível de T/P com base na proporção R/R.
  • alternar o botão tipo de ordem (compra/venda)
  • pressionar o botão "colocar ordem de mercado"
  • editar os campos de risco, R/R e ordem
  • fechar o EA após pressionar o botão "Exit" (sair)

Eventos que são manipulados são CHARTEVENT_OBJECT_CLICK para seleção de apontador e botões, CHARTEVENT_OBJECT_DRAG para arrastar o apontador S/L, e CHARTEVENT_OBJECT_ENDEDIT após os campos de edição serem atualizados pelo operador.

A primeira implementação da função OnChartEvent() levou algumas páginas de código, mas decidi dividir em diversos manipuladores de evento, convertendo a função OnChartEvent() em uma forma legível por humanos:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Check the event by pressing a mouse button
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      string clickedChartObject=sparam;

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

      if(clickedChartObject==switchOrderTypeButtonID)
        {
         EA_switchOrderType();
        };

      if(clickedChartObject==placeOrderButtonID)
        {
         EA_placeOrder();
        }

      if(clickedChartObject==quitEAButtonID) ExpertRemove();

      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      // BUY 
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         EA_dragBuyHandle();
        };

      // SELL
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         EA_dragSellHandle();
        };
      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      if((sparam==riskRatioEditID || sparam==riskValueEditID || sparam==orderLotsEditID) && orderPlaced==false)
        {
         EA_editParamsUpdate();
        }
     }
  }

A implementação de manipuladores de eventos será descrita em mais detalhes nas próximas seções. Vale observar o truque que usei para selecionar o objeto SL_arrow. Normalmente, para selecionar um objeto no gráfico, ele deve ser clicado duas vezes. Mas ele pode ser selecionado clicando uma vez e invocando o método Selected() do objeto CChartObject ou seu descendente na função OnChartEvent() dentro do manipulador de evento CHARTEVENT_OBJECT_CLICK:

      if(clickedChartObject==slButtonID)
             SL_arrow.Selected(!SL_arrow.Selected());

O objeto é marcado ou desmarcado, dependendo de seu estado anterior, após um clique.


5. Classe de gerenciamento de dinheiro estendida com base em CMoneyFixedRisk

Antes de ser capaz de descrever manipuladores ChartEvent, preciso passar pela classe de gerenciamento de dinheiro.

Para gerenciamento de dinheiro eu reutilizei a classe CMoneyFixedRisk fornecida pela MetaQuotes e implementei a classe CMoneyFixedRiskExt.

Os métodos da classe CMoneyFixedRisk retornam quantidades de lote de pedido permitidas por um determinado preço, nível de stop loss e risco de equidade entre tamanho de lote mínimo e máximo permitido por corretor. Alterei os métodos CheckOpenLong() e CheckOpenShort() para retornar tamanho de lote 0.0 se os requisitos de risco não forem atendidos e a estendi com quatro métodos: GetMaxSLPossible(), CalcMaxTicksLoss(), CalcOrderEquityRisk() e CalcOrderEquityReward():

class CMoneyFixedRiskExt : public CExpertMoney
  {
public:
   //---
   virtual double    CheckOpenLong(double price,double sl);
   virtual double    CheckOpenShort(double price,double sl);
   
   double GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType);
   double CalcMaxTicksLoss();
   double CalcOrderEquityRisk(double price, double sl, double lots);
   double CalcOrderEquityReward(double price, double sl, double lots, double rrratio);
  };

O método GetMaxSLPossible() calcula o valor de preço de stop loss máximo para determinado risco de equidade e tamanho de negociação mínimo permitido.

Por exemplo, se o balanço da conta é 10.000 da moeda base da conta e o risco é de 2%, podemos colocar um máximo de 200 de moeda da conta em risco. Se o tamanho mínimo de lote de negociação é 0,1 lote, este método retorna o nível de preço para a ordem ORDER_TYPE_BUY ou ORDER_TYPE_SELL que atenderá o valor de risco de equidade para a posição de 0,1 lote. Isto ajuda a estimar qual o nível máximo de stop loss que podemos dispor para a negociação com tamanho de lote mínimo. Este é um nível de preço que não podemos cruzar para o nível de risco de equidade fornecido.

double CMoneyFixedRiskExt::GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType)
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   double orderTypeMultiplier;
   
   if(m_symbol==NULL) return(0.0);
   
   switch (orderType)
   {
   case ORDER_TYPE_SELL: orderTypeMultiplier = -1.0; break;
   case ORDER_TYPE_BUY: orderTypeMultiplier = 1.0; break;
   default: orderTypeMultiplier = 0.0;
   }
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
 
   return (price - maxTicksLoss*m_symbol.TickSize()*orderTypeMultiplier);
}

O método CalcMaxTickLoss() retorna o número máximo de ticks que podemos perder por determinado risco e tamanho de lote mínimo permitido.

No início, a perda máxima de equidade é calculada como uma porcentagem do balanço atual, então, a perda do valor do tick para alteração de um tick para o tamanho de lote mínimo permitido para determinado símbolo é calculada. Então, a perda máxima de equidade é dividida pela perda do valor do tick e o resultado é arredondando para o valor inteiro com a função MathFloor():

double CMoneyFixedRiskExt::CalcMaxTicksLoss()
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   
   if(m_symbol==NULL) return(0.0);
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
   
   return (maxTicksLoss);
}

O método CalcOrderEquityRisk() retorna o risco de equidade para determinado preço, nível de stop loss e quantidade de lotes. Ele é calculado multiplicando o valor de perda de tick pelo número de lotes e preço e então multiplicando pela diferença entre o preço atual e nível de stop loss:

double CMoneyFixedRiskExt::CalcOrderEquityRisk(double price,double sl, double lots)
{
   double equityRisk;
   
   equityRisk = lots*m_symbol.TickValueLoss()*(MathAbs(price-sl)/m_symbol.TickSize()); 
   
   if (dbg) Print("calcEquityRisk: lots = " + DoubleToString(lots) +
                 " TickValueLoss = " + DoubleToString(m_symbol.TickValueLoss()) +
                 " risk = " + DoubleToString(equityRisk));
   
   return equityRisk;
}

O método CalcOrderEquityReward() é análogo ao método CalcOrderEquityRisk(), mas ele usa TickValueProfit() em vez do método TickValueLoss() e o resultado é multiplicado pela determinada proporção risco para recompensa:

double CMoneyFixedRiskExt::CalcOrderEquityReward(double price,double sl, double lots, double rrratio)
{
   double equityReward; 
   equityReward = lots*m_symbol.TickValueProfit()*(MathAbs(price-sl)/m_symbol.TickSize())*rrratio; 
   
   if (dbg) Print("calcEquityReward: lots = " + DoubleToString(lots) + 
                   " TickValueProfit = " + DoubleToString(m_symbol.TickValueProfit()) +
                 " reward = " + DoubleToString(equityReward));
   return equityReward;
}

Estes métodos são suficientes para calcular os níveis máximos de stop loss e retorno de equidade risco e recompensa em tempo real. O método CalcMaxTickLoss() é usado para corrigir o desenho de retângulo de risco - se o operador deseja realizar uma negociação que cruza o limite de número de ticks que ele pode perder, o retângulo é desenhado apenas até o número máximo de ticks que ele pode perder.

Ver isso diretamente no gráfico torna a vida mais simples. Você pode vê-lo na demonstração no final do artigo.


6. Implementação de manipuladores de evento de gráfico

O manipulador EA_switchOrderType() é disparado após receber o evento CHARTEVENT_OBJECT_CLICK no objeto m_switchOrderTypeButton. Ele alterna o tipo de ordem entre ORDER_TYPE_BUY e ORDER_TYPE_SELL, reinicializa o estado dos botões, variáveis da caixa de diálogo e apaga os objetos de retângulo risco e recompensa no gráfico:

void EA_switchOrderType()
  {
   symbolInfo.RefreshRates();

   visualRRDialog.SwitchOrderType();
   visualRRDialog.ResetButtons();
   visualRRDialog.SetSL(0.0);
   visualRRDialog.SetTP(0.0);
   visualRRDialog.SetMaxAllowedLots(0.0);
   visualRRDialog.SetOrderLots(0.0);
   visualRRDialog.SetMaxTicksLoss(0);
   visualRRDialog.SetOrderEquityRisk(0.0);
   visualRRDialog.SetOrderEquityReward(0.0);

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
   SL_arrow.SetInteger(OBJPROP_TIME,0,TimeCurrent());

   rectReward.Delete();
   rectRisk.Delete();

   visualRRDialog.Refresh();

  }

O manipulador EA_dragBuyHandle() é disparado após o objeto SL_arrow ter sido arrastado e solto no gráfico. Primeiro ele lê os parâmetros de tempo e preço do ponto de soltura do objeto SL_arrow do gráfico e define o nível de preço como um stop loss hipotético para nossa negociação.

Então ele calcula quantos lotes podemos abrir para determinado risco na equidade. Se o valor de stop loss não puder garantir o objetivo de risco para o lote de negociação mais baixo possível naquele símbolo, ele é automaticamente movido para o nível SL máximo possível. Isto auxilia a avaliar quanto espaço temos para stop loss para determinado risco.

Após calcular risco e recompensa, objetos retângulo são atualizados no gráfico.

void EA_dragBuyHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

// BUY
   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);

   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()<SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), 
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

EA_dragSellHandle() é disparado para a configuração de ordem de venda.

Os cálculos são baseados no preço symbolInfo.Bid(), e os retângulos são desenhados de acordo, ou seja, uma zona verde indica que o lucro está abaixo do nível de preço atual.

void EA_dragSellHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double maxSLAllowed=MM.GetMaxSLPossible(symbolInfo.Bid(),ORDER_TYPE_SELL);

   if(SL_price>maxSLAllowed)
     {
      SL_price=maxSLAllowed;
      SL_arrow.SetDouble(OBJPROP_PRICE,0,maxSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()>SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Bid(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(),
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

EA_placeOrder() é disparado após o objeto m_placeOrderButton ter sido pressionado. Ele realiza a ordem de mercado compra ou venda para os níveis SL e TP calculados e determinado tamanho de lote.

Por favor, perceba quão fácil é realizar ordens de mercado usando a classe CExpertTrade.

bool EA_placeOrder()
  {
   symbolInfo.RefreshRates();
   visualRRDialog.ResetButtons();

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
      orderPlaced=trade.Buy(visualRRDialog.GetOrderLots(),symbolInfo.Ask(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
      orderPlaced=trade.Sell(visualRRDialog.GetOrderLots(),symbolInfo.Bid(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));

   return orderPlaced;
  }

O manipulador EA_editParamsUpdate() é disparado quando a tecla Enter é pressionada após editar um dos campos de edição: riskRatioEdit, riskValueEdit e orderLotsEdit.

Quando isso acontece, o tamanho de lote permitido, nível TP, perda máxima de tick, equidade de risco e recompensa, precisam ser recalculados:

void EA_editParamsUpdate()
  {
   MM.Percent(visualRRDialog.GetRiskPercent());

   SL_arrow.GetDouble(OBJPROP_PRICE, 0, SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME, 0, startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);
   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                     SL_price, visualRRDialog.GetOrderLots()));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                       visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();
   rectUpdate(visualRRDialog.GetOrderType());

   ChartRedraw();
  }

EA_onTick() é invocado cada vez que um novo tick chega. Os cálculos são realizados apenas se a ordem não foi realizada ainda e o nível de stop loss já foi escolhido, arrastando o ponteiro SL_arrow.

Após a ordem ter sido realizada, o nível TP e risco e recompensa, bem como o redesenho do risco e recompensa, não são necessários.

void EA_onTick()
  {
   if(SL_price!=0.0 && orderPlaced==false)
     {
      double lotSize=0.0;
      SL_price=visualRRDialog.GetSL();
      symbolInfo.RefreshRates();

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
         lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
         lotSize=MM.CheckOpenShort(symbolInfo.Ask(),SL_price);

      visualRRDialog.SetMaxAllowedLots(lotSize);
      if(visualRRDialog.GetOrderLots()>lotSize) visualRRDialog.SetOrderLots(lotSize);

      visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                           SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(
                                           symbolInfo.Bid(), SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      visualRRDialog.Refresh();
      rectUpdate(visualRRDialog.GetOrderType());
     }

   ChartRedraw(0);
  }

A função rectUpdate() é responsável por redesenhar a cor dos retângulos de risco e recompensa. Os pontos de controle são tempo de início do objeto SL_arrow, valor de preço de compra e venda, dependendo do tipo de ordem, e níveis SL e TP. Um retângulo rosa-claro mostra a faixa de preços entre o preço atual e o nível SL e um retângulo verde-claro mostra a faixa de preços entre o preço atual e o nível TP.

Ambos os retângulos são uma grande ferramenta para observar o impacto da proporção risco para recompensa nos níveis de preço SL e TP e ajudam a ajustar o risco antes de entrar na negociação.

void rectUpdate(ENUM_ORDER_TYPE orderType)
  {
   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   if(orderType==ORDER_TYPE_BUY)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Ask(),currentTime,symbolInfo.Ask()+
                       (symbolInfo.Ask()-visualRRDialog.GetSL())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Ask());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
   else if(orderType==ORDER_TYPE_SELL)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Bid(),currentTime,symbolInfo.Bid()-
                        (visualRRDialog.GetSL()-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Bid());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
  }

7. Demonstração

Por favor, observe a demonstração abaixo do Expert Advisor em ação. Estou realizando a ordem de venda após um grande recuo logo após o mercado ter sido aberto na segunda, 11/01/2010.

Para melhor experiência de visualização, configure o vídeo para tela cheia e a qualidade para 480p. Os comentários estão inclusos no vídeo:

Conclusão

No seguinte artigo eu apresentei uma maneira de criar um Expert Advisor interativo para negociação manual, com base em uma proporção de risco para recompensa e risco predefinidos.

Mostrei como usar classes padrão para exibir conteúdo no gráfico e como manipular eventos de gráfico para inserir novos dados e manipular objetos de arrastar e soltar. Espero que as ideias que apresentei sirvam como base para criar outras ferramentas visuais configuráveis no MQL5.

Todos os arquivos de código-fonte e bitmaps estão anexados ao artigo.

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

Arquivos anexados |
visualrrea.mq5 (13.84 KB)
crrdialog.mqh (13.95 KB)
visualrrids.mqh (0.8 KB)
images.zip (159.05 KB)
Desenhando e implementando novos widgets GUI com base no objeto CChartObject Desenhando e implementando novos widgets GUI com base no objeto CChartObject
Depois que escrevi um artigo anterior sobre um Expert Advisor semiautomático com interface GUI, descobri que seria desejável aprimorar a interface com algumas novas funcionalidades para Expert Advisors e indicadores mais complexos. Após familiarizar-me com as classes da Biblioteca Padrão do MQL5, implementei novos widgets. Este artigo descreve um processo de planejamento e implementação de novos widgets GUI MQL5 que podem ser usados em indicadores e Expert Advisors. Os widgets apresentados no artigo são CChartObjectSpinner, CChartObjectProgressBar e CChartObjectEditTable.
Indicadores de William Blau e sistemas de comércio no MQL5. Parte 1: Indicadores Indicadores de William Blau e sistemas de comércio no MQL5. Parte 1: Indicadores
O artigo apresenta os indicadores descritos no livro de William Blau "Momentum, Direction, and Divergence". A abordagem de William Blau nos permite prontamente e precisamente aproximar as flutuações da curva de preço, para determinar a tendência de movimentos de preço e os pontos de virada e eliminar o ruído de preço. Entretanto, também somos capazes de detectar estados de excesso de compra ou venda do mercado e sinais indicando o fim da tendência e reverso do movimento de preço.
Cálculos paralelos no MetaTrader 5 Cálculos paralelos no MetaTrader 5
O tempo tem sido de grande valor por toda a história da humanidade, e tentamos não desperdiçá-lo sem necessidade. Este artigo dirá a você como acelerar o trabalho do seu Expert Advisor se seu computador tiver um processador com vários núcleos. Além disso, a implementação do método proposto não requer conhecimento de nenhuma outra linguagem além de MQL5.
Construindo um Analisador de Espectro Construindo um Analisador de Espectro
Este artigo é destinado a familiarizar seus leitores com uma possível variável de uso de objetos gráficos da linguagem MQL5. Ele analisa um indicador que implementa um painel de gerenciamento de um simples analisador de espectro usando objetos gráficos. O artigo é destinado para leitores familiarizados com o básico do MQL5.