English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Uma solução livre de DLL para comunicação entre os terminais MetaTrader utilizando pipes nomeados

Uma solução livre de DLL para comunicação entre os terminais MetaTrader utilizando pipes nomeados

MetaTrader 5Exemplos | 24 janeiro 2014, 09:01
2 091 0
investeo
investeo

Introdução

Imaginei por um tempo formas possíveis de comunicação entre os terminais MetaTrader 5. Meu objetivo foi utilizar um indicador de pontos e exibir pontos de diferentes fornecedores de cotações em um dos terminais.

A solução natural foi separar os arquivos em um disco rígido. Um terminal poderia escrever dados para o arquivo e o outro poderia ler. Esse método apesar de relevante para enviar mensagens únicas não parece ser o mais eficiente para transmissão de cotações.

Então eu me deparei com um bom artigo por Alexander sobre como exportar cotações para aplicações .NET utilizando serviços WCF e quando eu estava prestes a terminar apareceu outro artigo por Sergeev.

Ambos os artigos estavam próximos do que eu precisava mas eu procurava por uma solução sem DLL que poderia ser utilizada por diferentes terminais, um servindo como um servidor e outro servindo como um cliente. Enquanto procurando na internet encontrei uma observação sugerindo que alguém poderia utilizar pipes nomeados para comunicação e li cuidadosamente a especificação MSDN para a comunicação interprocessos utilizando pipes.

Descobri que pipes nomeados suportam comunicação sobre o mesmo computador ou sobre diferentes computadores sobre a intranet, decidi seguir essa abordagem.

Esse artigo introduz comunicação por pipes nomeados e descreve um processo de projeto da classe CNamedPipes. Ele também inclui o streaming do indicador do tick de teste entre os terminais do MetaTrader 5 e rendimento do sistema no geral.

1. Interprocesso de comunicação utilizando pipes nomeados

Quando pensamos em um pipe típico imaginamos um tipo de cilindro que é utilizado para transportar mídia. Esse também é um termo utilizado para um dos meios de comunicação por interprocessos em um sistema operacional. Você poderia simplesmente imaginar um pipe que conecta dois processos, em nosso caso os terminais MetaTrader 5 que trocam dados.

Pipes podem ser anônimos ou nomeados. Há duas diferenças principais entre eles: primeira é que os pipes anônimos não podem ser utilizados sobre uma rede e o segundo que dois processos devem ser relacionados. Isto é, um processo deve ser um processo pai e o outro o filho. Pipes nomeados não possuem essa limitação.

De forma a se comunicar utilizando pipes um processo de servidor deve configurar um pipe com um nome conhecido. O nome pipe é uma string e deve ser na forma de \\servername\pipe\pipename. Se pipes forem usados no mesmo computador, o nome do servidor pode ser omitido e o ponto pode ser colocado em seu lugar: \\.\pipe\pipename.

O cliente que tentar se conectar a um pipe deve saber o nome dele. Eu estou utilizando uma convenção de nome de \\.\pipe\mt[número_conta] de forma a distinguir terminais, mas a convenção de nomes pode ser modificada arbitrariamente.

2. Implementando a classe CNamedPipes

Começarei com uma curta descrição de mecanismos de baixo nível de criar e conectar a um pipe nomeado. Nos sistemas operacionais Windows todas as funções que lidam com pipes estão disponíveis através da biblioteca kernel32.dll. A função iniciando um pipe nomeado no lado do servidor é CreateNamedPipe().

Após o pipe ser criado, o servidor chama a função ConnectNamedPipe() para esperar que um cliente se conecte. Se a conexão é bem sucedida, ConnectNamedPipe() retorna um inteiro diferente de zero. É possível, no entanto, que o cliente tenha se conectado com sucesso após chamar CreateNamedPipe() e antes que ConnectNamedPipe() fosse chamada. Nesse caso ConnectNamedPipe() retorna a zero, e GetLastError() retorna com o erro 535 (0X217) : ERROR_PIPE_CONNECTED.

É possível ler e escrever a partir de um pipe com as mesmas funções que para acesso de arquivo:

