English Русский 中文 Español Deutsch 日本語
Os Expert Advisors prontos a partir do Assistente MQL5 funcionam no MetaTrader 4

Os Expert Advisors prontos a partir do Assistente MQL5 funcionam no MetaTrader 4

MetaTrader 5Integração | 24 abril 2017, 09:24
1 627 0
Stanislav Korotky
Stanislav Korotky
Os terminais de cliente MetaTrader 4 e MetaTrader 5 proporcionam aos seus usuários a possibilidade de criar facilmente programas-protótipo em linguagem MQL usando o Assistente embutido (MQL Wizard). Os Assistentes de ambas as versões são muito semelhantes, mas têm uma diferença importante. No Assistente do MetaTrader 5 existe um ponto de geração de EAs prontos, mas no MetaTrader 4, não. Na verdade esses EAs funcionam com base nas classes da biblioteca padrão MQL, isto é, o conjunto de arquivos de cabeçalho fornecidos com o terminal. No MetaTrader 4 também existe essa biblioteca, no entanto, ela não tem classes de negociação a partir da MQL5. Em particular, nela existem classes responsáveis pela elaboração e envio de ordens de negociação, cálculo de sinais com base nas leituras dos indicadores ou estruturas de preços, trailing, gestão do dinheiro, ou seja, a base necessária para construir EAs gerados automaticamente.

Algo interessante aconteceu historicamente como resultado do desenvolvimento gradual da MQL5. A nova linguagem apareceu originalmente no MetaTrader 5, uma vez que foi para este terminal que foi desenvolvida a biblioteca padrão de classes. Pouco depois, MQL5 foi integrada no MetaTrader 4, no entanto, como as funções de negociação do API de ambas as versões dos terminas eram bem diferentes, a biblioteca padrão foi transferida para um produto anterior sem classes de negociação. Como resultado, o Assistente do MetaTrader 4 não tem a opção de geração de EAs prontos.

Embora seja assim, o MetaTrader 4 ainda é popular. O recurso que permite gerar EAs prontos seria muito útil para ele. Como, no que diz respeito ao MetaTrader 4, jã não são adicionadas novas funções e apenas são realizadas correções de bugs, dificilmente veremos novas melhorias em seu Assistente. Porém, não somos impedidos de utilizar o Assistente do MetaTrader 5 e, depois, transferir o código obtido para o MetaTrader 4. Para que este código seja ganhador, é necessária uma bagatela, isto é, o conjunto de classes de negociação da biblioteca padrão adaptadas para a MQL anterior do API MetaTrader 4. Em outras palavras, é preciso copiar - a partir da biblioteca padrão do MetaTrader 5 - as classes que faltam no MetaTrader 4, e implementar para elas uma emulação do ambiente de negociação da quinta versão.

Para entender o seguinte material, é necessário conhecer os princípios das operações de negociação no MetaTrader 5, isto é, o trabalho com ordens, transações e posições. Se você não estiver familiarizado com esta versão do terminal, recomenda-se bastante ler o artigo "Ordens, posições e negócios no MetaTrader 5".

Planejamento

É melhor levar a cabo quaisquer trabalhos seguindo o plano já elaborado. Ao desenvolver programas, é aplicada uma abordagem com base no modelo em cascata. Ela é adequada para nosso caso, uma vez que o desenvolvimento não só é aplicado como tal, mas também descrito como um artigo. No entanto, na prática, é mais eficiente migrar o código da MQL5 para a MQL4 (ou vice-versa) usando uma abordagem flexível, como a programação extrema. Seu lema é "menos de planos, mais negócios". Em outras palavras, pegar no código-fonte, tentar pre-compilá-lo e, em seguida, corrigir os erros que surjam. O plano que proponho neste artigo não nasceu imediatamente, mas sim gradualmente, justamente com base nas "dicas" de um compilador descontente.‌

Ao comparar as bibliotecas dos dois terminais é fácil reparar em que na versão 4 faltam as pastas Trade, Expert e Models. Assim, o trabalho principal será migrar todas as classes disponíveis nestas pastas para a quarta versão. Além disso, precisaremos corrigir algo na pasta Indicators. Esse algo existe na biblioteca da versão 4, mas os princípios de trabalho com os indicadores em ambos os terminais diferem. Porém, em qualquer caso, deve-se tentar fazer o menor número de correções possíveis dos arquivos da biblioteca, pois ela é atualizada periodicamente, e, então, é necessário sincronizar nossas correções em relação às oficiais.

Até certo ponto, todos os arquivos referenciam os de negociação da MQL API da quinta versão. É por esta razão que temos de desenvolver um conjunto mais o menos completo de definições e funções que, após salvar a mesma interface de programação, converta todos os acessos às MQL API herdadas da quarta versão. Examinemos em detalhe que deve ser incluído no ambiente de negociação emulado. Comecemos com os tipos, uma vez que são os blocos nos quais será fundamentada a tarefa, isto é, o algoritmo e o programa.‌

Os tipos enumeração mais simples são utilizados na maioria das funções, diretamente ou indiretamente, através das estruturas. Portanto, o procedimento de adaptação é na seguinte ordem: enumerações, estruturas, constantes, funções.

Enumerações

Parte das enumerações necessárias já foram transferidas para o MetaTrader 4. Por exemplo, as propriedades das ordens: ENUM_ORDER_TYPE, ENUM_ORDER_PROPERTY_INTEGER, ENUM_ORDER_PROPERTY_DOUBLE, ENUM_ORDER_PROPERTY_STRING. Parece conveniente, mas nem todas estas enumerações são definidas da mesma maneira como no MetaTrader 5, e isto cria dificuldades.

