English Русский 中文 Español Deutsch 日本語
Expert Advisor multiplataforma: Introdução

Expert Advisor multiplataforma: Introdução

MetaTrader 5Integração | 28 setembro 2016, 16:32
1 407 0
Enrico Lambino
Enrico Lambino


Tabela de conteúdos


Introdução

Entre as formas de criar EAs multiplataforma para terminais MetaTrader, é possível enumerar os seguintes:

  • Você quer compartilhar seu Expert Advisor com outros traders, não importa qual versão da plataforma eles usem.
  • Você quer entender claramente as diferenças entre as plataformas MQL4 e MQL5.
  • Você quer economizar tempo gasto na geração de código.
  • Você deseja evitar problemas durante a migração de seus robôs de negociação na MetaTrader5, caso a MetaTrader 4 de repente parar de funcionar.
  • Você já está usando MetaTrader 5, mas, por alguma razão, você quer testar seu EA na MetaTrader 4.
  • Você ainda está trabalhando com MetaTrader 4, mas quer usar o serviço de nuvem MQL5 para testar e otimizar seus robôs de negociação.

Quando um desenvolvedor cria um EA, indicador ou um script, ele normalmente adota o seguinte curso de ação:

  1. Desenvolve um programa usando a mesma linguagem (MQL4 ou MQL5)

  2. Testá-lo cuidadosamente

  3. Implementa o mesmo programa num idioma diferente

As desvantagens deste sistema são as seguintes:

  1. É necessário re-executar a totalidade do robô de negociação, incluindo as funções que são idênticas para ambas as versões

  2. Podem surgir problemas durante a depuração e manutenção futura do programa

  3. É reduzida a produtividade

Se você escrever programas separados, a implementação paralela quase vai duplicar a quantidade de código necessário: um programa para MQL4, e outro para MQL5. No que diz respeito às dificuldades de depuração e à futura manutenção de tais EAs, o problema é o seguinte: se uma versão requer alterações, as mesmas atualizações deverão ser feitas na outra versão. Assim, em virtude das diferenças entre MQL4 e MQL5, em algum momento as duas versões do mesmo programa irão ter diferenças. Potencialmente, isso vai trazer um monte de problemas, porque as diferenças no código geralmente não são óbvias quando se usa implementações discretas.


Expert Advisor Hello World, exemplo

Vamos começar com um Expert Advisor simples escrito na MQL5: Expert Advisor Hello World. Abaixo é exibido um exemplo típico para criação de um Expert Advisor semelhante com o mencionado acima.

HelloWorld.mq5

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting();
   ExpertRemove();
  }

Na MQL4, nós obtermos o mesmo código seguindo o mesmo método.

(HelloWorld.mq4)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting();
   ExpertRemove();
  }


Arquivos fonte e de cabeçalho

Nota: os dois arquivos fonte, acima indicados, são os mesmos. É impossível ter um único arquivo fonte trabalhando em duas plataformas. A razão para isso é o jeito de compilar os arquivos fonte:

  • resultado de compilação na MQ4, criação do arquivo EX4.
  • resultado de compilação na MQ5, criação do arquivo EX5.

Em outras palavras, como já foi dito acima, nós não podemos obter um único arquivo fonte que esteja trabalhando em ambas as plataformas. No entanto, é possível fazer ambos os códigos-fonte tenham um caminho comum para o mesmo arquivo de cabeçalho, tal como é ilustrado na figura abaixo:

Arquivos fonte e de cabeçalho


Idealmente, gostaríamos de obter tudo em um arquivo de cabeçalho, com dois arquivos de origem que contêm apenas uma cadeia de caracteres: inclusão de cabeçalho do arquivo. Podemos reescrever o arquivo de cabeçalho para o EA Hello World acima da seguinte forma:

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Os arquivos de origem na MQL4 e MQL5 contém uma cadeia de caracteres de código, ela é a diretiva #include que se refere ao arquivo de cabeçalho:

(HelloWorld_SingleHeader.mq4 и HelloWorld_SingleHeader.mq5)

#include <HelloWorld_SingleHeader.mqh>

Esta abordagem tem várias vantagens. Em primeiro lugar, nós podemos, potencialmente, reduzir a quantidade de código de fonte escrito para as duas plataformas para 50% (pelo menos para este exemplo). A segunda vantagem é que esta abordagem irá permitir-nos trabalhar em apenas uma implementação e não em dois separadamente. Uma vez que temos apenas uma fonte para trabalhar, as alterações, feitas para a versão MQL4, serão também aplicadas à MQL5, e vice-versa.

