English Русский Deutsch 日本語
preview
Compreendendo os Paradigmas de Programação (Parte 2): Uma Abordagem Orientada a Objetos para Desenvolver um Expert Advisor de Ação de Preço

Compreendendo os Paradigmas de Programação (Parte 2): Uma Abordagem Orientada a Objetos para Desenvolver um Expert Advisor de Ação de Preço

MetaTrader 5Exemplos | 24 julho 2024, 10:31
11 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

Introdução

No primeiro artigo apresentei paradigmas de programação e foquei em como implementar programação procedural no MQL5. Também explorei a programação funcional. Após obter uma compreensão mais profunda de como a programação procedural funciona, criamos um expert advisor básico de ação de preço usando o indicador de média móvel exponencial (EMA) e dados de preços de velas.

Este artigo aprofundará no paradigma de programação orientada a objetos. Em seguida, aplicaremos esse conhecimento para transformar o código procedural do expert advisor desenvolvido anteriormente do primeiro artigo em código orientado a objetos. Este processo aprofundará nossa compreensão das principais diferenças entre esses dois paradigmas de programação.

Enquanto você lê, tenha em mente que o objetivo principal não é mostrar uma estratégia de ação de preço. Em vez disso, meu objetivo é ilustrar e ajudá-lo a obter uma compreensão mais profunda de como vários paradigmas de programação funcionam e como podemos implementá-los no MQL5. O expert advisor de ação de preço simples que desenvolvemos é o objetivo secundário e serve como um guia para demonstrar como podemos aplicar isso em um exemplo do mundo real.


Compreendendo a Programação Orientada a Objetos

A programação orientada a objetos (também abreviada como POO) é um estilo de codificação que organiza o código em torno da ideia de objetos. Ela considera principalmente itens como modelos para coisas ou conceitos reais.

Quando se aventuram na programação orientada a objetos, os iniciantes frequentemente têm perguntas específicas. Começarei abordando essas questões, pois isso ajudará a solidificar sua compreensão desse paradigma de programação.


O que é uma classe na programação orientada a objetos?


Uma classe é um projeto para criar objetos. Ela possui um conjunto de propriedades (atributos) que descrevem as características do objeto e funções (métodos) que executam as diferentes tarefas requeridas.

Deixe-me usar um telefone como exemplo para explicar melhor o paradigma orientado a objetos.

Imagine que você está começando uma nova empresa de fabricação de telefones e está em uma reunião com o chefe do departamento de design de produtos. Seu objetivo é criar um projeto para o telefone ideal que sua empresa produzirá. Nesta reunião, você discute os recursos e funcionalidades essenciais que todo telefone deve ter.

Você começa criando um projeto que será o ponto de partida de todo telefone que sua empresa produzirá. Na programação orientada a objetos, esse projeto é chamado de classe.

O designer de produtos sugere que, para fazer o projeto, você deve primeiro criar uma lista de diferentes tarefas que um telefone é capaz de executar. Você cria a seguinte lista de tarefas:

  • Fazer e receber chamadas telefônicas.
  • Enviar e receber mensagens de texto curtas (SMS).
  • Enviar e receber dados pela internet.
  • Tirar fotos e gravar vídeos.

Na programação orientada a objetos, as tarefas descritas no projeto acima são chamadas de métodos. Métodos são iguais a funções comuns, mas quando criados em uma classe, são chamados de métodos ou funções membros.

Você então decide que todo telefone deve ter propriedades e características que melhor o descrevam. Você faz um brainstorming por cinco minutos e cria a seguinte lista:

  • Número do modelo.
  • Cor.
  • Tipo de entrada.
  • Tipo de tela.
  • Tamanho da tela.

Na programação orientada a objetos, as propriedades e características descritas no projeto (classe) são chamadas de atributos ou variáveis membros. Atributos são declarados em uma classe como variáveis.


O que é um objeto na programação orientada a objetos?


Um objeto é uma implementação de uma classe. Em termos mais simples, uma classe é o plano ou projeto no papel e o objeto é a implementação real do plano ou projeto na vida real.

Continuando com nosso exemplo da empresa de telefones, você e seu designer de produto terminaram de projetar o projeto do telefone no papel. Você decide produzir dois tipos diferentes de telefones para diferentes mercados consumidores de telefones. O primeiro modelo será uma versão básica que pode apenas fazer ou receber chamadas e enviar ou receber mensagens de texto. O segundo modelo será uma versão avançada (smartphone) com todos os recursos do primeiro modelo básico, uma câmera de alta qualidade, uma grande bateria e uma tela sensível ao toque de alta resolução.

Você vai animadamente ao departamento de engenharia, entrega o projeto do telefone (classe) ao engenheiro-chefe e dá instruções para ele trazer seu design do projeto à vida. Ele imediatamente começa a trabalhar no projeto. Leva aproximadamente uma semana para os engenheiros terminarem de projetar os telefones. Quando eles terminam, entregam os produtos acabados para você testar.

Os telefones que você agora segura em suas mãos são os objetos derivados da classe (projeto). O modelo de telefone básico implementa apenas alguns métodos da classe, enquanto o modelo de telefone avançado (smartphone) implementa todos os métodos da classe.

Deixe-me demonstrar este exemplo de telefone com algum código. Siga os passos abaixo para criar um arquivo de classe em seu MetaEditor IDE.


Passo 1: Abra o MetaEditor IDE e inicie o Assistente MQL usando o botão do item de menu Novo.

Novo arquivo de inclusão do assistente do MetaEditor


Passo 2: Selecione a opção Nova Classe e clique em Próximo.

Novo arquivo de classe do assistente MQL


Passo 3: Na janela Criando classe, selecione a caixa de entrada Nome da Classe:, digite PhoneClass como o nome da classe, e na caixa de entrada Arquivo de Inclusão: digite 'Experts\OOP_Article\PhoneClass.mqh' para salvar o arquivo da classe na mesma pasta que nosso código-fonte do Expert Advisor. Deixe a caixa de entrada Classe Base: vazia. Clique em Concluir para gerar um novo arquivo de classe MQL5.

Detalhes do novo arquivo de classe do assistente MQL