Por exemplo, a ENUM_ORDER_TYPE no MetaTrader 5 contém mais tipos de ordens do que no MetaTrader 4. Se deixarmos a ENUM_ORDER_TYPE tal qual, obteremos um erro de compilação, pois o código copiado referencia elementos em falta. É impossível substituir ou acabar de definir a enumeração. Portanto, a opção mais simples é a definição de macros para o pré-processador, isto é:

// ENUM_ORDER_TYPE extension
#define ORDER_TYPE_BUY_STOP_LIMIT ((ENUM_ORDER_TYPE)6)
#define ORDER_TYPE_SELL_STOP_LIMIT ((ENUM_ORDER_TYPE)7)
#define ORDER_TYPE_CLOSE_BY ((ENUM_ORDER_TYPE)8)

Podemos identificar com coragem - por analogia com a quinta versão - outras enumerações que não existem no MetaTrader 4, por exemplo:

enum ENUM_ORDER_TYPE_FILLING
{
  ORDER_FILLING_FOK,
  ORDER_FILLING_IOC,
  ORDER_FILLING_RETURN
};

Assim, devemos definir (ou adicionar constantes) as enumerações a seguir. À primeira vista, parecem muitas, mas o trabalho é trivial, isto é, basta copiá-las a partir da documentação (links para as seções respetivas são dadas abaixo; o asterisco indica as enumerações existentes que precisam de "moagem").

  • Ordem (Order)
    • ENUM_ORDER_TYPE_TIME
    • ENUM_ORDER_STATE
    • ENUM_ORDER_TYPE_FILLING
    • ENUM_ORDER_TYPE (*)
    • ENUM_ORDER_PROPERTY_INTEGER (*)
    • ENUM_ORDER_PROPERTY_STRING (*)
  • Posição (Position)
    • ENUM_POSITION_TYPE
    • ENUM_POSITION_PROPERTY_INTEGER
    • ENUM_POSITION_PROPERTY_DOUBLE
    • ENUM_POSITION_PROPERTY_STRING
  • Transação (Deal)
    • ENUM_DEAL_ENTRY
    • ENUM_DEAL_TYPE
    • ENUM_DEAL_PROPERTY_INTEGER
    • ENUM_DEAL_PROPERTY_DOUBLE
    • ENUM_DEAL_PROPERTY_STRING
  • Tipos de operação de negociação
    • ENUM_TRADE_REQUEST_ACTIONS

A MetaTrader 4 já contém as definições das enumerações que descrevem os símbolos, tais como ENUM_SYMBOL_INFO_INTEGER, ENUM_SYMBOL_INFO_DOUBLE, ENUM_SYMBOL_INFO_STRING. Alguns elementos só são reservados nelas, mas não funcionam (o que se reflete na documentação). E nós temos de aceitar isto como tal, isto é, as limitações da plataforma MetaTrader 4 em comparação com a MetaTrader 5. O único importante para nós é que os dados da enumeração não precisam ser definidos no projeto.

Estruturas

Além das enumerações, nas funções de negociação do MetaTrader 5, são utilizadas estruturas. Suas definições também podem ser tomadas na documentação (links para as seções respetivas são dadas abaixo).

Definição de macros

Além dos tipos acima mencionados, os códigos-fonte da quinta versão utilizam muitas constantes que se definem facilmente usando a diretiva #define do pré-processador.

Funções de negociação

O último e mais importante ponto de nosso plano é diretamente as funções de negociação. Poderemos prosseguir com sua implementação só após serem definidos todos os tipos e constantes listados acima.

A lista de funções de negociação é bastante impressionante. Essas funções podem ser divididas em 4 grupos:

  • Ordens
  • Posições
  • Histórico de ordens
  • Histórico de transações

Finalmente, precisaremos das seguintes substituições:

#define MQL5InfoInteger MQLInfoInteger
#define MQL5InfoString  MQLInfoString

Na verdade, trata-se das mesmas funções essenciais do terminal, mas seus nomes são ligeiramente diferentes na MQL5 e na MQL4.

Antes de prosseguir diretamente com a implementação, temos que pensar como exibir o modelo de negociação do MetaTrader 5 para o modelo de negociação do MetaTrader 4.

Exibição

Tentemos traçar um paralelo entre as entidades do MetaTrader 5 e do MetaTrader 4. É mais fácil começar com a quarta versão. Ela contém o conceito universal de "ordem", que é usado quase para tudo, isto é, para ordens de mercado, ordens pendentes, histórico de operações de negociação. Em todos estes casos, a ordem se encontra em estados diferentes. Na quinta versão, as ordens de mercado são as posições, as ordens pendentes são apenas ordens, enquanto, no histórico de operações, as entradas são registradas usando transações.‌

No caso mais simples, o MetaTrader 5 funciona assim. Para a formação da posição, uma ordem para entrar no mercado é enviada para o servidor. Para fechar a posição, entra outra ordem para sair do mercado. A execução de cada uma das ordens ocorre de acordo com a transação que é, por sua vez, registrada no histórico de negociação. Assim, uma ordem de mercado da quarta versão deve ser exibida no ambiente de negociação da quinta como se segue:

  • ordem de entrada
  • transação de entrada
  • posição
  • ordem de saída
  • transação de saída

