English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Como exportar cotações do MetaTrader5 para aplicações .NET usando serviços WCF

Como exportar cotações do MetaTrader5 para aplicações .NET usando serviços WCF

MetaTrader 5Exemplos | 30 dezembro 2013, 11:23
2 190 0
Alexander
Alexander

Introdução

Programadores que usam o serviço DDE no MetaTrader 4 provavelmente já ouviram que na quinta versão este serviço não será mais suportado. E não há uma solução padrão para a exportação de cotações. Como solução para este problema, os desenvolvedores do MQL5 sugerem usar seu próprio dll, que o implementa. Então se temos que escrever a implementação, vamos fazer direito!

Por que .NET?

Para mim, com minha extensa experiência em programação no .NET, seria mais lógico, interessante e simples implementar a exportação de cotações usando esta plataforma. Infelizmente, não há um nenhum suporte nativo do .NET no MQL5 na quinta versão. Tenho certeza que os desenvolvedores devem ter algum motivo para isso. Por isso, usaremos o dll win32 como um wrapper para o suporte .NET.

Por que WCF?

Eu escolhi o Windows Communication Foundation Technology (WCF) por diversos motivos: por um lado, é fácil estender e adaptar, por outro lado, eu queria testá-lo com trabalho pesado. Além disso, de acordo com a Microsoft, o WCF se desempenha um pouco melhor em comparação com o .NET Remoting.

Requerimentos do sistema