Agora temos um arquivo de classe MQL5 em branco. Eu adicionei alguns comentários para nos ajudar a dividir as diferentes partes da classe. Codificar uma nova classe MQL5 agora é um processo simples e é feito automaticamente pelo Assistente MQL no MetaEditor IDE. Estude a sintaxe abaixo, pois ela contém o ponto de partida de um arquivo de classe MQL5 devidamente estruturado.

class PhoneClass //class name
  {
private: //access modifier

public:  //access modifier
                     PhoneClass(); //constructor method declaration
                    ~PhoneClass(); //destructor method declaration
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
PhoneClass::PhoneClass() //constructor method definition
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
PhoneClass::~PhoneClass() //destructor method definition
  {
  }
//+------------------------------------------------------------------+


Não preste atenção ao código #properties pois não é relevante para o tópico em questão. A sintaxe importante começa na linha onde temos a abertura da sintaxe da classe em: class PhoneClass { logo abaixo da linha #property version "1.00".

//+------------------------------------------------------------------+
//|                                                   PhoneClass.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//---------------------------------------
//Ignore the code segment above
//---------------------------------------


Vamos discutir as diferentes partes do arquivo de classe que acabamos de gerar.

Após a abertura da chave da classe, você encontra dois modificadores de acesso: private e public. Vou explicar em detalhes o que são quando eu abordar o tópico de herança.

Abaixo do modificador de acesso privado é onde adicionaremos os atributos da classe (propriedades e características do telefone) do nosso projeto de telefone. Adicionaremos essas propriedades como variáveis globais.

private: //access modifier
   //class attributes
   int               modelNumber;
   string            phoneColor;
   string            inputType;
   string            screenType;
   int               screenSize;

A seguir, logo abaixo do modificador de acesso public: estão as declarações dos métodos construtor e destrutor. Esses dois métodos são semelhantes às funções padrão do Expert Advisor OnInit() e OnDeInit(). O construtor da classe realiza operações semelhantes ao OnInit(), enquanto o destrutor da classe executa tarefas semelhantes ao OnDeinit().

public: //access modifier
                     PhoneClass(); //constructor method declaration
                    ~PhoneClass(); //destructor method declaration


O que são construtores e destrutores na programação orientada a objetos?


Construtores

Um construtor é um método especial dentro de uma classe que é chamado e executado automaticamente quando um objeto dessa classe é criado. Seu objetivo principal é inicializar os atributos do objeto e realizar quaisquer ações de configuração necessárias para que o objeto esteja em um estado válido e atualizado.

Principais características dos construtores:

  • Mesmo Nome da Classe: O método construtor tem o mesmo nome da classe. Esta convenção de nomenclatura ajuda a linguagem de programação a identificar e associar o construtor à classe. No MQL5, todos os construtores são do tipo void por padrão, ou seja, não retornam um valor.
  • Inicialização: Construtores são responsáveis por inicializar os atributos de um objeto com valores padrão ou fornecidos. Isso garante que o objeto comece com um estado bem definido. Demonstrar-lhe-ei como isso funciona em nossa PhoneClass abaixo.
  • Execução automática: O construtor é chamado ou executado automaticamente quando um objeto é criado. Isso ocorre no momento em que o objeto é instanciado.
  • Parâmetros Opcionais: Construtores podem aceitar parâmetros, permitindo personalização durante a criação do objeto. Esses parâmetros fornecem valores que o construtor usa para definir o estado inicial do objeto.

Você pode ter múltiplos construtores em uma classe, mas eles precisam ser distinguíveis pelos argumentos ou parâmetros que possuem. Dependendo de seus parâmetros, os construtores são categorizados em:

  • Construtor padrão: Este é um construtor sem quaisquer parâmetros.
  • Construtor paramétrico: Este é um construtor que possui parâmetros. Se algum dos parâmetros deste construtor fizer referência a um objeto da mesma classe, então ele se torna automaticamente um construtor de cópia.
  • Construtor de cópia: Este é um construtor que possui um ou mais parâmetros que fazem referência a um objeto da mesma classe.

Precisamos de uma maneira de inicializar ou salvar os atributos da classe (variáveis) com detalhes específicos do telefone toda vez que criarmos um novo objeto PhoneClass. Realizaremos essa tarefa usando um construtor paramétrico. Vamos alterar o construtor padrão atual e convertê-lo em um construtor paramétrico com cinco parâmetros. Comente a declaração da função do construtor padrão antes de declarar e definir o novo construtor paramétrico abaixo dela.

   //PhoneClass();
   //constructor declaration and definition
   PhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen)
     {      
      modelNumber = modelNo;
      phoneColor  = colorOfPhone;
      inputType   = typeOfInput;
      screenType  = typeOfScreen;
      screenSize  = sizeOfScreen;
     }

Salve e compile o arquivo da classe. Após compilar o arquivo da classe, você notará um erro na linha 40, coluna 13. Erro: PhoneClass - função membro já definida com parâmetros diferentes.

Observe que a numeração do código pode ser diferente para o seu arquivo de classe, dependendo de como você indenta ou estiliza seu código. Para o número de linha correto, consulte o log de erros do compilador do MetaEditor na seção inferior da janela, conforme indicado abaixo.

Erro de compilação de PhoneClass

Declaramos e definimos nosso novo construtor paramétrico em um bloco de código e mais abaixo em nosso código na linha 40 você encontrará outro segmento de código que também está definindo o construtor. Você precisa comentar as linhas 40 a 42 e, quando compilar o arquivo da classe, ele será compilado com sucesso, sem erros ou avisos. (Observe que este segmento de código pode estar em linhas de código diferentes em seu arquivo de classe!)

/*PhoneClass::PhoneClass() //constructor method definition
  {
  }*/


Destrutores

Um destrutor é um método especial dentro de uma classe que é chamado e executado automaticamente quando um objeto é terminado. Seu objetivo principal é a coleta de lixo e a limpeza de quaisquer recursos que o objeto tenha alocado quando o tempo de vida do objeto termina. Isso ajuda a evitar vazamentos de memória e outros problemas relacionados a recursos. Uma classe pode ter apenas um destrutor.