Lembremos que o MetaTrader 5 foi originalmente uma plataforma estritamente de compensação, quer dizer, simultaneamente segundo um símbolo podia existir apenas uma posição. Todas as ordens segundo um único símbolo aumentavam, diminuíam ou excluíam o volume total do símbolo, bem como mudavam para ele os níveis gerais dos Stop-Loss e Take-Profit. Esse modo não existe no MetaTrader 4, por tanto, nós teríamos de fazer um grande esforço para concretizar esse projeto, se no MetaTrader 5 não surgisse o suporte de cobertura. Trata-se do mesmo modo que professa o MetaTrader 4, isto é: a execução de cada ordem forma uma "posição" separada (nos terminais MetaTrader 5), sendo assim, mesmo segundo um mesmo símbolo podem existir várias ordens abertas, inclusive ordens opostas.

Atenção! Se você quiser comparar o trabalho dos EAs gerados no MetaTrader 5 e no MetaTrader 4, não se esqueça de que no MetaTrader 5, deve ser ativado o tipo de conta com cobertura. Para levar a cabo a comparação, é desejável usar o servidor da mesma corretora.

Implementação

Emulação do ambiente de negociação do MetaTrader 5

Para simplificar, colocamos todo o ambiente emulado - incluindo os tipos, constantes e funções - num só arquivo de cabeçalho MT5Bridge.mqh. Provavelmente, um bom estilo de programação teria exigido sua alocação em arquivos separados. Essa estruturação é especialmente importante para grandes projetos e projetos trabalhados por várias pessoas. No entanto, do ponto de vista da distribuição e instalação, um único arquivo é mais conveniente.

Assim, de acordo com o plano, definimos todas as enumerações, constantes e estruturas. Esta é uma simples rotina de cópia, sem quaisquer dificuldades. Quase não vale a pena explicar mais detalhadamente os comentários que já foram dados durante planejamento. Em seguida, daremos uma vista de olhos novamente para a documentação sobre Funções de negociação e prosseguiremos com a parte mais intelectual, isto é, escrever o código de todas as funções.‌

Começaremos com as operações atuais que incluem o processamento de ordens pendentes e de mercado, bem como posições.

Para fazer isto, é necessária a função OrderSend da quinta versão.

bool OrderSend(MqlTradeRequest &request, MqlTradeResult &result)
{

Nela, dependendo do tipo de pedido, é necessário usar um dos tipos de ordens do MetaTrader 4.

  int cmd;   
  result.retcode = 0;
  switch(request.type)
  {
    case ORDER_TYPE_BUY:
      cmd = OP_BUY;
      break;
    case ORDER_TYPE_SELL:
      cmd = OP_SELL;
      break;
    case ORDER_TYPE_BUY_LIMIT:
      cmd = OP_BUYLIMIT;
      break;
    case ORDER_TYPE_SELL_LIMIT:
      cmd = OP_SELLLIMIT;
      break;
    case ORDER_TYPE_BUY_STOP:
      cmd = OP_BUYSTOP;
      break;
    case ORDER_TYPE_SELL_STOP:
      cmd = OP_SELLSTOP;
      break;
    default:
      Print("Unsupported request type:", request.type);
      return false;
  }

O código de operação transmitido para o campo action permite processar de diferentes formas a instalação, remoção e modificação de ordens. Por exemplo, a abertura de uma ordem de mercado ou criação de uma ordem pendente pode ser aplicado como segue.

  ResetLastError();
  if(request.action == TRADE_ACTION_DEAL || request.action == TRADE_ACTION_PENDING)
  {
    if(request.price == 0)
    {
      if(cmd == OP_BUY)
      {
        request.price = MarketInfo(request.symbol, MODE_ASK);
      }
      else
      if(cmd == OP_SELL)
      {
        request.price = MarketInfo(request.symbol, MODE_BID);
      }
    }
    if(request.position > 0)
    {
      if(!OrderClose((int)request.position, request.volume, request.price, (int)request.deviation))
      {
        result.retcode = GetLastError();
      }
      else
      {
        result.retcode = TRADE_RETCODE_DONE;
        result.deal = request.position | 0x8000000000000000;
        result.order = request.position | 0x8000000000000000;
        result.volume = request.volume;
        result.price = request.price;
      }
    }
    else
    {
      int ticket = OrderSend(request.symbol, cmd, request.volume, request.price, (int)request.deviation, request.sl, request.tp, request.comment, (int)request.magic, request.expiration);
      if(ticket == -1)
      {
        result.retcode = GetLastError();
      }
      else
      {
        result.retcode = TRADE_RETCODE_DONE;
        result.deal = ticket;
        result.order = ticket;
        result.request_id = ticket;
        if(OrderSelect(ticket, SELECT_BY_TICKET))
        {
          result.volume = OrderLots();
          result.price = OrderOpenPrice() > 0 ? OrderOpenPrice() : request.price;
          result.comment = OrderComment();
          result.ask = MarketInfo(OrderSymbol(), MODE_ASK);
          result.bid = MarketInfo(OrderSymbol(), MODE_BID);
        }
        else
        {
          result.volume = request.volume;
          result.price = request.price;
          result.comment = "";
        }
      }
    }
  }

O trabalho principal é feito pela função OrderSend - usual para MetaTrader 4 - com um conjunto de parâmetros. Após ser chamada, os resultados de trabalho são registrados apropriadamente na estrutura de saída.

Comentamos especialmente que, no MetaTrader 5, o fechamento da ordem de mercado existente ocorre através da abertura de outra ordem oposta, enquanto, o identificador da posição a ser fechada é transferido para o campo position. Neste caso, quer dizer, quando o campo position não está vazio, o código acima tenta fechar a ordem usando a função OrderClose. Ao fazer isto, é utilizado o bilhete da mesma ordem como identificador. Isto é lógico, porque, na quarta versão, cada ordem cria sua própria posição. O transação recebe o mesmo bilhete.

No que diz respeito à ordem virtual para fechar a posição (que, neste caso, na verdade, não existe), como seu bilhete - de maneira artificial - é usado o número original complementado usando 1 bit maior. Isto será aplicado no futuro para a enumeração de ordens e transações.

Agora consideraremos como se pode implementar a alteração de níveis na posição aberta.

  else if(request.action == TRADE_ACTION_SLTP) // change opened position
  {
    if(OrderSelect((int)request.position, SELECT_BY_TICKET))
    {
      if(!OrderModify((int)request.position, OrderOpenPrice(), request.sl, request.tp, 0))
      {
        result.retcode = GetLastError();
      }
      else
      {
        result.retcode = TRADE_RETCODE_DONE;
        result.deal = OrderTicket();
        result.order = OrderTicket();
        result.request_id = OrderTicket();
        result.volume = OrderLots();
        result.comment = OrderComment();
      }
    }
    else
    {
      result.retcode = TRADE_RETCODE_POSITION_CLOSED;
    }
  }

É claro que para esta finalidade é usada OrderModify.

A função é usada para alterar a ordem pendente.

  else if(request.action == TRADE_ACTION_MODIFY) // change pending order
  {
    if(OrderSelect((int)request.order, SELECT_BY_TICKET))
    {
      if(!OrderModify((int)request.order, request.price, request.sl, request.tp, request.expiration))
      {
        result.retcode = GetLastError();
      }
      else
      {
        result.retcode = TRADE_RETCODE_DONE;
        result.deal = OrderTicket();
        result.order = OrderTicket();
        result.request_id = OrderTicket();
        result.price = request.price;
        result.volume = OrderLots();
        result.comment = OrderComment();
      }
    }
    else
    {
      result.retcode = TRADE_RETCODE_INVALID_ORDER;
    }
  }

A função padrão OrderDelete executa a exclusão da ordem pendente.

  else if(request.action == TRADE_ACTION_REMOVE)
  {
    if(!OrderDelete((int)request.order))
    {
      result.retcode = GetLastError();
    }
    else
    {
      result.retcode = TRADE_RETCODE_DONE;
    }
  }

Finalmente, o fechamento de uma posição usando outra (oposta) é equivalente - no contexto do MetaTrader 4 - ao fechamento de ordens opostas.

  else if(request.action == TRADE_ACTION_CLOSE_BY)
  {
    if(!OrderCloseBy((int)request.position, (int)request.position_by))
    {
      result.retcode = GetLastError();
    }
    else
    {
      result.retcode = TRADE_RETCODE_DONE;
    }
  }
  return true;
}