BOOL WINAPI ReadFile(
  __in         HANDLE hFile,
  __out        LPVOID lpBuffer,
  __in         DWORD nNumberOfBytesToRead,
  __out_opt    LPDWORD lpNumberOfBytesRead,
  __inout_opt  LPOVERLAPPED lpOverlapped
);
BOOL WINAPI WriteFile(
  __in         HANDLE hFile,
  __in         LPCVOID lpBuffer,
  __in         DWORD nNumberOfBytesToWrite,
  __out_opt    LPDWORD lpNumberOfBytesWritten,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

Tendo aprendido sobre pipes nomeados, projetei a classe CNamedPipes de forma a esconder as instruções de baixo nível subjacentes.

Agora é o suficiente colocar o arquivo CNamedPipes.mqh na pasta apropriada (/include) do terminal e incluir ela no código fonte e declarar um objeto CNamedPipe.

A classe que projetei expõe alguns métodos básicos para lidar com pipes nomeados.

Create(), Connect(), Disconnect(), Open(), Close(), WriteUnicode(), ReadUnicode(), WriteANSI(), ReadANSI(), WriteTick(), ReadTick()

A classe pode ser estendida adicionalmente de acordo com exigências adicionais.

O método Create() tenta criar um pipe com um nome dado. Para simplificar a conexão entre os terminais, a 'conta' do parâmetro de entrada é o número da conta de um cliente que usará um pipe.

Se um nome de conta não é inserido o método tenta abrir um pipe com uma número de conta do terminal atual. A função Create() retorna verdadeira se o pipe for criado com sucesso.

//+------------------------------------------------------------------+
/// Create() : try to create a new instance of Named Pipe
/// \param account - source terminal account number  
/// \return true - if created, false otherwise                                                                |
//+------------------------------------------------------------------+
bool CNamedPipe::Create(int account=0)
  {
   if(account==0)
      pipeNumber=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
   else
      pipeNumber=IntegerToString(account);

   string fullPipeName=pipeNamePrefix+pipeNumber;

   hPipe=CreateNamedPipeW(fullPipeName,
                          (int)GENERIC_READ|GENERIC_WRITE|(ENUM_PIPE_ACCESS)PIPE_ACCESS_DUPLEX,
                          (ENUM_PIPE_MODE)PIPE_TYPE_RW_BYTE,PIPE_UNLIMITED_INSTANCES,
                          BufferSize*sizeof(ushort),BufferSize*sizeof(ushort),0,NULL);

   if(hPipe==INVALID_HANDLE_VALUE) return false;
   else
      return true;

  }

O método Connect() espera um cliente se conectar ao pipe. Ele retorna verdadeiro se o cliente se conectou com sucesso a um pipe.

//+------------------------------------------------------------------+
/// Connect() : wait for a client to connect to a pipe   
/// \return true - if connected, false otherwise.
//+------------------------------------------------------------------+
bool CNamedPipe::Connect(void)
  {
   if(ConnectNamedPipe(hPipe,NULL)==false)
      return(kernel32::GetLastError()==ERROR_PIPE_CONNECTED);
   else return true;
  }

O método Disconnect() desconecta o servidor de um pipe.

//+------------------------------------------------------------------+
/// Disconnect(): disconnect from a pipe
/// \return true - if disconnected, false otherwise    
//+------------------------------------------------------------------+
bool CNamedPipe::Disconnect(void)
  {
   return DisconnectNamedPipe(hPipe);
  }

O método Open() deve ser utilizado por um cliente, ele tenta abrir um pipe criado anteriormente. Ele retorna verdadeiro se a abertura do pipe foi bem sucedida. Ele retorna falso se por alguma razão ele não pôde se conectar ao pipe criado dentro de um tempo de 5 segundos ou se a abertura do pipe falhou.

//+------------------------------------------------------------------+
/// Open() : try to open previously created pipe
/// \param account - source terminal account number
/// \return true - if successfull, false otherwise.
//+------------------------------------------------------------------+
bool CNamedPipe::Open(int account=0)
  {
   if(account==0)
      pipeName=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
   else
      pipeName=IntegerToString(account);

   string fullPipeName=pipeNamePrefix+pipeName;

   if(hPipe==INVALID_HANDLE_VALUE)
     {
      if(WaitNamedPipeW(fullPipeName,5000)==0)
        {
         Print("Pipe "+fullPipeName+" not available...");
         return false;
        }

      hPipe=CreateFileW(fullPipeName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
      if(hPipe==INVALID_HANDLE_VALUE)
        {
         Print("Pipe open failed");
         return false;
        }

     }
   return true;
  }

O método Close() fecha o pipe nomeado.

//+------------------------------------------------------------------+
/// Close() : close pipe handle
/// \return 0 if successfull, non-zero otherwise  
//+------------------------------------------------------------------+
int CNamedPipe::Close(void)
  {
   return CloseHandle(hPipe);
  }

Os próximos seis métodos são utilizados para ler e escrever através dos pipes. Os primeiros dois pares lidam com cadeias (strings) nos formatos Unicode e ANSI, ambos podem ser utilizados para enviar comandos ou mensagens entre os terminais.

A variável da string no MQL5 é armazenada como um objeto que contém Unicode, então, o modo natural foi fornecer métodos Unicode, mas já que o MQL5 fornece métodos UnicodeToANSI, também implementei a comunicação de string ANSI. Os dois últimos métodos lidam com enviar e receber o objeto MqlTick através de um pipe nomeado.

O método WriteUnicode() escreve a mensagem consistindo em caracteres Unicode. Uma vez que todo caractere consiste de dois bytes, ele envia um arranjo de ushort para um pipe.

//+------------------------------------------------------------------+
/// WriteUnicode() : write Unicode string to a pipe
/// \param message - string to send
/// \return number of bytes written to a pipe     
//+------------------------------------------------------------------+
int CNamedPipe::WriteUnicode(string message)
  {
   int ushortsToWrite, bytesWritten;
   ushort UNICODEarray[];
   ushortsToWrite = StringToShortArray(message, UNICODEarray);
   WriteFile(hPipe,ushortsToWrite,sizeof(int),bytesWritten,0);
   WriteFile(hPipe,UNICODEarray,ushortsToWrite*sizeof(ushort),bytesWritten,0);
   return bytesWritten;
  }

O método ReadUnicode() recebe array de ushorts e retorna um objeto string.

//+------------------------------------------------------------------+
/// ReadUnicode(): read unicode string from a pipe
/// \return unicode string (MQL5 string)
//+------------------------------------------------------------------+
string CNamedPipe::ReadUnicode(void)
  {
   string ret;
   ushort UNICODEarray[STR_SIZE*sizeof(uint)];
   int bytesRead, ushortsToRead;
 
   ReadFile(hPipe,ushortsToRead,sizeof(int),bytesRead,0);
   ReadFile(hPipe,UNICODEarray,ushortsToRead*sizeof(ushort),bytesRead,0);
   if(bytesRead!=0)
      ret = ShortArrayToString(UNICODEarray);
   
   return ret;
  }

O método WriteANSI() escreve arranjo uchar ANSI em um pipe.

//+------------------------------------------------------------------+
/// WriteANSI() : write ANSI string to a pipe
/// \param message - string to send
/// \return number of bytes written to a pipe                                                                  |
//+------------------------------------------------------------------+
int CNamedPipe::WriteANSI(string message)
  {
   int bytesToWrite, bytesWritten;
   uchar ANSIarray[];
   bytesToWrite = StringToCharArray(message, ANSIarray);
   WriteFile(hPipe,bytesToWrite,sizeof(int),bytesWritten,0);
   WriteFile(hPipe,ANSIarray,bytesToWrite,bytesWritten,0);
   return bytesWritten;
  }

O método ReadANSI() lê o arranjo uchar de um pipe e retorna um objeto em cadeia.

//+------------------------------------------------------------------+
/// ReadANSI(): read ANSI string from a pipe
/// \return unicode string (MQL5 string)
//+------------------------------------------------------------------+
string CNamedPipe::ReadANSI(void)
  {
   string ret;
   uchar ANSIarray[STR_SIZE];
   int bytesRead, bytesToRead;
 
   ReadFile(hPipe,bytesToRead,sizeof(int),bytesRead,0);
   ReadFile(hPipe,ANSIarray,bytesToRead,bytesRead,0);
   if(bytesRead!=0)
      ret = CharArrayToString(ANSIarray);
   
   return ret;
  }

O método WriteTick() escreve um único objeto MqlTick para um pipe.

//+------------------------------------------------------------------+
/// WriteTick() : write MqlTick to a pipe
/// \param MqlTick to send
/// \return true if tick was written correctly, false otherwise
//+------------------------------------------------------------------+
int CNamedPipe::WriteTick(MqlTick &outgoing)
  {
   int bytesWritten;

   WriteFile(hPipe,outgoing,MQLTICK_SIZE,bytesWritten,0);

   return bytesWritten;
  }

O método ReadTick() lê um único MqlTick objeto de um pipe. Se um pipe está vazio ele retorna 0, se não ele retorna o número de bytes de um objeto MqlTick.

//+------------------------------------------------------------------+
/// ReadTick() : read MqlTick from a pipe
/// \return true if tick was read correctly, false otherwise
//+------------------------------------------------------------------+
int CNamedPipe::ReadTick(MqlTick &incoming)
  {
   int bytesRead;

   ReadFile(hPipe,incoming,MQLTICK_SIZE,bytesRead,NULL);

   return bytesRead;
  }
//+------------------------------------------------------------------+

Uma vez que os métodos básicos para se lidar com pipes nomeados são conhecidos podemos começar com dois programas MQL: script simples para recebimento de cotações e um indicador para enviar cotações.

3. Scripts do servidor para receber cotações

O servidor do exemplo inicia um pipe nomeado e espera para um cliente se conectar. Após o cliente desconectar ele mostra quantos pontos foram recebidos por aquele cliente no total e espera um novo cliente se conectar. Se um cliente desconectou e o servidor encontra uma variedade global 'gvar0' ele sai. Se a variável 'gvar0' não existir, uma pessoa pode parar manualmente o servidor clicando com o botão direito em um gráfico e selecionando a opção Expert List.

//+------------------------------------------------------------------+
//|                                              NamedPipeServer.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#include 

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool tickReceived;
   int i=0;

   if(pipe.Create()==true)
      while (GlobalVariableCheck("gvar0")==false)
        {
         Print("Waiting for client to connect.");
         if (pipe.Connect()==true)
            Print("Pipe connected");
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();

               if(tickReceived==false)
                 {
                  if(GetError()==ERROR_BROKEN_PIPE)
                    {
                     Print("Client disconnected from pipe "+pipe.Name());
                     pipe.Disconnect();
                     break;
                    }
                 } else i++;
                  Print(IntegerToString(i) + "ticks received.");
              } while(tickReceived==true);
            if (i>0) 
            {
               Print(IntegerToString(i) + "ticks received.");
               i=0;
            };
            if(GlobalVariableCheck("gvar0")==true || (GetError()==ERROR_BROKEN_PIPE)) break;
           }

        }

 pipe.Close(); 
  }