Não se esqueça, normalmente, as alterações, feitas num arquivo de origem, têm de ser realizadas separadamente no arquivo fonte para outra versão da plataforma.

Mas os Expert Advisors raramente são escritos tão facilmente como no exemplo dado por mim. Eles são muito mais complexos. Quanto mais complexo se torna o EA, mais difícil se torna manter duas versões distintas do mesmo.


Compilação condicional

MQL4 e MQL5 têm muitas semelhanças, mas também muitas diferenças. Entre essas diferenças está a implementação da função StringConcatenate. Na MQL4 a função é definida da seguinte forma:

string  StringConcatenate( 
   void argument1,        // primeiro parâmetro de qualquer tipo simples
   void argument2,        // segundo parâmetro de qualquer tipo
   ...                    // seguinte parâmetro de qualquer tipo simples
   );

Sua implementação em MQL5 é ligeiramente diferente:

int  StringConcatenate( 
   string&  string_var,   // cadeia de caracteres que será formada 
   void argument1,        // primeiro parâmetro de qualquer tipo simples
   void argument2,        // segundo parâmetro de qualquer tipo
   ...                    // seguinte parâmetro de qualquer tipo simples
   );

Podemos usar esta função em Hello World ao reiniciar o método Greeting() em nossa classe. O novo método terá dois argumentos de cadeia de caracteres, enquanto o resultado conjunto será impresso no terminal. Atualizamos nosso arquivo de cabeçalho:

(HelloWorld_SingleHeader.mqh)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Usando esta versão atualizada, no terminal MetaTrader 4, vemos o seguinte resultado: impresso

Hello World!

No MetaTrader 5 o resultado será diferente do originalmente esperado:

12

Na MQL4, a função retorna o texto consolidado como uma sequência de caracteres. Em vez disso, na MQL5 é retornado um valor inteiro que representa o tamanho da linha comum. Para o Expert Advisor demostrar o mesmo comportamento que exibiu em duas plataformas sem reescrever a maioria do código, podemos simplesmente usar a compilação condicional, conforme mostrado no seguinte trecho de código.

CHelloWorld::Greeting(const string str1,const string str2)
  {
   #ifdef __MQL5__
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
   #else
      Print(StringConcatenate(str1,str2));
   #endif
  }

Observe que se trata de uma diretiva de pré-processador. Aqui você pode encontrar um aumento adicional no tempo de compilação, mas não de execução. Na MQL4, o compilador irá interpretar o código acima da seguinte forma:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      Print(StringConcatenate(str1,str2));
}

O compilador MQL5 lê o código assim:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
}


Implementação de divisão de classes

Neste ponto já podemos descobrir quais são os tipos de código, você pode criar o Expert Advisor multiplataforma.

  1. Funções compatíveis
    • Funções comuns
    • Computação
  2. Funções incompatíveis
    • Funções que funcionam de forma diferente
    • Funções disponíveis numa versão, mas que não estão disponíveis em outras
    • Diferentes modos de execução

Em MQL4 e MQL5 existem muitas funções que se comportam de forma idêntica em ambas as linguagens. Um exemplo de tais funções é Print(). Ela funciona da mesma maneira, independentemente de qual versão de plataforma está em execução. O exemplo de código fonte compatível também pode ser visto na forma de cálculos convencionais. O resultado 1+1 será o mesmo na MQL4 e MQL5, assim como em qualquer outra linguagem de programação do mundo real. Em ambos os casos, a implementação de divisão de classes é extremamente rara.

Mas em casos onde uma determinada parte do código-fonte não é compilada ou executado de forma diferente em diferentes versões da plataforma, vamos precisar da implementação de separação. Um exemplo do primeiro caso de código incompatível é a função StringConcatenate. Apesar do mesmo nome, esta função se comporta diferentemente em MQL4 e MQL5. Há também algumas características que não têm contrapartida direta em outra linguagem. Por exemplo, a função OrderCalcMargin, que (pelo menos no momento da redação deste texto), não possui equivalentes em MQL4. O terceiro caso é talvez o mais difícil para o desenvolvimento de multiplataforma, porque é aqui que a implementação pode variar em diferentes desenvolvedores. Neste caso, para reduzir a quantidade de código, precisa encontrar um terreno comum entre as duas versões da plataforma e, em seguida, uma implementação de divisão de classes conforme necessário.