Além da OrderSend, o MetaTrader 5 fornece a função assíncrona OrderSendAsync. Não o realizaremos; desativamos todos os casos de implementação de modo assíncrono na biblioteca, ou seja, na verdade, substituiremos pela versão síncrona.

Geralmente, ao colocar ordens, são chamadas outras 3 funções: OrderCalcMargin, OrderCalcProfit, OrderCheck.‌

A seguir uma das variantes de implementação usando recursos disponíveis no MetaTrader 4.

int EnumOrderType2Code(int action)

{   // ORDER_TYPE_BUY/ORDER_TYPE_SELL and derivatives   return (action % 2 == 0) ? OP_BUY : OP_SELL; }

bool OrderCalcMargin(   ENUM_ORDER_TYPE action,   string          symbol,   double          volume,   double          price,   double         &margin   ) {   int cmd = EnumOrderType2Code(action);   double m = AccountFreeMarginCheck(symbol, cmd, volume);   if(m <= 0 || GetLastError() == ERR_NOT_ENOUGH_MONEY)   {     return false;   }   margin = AccountFreeMargin() - m;   return true; }

bool OrderCalcProfit(   ENUM_ORDER_TYPE action,   string          symbol,   double          volume,   double          price_open,   double          price_close,   double         &profit   ) {   int cmd = EnumOrderType2Code(action);   if(cmd > -1)   {     int points = (int)((price_close - price_open) / MarketInfo(symbol, MODE_POINT));     if(cmd == OP_SELL) points = -points;     profit = points * volume * MarketInfo(symbol, MODE_TICKVALUE) / (MarketInfo(symbol, MODE_TICKSIZE) / MarketInfo(symbol, MODE_POINT));     return true;   }   return false; } bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result) {   if(request.volume > MarketInfo(request.symbol, MODE_MAXLOT)   || request.volume < MarketInfo(request.symbol, MODE_MINLOT)   || request.volume != MathFloor(request.volume / MarketInfo(request.symbol, MODE_LOTSTEP)) * MarketInfo(request.symbol, MODE_LOTSTEP))   {     result.retcode = TRADE_RETCODE_INVALID_VOLUME;     return false;   }   double margin;   if(!OrderCalcMargin(request.type, request.symbol, request.volume, request.price, margin))   {     result.retcode = TRADE_RETCODE_NO_MONEY;     return false;   }   if((request.action == TRADE_ACTION_DEAL || request.action == TRADE_ACTION_PENDING)   && SymbolInfoInteger(request.symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_EXECUTION_MARKET   && (request.sl != 0 || request.tp != 0))   {     result.retcode = TRADE_RETCODE_INVALID_STOPS;     return false;   }   result.balance = AccountBalance();   result.equity = AccountEquity();   result.profit = AccountEquity() - AccountBalance();   result.margin = margin;   result.margin_free = AccountFreeMargin();   result.margin_level = 0;   result.comment = "";   return true; }

Aqui são usadas ativamente as funções embutidas AccountEquity, AccountFreeMargin, AccountFreeMarginCheck, bem como o custo do ponto do instrumento e outras configurações obtidas chamando MarketInfo.

Para obter o número total de posições, basta retornar a quantidade de ordens de mercado abertas.

int PositionsTotal()
{
  int count = 0;
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderType() <= OP_SELL)
      {
        count++;
      }
    }
  }
  return count;
}

