A ordem de destruição e criação do objeto em MQL5

4 dezembro 2013, 13:34
MetaQuotes Software Corp.
1
823

Sobre o que é este artigo?

Os programas MQL5 são escritos em conceitos de programação orientada de objeto (OOP), e que não só abre novas possibilidades para a criação de bibliotecas personalizadas, mas também permite que você use as classes testadas e completas de outros desenvolvedores. Na Biblioteca Padrão, que está incluída no terminal do cliente MetaTrader 5, há centenas de classes que contém milhares de métodos.

Para tirar vantagens da OOP precisamos esclarecer alguns detalhes sobre a criação e exclusão de objetos em programas MQL5. A Criação e Exclusão de Objetos é brevemente descrita na documentação, e este artigo ilustrará este tópico nos exemplos.

Inicialização e deinicialização de variáveis ​globais

A Inicialização de variáveis globais é feita logo após o início do programa MQL5 e antes de qualquer chamada de função. Durante a inicialização, os valores iniciais são atribuídos às variáveis dos tipos simples, e o construtor de objetos é chamado, se for declarado nelas.

Como um exemplo, vamos declarar duas classes de CObjectA eCObjectB. Cada classe tem um construtor e um destrutor, contendo a simples função Print(). Vamos declarar as variáveis daqueles tipos de classe globalmente e executar o script.

//+------------------------------------------------------------------+
//|                                         GlobalVar_TestScript.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Construtor");}
                    ~CObjectA(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Construtor");}
                    ~CObjectB(){Print(__FUNCTION__," Destrutor");}
  };
//--- declarando os objetos globalmente
CObjectA first;
CObjectB second;
//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print(__FUNCTION__);
  }

O resultado do script é mostrado no histórico de especialistas:

GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectA::ObjectA Construtor
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectB::ObjectB Construtor
GlobalVar_TestScript (EURUSD,H1) 13:05:07 OnStart
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectB::~ObjectB Destrutor
GlobalVar_TestScript (EURUSD,H1) 13:05:07 CObjectA::~ObjectA Destrutor

A partir do histórico é claro que a ordem de inicialização corresponde a ordem da declaração de variáveis no script GlobalVar_TestScript.mq5, e a deinicialização é feita na ordem inversa antes do programa MQL5 desenrolar.

Inicialização e deinicialização de variáveis locais

As variáveis locais são deinicializadas no final do bloqueio do programa no qual são declaradas, e na ordem inversa da sua declaração. O bloqueador do programa é um operador composto que pode ser parte do interruptor e ciclo dos operadores (for, while e do-while), corpo da função ou parte do operador if-else.

As variáveis locais são inicializadas somente se elas são usadas no programa. Se uma variável é declarada, mas o bloqueio de código no qual é declarada não é executada, então esta variável não é criada e, então, não é inicializada.

Para ilustrar isto, vamos voltar para as nossas classes de CObjectA e CObjectB e criar as novas classes de CObjectС. As classes ainda são declaradas globalmente, mas as variáveis destas classes são agora declaradas localmente na função OnStart().

Vamos declarar a variável da classe CObjectA explicitamente na primeira linha da função, mas, os objetos das classes CObjectB e CObjectС, serão declaradas em blocos separados, que serão executados dependendo do valor da variável de entrada executada. Em MetaEditor as variáveis da entrada dos programas MQL5 são destacadas com marrom.