Principais características dos destrutores:

  • Mesmo Nome da Classe: O destrutor compartilha o mesmo nome da classe, mas é prefixado com um caractere til (~). Esta convenção de nomenclatura ajuda a linguagem de programação a identificar e associar o destrutor à classe.
  • Coleta de lixo: Limpeza de quaisquer recursos que o objeto tenha alocado, como memória, strings, arrays dinâmicos, objetos automáticos ou conexões de rede.
  • Execução automática: O destrutor é chamado ou executado automaticamente quando um objeto é terminado.
  • Sem Parâmetros: Todos os destrutores não possuem parâmetros e são do tipo void por padrão, ou seja, não retornam um valor.

Vamos adicionar algum código que imprime algum texto toda vez que o destrutor é executado. Vá para o segmento de definição do código do método destrutor e adicione o código abaixo:

PhoneClass::~PhoneClass() //destructor method definition
  {
   Print("-------------------------------------------------------------------------------------");
   PrintFormat("(ModelNo: %i) PhoneClass object terminated. O DESTRUTOR agora está limpando!", modelNumber);
}

Você notará que quando declaramos ou definimos os métodos construtores e destrutores da classe, não damos a eles um tipo de retorno, ou seja, (void). Especificar um tipo de retorno não é necessário, pois é uma regra simples que todos os construtores e destrutores no MQL5 são do tipo void e o compilador fará isso automaticamente por nós.

Para lhe dar uma compreensão clara de como os construtores e destrutores funcionam, aqui está um breve exemplo: Imagine uma viagem de acampamento onde você e seu amigo se atribuem funções específicas. Seu amigo, responsável por montar a barraca e arrumar tudo na chegada, age como o 'construtor'. Enquanto isso, você, cuidando da embalagem e da limpeza no final da viagem, desempenha o papel do destrutor. Na programação orientada a objetos, construtores inicializam objetos, enquanto destrutores limpam recursos quando o tempo de vida do objeto termina.

Em seguida, adicionaremos os métodos da classe (tarefas que o telefone executará conforme descrito no projeto). Adicione esses métodos logo abaixo da declaração do método destrutor: ~PhoneClass();.

//class methods
   bool              MakePhoneCall(int phoneNumber);
   void              ReceivePhoneCall();
   bool              SendSms(int phoneNumber, string message);
   void              ReceiveSms();
   bool              SendInternetData();
   void              ReceiveInternetData();
   void              UseCamera();
   void virtual      PrintPhoneSpecs();


O que são métodos virtuais no MQL5?

No MQL5, métodos virtuais são funções especiais dentro de uma classe que podem ser substituídas por métodos com o mesmo nome em classes derivadas. Quando um método é marcado como "virtual" na classe base, ele permite que classes derivadas forneçam uma implementação diferente desse método. Este mecanismo é essencial para o polimorfismo, o que significa que objetos de diferentes classes podem ser tratados como objetos de uma classe base comum. Ele permite flexibilidade e extensibilidade na programação orientada a objetos, permitindo que comportamentos específicos sejam definidos em subclasses enquanto mantêm uma interface comum na classe base.

Vou demonstrar como substituir o método PrintPhoneSpecs() mais adiante no artigo quando abordarmos a herança orientada a objetos.

Para codificar a definição do método para o método PrintPhoneSpecs(). Coloque este código abaixo da definição do método destrutor na parte inferior do nosso arquivo de classe.

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   Print("___________________________________________________________");
   PrintFormat("Model: %i Phone Specs", modelNumber);
   Print("---------------------");
   PrintFormat
      (
         "Model Number: %i \nPhoneColor: %s \nInput Type: %s \nScreen Type: %s \nScreen Size: %i\n",
         modelNumber, phoneColor, inputType, screenType, screenSize
      );  
  }

Há duas maneiras de definir um método de classe. 

  1. Dentro do corpo da classe: Você pode declarar e definir um método em uma etapa dentro do corpo da classe, como fizemos anteriormente com o construtor paramétrico. A sintaxe para declarar e definir um método dentro do corpo da classe é idêntica à sintaxe de uma função comum.
  2. Fora do corpo da classe: A segunda maneira é declarar o método dentro do corpo da classe e depois defini-lo fora do corpo da classe, como fizemos com os métodos destrutor e PrintPhoneSpecs(). Para definir um método MQL5 fora do corpo da classe, você deve primeiro começar com o tipo de retorno do método, seguido pelo nome da classe, o operador de resolução de escopo (::), o nome do método e, em seguida, a lista de parâmetros entre parênteses. Em seguida, o corpo do método é colocado entre chaves {}. Esta separação entre declaração e definição é a opção preferida, pois permite uma organização clara da estrutura da classe e seus métodos associados.

O que é o operador de resolução de escopo (::) na programação orientada a objetos?


O :: operador é conhecido como o operador de resolução de escopo, e é usado em C++ e MQL5 para especificar o contexto ao qual uma função ou método pertence. Ele define ou referencia funções ou métodos que são membros de uma classe para nos ajudar a especificar que eles são membros dessa classe específica.

Deixe-me explicar isso com mais detalhes usando a definição do método PrintPhoneSpecs():

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   //method body
  }

Na definição do método PrintPhoneSpecs() acima, você pode ver que o nome da classe é colocado antes do operador de escopo "::". Isso indica que esta função pertence à classe PhoneClass. É assim que você vincula o método à classe à qual ele está associado. O operador :: é essencial para definir e referenciar métodos dentro de uma classe. Ele ajuda a especificar o escopo ou contexto ao qual a função ou método pertence.


Nossa classe também inclui os seguintes métodos declarados que também precisam ser definidos:

  1. MakePhoneCall(int phoneNumber);
  2. ReceivePhoneCall();
  3. SendSms(int phoneNumber, string message);
  4. ReceiveSms();
  5. SendInternetData();
  6. ReceiveInternetData();
  7. UseCamera();

Coloque o código de definição desses métodos acima do segmento de definição do código do método PrintPhoneSpecs(). Veja como seu arquivo de classe deve parecer com as definições dos métodos acima adicionadas:

class PhoneClass //class name
  {
private: //access modifier
   //class attributes
   int               modelNumber;
   string            phoneColor;
   string            inputType;
   string            screenType;
   int               screenSize;

public: //access modifier
   //PhoneClass();
   //constructor declaration and definition
   PhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen)
     {      
      modelNumber = modelNo;
      phoneColor  = colorOfPhone;
      inputType   = typeOfInput;
      screenType  = typeOfScreen;
      screenSize  = sizeOfScreen;
     }
     
   ~PhoneClass(); //destructor method declaration
   
   //class methods
   bool              MakePhoneCall(int phoneNumber);
   void              ReceivePhoneCall();
   bool              SendSms(int phoneNumber, string message);
   void              ReceiveSms();
   bool              SendInternetData();
   void              ReceiveInternetData();
   void              UseCamera();
   void virtual      PrintPhoneSpecs();
  };

/*PhoneClass::PhoneClass() //constructor method definition
  {
  }*/

PhoneClass::~PhoneClass() //destructor method definition
  {
   Print("-------------------------------------------------------------------------------------");
   PrintFormat("(ModelNo: %i) PhoneClass object terminated. The DESTRUCTOR is now cleaning up!", modelNumber);
  }

bool PhoneClass::MakePhoneCall(int phoneNumber) //method definition
  {
      bool callMade = true;
      Print("Making phone call...");
      return(callMade);
  }

void PhoneClass::ReceivePhoneCall(void) { //method definition
      Print("Receiving phone call...");
   }

bool PhoneClass::SendSms(int phoneNumber, string message) //method definition
  {
      bool smsSent = true;
      Print("Sending SMS...");
      return(smsSent);
  }

void PhoneClass::ReceiveSms(void) { //method definition
      Print("Receiving SMS...");
   }

bool PhoneClass::SendInternetData(void) //method definition
  {
      bool dataSent = true;
      Print("Sending internet data...");
      return(dataSent);
  }
  
void PhoneClass::ReceiveInternetData(void) { //method definition
      Print("Receiving internet data...");
   }

void PhoneClass::UseCamera(void) { //method definition
      Print("Using camera...");
   }

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   Print("___________________________________________________________");
   PrintFormat("Model: %i Phone Specs", modelNumber);
   Print("---------------------");
   PrintFormat
      (
         "Model Number: %i \nPhoneColor: %s \nInput Type: %s \nScreen Type: %s \nScreen Size: %i\n",
         modelNumber, phoneColor, inputType, screenType, screenSize
      );  
  }

Encontre o código completo de PhoneClass.mqh anexado no final do artigo.

Agora é hora de criar um objeto PhoneClass. Isso é equivalente a como os engenheiros de telefone transformaram nosso projeto de telefone em um produto físico que foi capaz de realizar diferentes tarefas (por exemplo, fazer e receber chamadas) conforme descrito no exemplo da empresa de telefonia mencionado anteriormente.

Observe que os arquivos de classe são salvos com a extensão .mqh e são referidos como arquivos de inclusão. Crie um novo ExpertAdvisor na mesma pasta que contém nosso PhoneClass.mqh. Salvamos o arquivo PhoneClass.mqh no seguinte caminho: "Experts\OOP_Article". Use o Assistente MQL do MetaEditor para gerar um novo Expert Advisor (Template) e salve-o no seguinte diretório: "Experts\OOP_Article". Nomeie o novo EA como 'PhoneObject.mq5'.

Coloque este código abaixo do segmento de código #property version "1.00" do EA:

// Include the PhoneClass file so that the PhoneClass code is available in this EA
#include "PhoneClass.mqh"

int OnInit()
  {
//---
   // Create instaces or objects of the PhoneClass with specific parameters
   // as specified in the 'PhoneClass' consturctor
   PhoneClass myPhoneObject1(101, "Black", "Keyboard", "Non-touch LCD", 4);
   PhoneClass myPhoneObject2(102, "SkyBlue", "Touchscreen", "Touch AMOLED", 6);

   // Invoke or call the PrintPhoneSpecs method to print the specifications
   myPhoneObject1.PrintPhoneSpecs();
   myPhoneObject2.PrintPhoneSpecs();
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){}
void OnTick(){}

Encontre o código completo de PhoneObject.mq5 anexado no final do artigo.

Vamos analisar o que o código do EA 'PhoneObject.mq5' faz:

Adiciona um Arquivo de Classe Usando uma Declaração Include:

Primeiro, adicionamos nosso código PhoneClass ao EA usando #include "PhoneClass.mqh" para que ele esteja disponível para uso em nosso EA recém-criado (PhoneObject.mq5). Incluímos esta classe em um escopo global, tornando-a disponível em todas as seções do EA.


Cria o Objeto PhoneClass:

Dentro da função OnInit() do EA, criamos duas instâncias ou objetos da PhoneClass. Para criar esses objetos, começamos com o nome da classe seguido por nomes descritivos para as instâncias do telefone (myPhoneObject1 e myPhoneObject2). Em seguida, usamos parênteses para incluir valores para as especificações do telefone, como seu número do modelo, cor, tipo de entrada, tipo de tela e tamanho da tela conforme especificado pelos parâmetros do construtor da PhoneClass.


Chama ou Invoca um Método da Classe:

As linhas; myPhoneObject1.PrintPhoneSpecs() e myPhoneObject2.PrintPhoneSpecs() chamam o método PrintPhoneSpecs() do objeto PhoneClass para imprimir as especificações do telefone.


Exibe as Especificações do Telefone:

Carregue o EA em um gráfico de símbolos no terminal de negociação MT5 para executar o PhoneObjectEA, vá para a janela Toolbox e selecione a aba Experts para verificar as especificações do telefone impressas.

Os dados impressos também exibem uma mensagem de texto do destrutor da 'PhoneClass' ('~PhoneClass()'). Podemos ver que cada objeto de telefone cria um destrutor independente único e o chama na terminação do objeto.

Log de Experts do PhoneObjectEA


O que é herança na programação orientada a objetos?


Herança é um conceito onde uma nova classe, chamada de subclasse ou classe filha, pode herdar atributos e comportamentos (propriedades e métodos) de uma classe existente, conhecida como classe pai ou classe base. Isso permite que a classe filha reutilize e estenda as funcionalidades da classe pai.

Em termos simples, a herança é como uma árvore genealógica. Imagine uma 'classe base' como o 'pai' ou 'mãe'. Esta classe tem características específicas (propriedades e métodos). Agora, pense em uma 'subclasse' como o 'filho' ou 'filha'. A subclasse herda automaticamente todas as características (propriedades e métodos) da classe base, semelhante a um filho herdar características de seus pais.