Para obter o símbolo da posição segundo seu número, é necessário selecionar no ciclo todas as ordens contando apenas as de mercado.

string PositionGetSymbol(int index)
{
  int count = 0;
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderType() <= OP_SELL)
      {
        if(index == count)
        {
          return OrderSymbol();
        }
        count++;
      }
    }
  }
  return "";
}

Da mesma forma, é construída a função para obter o bilhete da posição segundo seu número.

ulong PositionGetTicket(int index)
{
  int count = 0;
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderType() <= OP_SELL)
      {
        if(index == count)
        {
          return OrderTicket();
        }
        count++;
      }
    }
  }
  return 0;
}

Para selecionar a posição, segundo o nome do símbolo, também no ciclo das ordens de mercado selecionamos o primeiro nome que coincida com o símbolo.

bool PositionSelect(string symbol)
{
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderSymbol() == symbol && (OrderType() <= OP_SELL))
      {
        return true;
      }
    }
  }
  return false;
}

A implementação de seleção de posição segundo o bilhete não requer o ciclo.

bool PositionSelectByTicket(ulong ticket)
{
  if(OrderSelect((int)ticket, SELECT_BY_TICKET))
  {
    if(OrderType() <= OP_SELL)
    {
      return true;
    }
  }
  return false;
}

As propriedades da posição selecionada deve retornar um trio de funções habituais para o MetaTrader 5, isto é: _GetDouble, _GetInteger, _GetString. Apresentamos aqui sua implementação para posições, enquanto para ordens e transações, como elas parecem muito familiares, permanecerão fora do escopo deste artigo. Os interessados ​​podem estudar seu código no arquivo anexado.

// posição= ordem, apenas OP_BUY ou OP_SELL
ENUM_POSITION_TYPE Order2Position(int type)
{
  return type == OP_BUY ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
}

bool PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property_id, long &long_var)
{
  switch(property_id)
  {
    case POSITION_TICKET:
    case POSITION_IDENTIFIER:
      long_var = OrderTicket();
      return true;
    case POSITION_TIME:
    case POSITION_TIME_UPDATE:
      long_var = OrderOpenTime();
      return true;
    case POSITION_TIME_MSC:
    case POSITION_TIME_UPDATE_MSC:
      long_var = OrderOpenTime() * 1000;
      return true;
    case POSITION_TYPE:
      long_var = Order2Position(OrderType());
      return true;
    case POSITION_MAGIC:
      long_var = OrderMagicNumber();
      return true;
  }
  return false;
}

bool PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property_id, double &double_var)
{
  switch(property_id)
  {
    case POSITION_VOLUME:
      double_var = OrderLots();
      return true;
    case POSITION_PRICE_OPEN:
      double_var = OrderOpenPrice();
      return true;
    case POSITION_SL:
      double_var = OrderStopLoss();
      return true;
    case POSITION_TP:
      double_var = OrderTakeProfit();
      return true;
    case POSITION_PRICE_CURRENT:
      double_var = MarketInfo(OrderSymbol(), OrderType() == OP_BUY ? MODE_BID : MODE_ASK);
      return true;
    case POSITION_COMMISSION:
      double_var = OrderCommission();
      return true;
    case POSITION_SWAP:
      double_var = OrderSwap();
      return true;
    case POSITION_PROFIT:
      double_var = OrderProfit();
      return true;
  }
  return false;
}

bool PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id, string &string_var)
{
  switch(property_id)
  {
    case POSITION_SYMBOL:
      string_var = OrderSymbol();
      return true;
    case POSITION_COMMENT:
      string_var = OrderComment();
      return true;
  }
  return false;
}

Da mesma maneira como no caso das posições que são ordens de mercado, é necessário implementar um conjunto de funções para processamento de ordens pendentes. Mas há um problema. Não podemos implementar a função OrdersTotal e outras OrderGet_, uma vez que elas já estão definidas no kernel e é impossível substituir as funções embutidas. O compilador gerará um erro como este:

'OrderGetString' - override system function MT5Bridge.mqh

É por isso que somos forçados a dar outros nomes a todas as funções com nomes que começam com o prefixo Order_. É lógico começar seus nomes com PendingOrder_, uma vez que eles processam, nomeadamente, ordens pendentes. Por exemplo:

int PendingOrdersTotal()
{
  int count = 0;
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderType() > OP_SELL)
      {
        count++;
      }
    }
  }
  return count;
}

Em seguida, no código da biblioteca padrão, será necessário substituir todas as chamadas para nossas novas funções a partir de MT5Bridge.mqh.

Como a função OrderGetTicket - que retorna o bilhete da ordem segundo o número - não existe no MetaTrader 4,deixamos seus nome sem alterações, ou seja, de acordo com o API MetaTrader 5.‌

ulong OrderGetTicket(int index)
{
  int count = 0;
  for(int i = 0; i < ::OrdersTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS))
    {
      if(OrderType() > OP_SELL)
      {
        if(index == count)
        {
          return OrderTicket();
        }
        count++;
      }
    }
  }
  return 0;
}

