Sincronização de vários gráficos num instrumento em diferentes timeframes

26 abril 2018, 09:58
Dmitriy Gizlyk
0
1 236

Introdução

Desde a época de Alexánder Élder até os dias atuais, os traders tomam decisões de negociação, analisando gráficos em diferentes timeframes. Acho que todos já tivemos a experiência em que colocamos objetos que exibem tendências globais nos gráficos dos timeframes maiores e, em seguida, analisamos o comportamento do preço nos timeframes mais pequenos, aproximando esses objetos. Durante essa análise, os objetos criados anteriormente podem ser corrigidos. As atuais ferramentas do MetaTrader 5 permitem realizar este trabalho num só gráfico, alterando o timeframe e preservando os objetos aplicados. Mas e se você precisar acompanhar o preço simultaneamente em vários gráficos?

Como opção, nessas situações, você pode usar modelos. No entanto, após cada alteração de cada um dos objetos, você precisará salvar novamente o modelo e aplicá-lo novamente a cada um dos gráficos. Neste artigo, proponho automatizar esse processo e aplicar a função de sincronização de gráficos ao indicador.


1. Formulação do problema

A principal tarefa do nosso indicador consiste em sincronizar os gráficos abertos no MetaTrader 5. Ao fazer isto, o programa deve determinar os gráficos necessários para o instrumento. O programa deve monitorar constantemente o estado de todos os objetos gráficos em todos os gráficos do nosso interesse. Cada alteração de objeto num dos gráficos deve ser repetido pelo programa nos gráficos restantes.

O estado de objetos gráficos pode ser monitorado de duas maneiras. A primeira abordagem consiste em determinar a periodicidade, verificar ciclicamente e sincronizar todos os objetos aplicados. A vantagem dessa abordagem é que basta apenas uma instância do programa num dos gráficos. Mas há dois problemas:

  • atraso de sincronização causado pela periodicidade de atualização;
  • é difícil determinar qual é considerado o último estado do objeto.

À primeira vista, ambos os problemas são facilmente resolvidos aumentando a frequência de sincronização e armazenando as últimas informações sincronizadas sobre objetos nas variáveis ​​do programa ou num arquivo em disco. Mas com o aumento do número de objetos nos gráficos, cresce tanto o tempo de execução de cada ciclo quanto a quantidade de informações armazenadas. Neste caso, é necessário considerar que, ao contrário dos EAs e scripts, os indicadores são inicializados no fluxo geral do MetaTrader 5. Por isso, uma carga excessiva no indicador pode levar a atrasos no desempenho de outros indicadores e do terminal em geral.

A segunda abordagem é aplicar rastreamento de alterações de objetos ao terminal e iniciar a sincronização de objetos segundo os eventos do terminal, processando-os na função OnChartEvent. Essa abordagem permite que o programa reaja imediatamente após a criação ou modificação do objeto e, assim, minimize o atraso. Por isso, não precisamos salvar informações sobre todos os objetos sincronizados nem verificar periodicamente seu estado em todos os gráficos. Isso reduz seriamente a carga do programa.

Parece que a segunda opção é perfeita para nós. Mas há também um pequeno problema, isto é, a função OnChartEvent chama apenas os eventos do gráfico em que o programa está sendo executado. Isso não seria motivo para nós parar se fossemos nós a definir um gráfico mestre em que realizar todas alterações. Então, bastaria uma instância do indicador. Mas, quanto à alteração de objetos, não queremos nos limitar a apenas um gráfico. Precisamos executar instâncias do indicador em cada gráfico. Este trabalho pode ser automatizado, graças à função ChartIndicatorAdd.

Diante do exposto, para a realização do programa, escolhi a segunda abordagem. Assim, o trabalho do nosso indicador pode ser dividido em dois blocos.

  1. Após iniciar o indicador, os gráficos abertos são filtrados segundo o símbolo. Verifica-se, nos gráficos abertos, a presença do indicador do instrumento correspondente. Todos os objetos do gráfico atual são clonados nos gráficos selecionados.

  2. Processamento de eventos do gráfico. Quando surge um evento de criação ou alteração de um objeto gráfico, o programa considera - a partir do gráfico - as informações sobre o objeto alterado e transfere esses dados para todos os gráficos da lista criada anteriormente.


No processo de funcionamento de nosso programa, o usuário pode abrir e fechar gráficos. Portanto, para manter a relevância da lista de gráficos, sugiro iniciar o primeiro processo, usando o temporizador com uma certa periodicidade.