Por exemplo, se a mãe tem olhos castanhos, a filha também terá olhos castanhos sem precisar declarar isso explicitamente. Na programação, uma subclasse herda métodos e atributos da classe base, criando uma hierarquia para organizar e reutilizar o código.

Essa estrutura de "família" ajuda a organizar e reutilizar o código. A criança (subclasse) obtém tudo o que o pai (classe base) possui e pode até adicionar seus próprios recursos exclusivos. Programadores usam diferentes termos para esses "membros da família":

  • Classe base: classe pai, superclasse, classe raiz, classe fundação, classe mestre
  • Subclasse: classe filha, classe derivada, classe descendente, classe herdeira

Abaixo está como podemos implementar a herança no contexto do nosso código fornecido PhoneClass:

  • A PhoneClass serve como uma classe base (projeto) que define os blocos de construção básicos da funcionalidade do telefone.
  • Vamos criar outra classe para implementar o modelo de telefone de alto padrão (smartphone) que discutimos anteriormente no exemplo da empresa de telefonia.
  • Vamos nomear esta nova classe de SmartPhoneClass. Ela herdará todas as propriedades e métodos da PhoneClass enquanto introduz novas funcionalidades específicas para smartphones e sobrescreve o método PrintPhoneSpecs() existente da PhoneClass para implementar o comportamento de smartphone.
Para gerar um arquivo de classe em branco para a nova SmartPhoneClass.mqh, siga os passos que usamos para criar a PhoneClass usando o Assistente MQL no MetaEditor. Insira o seguinte código no corpo do SmartPhoneClass.mqh:

#include "PhoneClass.mqh" // Include the PhoneClass file
class SmartPhoneClass : public PhoneClass
{
private:
    string operatingSystem;
    int numberOfCameras;

public:
    SmartPhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen, string os, int totalCameras)
        : PhoneClass(modelNo, colorOfPhone, typeOfInput, typeOfScreen, sizeOfScreen)
    {
        operatingSystem = os;
        numberOfCameras = totalCameras;
    }

    void UseFacialRecognition()
    {
        Print("Using facial recognition feature...");
    }

    // Override methods from the base class if needed
    void PrintPhoneSpecs() override
    {
        Print("-----------------------------------------------------------");
        Print("Smartphone Specifications (including base phone specs):");
        Print("-----------------------------------------------------------");
        PrintFormat("Operating System: %s \nNumber of Cameras: %i", operatingSystem, numberOfCameras);
        PhoneClass::PrintPhoneSpecs(); // Call the base class method
        Print("-----------------------------------------------------------");
    }
};

Aqui está o link direto para o código completo de SmartPhoneClass.mqh

No exemplo acima, SmartPhoneClass herda de PhoneClass. Ela introduz novas propriedades (operatingSystem e numberOfCameras) e um novo método (UseFacialRecognition). O construtor de SmartPhoneClass também chama o construtor da classe base (PhoneClass) usando ': PhoneClass(...)'. Você também notará que sobrescrevemos o método PrintPhoneSpecs() da classe base. Incluímos o especificador override na definição do método PrintPhoneSpecs() na SmartPhoneClass para informar ao compilador que estamos intencionalmente sobrescrevendo um método da classe base.

Dessa forma, você pode criar instâncias de SmartPhoneClass que incluem todos os recursos de um telefone comum (PhoneClass) e novos recursos adicionais específicos para smartphones.


Modificadores de Acesso

Os modificadores de acesso desempenham um papel crucial na herança na programação orientada a objetos, definindo como os membros (atributos e métodos) de uma classe base são herdados e acessados nas classes derivadas.

  • Público: O acesso público permite que as propriedades e métodos de uma classe sejam acessíveis de fora da classe. Você pode usar ou modificar livremente qualquer membro público de qualquer parte do programa. Este é o nível de acesso mais aberto.
  • Privado: O acesso privado restringe a visibilidade das propriedades e métodos para acesso ou modificação dentro da própria classe. Membros declarados como privados não são diretamente acessíveis de fora da classe. Isso ajuda a ocultar detalhes de implementação e a garantir a integridade dos dados. Isso é o que se chama encapsulamento.
  • Protegido: O acesso protegido é um meio-termo entre público e privado. Membros declarados como protegidos são acessíveis dentro da classe e suas subclasses (classes derivadas). Isso permite um certo nível de compartilhamento controlado entre classes relacionadas, enquanto ainda restringe o acesso externo.

Para criar o objeto SmartPhoneClass, crie um novo EA e salve-o como SmartPhoneObject.mq5, e insira o código abaixo:

// Include the PhoneClass file so that it's code is available in this EA
#include "SmartPhoneClass.mqh"

int OnInit()
  {
//---
   // Create instaces or objects of the PhoneClass with specific parameters
   // as specified in the 'PhoneClass' consturctor (base/mother class)
   PhoneClass myPhoneObject1(103, "Grey", "Touchscreen", "Touch LCD", 8);
   
   // as specified in the 'SmartPhoneClass' consturctor
   SmartPhoneClass mySmartPhoneObject1(104, "White", "Touchscreen", "Touch AMOLED", 6, "Android", 3);

   // Invoke or call the PrintPhoneSpecs method to print the specifications
   myPhoneObject1.PrintPhoneSpecs(); // base class method
   mySmartPhoneObject1.PrintPhoneSpecs(); // overriden method by the derived class (SmartPhoneClass)
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){}
void OnTick(){}

Encontre o código completo de SmartPhoneObject.mq5 anexado no final do artigo.

Salve e compile o código-fonte SmartPhoneObject.mq5 antes de carregá-lo em um gráfico de símbolos no terminal de negociação MT5 para executar o objeto SmartPhoneClass. Vá para a janela Toolbox e selecione a aba Experts para verificar a saída impressa do EA.


Principais Propriedades da Programação Orientada a Objetos