4. Indicador simples para enviar cotações

O indicador para enviar cotações abre um pipe dentro do método OnInit() e envia um único MqlTick cada vez que o método OnCalculate() é acionado:
//+------------------------------------------------------------------+
//|                                        SendTickPipeIndicator.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property indicator_chart_window

#include 

CNamedPipe pipe;
int ctx;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
 
   while (!pipe.Open(AccountInfoInteger(ACCOUNT_LOGIN)))
   {
      Print("Pipe not created, retrying in 5 seconds...");
      if (GlobalVariableCheck("gvar1")==true) break;
   }
   
   ctx = 0;
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
   ctx++;
   MqlTick outgoing;
   SymbolInfoTick(Symbol(), outgoing);
   pipe.WriteTick(outgoing);
   Print(IntegerToString(ctx)+" tick send to server by SendTickPipeClick.");
   return(rates_total);
  }
//+------------------------------------------------------------------+

5. Executando indicadores de pontos para provedores múltiplos em um único terminal de cliente

A situação ficou mais complicada conforme eu quis exibir cotações que chegavam em indicadores de pontos separados. Alcancei isso implementando um servidor de pipe que transmite pontos de entrada a um indicador de pontos acionando o método EventChartCustom().

Cotações de compra ou venda são enviadas como uma única cadeia dividida por um ponto e vírgula por exemplo '1.20223;120225'. O indicador apropriado lida com um evento personalizado dentro de OnChartEvent() e exibe um gráfico de pontos.