Como a função OrderSelect existe no MetaTrader 4 com uma lista expandida de parâmetros em comparação com o MetaTrader 5, deixamos suas chamadas adicionando o parâmetro necessário SELECT_BY_TICKET.

No arquivo de cabeçalho em anexo, é possível encontrar toda a aplicação da função de leitura de propriedades de ordens pendentes.

Passamos agora para as funções a fim de trabalhar com o histórico de ordens e transações. Sua aplicação exigirá algum engenho. A opção seguinte é apenas uma das muitas possíveis e foi escolhida por causa de sua simplicidade.

Cada ordem de mercado do MetaTrader 4 é exibida no histórico usando duas "ordens a la MetaTrader 5", isto é: entrada e saída. Além disso, no histórico, deve existir o par correspondente das transações. As ordens pendentes são mostradas sem alterações. O histórico será salvo em duas matrizes com bilhetes.

int historyDeals[], historyOrders[];

Serão preenchidas pela função HistorySelect a partir da MQL5 API.

bool HistorySelect(datetime from_date, datetime to_date)
{
  int deals = 0, orders = 0;
  ArrayResize(historyDeals, 0);
  ArrayResize(historyOrders, 0);
  for(int i = 0; i < OrdersHistoryTotal(); i++)
  {
    if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
    {
      if(OrderOpenTime() >= from_date || OrderCloseTime() <= to_date)
      {
        if(OrderType() <= OP_SELL) // deal
        {
          ArrayResize(historyDeals, deals + 1);
          historyDeals[deals] = OrderTicket();
          deals++;
        }
        ArrayResize(historyOrders, orders + 1);
        historyOrders[orders] = OrderTicket();
        orders++;
      }
    }
  }
  return true;
}

Uma vez que as matrizes são preenchidas, é possível obter o tamanho do histórico.

int HistoryDealsTotal()
{
  return ArraySize(historyDeals) * 2;
}

int HistoryOrdersTotal()
{
  return ArraySize(historyOrders) * 2;
}

As dimensões das matrizes são multiplicadas por 2, porque cada ordem do MetaTrader 4 compreende dois ordens ou duas transações no MetaTrader 5. Para ordens pendentes, não é assim, mas para preservar a semelhança da abordagem, nós ainda reservamos 2 bilhetes, apenas um deles não será usado (ver a seguinte função HistoryOrderGetTicket). A transação de entrada no mercado terá o mesmo bilhete que em sua ordem gerada no MetaTrader 4. Para a transação de entrada complementaremos este bilhete com um bit maior.

ulong HistoryDealGetTicket(int index)
{
  if(OrderSelect(historyDeals[index / 2], SELECT_BY_TICKET, MODE_HISTORY))
  {
    // odd - enter - positive, even - exit - negative
    return (index % 2 == 0) ? OrderTicket() : (OrderTicket() | 0x8000000000000000);
  }
  return 0;
}

No histórico, quatro números sempre contêm os bilhetes para entrada (reais), os impares, para saída (virtuais).

Quanto as ordens tudo é um pouco mais complicado, uma vez que, entre elas, podem haver pendentes exibidas da mesma maneira. Neste caso, o número par retornará o bilhete correto da ordem pendente, enquanto impar a seguir, 0.‌

ulong HistoryOrderGetTicket(int index)
{
  if(OrderSelect(historyOrders[index / 2], SELECT_BY_TICKET, MODE_HISTORY))
  {
    if(OrderType() <= OP_SELL)
    {
      return (index % 2 == 0) ? OrderTicket() : (OrderTicket() | 0x8000000000000000);
    }
    else if(index % 2 == 0) // pending order is returned once
    {
      return OrderTicket();
    }
    else
    {
      Print("History order ", OrderType(), " ticket[", index, "]=", OrderTicket(), " -> 0");
    }
  }
  return 0;
}

Ao selecionar a transação segundo o bilhete, é levada em conta esta peculiaridade com a definição do bit maior — aqui ele tem de ser zerado.

bool HistoryDealSelect(ulong ticket)
{
  ticket &= ~0x8000000000000000;
  return OrderSelect((int)ticket, SELECT_BY_TICKET, MODE_HISTORY);
}

Para as ordens tudo é da mesma maneira.

#define HistoryOrderSelect HistoryDealSelect

Tendo uma transação, selecionada usando HistoryDealSelect ou HistoryDealGetTicket, é possível escrever a implementação das funções de acesso às propriedades da transação.

#define REVERSE(type) ((type + 1) % 2)

ENUM_DEAL_TYPE OrderType2DealType(const int type)
{
  static ENUM_DEAL_TYPE types[] = {DEAL_TYPE_BUY, DEAL_TYPE_SELL, -1, -1, -1, -1, DEAL_TYPE_BALANCE};
  return types[type];
}

bool HistoryDealGetInteger(ulong ticket_number, ENUM_DEAL_PROPERTY_INTEGER property_id, long &long_var)
{
  bool exit = ((ticket_number & 0x8000000000000000) != 0);
  ticket_number &= ~0x8000000000000000;
  if(OrderSelect((int)ticket_number, SELECT_BY_TICKET, MODE_HISTORY))
  {
    switch(property_id)
    {
      case DEAL_TICKET:
      case DEAL_ORDER:
      case DEAL_POSITION_ID:
        long_var = OrderTicket();
        return true;
      case DEAL_TIME:
        long_var = exit ? OrderCloseTime() : OrderOpenTime();
        return true;
      case DEAL_TIME_MSC:
        long_var = (exit ? OrderCloseTime() : OrderOpenTime()) * 1000;
        return true;
      case DEAL_TYPE:
        long_var = OrderType2DealType(exit ? REVERSE(OrderType()) : OrderType());
        return true;
      case DEAL_ENTRY:
        long_var = exit ? DEAL_ENTRY_OUT : DEAL_ENTRY_IN;
        return true;
      case DEAL_MAGIC:
        long_var = OrderMagicNumber();
        return true;
    }
  }
  return false;
}
  