2. Organização do trabalho com gráficos

O primeiro processo é a clonagem do indicador nos gráficos. Para resolver este problema, criamos a classe CCloneIndy. Em suas variáveis, mantemos o nome do instrumento, do indicador e o caminho para chamada do indicador. A classe pública terá uma função para selecionar os gráficos desejados, SearchCharts, que receberá o identificador do gráfico original e retornará a matriz dos gráficos selecionados.

class CCloneIndy
  {
private:
   string            s_Symbol;
   string            s_IndyName;
   string            s_IndyPath;

public:
                     CCloneIndy();
                    ~CCloneIndy();
   bool              SearchCharts(long chart,long &charts[]);

protected:
   bool              AddChartToArray(const long chart,long &charts[]);
   bool              AddIndicator(const long master_chart,const long slave_chart);
  };

Após inicializar a classe, salvamos os dados originais em variáveis ​​e especificamos um nome abreviado para o indicador. Nós precisaremos dele para obter os parâmetros necessários ao executar cópias do indicador.

CCloneIndy::CCloneIndy()
  {
   s_Symbol=_Symbol;
   s_IndyName=MQLInfoString(MQL_PROGRAM_NAME);
   s_IndyPath=MQLInfoString(MQL_PROGRAM_PATH);
   int pos=StringFind(s_IndyPath,"\\Indicators\\",0);
   if(pos>=0)
     {
      pos+=12;
      s_IndyPath=StringSubstr(s_IndyPath,pos);
     }
   IndicatorSetString(INDICATOR_SHORTNAME,s_IndyName);
  }

2.1. Função de seleção de gráfico segundo o instrumento

Vamos considerar em detalhes como funciona a função de seleção de gráficos necessários. Primeiro, verificamos o ID do gráfico mestre transmitido nos parâmetros da função. Se não for válido, a função retorna imediatamente false. Se o identificador não for especificado, atribuímos o ID do gráfico atual ao parâmetro. Em seguida, verificamos se o nome do instrumento salvo corresponde ao símbolo do gráfico mestre. Se houver uma discrepância, registramos novamente o nome do instrumento para filtrar os gráficos.

bool CCloneIndy::SearchCharts(long chart,long &charts[])
  {
   switch((int)chart)
     {
      case -1:
        return false;
        break;
      case 0:
        chart=ChartID();
        break;
      default:
        if(s_Symbol!=ChartSymbol(chart))
           s_Symbol=ChartSymbol(chart);
        break;
     }

Na próxima etapa, organizamos uma pesquisa detalhada de todos os gráficos abertos. Se o identificador do gráfico verificado for igual ao ID do gráfico mestre, prossiga para o próximo passo.

   long check_chart=ChartFirst();
   while(check_chart!=-1)
     {
      if(check_chart==chart)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Em seguida, verificamos se o símbolo do gráfico analisado corresponde ao que procuramos. Se o instrumento não atender à condição de pesquisa, vamos para o próximo gráfico.

      if(ChartSymbol(check_chart)!=s_Symbol)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Depois disso, examinamos se nosso indicador está no gráfico que está sendo verificado. Se já estiver anexado, salvamos o ID do gráfico na matriz e passamos para o próximo.

      int handl=ChartIndicatorGet(check_chart,0,s_IndyName);
      if(handl!=INVALID_HANDLE)
        {
         AddChartToArray(check_chart,charts);
         check_chart=ChartNext(check_chart);
         continue;
        }

Se o indicador ainda não estiver no gráfico que está sendo verificado, iniciamos a função para chamá-lo, especificando os identificadores do gráfico mestre e do gráfico que está sendo verificado. Se a operação falhar, vamos para o próximo gráfico e tentamos anexar o indicador, na próxima chamada da função.

      if(!AddIndicator(chart,check_chart))
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Se o indicador foi anexado com sucesso ao gráfico, adicionamos o identificador desse gráfico à matriz. Isso iniciará a função de clonagem de todos os objetos do gráfico mestre para o gráfico a ser verificado.

      AddChartToArray(check_chart, charts);
      check_chart=ChartNext(check_chart);
     }
//---
   return true;
  }

No final do ciclo, saímos da função e retornamos o resultado true.

2.2. Função de chamada do indicador

Vamos nos debruçar sobre a função de associação do indicador ao gráfico. Nos parâmetros, ela recebe os identificadores do gráfico mestre e do gráfico receptor. Sua validade é verificada no início da função, isto é, os identificadores devem ser válidos e não idênticos.