//+------------------------------------------------------------------+
//|                                   NamedPipeServerBroadcaster.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"
#property script_show_inputs
#include 

input int account = 0;

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool tickReceived;
   int i=0;

   if(pipe.Create(account)==true)
      while(GlobalVariableCheck("gvar0")==false)
        {
         if(pipe.Connect()==true)
            Print("Pipe connected");
            i=0;
         while(true)
           {
            do
              {
               tickReceived=pipe.ReadTick();
               if(tickReceived==false)
                 {
                  if(kernel32::GetLastError()==ERROR_BROKEN_PIPE)
                    {
                     Print("Client disconnected from pipe "+pipe.GetPipeName());
                     pipe.Disconnect();
                     break;
                    }
                  } else  {
                   i++; Print(IntegerToString(i)+" ticks received BY server.");
                  string bidask=DoubleToString(pipe.incoming.bid)+";"+DoubleToString(pipe.incoming.ask);
                  long currChart=ChartFirst(); int chart=0;
                  while(chart<100) 
                    {
                     EventChartCustom(currChart,6666,0,(double)account,bidask);
                     currChart=ChartNext(currChart); 
                     if(currChart==0) break;         // Reached the end of the charts list
                     chart++;
                    }
                     if(GlobalVariableCheck("gvar0")==true || (kernel32::GetLastError()==ERROR_BROKEN_PIPE)) break;
              
                 }
              }
            while(tickReceived==true);
            if(i>0)
              {
               Print(IntegerToString(i)+"ticks received.");
               i=0;
              };
            if(GlobalVariableCheck("gvar0")==true || (kernel32::GetLastError()==ERROR_BROKEN_PIPE)) break;
            Sleep(100);
           }

        }


  pipe.Close(); 
  }