bool HistoryDealGetDouble(ulong ticket_number, ENUM_DEAL_PROPERTY_DOUBLE property_id, double &double_var)
{
  bool exit = ((ticket_number & 0x8000000000000000) != 0);
  ticket_number &= ~0x8000000000000000;
  switch(property_id)
  {
    case DEAL_VOLUME:
      double_var = OrderLots();
      return true;
    case DEAL_PRICE:
      double_var = exit ? OrderClosePrice() : OrderOpenPrice();
      return true;
    case DEAL_COMMISSION:
      double_var = exit? 0 : OrderCommission();
      return true;
    case DEAL_SWAP:
      double_var = exit ? OrderSwap() : 0;
      return true;
    case DEAL_PROFIT:
      double_var = exit ? OrderProfit() : 0;
      return true;
  }
  return false;
}

bool HistoryDealGetString(ulong ticket_number, ENUM_DEAL_PROPERTY_STRING property_id, string &string_var)
{
  switch(property_id)
  {
    case DEAL_SYMBOL:
      string_var = OrderSymbol();
      return true;
    case DEAL_COMMENT:
      string_var = OrderComment();
      return true;
  }
  return false;
}

Eu espero que a ideia seja clara. Da mesma forma, é implementado o grupo de funções para trabalhar com ordens no histórico.

Alteração nos arquivos da biblioteca padrão

Já foram discutidas algumas correções da biblioteca durante a realização da função. Você pode comparar tanto os arquivos fornecidos com o MetaTrader 5 quanto os obtidos neste projeto para completar a lista de alterações. Em seguida, são considerados apenas os pontos mais importantes, enquanto os comentários quanto a correções menores são omitidos. Em muitos arquivos é inserida a nova diretiva #include para ligar o MT5Bridge.mqh.

Tabela de principais alterações nos arquivos de biblioteca padrão


Arquivo/Método Alterações
Trade.mqh SetAsyncMode excluída a cadeia de caracteres que aplicava o modo assíncrono, porque não era suportada
SetMarginMode escrito claramente o modo ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
OrderOpen claramente escrita a combinação de sinalizadores que especificam o modo de expiração como SYMBOL_EXPIRATION_GTC | SYMBOL_EXPIRATION_SPECIFIED
OrderTypeCheck excluídos os casos de processamento de tipos inexistentes ORDER_TYPE_BUY_STOP_LIMIT, ORDER_TYPE_SELL_STOP_LIMIT
OrderSend excluída a chamada da função assíncrona ausente OrderSendAsync
 
OrderInfo.mqh todas as chamadas das funções OrderGetInteger, OrderGetDouble, OrderGetString foram substituídas por função de nome idêntico prefixadas com PendingOrder
todas as chamadas da OrderSelect(m_ticket) substituídas pela OrderSelect((int)m_ticket, SELECT_BY_TICKET)
 
PositionInfo.mqh FormatPosition
SelectByIndex
definido o modo de margem ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
 
SymbolInfo.mqh Refresh excluídas muitas verificações não suportadas no MetaTrader 4
 
AccountInfo.mqh MarginMode retorna para a constante ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
 
Expert.mqh TimeframeAdd
TimeframesFlags
excluídos os timeframes não suportados
 
ExpertBase.mqh adicionado #include <Indicators\IndicatorsExt.mqh>
SetMarginMode definido definitivamente na ACCOUNT_MARGIN_MODE_RETAIL_HEDGING


O arquivo IndicatorsExt.mqh é necessário para corrigir pequenos erros no arquivo padrão Indicators.mqh. Além disso, ele inclui o arquivo - necessário para os indicador de cabeçalho - TimeSeriesExt.mqh.‌

O arquivo TimeSeriesExt.mqh contém as definições de classes necessárias para "negociar a la MetaTrader 5", mas não existem no arquivo padrão TimeSeries.mqh fornecido com o MetaTrader 4.

Em particular, são classes: CTickVolumeBuffer, CSpreadBuffer, CiSpread, CiTickVolume, CRealVolumeBuffer, CiRealVolume. Muitas delas são somente stubs que não estão fazendo nada (e não podem fazer devido à inacessibilidade da funcionalidade apropriada para o MetaTrader 4).

Teste

Após instalar as classes de negociação adaptadas - da biblioteca padrão - no diretório Include do MetaTrader 4 (preservando a hierarquia de subpastas), bem como depois de copiar o MT5Bridge.mqh e colar na pasta Include/Trade, nós podemos compilar e executar os EAs - gerados pelo Assistente MetaTrader 5 - diretamente no MetaTrader 4.

O MetaTrader 5 vem com vários exemplos de EAs gerados (na pasta Experts/Advisors). Tomemos um deles, ExpertMACD.mq5. Copiamo-lo e colamos na pasta MQL4/Experts e alteramos seu nome para ExpertMACD.mq4. A compilação no editor deve dar aproximadamente o seguinte resultado:

Expert Advisor vindo do Assistente MetaTrader 5 compilado no MetaTrader 4