Vamos pensar no que queremos do nosso sistema. Eu acho que são duas exigências:

    1. Obviamente, precisamos exportar ticks, usando melhor a estrutura nativa MqlTick;
    2. é preferível saber a lista dos símbolos exportados atualmente.

    Vamos começar...

    1. Contratos e classes gerais

    Primeiramente, vamos criar uma nova biblioteca de classe e nomear como QExport.dll. Definimos a estrutura MqlTick como um DataContract:

        [StructLayout(LayoutKind.Sequential)]
        [DataContract]
        public struct MqlTick
        {
            [DataMember] 
            public Int64 Time { get; set; }
            [DataMember]
            public Double Bid { get; set; }
            [DataMember]
            public Double Ask { get; set; }
            [DataMember]
            public Double Last { get; set; }
            [DataMember]
            public UInt64 Volume { get; set; }
        }
    

    Depois, definiremos os contratos do serviço. Não gosto de usar as classes de configuração e classes proxy geradas, então você não vai encontrar muitos recursos aqui.

    Vamos definir o primeiro contrato do servidor de acordo com os requerimentos descritos acima:

        [ServiceContract(CallbackContract = typeof(IExportClient))]
        public interface IExportService
        {
            [OperationContract]
            void Subscribe();
    
            [OperationContract]
            void Unsubscribe();
    
            [OperationContract]
            String[] GetActiveSymbols();
        }
    

    Como vemos, há um esquema padrão de assinatura e cancelamento de assinatura das notificações do servidor. Os detalhes resumidos das operações são descritos abaixo:

    OperaçãoDescrição
    Subscribe()Assinar para exportação de ticks
    Unsubscribe()Cancelar assinatura para exportação de ticks
    GetActiveSymbols()Retorna a lista de símbolos exportados


    E as seguintes informações devem ser enviadas para o callback do cliente: a cotação em si e a notificação sobre as alterações das listas dos símbolos exportados. Vamos definir as operações necessárias como "Operações One Way" para aumentar o desempenho:

        [ServiceContract]
        public interface IExportClient
        {
            [OperationContract(IsOneWay = true)]
            void SendTick(String symbol, MqlTick tick);
    
            [OperationContract(IsOneWay = true)]
            void ReportSymbolsChanged();
        }
    
    OperaçãoDescrição
    SendTick(String, MqlTick)Envia o tick
    ReportSymbolsChanged()Notifica o cliente sobre as alterações na lista de símbolos exportados

    2. Implementação do servidor

    Vamos criar um novo build com o nome Qexport.Service.dll para o serviço com a implementação do contrato do servidor.

    Vamos escolher o NetNamedPipesBinding para uma ligação, porque possui o maior desempenho em comparação com as ligações padrão. Se precisarmos transmitir as cotações, por exemplo, por uma rede, o NetTcpBinding deve ser usado.

    Aqui estão alguns detalhes sobre a implementação do contrato do servidor:

    A definição de classe. Primeiro de tudo, ela deve ser marcada com o atributo ServiceBehavior com os seguintes modificadores:

    • InstanceContextMode = InstanceContextMode.Single- para fornecer o uso de uma instância de serviço para todas as solicitações processadas, isso aumentará o desempenho da solução. Além disso, teremos a possibilidade de servir e gerenciar a lista de símbolos exportados;
    • ConcurrencyMode = ConcurrencyMode.Multiple -significa o processamento paralelo para todas as solicitações do cliente;
    • UseSynchronizationContext = false– significa que não anexamos à thread GUI para evitar situações de travamento. Isso não é necessário aqui para a nossa tarefa, mas é necessário se quisermos hospedar o serviço usando as aplicações Windows;
    • IncludeExceptionDetailInFaults = true– para incluir os detalhes de exceção ao objeto FaultExceptionquando passado para o cliente.

    O ExportService sozinho contém duas interfaces: IExportService, IDisposable. O primeiro implementa todas as funções do serviço, o segundo implementa o modelo padrão do release de recursos .NET.

        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
            ConcurrencyMode = ConcurrencyMode.Multiple,
            UseSynchronizationContext = false,
            IncludeExceptionDetailInFaults = true)]
        public class ExportService : IExportService, IDisposable
        {
    

    Vamos descrever as variáveis do serviço:

            // full address of service in format net.pipe://localhost/server_name
            private readonly String _ServiceAddress;
    
            // service host
            private ServiceHost _ExportHost;
    
            // active clients callbacks collection
            private Collection _Clients = new Collection();
    
            // active symbols list
            private List _ActiveSymbols = new List<string>();
            
            // object for locking
            private object lockClients = new object();
    

    Vamos definir os métodos Open() e Close(), que abre e fecha nosso serviço:

            public void Open()
            {
                _ExportHost = new ServiceHost(this);
    
                // point with service
                _ExportHost.AddServiceEndpoint(typeof(IExportService),  // contract
                    new NetNamedPipeBinding(),                          // binding
                    new Uri(_ServiceAddress));                          // address
    
                // remove the restriction of 16 requests in queue
                ServiceThrottlingBehavior bhvThrot = new ServiceThrottlingBehavior();
                bhvThrot.MaxConcurrentCalls = Int32.MaxValue;
                _ExportHost.Description.Behaviors.Add(bhvThrot);
    
                _ExportHost.Open();
            }
    
            public void Close()
            {
                Dispose(true);
            }
           
            private void Dispose(bool disposing)
            {
                try
                {
                    // closing channel for each client
                    // ...
    
                    // closing host
                    _ExportHost.Close();
                }
                finally
                {
                    _ExportHost = null;
                }
    
                // ...
            }
    

    Depois, a implementação dos métodos IExportService:

            public void Subscribe()
            {
                // get the callback channel
                IExportClient cl = OperationContext.Current.GetCallbackChannel();
                lock (lockClients)
                    _Clients.Add(cl);
            }
    
            public void Unsubscribe()
            {
                // get the callback chanell
                IExportClient cl = OperationContext.Current.GetCallbackChannel();
                lock (lockClients)
                    _Clients.Remove(cl);
            }
    
            public String[] GetActiveSymbols()
            {
                return _ActiveSymbols.ToArray();
            }
    

    Agora, precisamos adicionar métodos para enviar ticks, registrar e excluir os símbolos exportados.

         public void RegisterSymbol(String symbol)
            {
                if (!_ActiveSymbols.Contains(symbol))
                    _ActiveSymbols.Add(symbol);
    
                  // sending notification to all clients about changes in the list of active symbols
                  //...
            }
    
            public void UnregisterSymbol(String symbol)
            {
                _ActiveSymbols.Remove(symbol);
    
                 // sending notification to all clients about the changes in the list of active symbols
                 //...
            }
    
            public void SendTick(String symbol, MqlTick tick)
            {
                lock (lockClients)
                    for (int i = 0; i < _Clients.Count; i++)
                        try
                        {
                            _Clients[i].SendTick(symbol, tick);
                        }
                        catch (CommunicationException)
                        {
                            // it seems that connection with client has lost - we just remove the client
                            _Clients.RemoveAt(i);
                            i--;
                        }
            }
    

    Vamos resumir a lista das principais funções do servidor (apenas as que precisamos):

    MétodosDescrição
    Open()Executa o servidor
    Close()Para o servidor
    RegisterSymbol(String)Adiciona o símbolo à lista de símbolos exportados
    UnregisterSymbol(String)Exclua o símbolo da lista de símbolos exportados
    GetActiveSymbols()Retorna o número de símbolos exportados
    SendTick(String, MqlTick)Envia o tick para os clientes

    3. Implementação do cliente

    Temos considerado o servidor, acho que isso está claro, então é hora de considerar o cliente. Vamos criar o Qexport.Client.dll. O contrato do cliente será implementado aqui. Primeiro, deve ser marcado com o atributo CallbackBehavior, que define seu comportamento. Tem os seguintes modificadores:

      • ConcurrencyMode = ConcurrencyMode.Multiple - significa o processamento paralelo para todos os callbacks e respostas do servidor. Este modificador é muito importante. Imagine que o servidor queira notificar o cliente sobre as alterações na lista de símbolos exportados chamando callback ReportSymbolsChanged(). E o cliente (em seu callback) deseja receber a nova lista de símbolos exportados por chamado do método do servidor GetActiveSymbols(). Acontece que o cliente não pode receber a resposta do servidor porque realiza o callback esperando pela resposta do servidor. Como resultado, o cliente cairá por causa do limite de tempo.
      • UseSynchronizationContext = false - especifica que não anexamos à GUI para evitar situações de travamento. Predefinidamente, os callbacks do WCF são anexados à thread aliada. Se a thread aliada possui uma GUI, a situação é possível quando o callback aguarda a conclusão do método pelo qual foi chamado, mas o método não pode concluir porque aguarda o callback terminar. é um pouco similar ao caso anterior, embora os dois sejam coisas diferentes.

      Como para o caso do servidor, o cliente também implementa duas interfaces: IExportClient e IDisposable:

       [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple,
              UseSynchronizationContext = false)]
          public class ExportClient : IExportClient, IDisposable
          {
      

      Vamos descrever as variáveis do serviço:

              // full service address
              private readonly String _ServiceAddress;
      
              // service object 
              private IExportService _ExportService;
      
              // Returns service instance
              public IExportService Service
              {
                  get
                  {
                      return _ExportService;
                  }
              }
      
              // Returns communication channel
              public IClientChannel Channel
              {
                  get
                  {
                      return (IClientChannel)_ExportService;
                  }
              }
      

      Agora, vamos criar eventos para nossos métodos de callback. Isso é necessário para a aplicação de cliente ser capaz de assinar os eventos e obter as notificações sobre as alterações do estado do cliente.

              // calls when tick received
              public event EventHandler TickRecieved;
      
              // call when symbol list has changed
              public event EventHandler ActiveSymbolsChanged;
      

      Também define os métodos Open() e Close() para o cliente:

              public void Open()
              {
                  // creating channel factory
                  var factory = new DuplexChannelFactory(
                      new InstanceContext(this),
                      new NetNamedPipeBinding());
      
                  // creating server channel
                  _ExportService = factory.CreateChannel(new EndpointAddress(_ServiceAddress));
      
                  IClientChannel channel = (IClientChannel)_ExportService;
                  channel.Open();
      
                  // connecting to feeds
                  _ExportService.Subscribe();
              }
      
              public void Close()
              {
                  Dispose(true);
              }
      
              private void Dispose(bool disposing)
              {
                  try
                  {
                      // unsubscribe feeds
                      _ExportService.Unsubscribe();
                      Channel.Close();
      
                  }
                  finally
                  {
                      _ExportService = null;
                  }
                  // ...
              }
      

      Observe que a conexão e desconexão dos feeds são chamadas quando um cliente é aberto ou fechado, então não é necessário chamá-las diretamente.

      E agora, vamos escrever o contrato do cliente. Sua implementação leva a geração dos seguintes eventos:

              public void SendTick(string symbol, MqlTick tick)
              {
                  // firing event TickRecieved
              }
      
              public void ReportSymbolsChanged()
              {
                  // firing event ActiveSymbolsChanged        
              }
      

      Finalmente, as principais propriedades e métodos do cliente são definidos da seguinte forma:

      PropriedadeDescrição
      ServiçoCanal de comunicação do serviço
      CanalInstância do contrato de serviço IExportService


      MétodoDescrição
      Open()Conecta ao servidor
      Close()Desconecta do servidor

      EventoDescrição
      TickRecievedGerado após o recebimento de uma nova cotação
      ActiveSymbolsChangedGerado após as alterações na lista dos símbolos ativos

      4. Velocidade de transferência entre duas aplicações .NET

      Foi interessante, para eu, medir a velocidade de transferência entre duas aplicações .NET, na verdade, é a taxa de transferência, que é medida em ticks por segundo. Escrevi diversas aplicações de console para medir o desempenho do serviço: a primeira para o servidor e a segunda para o cliente. Escrevi o seguinte código na função Main() do servidor:

                  ExportService host = new ExportService("mt5");
                  host.Open();
      
                  Console.WriteLine("Press any key to begin tick export");
                  Console.ReadKey();
      
                  int total = 0;
      
                  Stopwatch sw = new Stopwatch();
      
                  for (int c = 0; c < 10; c++)
                  {
                      int counter = 0;
                      sw.Reset();
                      sw.Start();
      
                      while (sw.ElapsedMilliseconds < 1000)
                      {
                          for (int i = 0; i < 100; i++)
                          {
                              MqlTick tick = new MqlTick { Time = 640000, Bid = 1.2345 };
                              host.SendTick("GBPUSD", tick);
                          }
                          counter++;
                      }
      
                      sw.Stop();
                      total += counter * 100;
      
                      Console.WriteLine("{0} ticks per second", counter * 100);
                  }
      
                  Console.WriteLine("Average {0:F2} ticks per second", total / 10);
                  
                  host.Close();
      

      Como vemos, o código realiza dez medições de taxa de transferência. Obtive os seguintes resultados de teste no meu Athlon 3000+:

      2600 ticks per second
      3400 ticks per second
      3300 ticks per second
      2500 ticks per second
      2500 ticks per second
      2500 ticks per second
      2400 ticks per second
      2500 ticks per second
      2500 ticks per second
      2500 ticks per second
      Average 2670,00 ticks per second
      

      2500 ticks por segundo - Acho que é o suficiente para exportar cotações para 100 símbolos (claro, virtualmente, porque parece que ninguém quer abrir tantos gráficos e anexar especialistas =)). Além disso, com o número crescente de clientes, o número máximo de símbolos exportados para cada cliente é reduzido.

      5. Criando um "estrato"

      Agora é hora de pensar como conectar isso com o terminal do cliente. Vamos ver o que temos na primeira chamada da função no MetaTrader 5: o ambiente runtime .NET (CLR) é carregado ao processo e o domínio da aplicação é criado por predefinição. O interessante é que não seja descarregado após a execução do código.

      A única maneira de descarregar o CLR do processo é finalizá-lo, (fechar o terminal do cliente) o que irá forçar o Windows a limpar todos os recursos do processo. Então, podemos criar nossos objetos e eles existirão até que o domínio da aplicação seja descarregado ou até ser destruído pelo Garbage Collector.

      Você pode dizer que isso parece bom, mas mesmo se impedirmos a destruição do objeto pelo Garbage Collector, não podemos acessar os objetos a partir do MQL5. Felizmente, tal acesso pode ser organizado facilmente. O truque é o seguinte: para cada domínio de aplicação há uma tabela de identificadores de Garbage Collector (tabela de identificação GC), que é usada pela aplicação para rastrear a vida útil do objeto e permitir o gerenciamento manual.

      A aplicação adiciona e exclui elementos da tabela usando o tipo System.Runtime.InteropServices.GCHandle. Tudo que precisamos é acondicionar nosso objeto com tal descritor e teremos um acesso a ele por toda a propriedade GCHandle.Target. Dessa forma, podemos ter a referência para o objeto GCHandle, que está na tabela de identificadores e é garantido que ele não será movido ou excluído pelo Garbage Collector. O objeto acondicionado irá também evitar a reciclagem, por causa da referência do descritor.

      Agora é a hora de testar a teoria na prática. Para isso, vamos criar um novo dll win 32 com o nome QExpertWrapper.dll e adicionar o suporte CLR, System.dll, QExport.dll, Qexport.Service.dll para referência build. Também criamos uma classe auxiliar ServiceManaged para propósitos de gerenciamento – para realizar a organização, receber objetos por identificadores, etc.

      ref class ServiceManaged
      {
              public:
                      static IntPtr CreateExportService(String^);
                      static void DestroyExportService(IntPtr);
                      static void RegisterSymbol(IntPtr, String^);
                      static void UnregisterSymbol(IntPtr, String^);
                      static void SendTick(IntPtr, String^, IntPtr);
      };
      

      Vamos considerar a implementação destes métodos. O método CreateExportService cria o serviço, e o acondiciona ao GCHandle usando GCHandle.Alloc e retorna sua referência. Se algo der errado, ele mostra a Caixa de mensagens com um erro. Eu o usei com o propósito de debug, então não tenho certeza se é realmente necessário, mas o deixei aqui por precaução.

      IntPtr ServiceManaged::CreateExportService(String^ serverName)
      {
              try
              {
                      ExportService^ service = gcnew ExportService(serverName);
                      service->Open();
              
                      GCHandle handle = GCHandle::Alloc(service);
                      return GCHandle::ToIntPtr(handle);
              }
              catch (Exception^ ex)
              {
                      MessageBox::Show(ex->Message, "CreateExportService");
              }
      }
      

      O método DestroyExportService obtém o indicador para o GCHandle do serviço, obtém o serviço da propriedade alvo e chama seu método Close(). É importante liberar o objeto de serviço chamando seu método Free(). Caso contrário, irá permanecer na memória, o Garbage Collector não o remove.

      void ServiceManaged::DestroyExportService(IntPtr hService)
      {
              try
              {
                      GCHandle handle = GCHandle::FromIntPtr(hService);
      
                      ExportService^ service = (ExportService^)handle.Target;
                      service->Close();
      
                      handle.Free();
              }
              catch (Exception^ ex)
              {
                      MessageBox::Show(ex->Message, "DestroyExportService");
              }
      }
      

      O método RegisterSymbol adiciona o símbolo à lista de símbolos exportados:

      void ServiceManaged::RegisterSymbol(IntPtr hService, String^ symbol)
      {
              try
              {
                      GCHandle handle = GCHandle::FromIntPtr(hService);
                      ExportService^ service = (ExportService^)handle.Target;
      
                      service->RegisterSymbol(symbol);
              }
              catch (Exception^ ex)
              {
                      MessageBox::Show(ex->Message, "RegisterSymbol");
              }
      }
      

      O método UnregisterSymbol exclui um símbolo da lista:

      void ServiceManaged::UnregisterSymbol(IntPtr hService, String^ symbol)
      {
              try
              {
                      GCHandle handle = GCHandle::FromIntPtr(hService);
                      ExportService^ service = (ExportService^)handle.Target;
      
                      service->UnregisterSymbol(symbol);
              }
              catch (Exception^ ex)
              {
                      MessageBox::Show(ex->Message, "UnregisterSymbol");
              }
      }
      

      E agora, o método SendTick. Como vemos, o ponteiro é transformado na estrutura MqlTick usando a classe Marshal. Outro ponto: não há nenhum código no bloco catch - isso é feito para evitar os lags da fila de tick geral em caso de erro.

      void ServiceManaged::SendTick(IntPtr hService, String^ symbol, IntPtr hTick)
      {
              try
              {
                      GCHandle handle = GCHandle::FromIntPtr(hService);
                      ExportService^ service = (ExportService^)handle.Target;
              
                      MqlTick tick = (MqlTick)Marshal::PtrToStructure(hTick, MqlTick::typeid);
      
                      service->SendTick(symbol, tick);
              }
              catch (...)
              {
              }
      }
      

      Vamos considerar a implementação das funções, que serão chamadas de nossos programas ex5.

      #define _DLLAPI extern "C" __declspec(dllexport)
      
      // ---------------------------------------------------------------
      // Creates and opens service 
      // Returns its pointer
      // ---------------------------------------------------------------
      _DLLAPI long long __stdcall CreateExportService(const wchar_t* serverName)
      {
              IntPtr hService = ServiceManaged::CreateExportService(gcnew String(serverName));
              
              return (long long)hService.ToPointer(); 
      }
      
      // ----------------------------------------- ----------------------
      // Closes service
      // ---------------------------------------------------------------
      _DLLAPI void __stdcall DestroyExportService(const long long hService)
      {
              ServiceManaged::DestroyExportService(IntPtr((HANDLE)hService));
      }
      
      // ---------------------------------------------------------------
      // Sends tick
      // ---------------------------------------------------------------
      _DLLAPI void __stdcall SendTick(const long long hService, const wchar_t* symbol, const HANDLE hTick)
      {
              ServiceManaged::SendTick(IntPtr((HANDLE)hService), gcnew String(symbol), IntPtr((HANDLE)hTick));
      }
      
      // ---------------------------------------------------------------
      // Registers symbol to export
      // ---------------------------------------------------------------
      _DLLAPI void __stdcall RegisterSymbol(const long long hService, const wchar_t* symbol)
      {
              ServiceManaged::RegisterSymbol(IntPtr((HANDLE)hService), gcnew String(symbol));
      }
      
      // ---------------------------------------------------------------
      // Removes symbol from list of exported symbols
      // ---------------------------------------------------------------
      _DLLAPI void __stdcall UnregisterSymbol(const long long hService, const wchar_t* symbol)
      {
              ServiceManaged::UnregisterSymbol(IntPtr((HANDLE)hService), gcnew String(symbol));
      }
      

      O código está pronto, agora precisamos compilá-lo e criá-lo. Vamos especificar o diretório de saída "C:\Program Files\MetaTrader 5\MQL5\Libraries" nas opções de projeto. Após a compilação, três bibliotecas aparecerão na pasta especificada.

      O programa mql5 usa apenas uma delas, ou seja, a QExportWrapper.dll, as duas outras bibliotecas são usadas por ele. Por causa disso, precisamos colocar as bibliotecas Qexport.dll e Qexport.Service.dll na pasta raiz do MetaTrader. Não é conveniente.

      A solução é criar o arquivo de configuração e especificar o caminho para as bibliotecas lá. Vamos criar o arquivo com o nome terminal.exe.config na pasta raiz do MetaTrader e escreva as seguintes strings lá:

      
      <configuration>
         <runtime>
            <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
               <probing privatePath="mql5\libraries" />
            </assemblyBinding>
         </runtime>
      </configuration>
      

      Está pronto. Agora o CLR irá procurar pelas bibliotecas na pasta que especificamos.

      6. Implementação da parte do servidor no MQL5

      Finalmente, conseguimos a programação da parte do servidor no mql5. Vamos criar um novo arquivo QService.mqh e definir as funções importadas do QExpertWrapper.dll:

      #import "QExportWrapper.dll"
         long  CreateExportService(string);
         void DestroyExportService(long);
         void RegisterSymbol(long, string);
         void UnregisterSymbol(long, string);
         void SendTick(long, string, MqlTick&);
      #import
       
      

      É ótimo que o mql5 possua classes porque é um recurso ideal para encapsular todas as lógicas internas, que significativamente simplificam o trabalho e entendimento do código. Por isso, vamos projetar uma classe que será uma shell para os métodos da biblioteca.

      Além disso, para evitar um problema com a criação de serviço para cada símbolo, vamos organizar a verificação do serviço de trabalho com tal nome, e trabalharemos por isso em tal caso. Um método ideal para fornecer essa informações são variáveis globais, pelos seguintes motivos:

      • As variáveis globais desaparecem após o fechamento do terminal do cliente. O mesmo acontece com o serviço;
      • Podemos fornecer o número de objetos Qservice que usam o serviço. Isso permite fechar o serviço físico apenas após o último objeto ser fechado.

      Então, vamos criar uma classe Qservice:

      class QService
      {
         private:
            // service pointer
            long hService;
            // service name
            string serverName;
            // name of the global variable of the service
            string gvName;
            // flag that indicates is service closed or not
            bool wasDestroyed;
            
            // enters the critical section
            void EnterCriticalSection();
            // leaves the critical section
            void LeaveCriticalSection();
            
         public:
         
            QService();
            ~QService();
            
            // opens service
            void Create(const string);
            // closes service
            void Close();
            // sends tick
            void SendTick(const string, MqlTick&);
      };
      
      //--------------------------------------------------------------------
      QService::QService()
      {
         wasDestroyed = false;
      }
      
      //--------------------------------------------------------------------
      QService::~QService()
      {
         // close if it hasn't been destroyed
         if (!wasDestroyed)
            Close();
      }
      
      //--------------------------------------------------------------------
      QService::Create(const string serviceName)
      {
         EnterCriticalSection();
         
         serverName = serviceName;
         
         bool exists = false;
         string name;
         
         // check for the active service with such name
         for (int i = 0; i < GlobalVariablesTotal(); i++)
         {
            name = GlobalVariableName(i);
            if (StringFind(name, "QService|" + serverName) == 0)
            {
               exists = true;
               break;
            }
         }
         
         if (!exists)   // if not exists
         {
            // starting service
            hService = CreateExportService(serverName);
            // adding a global variable
            gvName = "QService|" + serverName + ">" + (string)hService;
            GlobalVariableTemp(gvName);
            GlobalVariableSet(gvName, 1);
         }
         else          // the service is exists
         {
            gvName = name;
            // service handle
            hService = (int)StringSubstr(gvName, StringFind(gvName, ">") + 1);
            // notify the fact of using the service by this script
            // by increase of its counter
            GlobalVariableSet(gvName, NormalizeDouble(GlobalVariableGet(gvName), 0) + 1);
         }
         
         // register the chart symbol
         RegisterSymbol(hService, Symbol());
         
         LeaveCriticalSection();
      }
      
      //--------------------------------------------------------------------
      QService::Close()
      {
         EnterCriticalSection();
         
         // notifying that this script doen't uses the service
         // by decreasing of its counter
         GlobalVariableSet(gvName, NormalizeDouble(GlobalVariableGet(gvName), 0) - 1);
           
         // close service if there isn't any scripts that uses it
         if (NormalizeDouble(GlobalVariableGet(gvName), 0) < 1.0)
         {
            GlobalVariableDel(gvName);
            DestroyExportService(hService);
         }  
         else UnregisterSymbol(hService, Symbol()); // unregistering symbol
          
         wasDestroyed = true;
         
         LeaveCriticalSection();
      }
      
      //--------------------------------------------------------------------
      QService::SendTick(const string symbol, MqlTick& tick)
      {
         if (!wasDestroyed)
            SendTick(hService, symbol, tick);
      }
      
      //--------------------------------------------------------------------
      QService::EnterCriticalSection()
      {
         while (GlobalVariableCheck("QService_CriticalSection") > 0)
            Sleep(1);
         GlobalVariableTemp("QService_CriticalSection");
      }
      
      //--------------------------------------------------------------------
      QService::LeaveCriticalSection()
      {
         GlobalVariableDel("QService_CriticalSection");
      }

      A classe contém os seguintes métodos:

      MétodoDescrição
      Create(const string)Inicia o serviço
      Close()Fecha o serviço
      SendTick(const string, MqlTick&)Envia a cotação

      Observe também que os métodos privados EnterCriticalSection() e LeaveCriticalSection() permitem que você execute as seções críticas do código entre eles.

      Isso nos poupará de casos de chamadas simultâneas da função Create() e a criação de novos serviços para cada QService.

      Então, descrevemos a classe para trabalhar com o serviço, agora vamos escrever um Expert Advisor para a transmissão de cotações. O Expert Advisor foi escolhido por causa da sua possibilidade de processar todos os ticks que chegaram.

      //+------------------------------------------------------------------+
      //|                                                    QExporter.mq5 |
      //|                                             Copyright GF1D, 2010 |
      //|                                             garf1eldhome@mail.ru |
      //+------------------------------------------------------------------+
      #property copyright "GF1D, 2010"
      #property link      "garf1eldhome@mail.ru"
      #property version   "1.00"
      
      #include "QService.mqh"
      //--- input parameters
      input string  ServerName = "mt5";
      
      QService* service;
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
      {
         service = new QService();
         service.Create(ServerName);
         return(0);
      }
      
      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
      {
         service.Close();
         delete service;
         service = NULL;
      }
      
      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
      {
         MqlTick tick;
         SymbolInfoTick(Symbol(), tick);
         
         service.SendTick(Symbol(), tick);
      }
      //+------------------------------------------------------------------+
      

      7. Teste do desempenho da comunicação entre ex5 e o cliente .NET

      É evidente que o desempenho total do serviço irá diminuir se as cotações chegarem diretamente do terminal do cliente, então me interessei em medi-lo. Eu estava certo que deveria diminuir devido a perda inevitável do tempo do CPU para organização e conversão de tipo.

      Por este motivo, escrevi um simples script que é o mesmo que o do primeiro teste. A função Start() parece com o seguinte:

         QService* serv = new QService();
         serv.Create("mt5");
      
         MqlTick tick;
         SymbolInfoTick("GBPUSD", tick);
       
         int total = 0;
         
         for(int c = 0; c < 10; c++)
         {
            int calls = 0;
            
            int ticks = GetTickCount();
      
            while(GetTickCount() - ticks < 1000)
            {
               for(int i = 0; i < 100; i++) serv.SendTick("GBPUSD", tick);
               calls++;
            }
            
            Print(calls * 100," calls per second");
            
            total += calls * 100;
         }
           
         Print("Average ", total / 10," calls per second");
      
         serv.Close();
         delete serv;
      

      Eu obtive os seguintes resultados:

      1900  calls per second
      2400  calls per second
      2100  calls per second
      2300  calls per second
      2000  calls per second
      2100  calls per second
      2000  calls per second
      2100  calls per second
      2100  calls per second
      2100  calls per second
      Average  2110  calls per second
      

      2500 ticks/seg. vs 1900 ticks/seg. 25% é o preço que deveria ser pago pelo uso dos serviços do MT5, mas de qualquer forma é insuficiente. é interessante observar que o desempenho pode diminuir usando a pool de threads e o método estático System.Threading.ThreadPool.QueueUserWorkItem.

      Usando este método, obtive a velocidade de transferência de até 10000 ticks por segundo. Mas seu trabalho em um teste severo foi instável devido ao fato de que o Garbage Collector não tem tempo para excluir objetos - como resultado, a memória alocada pelo MetaTrader cresce rapidamente e finalmente trava. Mas foi um teste severo, bem mais intenso do que a realidade, então não há perigo em usar a pool de threads.

      8. Teste de tempo real

      Criei um exemplo de tabela de ticks usando o serviço. O projeto está em anexo no arquivo com o nome WindowsClient. O resultado de seu trabalho é apresentado abaixo:

      Fig 1. Janela principal da aplicação WindowsClient com a tabela de cotações

      Conclusão

      Neste artigo, descrevi um dos métodos de exportação de cotações para aplicações .NET. Tudo que era necessário foi implementado e agora temos classes prontas que podem ser usadas em suas próprias aplicações. A única coisa que não é conveniente é anexar scripts para cada um dos gráficos necessários.

      No presente momento acho que este problema pode ser resolvido usando os perfis do MetaTrader. Por outro lado, se você não precisa de todas as cotações, você pode organizar isso com um script que transmite as cotações para os símbolos necessários. Como você pode entender, a transmissão de profundidade de mercado ou até mesmo o acesso pelos dois lados podem ser organizados da mesma maneira.

      Descrição dos arquivos:

      Bin.rar - arquivo com uma solução pronta. Para os usuários que querem ver como isso funciona. Observe ainda que o .NET Framework 3.5 (talvez também funcione com a versão 3.0) deve estar instalado no seu computador.

      Src.rar - código-fonte completo do projeto. Para trabalhar com isso você precisará do MetaEditor e Visual Studio 2008.

      QExportDemoProfile.rar - Perfil MetaTrader, que anexa o script a 10 gráficos, como mostrado na Fig. 1.

      Traduzido do Russo pelo artigo original MetaQuotes Software Corp.
      : https://www.mql5.com/pt/articles/27

      Traduzido do russo pela MetaQuotes Ltd.
      Artigo original: https://www.mql5.com/ru/articles/27

      Implementação prática dos filtros digitais no MQL5 para principiantes Implementação prática dos filtros digitais no MQL5 para principiantes
      A ideia da filtragem de sinal digital foi amplamente discutida em tópicos de fóruns sobre a construção dos sistemas de negócio. E seria imprudente não criar um código padrão de filtros digitais no MQL5. Neste artigo, o autor descreve a transformação de um simples código do indicador SMA em seu artigo "Indicadores personalizados no MQL5 para iniciantes" em um código do mais complicado e universal filtro digital. Este artigo é uma sequência lógica do artigo anterior. Ele também fala como substituir o texto no código e como corrigir erros de programação.
      Desenhando emissões de indicador no MQL5 Desenhando emissões de indicador no MQL5
      Neste artigo, consideraremos a emissão dos indicadores - uma nova abordagem para pesquisa de mercado. O cálculo da emissão é baseado na intersecção de diferentes indicadores: mais e mais pontos com diferentes cores e formas aparecem após cada tick. Eles formam vários clusters na forma de uma nebulosa, nuvens, pistas, linhas, arcos, etc. Estas formas podem ajudar a detectar as molas e forças invisíveis que afetam o movimento dos preços do mercado.
      MQL5: análise e processamento dos relatórios Commodity Futures Trading Commission (CFTC) no MetaTrader 5 MQL5: análise e processamento dos relatórios Commodity Futures Trading Commission (CFTC) no MetaTrader 5
      Neste artigo, desenvolverei uma ferramenta para análise de relatório CFTC. Resolveremos o seguinte problema: desenvolver um indicador que permita usar os dados do relatório CFTC diretamente dos arquivos de dados fornecidos pela Comissão sem conversão e processamento intermediários. Além disso, ele pode ser utilizado para diferentes propósitos: organizar dados como um indicador, prosseguir com os dados em outros indicadores, em scripts para análise automatizada, em Expert Advisors para uso em estratégias de trading.
      Troca de dados entre indicadores: é fácil! Troca de dados entre indicadores: é fácil!
      Queremos criar um ambiente que fornecesse acesso aos dados de indicadores anexos ao gráfico e que teria as seguintes propriedades: ausência de cópia de dados; modificação mínima do código dos métodos disponíveis, se precisarmos usá-los; preferencialmente código MQL (claro, temos que usar o DLL, mas usaremos apenas algumas strings do código C++). O artigo descreve um método fácil para desenvolver um ambiente de programa para o terminal MetaTrader, que poderia fornecer meios para acessar os buffers dos indicadores de outros programas MQL.