As seis principais propriedades da POO são:

  1. Encapsulamento: Agrupamento de dados e métodos em uma única unidade (classe) para ocultar detalhes internos.
  2. Abstração: Simplificação de sistemas complexos focando nas propriedades e comportamentos essenciais.
  3. Herança: Permitir que uma classe herde propriedades e comportamentos de outra classe para reutilização de código.
  4. Polimorfismo: Permitir que objetos de diferentes classes sejam tratados como objetos de uma classe base comum para flexibilidade.
  5. Classes e Objetos: Classes são projetos e objetos são instâncias; eles organizam o código de maneira modular.
  6. Passagem de Mensagens: Objetos se comunicam enviando mensagens, promovendo interação.

EIP (Encapsulamento, Herança, Polimorfismo): Estes são os princípios fundamentais da programação orientada a objetos para organização e flexibilidade do código. Ao entender e aplicar essas propriedades principais, você pode escrever códigos mais organizados, manuteníveis e reutilizáveis.


Convenção de Nomeação de Classes em MQL5

A convenção de nomenclatura comumente usada para nomes de classes em MQL5 é prefixá-los com um 'C'. No entanto, não é obrigatório seguir esta convenção. O 'C' significa "classe" e é uma prática comum para deixar claro que um identificador particular representa uma classe.

Por exemplo, você pode ver nomes de classes como CExpert, CIndicator ou CStrategy no código MQL5. Esta convenção de nomenclatura ajuda a distinguir classes de outros tipos de identificadores como funções ou variáveis.

Embora usar 'C' como prefixo seja uma convenção e geralmente recomendada para clareza, o MQL5 não impõe regras estritas quanto à nomeação de classes. Tecnicamente, você pode nomear suas classes sem o prefixo 'C', mas é uma boa prática seguir convenções estabelecidas para melhorar a legibilidade e manutenibilidade do código.


A Abordagem Orientada a Objetos para Desenvolver um EA de Ação de Preço

Agora que você entende tudo sobre o paradigma de programação orientada a objetos, é hora de enfrentar um exemplo prático e converter nosso expert advisor de ação de preço previamente desenvolvido de código procedural para código orientado a objetos. Anexei o código procedural do expert advisor de ação de preço Procedural_PriceActionEMA.mq5 no final deste artigo.

Aqui está uma visão geral rápida das especificidades da estratégia de negociação de ação de preço. Para uma explicação mais detalhada da estratégia de negociação, você pode encontrá-la no primeiro artigo.


A Estratégia EMA de Ação de Preço


A estratégia é muito simples e usa apenas médias móveis exponenciais (EMA) e preços de velas para tomar decisões de negociação. Você deve usar o testador de estratégias para otimizá-la para a melhor configuração de EMA e timeframe. Eu prefiro negociar em timeframes de 1 hora ou superiores para obter melhores resultados.

Regras de Entrada:

  • COMPRA: Abra uma posição de compra quando a vela mais recentemente fechada for uma vela de compra (abertura < fechamento) e seus preços de baixa e alta estiverem acima da linha da Média Móvel Exponencial (EMA).
  • VENDA: Abra uma posição de venda quando a vela mais recentemente fechada for uma vela de venda (abertura > fechamento) e seus preços de baixa e alta estiverem abaixo da linha da Média Móvel Exponencial (EMA).
  • Continue abrindo novas posições de compra ou venda quando uma nova vela for formada, se qualquer uma das condições acima for atendida.

Regras de Saída:

  • Feche automaticamente todas as posições abertas quando a porcentagem de lucro ou perda especificada pelo usuário for alcançada.
  • Alternativamente, use ordens de stop-loss ou take-profit predefinidas para gerenciar e sair das posições.

Visão geral da estratégia EMA de ação de preço


Como o objetivo deste artigo é mostrar como você pode desenvolver a estratégia acima em um EA mql5 usando princípios orientados a objetos, vamos em frente e escrever o código.

Crie Uma Nova Classe CEmaExpertAdvisor


Use o MQL5 Wizard para criar um arquivo de classe em branco com 'EmaExpertAdvisor' como o nome do arquivo de classe e salve-o no seguinte caminho: 'Experts\OOP_Article\PriceActionEMA'. Dentro do novo arquivo de classe EmaExpertAdvisor.mqh, crie uma nova classe com o nome CEmaExpertAdvisor. Usaremos a classe CEmaExpertAdvisor para encapsular o comportamento do EA e as variáveis membros para representar seu estado.

Insira o seguinte código de propriedades/variáveis membros e métodos na classe CEmaExpertAdvisor:

//+------------------------------------------------------------------+
// Include the trade class from the standard library
//--- 
#include <Trade\Trade.mqh>

class CEmaExpertAdvisor
  {

public:
   CTrade            myTrade;
   
public:
   // Constructor
                     CEmaExpertAdvisor(
      long _magicNumber, ENUM_TIMEFRAMES _tradingTimeframe,
      int _emaPeriod, int _emaShift, bool _enableTrading,
      bool _enableAlerts, double _accountPercentageProfitTarget,
      double _accountPercentageLossTarget, int _maxPositions, int _tp, int _sl
   );

   // Destructor
                    ~CEmaExpertAdvisor();
  };

Antes da palavra-chave da classe, incluí a classe Trade da biblioteca padrão do MQL5 para nos ajudar a gerenciar várias operações de trade de maneira eficiente e com menos código. Isso significa que precisamos reescrever os métodos ManageProfitAndLoss() e BuySellPosition(...) para acomodar essa nova atualização eficiente.

#include <Trade\Trade.mqh>

Mais adiante, você verá que instanciei a classe CTrade e criei um objeto pronto para uso com o nome myTrade que usaremos para abrir e fechar novas posições.

//Crie uma instância/objeto da classe CTrade incluída
CTrade myTrade;

Todas as variáveis globais de entrada do usuário do código procedural se tornarão variáveis globais privadas da classe CEmaExpertAdvisor. As variáveis de entrada do usuário do EA precisarão ser inicializadas assim que a classe for instanciada e realizaremos isso passando-as como parâmetros para o construtor. Isso ajudará a encapsular o processo de inicialização dentro da classe.

private:
   // Private member variables/attributes (formerly procedural global variables)
   //------------------------
   // User input varibles
   long              magicNumber;
   ENUM_TIMEFRAMES   tradingTimeframe;
   int               emaPeriod;
   int               emaShift;
   bool              enableTrading;
   bool              enableAlerts;
   double            accountPercentageProfitTarget;
   double            accountPercentageLossTarget;
   int               maxPositions;
   int               TP;
   int               SL;