Expert Advisor vindo do Assistente MetaTrader 5 compilado no MetaTrader 4

É evidente que os arquivos de biblioteca são processados sem erros e avisos. Claro,a ausência de erros de compilação não garante a ausência de problemas na lógica do programa, mas isso é assunto para mais verificações futuras.

Executamos o Expert Advisor compilado - com configurações por padrão - no testador do MetaTrader 4.

Relatório de teste MetaTrader 4 para o Expert Advisor gerado no MetaTrader 5

Relatório de teste MetaTrader 4 para o Expert Advisor gerado no MetaTrader 5

Se desejar, é possível ter certeza de que no diário não ha nenhum registro de erros de processamento de ordens.‌

No gráfico EURUSD M15, a negociação deste Expert Advisor parece estar bem, incluindo, em particular, a instalação de níveis Stop-Loss e Take-Profit.

Gráfico que ilustra o trabalho do Expert Advisor a partir do Assistente MetaTrader 5 no MetaTrader 4

Gráfico que ilustra o trabalho do Expert Advisor a partir do Assistente MetaTrader 5 no MetaTrader 4

Comparamos com os resultados do testador MetaTrader 5.

Relatório de teste MetaTrader 5 para o Expert Advisor gerado

Relatório de teste MetaTrader 5 para o Expert Advisor gerado

É óbvio que existem diferenças. Elas podem ser devidas a divergências tanto nas próprias cotações (por exemplo, MetaTrader 5 utiliza spread flutuante) quanto nos algoritmos do testador. Em geral, os testes são semelhantes: aproximadamente os mesmos número de operações e natureza geral da curva de balanço.

Claro, o usuário pode gerar seu próprio Expert Advisor - no Assistente - com um conjunto completamente arbitrário de módulos tendo em conta que ele tem de ser transferido para o MetaTrader 4. Durante o teste do projeto foram verificados, em particular, os Expert Advisors com trailing e tamanho variável de lote.

Conclusão

Consideramos uma das possíveis formas de transferir EAs - gerados pelo Assistente MetaTrader 5 - para a plataforma MetaTrader 4. Sua principal vantagem é a relativa facilidade de implementação baseada no uso máximo de código existente de classes de negociação a partir da biblioteca padrão do MetaTrader 5. A principal desvantagem é a necessidade de ter no computador ambos os terminais: um para a geração de Expert Advisors e outro para usá-los.

Abaixo foram anexados 2 arquivos:

  • arquivo com arquivos modificados da biblioteca padrão - que é necessário expandir - com a mesma hierarquia dos subdiretórios no diretório Include do MetaTrader 4. O arquivo contém apenas os arquivos que não são fornecidos com o terminal, por isso não há nenhum risco de sobrescrever arquivos existentes;
  • arquivo MT5Bridge.mqh que deve ser copiado e colado na pasta Include/Trade.

Esta versão da biblioteca foi tomada a partir da compilação 1545 do MetaTrader 5. As compilações futuras podem conter alterações na biblioteca padrão que podem ser uteis (o que exigirá aplicar novamente as correções do emulador). Seria ideal um dia ver uma versão da biblioteca padrão - vinda da MetaQuotes - que desde o início use uma diretiva de compilação condicional que possua as duas opções a fim de implementar as classes de negociação — para o MetaTrader 5 e para o MetaTrader 4.

Vale a pena salientar que será impossível aplicar uma emulação completa do ambiente de negociação do MetaTrader 5 no MetaTrader 4. O novo terminal fornece novos recursos não encontrados no antigo, no nível do kernel. É por isso que é muito provável que alguns módulos - usados nos Expert Advisors gerados - se recusem a trabalhar.

Além disso, não se esqueça de que a implementação do emulador é distribuída em versão beta e pode conter bugs. Só ao testar com paciência e de múltiplas maneiras é que obtemos um produto final adequado para negociação. A disponibilidade do código-fonte permite fazer isto simultaneamente.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/3068

Arquivos anexados |
MT4-1.1-SL5-b1545.zip (145.99 KB)
MT5Bridge.mqh (32.35 KB)
Análise comparativa de 10 estratégias de tendência Análise comparativa de 10 estratégias de tendência
No artigo, além de una breve visão geral de 10 estratégias de tendência, são levados a cabo seu teste e análise comparativa. Com base nos resultados obtidos, é feita uma conclusão geral sobre a viabilidade, vantagens e desvantagens da negociação de tendência.
Receitas MQL5 - sinais de negociação de pivô Receitas MQL5 - sinais de negociação de pivô
No artigo, é apresentado o processo de desenvolvimento e implementação de uma classe-robô de sinais com base em pivôs, isto é, níveis de reversão. Com base nesta classe é construída uma estratégia usando a Biblioteca padrão. São consideradas as possibilidades de desenvolver uma estratégia de pivôs adicionando filtros.
Interfaces gráficas X: Ordenação, reconstrução da tabela e controles nas células (build 11) Interfaces gráficas X: Ordenação, reconstrução da tabela e controles nas células (build 11)
Nós continuamos a adicionar novos recursos para a tabela renderizada: ordenação dos dados, gerenciamento do número de colunas e linhas, definição dos tipos de células da tabela para colocar os controles dentro delas.
Tendência universal com GUI Tendência universal com GUI
No artigo, criaremos um indicador de tendência universal com base numa série de indicadores padrão. Será desenvolvida uma interface gráfica do usuário para selecionar o tipo de indicador e seus parâmetros. Exibiremos o indicador numa janela separada com fileiras de ícones coloridos.