Para exibir os ticks que eu escolhi no indicador de ticks colocados em MQLmagazine, mas, ao invés de OnCalculate() implementei um método de processamento dentro do OnChartEvent() e adicionei instruções condicionais. Uma cotação é aceita para processar apenas se o parâmetro dparam for igual ao número do pipe e a identificação do evento for igual a CHARTEVENT_CUSTOM+6666:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
  if (dparam==(double)incomingPipe)
   if(id>CHARTEVENT_CUSTOM)
     {
      if(id==CHARTEVENT_CUSTOM+6666)
        {
        // Process incoming tick
        }
     } else
        {
         // Handle the user event 
        }
  }

Na captura de tela abaixo há três indicadores de pontos.

Dois deles exibem pontos recebidos através de pipes e um terceiro indicador que não utiliza pipes foi executado para verificar se nenhum ponto havia sido perdido.

Indicador de pontos com dados de diferentes terminais

Fig. 1 Cotas recebidas através de um pipe nomeado

Por favor, observe anexo um vídeo com comentários sobre como eu executo os indicadores:

Fig. 2 Vídeo descrevendo a configuração de indicadores

6. Testando a vazão do sistema

Uma vez que pipes utilizam memória compartilhada a comunicação é muito rápida. Conduzi testes de enviar 100 000 e 1 000 000 pontos consecutivos entre dois terminais do MetaTrader 5. O script de envio utiliza a função WriteTick() e mede o intervalo de tempo utilizando GetTickCount():

   Print("Sending...");
   uint start = GetTickCount();
   for (int i=0;i<100000;i++)
      pipe.WriteTick(outgoing);
   uint stop = GetTickCount();
   Print("Sending took" + IntegerToString(stop-start) + " [ms]");
   pipe.Close();

O servidor lê as cotações de entrada. O período de tempo é medido da primeira cotação de chegada até que o cliente de desconecte:

//+------------------------------------------------------------------+
//|                                          SpeedTestPipeServer.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#property script_show_inputs
#include 

input int account=0;
bool tickReceived;
uint start,stop;