bool CCloneIndy::AddIndicator(const long master_chart,const long slave_chart)
  {
   if(master_chart<0 || slave_chart<=0 || master_chart==slave_chart)
      return false;

Em seguida, obtemos o identificador do indicador no gráfico mestre. Se for inválido, sairemos da função com o resultado false.

   int master_handle=ChartIndicatorGet(master_chart,0,s_IndyName);
   if(master_handle==INVALID_HANDLE)
      return false;

Se o identificador for válido, obtemos parâmetros para chamar um indicador semelhante num novo gráfico. Se o parâmetro não for recebido, sairemos da função com o resultado false.

   MqlParam params[];
   ENUM_INDICATOR type;
   if(IndicatorParameters(master_handle,type,params)<0)
      return false;

Na próxima etapa, na primeira célula da matriz de parâmetros, registramos o caminho salvo - durante a inicialização - para chamar o indicador, enquanto na segunda célula - o identificador do gráfico que o indicador servirá. Reconhecemos o timeframe do gráfico receptor e chamamos o indicador. Se a chamada do indicador falhar, sairemos da função com o resultado false.

   params[0].string_value=s_IndyPath;
   params[1].integer_value=slave_chart;
   ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart);
   int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params);
   if(slave_handle<0)
      return false;

Na conclusão da função, adicionamos o indicador ao gráfico receptor com o identificador recebido.

   return ChartIndicatorAdd(slave_chart,0,slave_handle);
  }

Talvez o leitor tenha dúvidas sobre por que o indicador não pode ser imediatamente anexado ao gráfico receptor, segundo o identificador a partir do gráfico mestre. A resposta a essa pergunta é simples, quer dizer, para fazer isso, o indicador e o gráfico devem corresponder, quanto ao instrumento e timeframe. Já no que diz respeito às nossas tarefas, o timeframe dos gráficos será diferente.

Mais detalhes sobre o código-fonte da classe podem ser encontrados no anexo.

3. Classes para trabalhar com objetos gráficos

O próximo processo do nosso programa é o processamento de eventos e a transferência de dados sobre objetos gráficos para outros gráficos. Antes de começar a escrever o código, você precisa decidir sobre os métodos para transferir dados entre gráficos.

As ferramentas MQL5 permitem os programas de um gráfico alterarem os objetos em outro, especificando o identificador do gráfico nas funções de trabalho com objetos gráficos. Isso é adequado para trabalhar com um pequeno número de gráficos e objetos gráficos.

Mas há outra opção. Anteriormente, decidimos usar eventos para rastrear a alteração de objetos no gráfico. Nós até já escrevemos o código para adicionar cópias do indicador a todos os gráficos que nos interessam. Então, por que não usamos o modelo de evento para enviar dados sobre os objetos que estão sendo alterados entre os indicadores em diferentes gráficos? Damos o trabalho com objetos ao indicador que está no gráfico. Essa abordagem nos permitirá distribuir o trabalho com objetos entre todos os indicadores e criar um modelo assíncrono.

Soa muito bem, mas, como você sabe, a função OnChartEvent recebe apenas 4 parâmetros:

  • identificador de evento;
  • parâmetro de evento do tipo long;
  • parâmetro de evento do tipo double;
  • parâmetro de evento do tipo string.

Como ajustar todas as informações sobre o objeto nesses 4 parâmetros? Vamos simplesmente enviar o ID do evento e gravar todas as informações sobre o objeto num parâmetro do tipo string. Para coletar informações sobre um objeto numa variável do tipo string, usamos os resultados do artigo "Usando armazenamento em nuvem para intercâmbio de dados entre os terminais".

Vamos criar uma classe CCloneObjects que coletará os dados sobre os objetos gráficos e os exibirá no gráfico.

class CCloneObjects
  {
private:
   string            HLineToString(long chart, string name, int part);
   string            VLineToString(long chart, string name, int part);
   string            TrendToString(long chart, string name, int part);
   string            RectangleToString(long chart, string name, int part);
   bool              CopySettingsToObject(long chart,string name,string &settings[]);

public:
                     CCloneObjects();
                    ~CCloneObjects();
//---
   string            CreateMessage(long chart, string name, int part);
   bool              DrawObjects(long chart, string message);
  };