As variáveis globais restantes do código procedural serão declaradas como públicas e definidas como variáveis globais na classe.

public:
   //--- EA global variables
   // Moving average variables
   double            movingAverage[];
   int               emaHandle;
   bool              buyOk, sellOk;
   string            movingAverageTrend;

   // Strings for the chart comments
   string            commentString, accountCurrency, tradingStatus, accountStatus;

   // Capital management variables
   double            startingCapital, accountPercentageProfit;

   // Orders and positions variables
   int               totalOpenBuyPositions, totalOpenSellPositions;
   double            buyPositionsProfit, sellPositionsProfit, buyPositionsVol, sellPositionsVol;

   datetime          closedCandleTime;//used to detect new candle formations

Sob a declaração do método destrutor, logo acima da sintaxe de fechamento da chave da classe, adicione todas as funções do código procedural como declarações de métodos da classe.

// Class method declarations (formerly procedural standalone functions)
   int               GetInit();
   void              GetDeinit();
   void              GetEma();
   void              GetPositionsData();   
   bool              TradingIsAllowed();
   void              TradeNow();
   void              ManageProfitAndLoss();
   void              PrintOnChart();
   bool              BuySellPosition(int positionType, string positionComment);
   bool              PositionFound(string symbol, int positionType, string positionComment);

Usaremos o estilo de codificação C++ e definiremos todos os métodos da classe abaixo do corpo da classe, como mostrado abaixo:

//+------------------------------------------------------------------+
//|   METHODS DEFINITIONS                                                                |
//+------------------------------------------------------------------+
CEmaExpertAdvisor::CEmaExpertAdvisor(long _magicNumber, ENUM_TIMEFRAMES _tradingTimeframe,
                                   int _emaPeriod, int _emaShift, bool _enableTrading,
                                   bool _enableAlerts, double _accountPercentageProfitTarget,
                                   double _accountPercentageLossTarget,
                                   int _maxPositions, int _tp, int _sl)
  {
   magicNumber = _magicNumber;
   tradingTimeframe = _tradingTimeframe;
   emaPeriod = _emaPeriod;
   emaShift = _emaShift;
   enableTrading = _enableTrading;
   enableAlerts = _enableAlerts;
   accountPercentageProfitTarget = _accountPercentageProfitTarget;
   accountPercentageLossTarget = _accountPercentageLossTarget;
   maxPositions = _maxPositions;
   TP = _tp;
   SL = _sl;
  }
//+------------------------------------------------------------------+
CEmaExpertAdvisor::~CEmaExpertAdvisor() {}
//+------------------------------------------------------------------+
int CEmaExpertAdvisor::GetInit()
  {
    //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetDeinit()
  {
    //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetEma()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetPositionsData()
  {
   //method body....
  }
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::TradingIsAllowed()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::TradeNow()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::ManageProfitAndLoss()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::PrintOnChart()
  {
   //method body....
  } 
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::BuySellPosition(int positionType, string positionComment)
  {
   //method body....
  }
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::PositionFound(string symbol, int positionType, string positionComment)
  {
   //method body....
  }

Todas as definições de método serão idênticas à sintaxe no código procedural, exceto pelos métodos ManageProfitAndLoss() e BuySellPosition(...), que foram atualizados para utilizar o novo objeto myTrade da classe CTrade que importamos anteriormente para o código da classe.

Aqui está o novo e atualizado método ManageProfitAndLoss():

void CEmaExpertAdvisor::ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol) //close the position
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );
               
               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(myTrade.PositionClose(positionTicket, SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * 3)) //success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  myTrade.PrintResult();
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }


Aqui está o novo e atualizado método BuySellPosition(...):

bool CEmaExpertAdvisor::BuySellPosition(int positionType, string positionComment)
  {
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);
   double tpPrice = 0.0, slPrice = 0.0, symbolPrice;
   
   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      
      volumeLot = NormalizeDouble(volumeLot, 2);
      symbolPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      
      if(TP > 0)
        {
         tpPrice = NormalizeDouble(symbolPrice + (TP * _Point), _Digits);
        }
      if(SL > 0)
        {
         slPrice = NormalizeDouble(symbolPrice - (SL * _Point), _Digits);
        }
      //if(myTrade.Buy(volumeLot, NULL, 0.0, 0.0, 0.0, positionComment)) //successfully openend position
      if(myTrade.Buy(volumeLot, NULL, 0.0, slPrice, tpPrice, positionComment)) //successfully openend position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION!");
           }
         myTrade.PrintResult();
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      volumeLot = NormalizeDouble(volumeLot, 2);
      symbolPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(TP > 0)
        {
         tpPrice = NormalizeDouble(symbolPrice - (TP * _Point), _Digits);
        }
      if(SL > 0)
        {
         slPrice = NormalizeDouble(symbolPrice + (SL * _Point), _Digits);
        }
      if(myTrade.Sell(volumeLot, NULL, 0.0, slPrice, tpPrice, positionComment)) //successfully openend position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION!");
           }
           myTrade.PrintResult();
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

Lembre-se de implementar todos os métodos/funções membro com a lógica do código procedural original. Você encontrará o código completo de CEmaExpertAdvisor dentro do arquivo de inclusão EmaExpertAdvisor.mqh, anexado ao final deste artigo.


Crie o Novo EA OOP_PriceActionEMA


Com a conclusão do projeto da estratégia de negociação (classe CEmaExpertAdvisor), é hora de colocá-lo em ação. Vamos dar vida ao nosso projeto criando um objeto real capaz de executar negociações.

Gere um novo expert advisor, nomeando-o "OOP_PriceActionEMA.mq5", e salve-o no caminho de arquivo especificado: 'Experts\OOP_Article\PriceActionEMA'. Este EA será responsável por executar nossa estratégia de negociação.

Comece importando o arquivo de inclusão 'EmaExpertAdvisor.mqh', que contém a classe CEmaExpertAdvisor.

// Inclua o arquivo CEmaExpertAdvisor para que seu código esteja disponível neste EA
#include "EmaExpertAdvisor.mqh"