//+------------------------------------------------------------------+
//|                                          LocalVar_TestScript.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property script_show_inputs
//--- inclusão de parâmetros
input bool     execute=false;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Construtor");}
                    ~CObjectA(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Construtor");}
                    ~CObjectB(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectC
  {
public:
                     CObjectC(){Print(__FUNCTION__," Construtor");}
                    ~CObjectC(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CObjectA objA;
//--- este bloco NÃO será executado se execute==false
   if(execute)
     {
      CObjectB objB;
     }
//--- este bloco SERÁ executado se execute==false
   if(!execute)
     {
      CObjectC objC;
     }
  }
//+------------------------------------------------------------------+

O resultado é:

LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectA::CObjectA Construtor
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectC::CObjectC Construtor
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectC::~CObjectC Destrutor
LocalVar_TestScript (GBPUSD,H1) 18:29:00 CObjectA::~CObjectA Destrutor

O objeto da classeCObjectA sempre será primeiro inicializado automaticamente, não importa que valor tenha o parâmetro de entrada executado. Então ambos objetos, objB ou objeto objC, são automaticamente inicializados - dependendo em que bloco é executado de acordo ao valor do parâmetro de entrada executado. Por padrão, este parâmetro tem um valor falso, e neste caso, após a inicialização da variável do objA vem a inicialização da variável do objC. Isto é óbvio na execução do construtor e do destrutor.

Mas qualquer que seja a ordem de inicialização (independentemente de executar o parâmetro), a deinicialização das variáveis do tipo complexas é feita na ordem inversa de sua inicialização. Isto se aplica tanto aos objetos de classe local quanto global criados automaticamente. Neste caso, não há diferença entre eles.

Inicialização e deinicialização dos objetos criados dinamicamente

Em MQL5 objetos compostos são inicializados automaticamente, mas se você quiser controlar manualmente o processo de criação de objetos você deve usar os ponteiros do objeto. Uma variável declarada como um ponteiro de objeto de alguma classe não contém o objeto em si e não há inicialização automática daquele objeto.

Os ponteiros podem ser declarados localmente e/ou globalmente, e ao mesmo tempo eles podem ser inicializados com valor vazio NULL do tipo herdado. A criação do objeto só é feita quando o operador new é aplicado ao ponteiro do objeto e não depende da declaração do ponteiro do objeto.

Os objetos criados dinamicamente são deletados usando o operador delete, por isso, temos que lidar com isso. Como exemplo, vamos globalmente declarar duas variáveis: uma do tipo CObjectA, e uma do tipo CObjectB, e outra variável do tipo CObjectC, com ponteiro de objeto.

//+------------------------------------------------------------------+
//|                                       GlobalVar_TestScript_2.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
class CObjectA
  {
public:
                     CObjectA(){Print(__FUNCTION__," Construtor");}
                    ~CObjectA(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectB
  {
public:
                     CObjectB(){Print(__FUNCTION__," Construtor");}
                    ~CObjectB(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CObjectC
  {
public:
                     CObjectC(){Print(__FUNCTION__," Construtor");}
                    ~CObjectC(){Print(__FUNCTION__," Destrutor");}
  };
CObjectC *pObjectC;
CObjectA first;
CObjectB second;
//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   pObjectC=new CObjectC;
   Print(__FUNCTION__);
   delete(pObjectC);
  }
//+------------------------------------------------------------------+

Apesar do fato, tal ponteiro de objeto criado dinamicamente, pObjectC, é declarado antes das variáveis​ estáticas em primeiro lugar e em segundo lugar, este próprio objeto é inicializado apenas quando ele é criado pelo operador new. Neste exemplo, o operador new está na função OnStart().

GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectA::CObjectA Construtor
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectB::CObjectB Construtor
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectC::CObjectC Construtor
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 OnStart
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectC::~CObjectC Destrutor
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectB::~CObjectB Destrutor
GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 CObjectA::~CObjectA Destrutor

Quando a execução do programa na função OnStart() alcança o operador:

   pObjectC=new CObjectC;

o objeto é inicializado e o construtor para este objeto é chamado. Em seguida o programa executa esta string:

   Print(__FUNCTION__);

que gera o seguinte texto no histórico:

GlobalVar_TestScript_2 (EURUSD,H1) 15:03:21 OnStart

e, então, os objetos criados dinamicamente são deletados pela chamada do operador delete:

   delete(pObjectC);

Assim, os objetos são dinamicamente inicializados durante a sua criação pelo operador new e excluídos pelo operador delete.

Requisito obrigatório: todos os objetos criados usando a expressão object_pointer = new class_name, devem sempre ser excluídos usando operador delete (object_pointer). Se por alguma razão o objeto criado dinamicamente (depois do final do bloco quando foi inicializado), não foi deletado usando o operador delete, uma mensagem correspondente será mostrada no histórico do Expert.


Deletando objetos criados dinamicamente

Como foi mencionado mais cedo, cada objeto criado dinamicamente é inicializado usando o operador new e deve sempre ser deletado usando o operador delete. Mas não esqueça que o operador new cria um objeto e retorna um ponteiro para esse objeto. A criação do objeto em si não está na variável contendo o ponteiro objeto. Você pode declarar diversos ponteiros e atribuir-lhes o mesmo ponteiro objeto.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_1.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  classe simples                                                  |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Construtor");}
                    ~CItem(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declarando a primeira matriz do ponteiro do objeto 
   CItem* array1[5];
//--- declarando a primeira matriz do ponteiro do objeto 
   CItem* array2[5];
//--- preenchendo matrizes no loop
   for(int i=0;i<5;i++)
     {
      //--- criação de um ponteiro para a primeira matriz usando operador new
      array1[i]=new CItem;
      //--- criando um ponteiro para a segunda matriz com uma cópia da primeira matriz
      array2[i]=array1[i];
     }
   // Nós "esquecemos" de apagar os objetos antes de sair da função. See "Experts" tab.
  }
//+------------------------------------------------------------------+

A saída diz que há diversos objetos não deletados à esquerda. Mas haverá apenas 5 objetos não deletados em vez de 10, como você pode pensar, porque o operador new criou apenas 5 objetos.

(GBPUSD,H1) 12:14:04 CItem::CItem Construtor
(GBPUSD,H1) 12:14:04 CItem::CItem Construtor
(GBPUSD,H1) 12:14:04 CItem::CItem Construtor
(GBPUSD,H1) 12:14:04 CItem::CItem Construtor
(GBPUSD,H1) 12:14:04 CItem::CItem Construtor
(GBPUSD,H1) 12:14:04 5 objetos esquerdos restaurados

Mesmo se o destrutor para o objeto criado dinamicamente não seja chamado (o objeto não é excluído usando operador delete), a memória ainda assim será apagada. Mas no histórico de "Experts" diz que objeto não foi excluído. Isto pode lhe ajudar a encontrar o gerenciamento do objeto impróprio e concertar o erro.

No próximo exemplo vamos tentar deletar os ponteiros em cada uma das duas matrizes de ponteiro matriz 1 e 2.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_2.mq5 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  classe simples                                                  |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Construtor");}
                    ~CItem(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declarando a primeira matriz do ponteiro do objeto
   CItem* array1[5];
//--- declarando a segunda matriz do ponteiro do objeto
   CItem* array2[5];
//--- preenchendo matrizes no loop
   for(int i=0;i<5;i++)
     {
      //--- criação de um ponteiro para a primeira matriz usando operador new
      array1[i]=new CItem;
      //--- criando um ponteiro para a segunda matriz com uma cópia da primeira matriz
      array2[i]=array1[i];
     }
//--- apagando objetos usando os ponteiros da segunda matriz
   for(int i=0;i<5;i++) delete(array2[i]);
//--- vamos tentar apagar objetos usando ponteiros da primeira matriz
   for(int i=0;i<5;i++) delete(array2[i]);
// na aba Histórico existem mensagens sobre a tentativa de se apagar ponteiros inválidos
  }
//+------------------------------------------------------------------+

O resultado na tabela Experts é diferente agora.

(GBPUSD,H1) 15:02:48 CItem::CItem Construtor
(GBPUSD,H1) 15:02:48 CItem::CItem Construtor
(GBPUSD,H1) 15:02:48 CItem::CItem Construtor
(GBPUSD,H1) 15:02:48 CItem::CItem Construtor
(GBPUSD,H1) 15:02:48 CItem::CItem Construtor
(GBPUSD,H1) 15:02:48 CItem::~CItem Destrutor
(GBPUSD,H1) 15:02:48 CItem::~CItem Destrutor
(GBPUSD,H1) 15:02:48 CItem::~CItem Destrutor
(GBPUSD,H1) 15:02:48 CItem::~CItem Destrutor
(GBPUSD,H1) 15:02:48 CItem::~CItem Destrutor
(GBPUSD,H1) 15:02:48 excluir ponteiro inválido
(GBPUSD,H1) 15:02:48 excluir ponteiro inválido
(GBPUSD,H1) 15:02:48 excluir ponteiro inválido
(GBPUSD,H1) 15:02:48 excluir ponteiro inválido
(GBPUSD,H1) 15:02:48 excluir ponteiro inválido

Os objetos criados Citem foram excluídos com êxito no primeiro ciclo for(), mas outras tentativas de excluir objetos que não existem, no segundo loop, causou algumas mensagens sobre ponteiros inválidos. O objeto criado dinamicamente deve ser excluído uma vez, e antes do uso de qualquer ponteiro de objeto ele deve ser verificado com a função CheckPointer().

Verificação do ponteiro usando a função CheckPointer()

O CheckPointer() é usado para verificar ponteiros e permite identificar o tipo de ponteiro. Quando você está trabalhando com objetos criados dinamicamente existem duas formas possíveis:

  • Restaurando-o no fim do bloco de execução;
  • Tentar excluir o objeto já eliminado.

Vamos dar outro exemplo que ilustra a interrelação dos objetos. Vamos criar duas classes: a primeira classe CItemArray contém a matriz do ponteiro para outra classe CItem.

//+------------------------------------------------------------------+
//|                                        LocalVar_TestScript_3.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//|  classe simples                                                  |
//+------------------------------------------------------------------+
class CItem
  {
public:
                     CItem(){Print(__FUNCTION__," Construtor");}
                    ~CItem(){Print(__FUNCTION__," Destrutor");}
  };
//+------------------------------------------------------------------+
//| classe contendo a matriz do ponteiro da classe CItem             |
//+------------------------------------------------------------------+
class CItemArray
  {
private:
   CItem            *m_array[];
public:
                     CItemArray(){Print(__FUNCTION__," Construtor");}
                    ~CItemArray(){Print(__FUNCTION__," Destrutor");Destroy();}
   void               SetArray(CItem &array[]);
protected:
   void               Destroy();
  };
//+------------------------------------------------------------------+
//|  preenchendo a matriz dos ponteiros                              |
//+------------------------------------------------------------------+
CItemArray::SetArray(CItem &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++)m_array[i]=GetPointer(array[i]);
  }
//+------------------------------------------------------------------+
//|  liberando                                                       |
//+------------------------------------------------------------------+
CItemArray::Destroy(void)
  {
   for(int i=0;i<ArraySize(m_array);i++)
     {
      if(CheckPointer(m_array[i])!=POINTER_INVALID)
        {
         if(CheckPointer(m_array[i])==POINTER_DYNAMIC) delete(m_array[i]);
        }
      else Print("Ponteiro inválido para apagar");
     }
  }

As próprias classes não contêm qualquer erro, mas seu uso pode trazer surpresas. A primeira variante do script:

//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItemArray items_array;
   CItem array[5];
   items_array.SetArray(array);
  }

Ao executar esta variante do script, será exibida a seguinte mensagem:

(GBPUSD,H1) 16:06:17 CItemArray::CItemArray Construtor
(GBPUSD,H1) 16:06:17 CItem::CItem Construtor
(GBPUSD,H1) 16:06:17 CItem::CItem Construtor
(GBPUSD,H1) 16:06:17 CItem::CItem Construtor
(GBPUSD,H1) 16:06:17 CItem::CItem Construtor
(GBPUSD,H1) 16:06:17 CItem::CItem Construtor
(GBPUSD,H1) 16:06:17 CItem::~CItem Destrutor
(GBPUSD,H1) 16:06:17 CItem::~CItem Destrutor
(GBPUSD,H1) 16:06:17 CItem::~CItem Destrutor
(GBPUSD,H1) 16:06:17 CItem::~CItem Destrutor
(GBPUSD,H1) 16:06:17 CItem::~CItem Destrutor
(GBPUSD,H1) 16:06:17 CItemArray::~CItemArray Destrutor
(GBPUSD,H1) 16:06:17 Ponteiro inválido para excluir
(GBPUSD,H1) 16:06:17 Ponteiro inválido para excluir
(GBPUSD,H1) 16:06:17 Ponteiro inválido para excluir
(GBPUSD,H1) 16:06:17 Ponteiro inválido para excluir

Como a declaração da variável da classe CItemArray vem em primeiro lugar, ela é inicializada primeiro e a classe do destrutor é chamada. Então aarray[5] é declarada, contendo ponteiros de objeto da classe CItem. É por isso que vemos cinco mensagens sobre a inicialização em cada objeto.

Na última linha deste script simples, os ponteiros da matriz, array[5], são copiados para a matriz interna dos ponteiros dos objetos chamados items_array (Ver 'LocalVar_TestScript_4.mq5').

   items_array.SetArray(array);

Por agora, o script para a execução e os objetos criados automaticamente são excluídos automaticamente. O primeiro objeto a ser deletado é um que foi inicializado por último - são os ponteiros da matriz array[5]. Cinco registros do histórico sobre a chamada do destrutor da classe CItem confirmam isto. Em seguida, vem a mensagem sobre a chamada do destrutor para o objeto items_array, como foi inicializado logo antes da variável array[5].

Mas o destrutor da classe CArrayItem chama a função de proteção Destroy(), que tentar excluir objetos CItem através dos ponteiros na m_array[] via operador delete. O ponteiro é verificado primeiro e se é inválido, os objetos não são deletados, e a mensagem "ponteiro inválido para deleção" é exibida.

Há 5 tais registro no Histórico, por exemplo, todos os ponteiros na matriz m_array[] são inválidos. Isto acontece porque os objetos destes ponteiros já forma deinicializados durante a deinicialização da matriz array[].

Vamos ajustar o nosso script, trocando as declarações das variáveis de items_array e items_array[].

//+------------------------------------------------------------------+
//| Função de inicialização do Script                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CItem array[5];
   CItemArray items_array;
   items_array.SetArray(array);
  }

O script correto não produz erros. Primeiro, a variável items_array  foi deinicializada, como declarado por último. Durante a deinicialização o destrutor da classe ~CItemArray() foi chamado, que, por sua vez, chamou a função Destroy().

Nesta ordem de declaração, items_array é deletada antes da matriz array[5]. Na função Destroy(), que é chamada a partir do destrutor items_array, os objetos do ponteiro ainda existem, então não ocorre erro.

A deleção correta do objetos criados dinamicamente também pode ser vista no exemplo da função GetPointer(). Neste exemplo a função Destroy() é explicitamente chamada para assegurar a ordem apropriada da deleção dos objetos.

Conclusão

Como você pode ver, a criação e exclusão dos objetos são feitas de forma simples. Apenas reveja todos os exemplos deste artigo e você poderá fazer suas próprias variantes de interrelações entre os objetos automaticamente e dinamicamente criados.

Você sempre deve verificar suas classes para a correta deleção de objetos e projetar corretamente seus destrutores, para que não haja erros ao acessar ponteiros inválidos. Lembre-se que se você usar os objetos que são criados dinamicamente usando o operador new, você deve excluir corretamente esses objetos usando operador delete.

A partir deste artigo você aprendeu apenas a ordem da criação e deleção dos objetos em MQL5. Organizar um trabalho seguro com ponteiros de objeto está além do escopo deste artigo.

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/28

Últimos Comentários | Ir para discussão (1)
Alexander Knyazev
Alexander Knyazev | 23 dez 2013 em 12:31
Attached source code files and source code insets in HTML code are now completely translated into Portuguese for your convenience.
Limitações e verificações em Expert Advisors Limitações e verificações em Expert Advisors

É permitido negociar este símbolo na segunda? Há dinheiro suficiente para abrir posição? Qual o tamanho da perda se o Stop Loss acionar? Como limitar o número de ordens pendentes? A operação de negócio foi executada na barra atual ou na anterior? Se um robô de negócio não puder realizar este tipo de verificações, então qualquer estratégia de negócio pode se tornar uma de perda. Este artigo mostra os exemplos de verificações que são úteis em qualquer Expert Advisor.

Aplicar um indicador a outro Aplicar um indicador a outro

Ao escrever um indicador que usa a forma curta da solicitação da função OnCalculate(), você pode deixar passar o fato de que um indicador pode ser calculado, não apenas pelos dados de preço, mas também pelos dados de algum outro indicador (não importando se for incorporado ou um personalizado). Você quer melhorar um indicador para sua aplicação correta a outros dados do indicador? Vamos analisar neste artigo todas as etapas necessárias para tal modificação.

Novas oportunidades com o MetaTrader 5 Novas oportunidades com o MetaTrader 5

O MetaTrader 4 ganhou sua popularidade com negociantes de todo o mundo e parecia que nada mais poderia ser desejado. Com sua alta velocidade de processamento, estabilidade, grande variedade de possibilidades para escrever indicadores, Expert Advisors e sistemas de negócio informativos, e a habilidade de escolher entre centenas de diferentes brokers, - o terminal se diferenciava-se muito do restante. Mas, o tempo não para e nos deparamos tendo que escolher entre o MetaTrade 4 ou MetaTrade 5. Neste artigo, descreveremos as principais diferenças do terminal da 5ª geração a nosso favor.

Acelerar cálculos com a rede MQL5 em nuvem Acelerar cálculos com a rede MQL5 em nuvem

Quantos núcleos você tem no seu computador em casa? Quantos computadores você pode utilizar para otimizar a estratégia de negócio? Mostraremos aqui como usar a Rede de nuvem do MQL5 para acelerar os cálculos recebendo energia computacional através do mundo com apenas um clique no mouse. A frase "tempo é dinheiro" se torna ainda mais atual a cada ano que passa, e não podemos nos dar ao luxo de esperar por cálculos importantes por dezenas de horas ou até mesmo dias.