Neste caso depender exclusivamente da compilação condicional pode ser uma má ideia. Porque, com o tempo, o código se torna mais longo, a presença de um grande número de tais instruções complica grandemente a depuração e a manutenção futura do programa. Em programação orientada a objeto, podemos precisar de dividir a aplicação em três partes: (1) implementação básica, (2) implementação específica MQL4 e (3) implementação específica MQL5.

A implementação da classe base irá conter o código que suporta ambas as versões. Nos casos em que há inconsistências, você precisa se afastar da implementação base, ou mesmo deixá-la vazia - e aplicar uma diferentes implementações para ambas as linguagens.

Para nosso EA Hello World, declaramos uma classe base e damo-lhe o nome CHelloWorldBase. Ele irá conter o código que é comum para MQL4 e MQL5. Ele inclui o método original Greeting(), que foi definido no início deste artigo:

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+


Em seguida, criamos os objetos de classe, específicos para a plataforma ou linguagem, herdados de uma classe base, e introduzimos uma variedade de opções de implementação para alcançar o mesmo resultado desejado.

(HelloWorld_SingleHeader_MQL4.mqh)

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+


HelloWorld_SingleHeader_MQL5.mqh

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Depois colocamos as funções de desenvolvimento de eventos no lugar onde normalmente se encontram (nos limites do arquivo principal fonte).

HelloWorld_SingleHeader.mq5

#include <HelloWorld_SingleHeader_MQL5.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

HelloWorld_SingleHeader.mq4

#include <HelloWorld_SingleHeader_MQL4.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

Especificamente neste exemplo é mais prático usar um arquivo de cabeçalho que contém a classe base e duas classes herdeiras dentro da diretiva de compilação condicional. No entanto, na maioria dos casos, você deve colocar as classes em arquivos separados, especialmente se os códigos fonte atraídos são grandes.


Conexão de arquivos

O comportamento natural para os desenvolvedores é simplesmente referir-se ao cabeçalho do arquivo que contém a definição de classes que serão usadas no programa. Por exemplo, na implementação MQL5 do Expert Advisor HelloWorld, nós podemos ver que existem duas versões (HelloWorld_SingleHeader.mq4 e HelloWorld_SingleHeader.mq5) quase idênticas, exceto para o arquivo de cabeçalho específico que eles contêm.

#include <HelloWorld_SingleHeader_MQL4.mqh>
#include <HelloWorld_SingleHeader_MQL5.mqh>
Outra abordagem é referir-se ao arquivo de cabeçalho que contém a implementação base. Então no final do arquivo de cabeçalho podemos usar a diretiva de compilação condicional para fazer referência a um arquivo de cabeçalho que contém a classe de herdeiro apropriada, dependendo do compilador utilizado:
#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "HelloWorld_SingleHeader_MQL5.mqh"
#else
   #include "HelloWorld_SingleHeader_MQL4.mqh"
#endif
//+------------------------------------------------------------------+

Em seguida, no arquivo fonte principal, nós nos referimos a esse arquivo de cabeçalho, ao invés de esse que é específico para uma linguagem determinada:

#include <HelloWorld_SingleHeader.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

Seguidamente, removemos a diretiva #include para os arquivos de cabeçalho que são específicos para linguagens (as cadeias de caracteres riscadas mostram trechos de código remoto).

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Essa abordagem é recomendada e tem algumas vantagens. Em primeiro lugar, ela retém a mesma diretiva #include para ambos os arquivos fonte principais (MQL4 e MQL5). Isto também libera da tediosa necessidade de selecionar qual é arquivo de cabeçalho e qua é o caminho (por exemplo, MQL4/MQL5/ou) Ligue o processador de diretriz. A terceira vantagem consiste em que este maneira retém a inclusão básica nos arquivos de cabeçalho básico. Se você usar as diretivas de inclusão em arquivos de cabeçalho de específicos para a linguagem, eles serão usados exclusivamente para as versões de linguagem relevante (MQL4 ou MQL5).

Arquivos e pastas divididos

Ao projetar um Expert Advisor, em programação orientada a objeto, é quase impossível escrever todo o código, definindo apenas uma classe. Uma prova disto são as classes de estratégias de negociação na Biblioteca Padrão de MQL5. Á medida que cresce o número de linhas de código, será mais prático a separação de código alguns arquivos de cabeçalho. Neste artigo, recomendamos as seguintes pastas de formato:

/Include

/Base

/MQL4

/MQL5