CNamedPipe pipe;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int i=0;
   if(pipe.Create(account)==true)
      if(pipe.Connect()==true)
         Print("Pipe connected");

   do
     {
      tickReceived=pipe.ReadTick();
      if(i==0) start=GetTickCount();
      if(tickReceived==false)
        {
         if(kernel32::GetLastError()==ERROR_BROKEN_PIPE)
           {
            Print("Client disconnected from pipe "+pipe.GetPipeName());
            pipe.Disconnect();
            break;
           }
        }
      else i++;
     }
   while(tickReceived==true);
   stop=GetTickCount();

   if(i>0)
     {
      Print(IntegerToString(i)+" ticks received.");
      i=0;
     };
   
   pipe.Close();
   Print("Server: receiving took "+IntegerToString(stop-start)+" [ms]");

  }
//+------------------------------------------------------------------+

Os resultados para 10 execuções de amostra foram como segue:

Executar
Cotações
Tempo de envio [ms]
Tempo de recebimento [ms]
1
100000
624
624
2 100000 702 702
3 100000 687 687
4 100000 592 608
5 100000 624 624
6 1000000 5616 5616
7 1000000 5788 5788
8 1000000 5928 5913
9
1000000 5772 5756
10
1000000 5710 5710

Tabela 1 Medições da velocidade de processamento

A velocidade média de envio de 1 000 000 cotações foi de cerca de 170 000 pontos/segundo em um notebook com Windows Vista com uma CPU 2.0GHz T4200 e 3GB RAM.

Conclusão

Apresentei um método de comunicação entre os terminais MetaTrader 5 utilizando pipes nomeados. O método acabou por ser suficiente pra enviar cotações em tempo real entre terminais.

A classe CNamedPipes pode ser ainda estendida de acordo com as necessidades adicionais, por exemplo, tornar possível a limitação em duas contas independentes. Por favor, encontre o código-fonte da classe CNamedPipe com a documentação no formato chm e outro código-fonte que implementei para escrever o artigo anexos.

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

Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5 Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5
Este artigo foca na abordagem orientada a objeto para fazer o que fizemos no artigo "Guia passo a passo para escrever um Expert Advisor no MQL5 para iniciantes" - criando um simples Expert Advisor. A maior parte das pessoas acha que isso é difícil, mas quero assegurá-lo que quando você terminar de ler esse artigo, você será capaz de escrever o seu próprio consultor especialista que é baseado em orientação a objeto.
Uma biblioteca para construção de um gráfico pelo Google Chart API Uma biblioteca para construção de um gráfico pelo Google Chart API
A construção de vários tipos de diagramas é uma parte essencial da análise da situação de mercado e o teste de um sistema de negócio. Frequentemente, a fim de construir um diagrama de boa aparência, é necessário organizar a saída de dados em um arquivo, após o qual é usado em aplicações como MS Excel. Isso não é muito conveniente e nos tira a capacidade de atualizar os dados dinamicamente. O Google Charts API fornece meios para criar gráficos em modos online, enviando uma solicitação especial para o servidor. Neste artigo, tentamos automatizar o processo de criação de tal solicitação e obter um gráfico a partir do servidor Google.
Como Encomendar um Robô de Negociação em MQL5 e MQL4 Como Encomendar um Robô de Negociação em MQL5 e MQL4
A "Freelance" é o maior serviço freelance para a encomenda de robôs de negociação em MQL4 e indicadores técnicos. Centenas de desenvolvedores profissionais estão prontos para desenvolver aplicativos de negociação personalizados para a plataforma MetaTrader 4/5.
O uso de ORDER_MAGIC para negociação com diferentes consultores especialistas em um único instrumento O uso de ORDER_MAGIC para negociação com diferentes consultores especialistas em um único instrumento
Este artigo considera as questões de codificação de informação, usando a identificação mágica, assim como a divisão, montagem e sincronização de negociação automática de diferentes Expert Advisors. Este artigo será interessante para iniciantes, assim como para negociantes mais experientes, porque trata da questão das posições virtuais, o que pode ser útil na implementação de sistemas completos de sincronização de Expert Advisors e várias estratégias.