O trabalho das funções desta classe é descrita em detalhes, portanto, acho que não faz sentido repetir a descrição aqui. No entanto, preste atenção a uma nuance, isto é, ao gerar um evento personalizado, usando a função EventChartCustom, o comprimento do parâmetro sparam é limitado a 63 caracteres. Por isso, ao transferir dados sobre o objeto para outros gráficos, dividiremos a mensagem em duas partes. Para este propósito, um parâmetro foi adicionado à função de criação de mensagem, a fim de indicar a porção de dados requerida. Como exemplo, abaixo está o código de função para coletar as informações sobre a linha de tendência.

string CCloneObjects::TrendToString(long chart,string name, int part)
  {
   string result = NULL;
   if(ObjectFind(chart,name)!=0)
      return result;
   
   switch(part)
     {
      case 0:
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,0),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,0))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=1="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,1),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=1="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,1))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_LEFT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_LEFT))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_RIGHT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_RIGHT))+"|";
        break;
      default:
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"="+ObjectGetString(chart,name,OBJPROP_TEXT)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"="+ObjectGetString(chart,name,OBJPROP_TOOLTIP);
        break;
     }
   return result;
  }

O código de todas as funções pode ser explorado, em detalhes, no anexo ao artigo.


4. Coletamos o indicador

Nós preparamos tudo. Agora vamos coletar nosso indicador para rastreamento e clonagem de objetos gráficos. O parâmetro para nosso indicador será apenas um, nomeadamente, o identificador do gráfico.

sinput long    Chart =  0;

Quando o indicador é iniciado, o valor deste parâmetro deve sempre permanecer zero. E o leitor me dirá: "mas para que imprimir um parâmetro que nunca mudará?"

Seu valor mudará quando o indicador for chamado do programa, a fim de ser anexado a outros gráficos.

Na verdade, a função ChartID retorna o identificador do gráfico a partir do qual o indicador foi chamado, e não daquele ao qual ele está anexado. Isso se deve a peculiaridades quanto ao processamento de indicadores no MetaTrader 5. Se o mesmo indicador for chamado várias vezes segundo o mesmo instrumento e timeframe, ele será iniciado apenas uma vez, isto é, na primeira vez que for chamado. As subsequentes chamadas, mesmo de outros gráficos, invocam o indicador que já está em execução. Por sua vez, o indicador trabalha em seu gráfico e retorna informações sobre ele. Assim, ao chamar as instâncias do indicador na classe CCloneIndy, novas cópias do indicador funcionarão e retornarão informações sobre o gráfico a partir do qual foi executada sua primeira instância. Para evitar isso, devemos indicar, especificamente, para cada instância do indicador, o gráfico que será atendido por eles.

Vamos considerar o código do indicador em mais detalhes. No bloco de variáveis ​​globais, declaramos:

  • instâncias das classes criadas anteriormente,
  • variável para armazenar o identificador do gráfico de trabalho
  • e uma matriz para armazenar os identificadores dos gráficos para os quais os objetos serão clonados.
CCloneIndy    *CloneIndy;
CCloneObjects *CloneObjects;
long           l_Chart;
long           ar_Charts[];

Na função OnInit, inicializamos as instâncias de classes para trabalhar com gráficos e objetos.

int OnInit()
  {
//--- indicator Create classes
   CloneIndy   =  new   CCloneIndy();
   if(CheckPointer(CloneIndy)==POINTER_INVALID)
      return INIT_FAILED;
   CloneObjects=  new CCloneObjects();
   if(CheckPointer(CloneObjects)==POINTER_INVALID)
      return INIT_FAILED;

Inicializamos o identificador do gráfico de trabalho.

   l_Chart=(Chart>0 ? Chart : ChartID());

Procuramos gráficos abertos de acordo com o instrumento. Ao mesmo tempo, cópias do indicador serão adicionadas aos gráficos encontrados, se necessário.

   CloneIndy.SearchCharts(l_Chart,ar_Charts);

No final da função, inicializamos o temporizador com um intervalo de 10 segundos. A única tarefa do temporizador será atualizar a lista de gráficos para clonagem de objetos.

   EventSetTimer(10);
//---
   return(INIT_SUCCEEDED);
  }

OnCalculate não executará nenhuma operação. Como mencionado acima, nosso indicador é construído com base num modelo de evento. Portanto, toda a funcionalidade do nosso indicador será concentrada na função OnChartEvent. No início da função, declaramos variáveis ​​locais auxiliares.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   string message1=NULL;
   string message2=NULL;
   int total=0;

Em seguida, na instrução switch, criamos a ramificação de operações dependendo do evento entrante.