Três pastas podem ser localizadas dentro do diretório Include ou num subdiretório dentro dele.

Para nosso exemplo, o código terá a seguinte estrutura de pastas:

/Include

/MQLx-Intro

/Base

HelloWorldBase.mqh

/MQL4

HelloWorld.mqh

/MQL5

HelloWorld.mqh

O uso dessa estrutura nos permite organizar melhor nosso código. Ele também pode aliviar os conflitos na nomeação de arquivos, que nós tentamos escapar anteriormente.

Devido à mudança de locais de pasta de nossos arquivos de cabeçalho, precisamos atualizar o arquivo de cabeçalho principal para a classe com um novo arranjo de seus dois herdeiros:

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\HelloWorld.mqh"
#else
   #include "..\MQL4\HelloWorld.mqh"
#endif
//+------------------------------------------------------------------+

Também atualizamos o arquivo de origem básico com a alteração do local para nossa classe base. Neste ponto, já os arquivos de origem para as duas versões são as mesmas:

HelloWorld_Sample.mq4 and HelloWorld_Sample.mq5

#include <MQLx-Intro\Base\HelloWorldBase.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+



Herança

Suponha que desejamos estender a classe que nós identificamos CHelloWorld antes da classe chamada de CGoodByeWorld. Nesta classe, para criar uma mensagem com o texto "Goodbye World!" vamos usar o método Greeting(). Uma maneira (recomendado por mim) de fazer isso consiste em se referir à classe pai, cujo papel será realizado por CHelloWorldBase. Então, da mesma forma que CHelloWorldBase, no final deste arquivo, conecta-se a diretiva de pré-processamento de compilação condicional que faz referência o classe herdeiro correta. A hierarquia de herança será exibido da seguinte maneira:

Hierarquia de herança

No entanto, uma maneira de conectar os arquivos de cabeçalho será ligeiramente diferente:

Estrutura de conexão

Conectamos os arquivos de cabeçalho apenas para a classe base. Neste caso, quando é usada apenas uma hierarquia de classe única, ligamos apenas o arquivo de cabeçalho de classe base com a maior abstração (GoodByeWorldBase), pois o link para este arquivo conecta automaticamente outros arquivos de cabeçalho. Por favor, note que não usamos #include para nos referir a arquivos de cabeçalho específicos de plataforma, porque a sua conexão será responsabilidade dos arquivos de cabeçalho base.

Também será atualizada nossa estrutura de pastas, que eventualmente já conterá novos arquivos de cabeçalho:

/Include

/MQLx-Intro

/Base

HelloWorldBase.mqh

GoodByeWorldBase.mqh

/MQL4

HelloWorld.mqh

GoodByeWorld.mqh

/MQL5

HelloWorld.mqh

GoodByeWorld.mqh


A implementação da classe CGoodByeWorldBase é mostrada abaixo:

#include "HelloWorldBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorldBase : public CHelloWorld
  {
public:
                     CGoodByeWorldBase(void);
                    ~CGoodByeWorldBase(void);
   virtual void      GoodBye(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::~CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::GoodBye(void)
  {
   Greeting("Goodbye ","World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\GoodByeWorld.mqh"
#else
   #include "..\MQL4\GoodByeWorld.mqh"
#endif
//+------------------------------------------------------------------+

Observe que mesmo se o arquivo incluir "HelloWorldBase.mqh", a classe CGoodByeWorldBase herda de CHelloWorld, não de CHelloWorldBase. Qual versão CHelloWorld será lançada depende, em última análise, da sua versão de compilador MQL. Caso contrário, funcionará a CHelloWorldBase estendida. No entanto, neste exemplo, como o método Goodbye() usa o método a Greeting(), CGoodByeWorldBase deve ser herdado diretamente da implementação da CHelloWorld específica para a plataforma.

Como o método GoodBye() é comum para ambas as versões, é ideal para ser contido na implementação subjacente. Para um objeto desta classe não há mais outros métodos complementares, então seus sucessores não terão novos métodos de classe. Agora podemos implementar o herdeiro da seguinte forma:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorld : public CGoodByeWorldBase
  {
  };
//+------------------------------------------------------------------+

Da mesma forma você precisa atualizar o arquivo fonte principal, desta vez, criando um objeto que herda de CGoodByeWorld e GoodBye()dentro do manipulador OnTick.

(HelloWorld_Sample.mq4 and HelloWorld_Sample.mq5)

#include <MQLx-Intro\Base\GoodByeWorldBase.mqh>
CGoodByeWorld hello;
//+------------------------------------------------------------------+
//| Função de inicialização do Expert Advisor                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//--- 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de anulação da inicialização do Expert Advisor                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 

  }
//+------------------------------------------------------------------+
//| Função de ticks do Expert Advisor                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 
   hello.Greeting("Hello ","World!");
   hello.GoodBye();
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Após executar as versões do nosso EA, no terminal, aparecem os seguintes resultados:

Hello World!
Goodbye World!
ExpertRemove() function called

Restrições

Na maioria dos casos, esta abordagem permite que o programador acelere e facilite eficientemente o desenvolvimento de EAs multiplataformas. No entanto, gostaria de alertar o leitor sobre algumas limitações que podem fazer o método descrito neste artigo, difícil ou mesmo impossível.

1. Restrições para MetaTrader 4.

Na MetaTrader 4, sendo a plataforma de negociação mais velha, faltam características especiais que existem na MetaTrader 5. Em casos onde para o trabalho do EA, é necessário a ausência de uma função numa das plataformas, é preciso desenvolver uma solução personalizada exclusivamente para a outra versão. Este é basicamente um problema de Expert Advisor nativos, escritos com base na MetaTrader 5s que exigem equivalente na MetaTrader 4. Os usuários MetaTrader 4 experimentam menos ansiedade sobre isso. Afinal, a maioria das características da MetaTrader 4, se não todas, tem equivalente ou pelo menos "caminhos equivalentes" na Metatrader 5.

2. Diferenças significativas no desempenho ou convenções entre as duas plataformas 

Duas plataformas são muito diferentes em algumas operações. Isto é especialmente verdadeiro para as operações de negociação. Em tais situações, o desenvolvedor deverá escolher que condições aceitar. Por exemplo, ele pode usar convenções MetaTrader 4 e traduzi-las para convenções MetaTrader 5, para alcançar o mesmo comportamento de resultado. Ou, caso contrário, ele precisará aplicar a abordagem habitual para MetaTrader 5 nos Expert Advisors da MetaTrader 4.

Conclusão

Neste artigo, demonstramos o método pelo qual podem ser desenvolvidos Expert Advisors multiplataforma. O método descrito propõe o uso de classes base que contêm implementações compartilhadas por ambas as plataformas de negociação. Em áreas onde as duas línguas se afastar uns das outros, as implementações de divisão podem ser introduzidas como descendentes de classes que herdam essa classe base. O mesmo método é repetido para as classes a serem definidas ainda mais na hierarquia. Esse método pode ser útil no desenvolvimento de aplicações multiplataforma com menos tempo. Ele facilita a manutenção do código, evitando a necessidade de realizar implementações paralelas, separadas.


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

Arquivos anexados |
MQLx-Intro.zip (69.5 KB)
Expert Advisor multiplataforma: reutilização de componentes a partir da Biblioteca padrão MQL5 Expert Advisor multiplataforma: reutilização de componentes a partir da Biblioteca padrão MQL5
Na biblioteca padrão MQL5, existem alguns componentes que podem ser úteis em versões de EAs MQL4 multiplataforma. Este artigo descreve um método para a criação de alguns componentes da biblioteca padrão MQL5 compatíveis com o compilador MQL4.
Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1) Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1)
Este artigo é o começo da sexta parte da série dedicada ao desenvolvimento da biblioteca para a criação de interfaces gráficas nos terminais MetaTrader. No primeiro capítulo, nós vamos discutir o controle caixa de seleção, o controle campo de edição e seus tipos combinados.
Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2) Interfaces Gráficas VI: Os Controles Deslizante e Deslizante Duplo (Capítulo 2)
No artigo anterior, nós enriquecemos a nossa biblioteca com quatro controles que são frequentemente usados ​​nas interfaces gráficas: caixa de seleção, campo de edição, campo de edição com caixa de seleção e a lista combinada com a caixa de seleção. O segundo capítulo da sexta parte será dedicado aos controles deslizante e deslizante duplo.
Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência
É essencial detectar se um mercado é lateral ou se o mesmo não está para muitas estratégias. Usando o conhecido ADX, demonstraremos como podemos usar o Testador de Estratégia, tanto para otimizar esse indicador quanto ao nosso objetivo específico, como também podemos decidir se este indicador irá satisfazer as nossas necessidades quanto a variação média dos mercados lateral e de tendência, que são muito importantes para determinar os stops e os alvos dos mercados.