Em seguida, declaramos e definimos as variáveis de entrada do usuário como variáveis globais. Estas são as variáveis de entrada do usuário para configurar o EA. Elas são semelhantes aos parâmetros que anteriormente eram variáveis globais na versão procedural.

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 15;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 6.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int TP = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int SL = 500;//SL (Stop Loss Points/Pips [Zero (0) to diasable])

Em seguida, crie uma instância de CEmaExpertAdvisor. Esta linha gera uma instância (ea) da classe CEmaExpertAdvisor usando o construtor, inicializando-a com os valores das variáveis de entrada do usuário.

//Create an instance/object of the included CEmaExpertAdvisor class
//with the user inputed data as the specified constructor parameters
CEmaExpertAdvisor ea(
      magicNumber, tradingTimeframe, emaPeriod, emaShift,
      enableTrading, enableAlerts, accountPercentageProfitTarget,
      accountPercentageLossTarget, maxPositions, TP, SL
   );

Na função OnInit, chamamos o método GetInit da instância ea. Este método faz parte da classe CEmaExpertAdvisor e é responsável por inicializar o EA. Se a inicialização falhar, ele retorna INIT_FAILED; caso contrário, retorna INIT_SUCCEEDED.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(ea.GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

Na função OnDeinit, chamamos o método GetDeinit da instância ea. Este método faz parte da classe CEmaExpertAdvisor e é responsável por desinicializar o EA.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ea.GetDeinit();
  }

Na função OnTick, chamamos vários métodos da instância ea, como GetEma, GetPositionsData, TradingIsAllowed, TradeNow, ManageProfitAndLoss e PrintOnChart. Esses métodos encapsulam diferentes aspectos do comportamento do EA, tornando o código mais modular e organizado.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   ea.GetEma();
   ea.GetPositionsData();
   if(ea.TradingIsAllowed())
     {
      ea.TradeNow();
      ea.ManageProfitAndLoss();
     }
   ea.PrintOnChart();
  }

Anexei o código-fonte completo do EA no final deste artigo no arquivo OOP_PriceActionEMA.mq5 .


Testando Nosso EA no Testador de Estratégia

É obrigatório confirmar que nosso EA opera conforme planejado. Isso pode ser feito carregando-o em um gráfico de símbolo ativo e negociando em uma conta demo ou utilizando o testador de estratégias para uma avaliação completa. Embora você possa testá-lo em uma conta demo, por enquanto, usaremos o testador de estratégias para avaliar seu desempenho.

Aqui estão as configurações que aplicaremos no testador de estratégias:

  • Corretora: conta demo MT5 Metaquotes (criada automaticamente na instalação do MT5)

  • Símbolo: EURJPY

  • Período de Teste (Data): 1 ano e 2 meses (jan 2023 a mar 2024)

  • Modelagem: Cada tick baseado em ticks reais

  • Depósito: $10.000 USD

  • Alavancagem: 1:100

Configurações do Testador OOP_PriceActionEMA

Entradas do Testador OOP_PriceActionEMA


Com uma configuração de EA bem otimizada, nossa estratégia simples de ação de preço produz um lucro anual de 41% ao negociar com um capital inicial de $10.000 no par EURJPY, utilizando uma conta de alavancagem de 1:100 e mantendo um baixo rebaixamento de patrimônio de apenas 5%. Essa estratégia mostra potencial e pode ser ainda mais aprimorada integrando indicadores técnicos adicionais ou otimizando-a para obter melhores resultados, especialmente quando aplicada a vários símbolos simultaneamente.


Gráfico de Backtest do OOP_PriceActionEMA

Resultados da Backtest do OOP_PriceActionEMA

Resultados da Backtest do OOP_PriceActionEMA



Conclusão

Chegamos ao final da nossa exploração do paradigma de programação orientada a objetos, uma ferramenta poderosa para construção de software. Navegamos pelas complexidades desse poderoso paradigma que transforma o código em estruturas modulares e reutilizáveis. A mudança da programação procedural para a programação orientada a objetos traz um novo nível de organização, encapsulamento e abstração, fornecendo aos desenvolvedores uma estrutura robusta para gerenciar projetos complexos.

Neste artigo, você também aprendeu como converter código procedural MQL5 para código orientado a objetos usando princípios orientados a objetos, enfatizando a importância de classes, objetos e herança. Ao encapsular dados e funcionalidades dentro de classes, aumentamos a modularidade e a manutenibilidade do código.

Ao assumir seus próprios projetos MQL5, lembre-se de que a força da programação orientada a objetos reside em sua capacidade de modelar entidades e relacionamentos do mundo real, promovendo um código que reflete as complexidades dos sistemas que representa. Anexei todos os arquivos de código-fonte para as várias classes e EAs que criamos no final do artigo.

Obrigado por me acompanhar nessa exploração profunda dos diferentes paradigmas de programação. Que seus empreendimentos de codificação sejam enriquecidos pelos princípios e práticas que descobrimos. Fique atento para mais insights e exemplos práticos em nossa busca contínua para desenvolver sistemas de negociação simples e práticos com a querida e poderosa linguagem MQL5.

Obrigado por investir seu tempo para ler este artigo, desejo-lhe o melhor em sua jornada de desenvolvimento MQL5 e em seus esforços de negociação.



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

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Ciência de Dados e Aprendizado de Máquina (Parte 19): Supercharge Seus Modelos de IA com AdaBoost Ciência de Dados e Aprendizado de Máquina (Parte 19): Supercharge Seus Modelos de IA com AdaBoost
AdaBoost, um poderoso algoritmo de boosting projetado para elevar o desempenho dos seus modelos de IA. AdaBoost, abreviação de Adaptive Boosting, é uma técnica sofisticada de aprendizado em conjunto que integra perfeitamente aprendizes fracos, aprimorando sua força preditiva coletiva.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Inferência causal em problemas de classificação de séries temporais Inferência causal em problemas de classificação de séries temporais
Neste artigo, examinaremos a teoria da inferência causal usando aprendizado de máquina, bem como a implementação de uma abordagem personalizada em Python. A inferência causal e o pensamento causal têm suas raízes na filosofia e psicologia e desempenham um papel importante na nossa compreensão da realidade.