O primeiro bloco de operações coletará e transferirá - para outros gráficos - informações sobre como criar ou alterar o objeto. Ele é chamado pelos eventos de criação, alteração ou movimentação do objeto no gráfico. Se esses eventos ocorrerem, o indicador criará duas mensagens com o estado do objeto e, em seguida, executará o ciclo para enviá-las para todos os gráficos com os identificadores a partir de nossa matriz.

   switch(id)
     {
      case CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_OBJECT_DRAG:
        message1=CloneObjects.CreateMessage(l_Chart,sparam,0);
        message2=CloneObjects.CreateMessage(l_Chart,sparam,1);
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
          {
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message1);
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message2);
          }
        break;

O próximo bloco é iniciado quando o objeto é excluído do gráfico. Neste caso, não é necessário preparar uma mensagem separada, uma vez que, para excluir o objeto, basta seu nome, e nós já o temos na variável sparam. Por isso, é imediatamente inicializado o ciclo de envio de mensagens para outros gráficos.

      case CHARTEVENT_OBJECT_DELETE:
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,sparam);
        break;

Os dois blocos seguintes são para processamento de mensagens recebidas de outros gráficos. Quando são recebidas informações sobre como criar ou alterar um objeto, chamamos uma função que exibe o objeto no gráfico. Nos parâmetros da função, transmitimos o identificador do gráfico de trabalho e a mensagem recebida.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DRAG:
        CloneObjects.DrawObjects(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;

Ao receber informações sobre a exclusão do objeto, chamamos a função para excluir um objeto semelhante no gráfico de trabalho.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DELETE:
        ObjectDelete(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;
     }
  }

Detalhes do código do indicador e das classes usadas podem ser encontrados no anexo.


Fim do artigo

O artigo propõe um método para construir um indicador que copia automaticamente objetos gráficos entre gráficos do terminal em tempo real. Ao mesmo tempo, no desenvolvimento, é realizado um intercâmbio de dados bidirecional entre os gráficos abertos no terminal. Este método não limita o usuário no número de gráficos sincronizados. Além disso, o usuário pode criar, modificar e excluir objetos gráficos em qualquer um dos gráficos sincronizados. O funcionamento do indicador é mostrado no vídeo:



Referências

  1. Usando armazenamento em nuvem para intercâmbio de dados entre os terminais

Programas utilizados no artigo:

#
 Nome
Tipo 
Descrição 
1 ChartObjectsClone.mq5  Indicador  Indicador de intercâmbio de dados entre gráficos
2 CloneIndy.mqh  Biblioteca de classes  Classe para seleção de gráficos de acordo com o instrumento
3 CloneObjects.mqh  Biblioteca de classes  Classe para trabalhar com objetos gráficos
4 ChartObjectsClone.mqproj    Arquivo de descrição do projeto

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

Arquivos anexados |
MQL5.zip (56.08 KB)
Comparamos a velocidade de indicadores de armazenamento automático em cache Comparamos a velocidade de indicadores de armazenamento automático em cache

O artigo compara o acesso MQL5 clássico a indicadores com métodos alternativos em estilo MQL4. São consideradas algumas variações de acesso - em estilo MQL4 - a indicadores, nomeadamente, o acesso com cache de identificadores e sem ele. É estudada a contabilização de identificadores de indicadores dentro do kernel MQL5.

Como criar uma Especificação de Requisitos para solicitar um indicador Como criar uma Especificação de Requisitos para solicitar um indicador

Na maioria das vezes, a primeira etapa no desenvolvimento de um sistema de negociação é a criação de um indicador técnico, que pode identificar padrões favoráveis ​​de comportamento do mercado. Um indicador desenvolvido de forma profissional pode ser encomendado no serviço Freelance. Neste artigo você aprenderá a criar uma Especificação de Requisitos apropriada, que o ajudará a obter o indicador desejado mais rapidamente.

Criando EAs multimódulo Criando EAs multimódulo

A linguagem de programação MQL permite concretizar o conceito de design modular de estratégias de negociação. O artigo mostra um exemplo de criação de um Expert Advisor multimodular que consiste em módulos de arquivo compilados separadamente.

Visualizando a otimização de uma estratégia de negociação na MetaTrader 5 Visualizando a otimização de uma estratégia de negociação na MetaTrader 5

O artigo implementa um aplicativo MQL com uma interface gráfica para a visualização estendida do processo de otimização. A interface gráfica utiliza a última versão da biblioteca EasyAndFast. Muitos usuários podem questionar-se sobre a necessidade de utilizar interfaces gráficas em aplicativos MQL. Este artigo demonstra um dos vários casos em que eles podem ser úteis para os traders.