Descargar MetaTrader 5

Como exportar cotizaciones desde MetaTrader 5 a aplicaciones .NET usando los servicios de WCF

22 enero 2014, 09:00
Alexander
0
741

Introducción

Los programadores que utilizan el servicio DDE en MetaTrader 4 han oído probablemente que en la quinta versión ya no está soportada. Y no hay una solución estándar para exportar cotizaciones. Como solución a este problema, los desarrolladores de MQL5 recomiendan usar nuestras propias dll para implementarlo. Por tanto, si tenemos que escribir la implementación, ¡hagámoslo de forma inteligente!

¿Por qué .NET?

Para mí, con mi dilatada experiencia como programador en .NET, sería más razonable, interesante y simple implementar la exportación de cotizaciones usando esta plataforma. Por desgracia, no hay soporte nativo de .NET en MQL5 en la quinta versión. Estoy seguro de que los desarrolladores tienen razones para ello. Por tanto, vamos a usar win32dll como envolvente para el soporte .NET.

¿Por qué WCF?

He elegido Windows Communication Foundation Technology (WCT) por varias razones: por un lado, es fácil de aplicar y adaptar y, por otro, quería probarla bajo condiciones complejas. Además, según Microsoft, WCF tiene un rendimiento algo superior comparado con .NET Remoting.


Requisitos del sistema

Vamos a pensar qué queremos para nuestro sistema. Creo que hay dos requisitos principales:

    1. Por supuesto, necesitamos exportar ticks y mejor que usemos la estructura nativa MqlTick;
    2. Es preferible conocer la lista de símbolos exportados actualmente.

    Vamos a empezar...


    1. Clases y contratos generales

    En primer lugar vamos a crear una nueva librería de clase y la llamaremos QExport.dll. Definimos la estructura MqlTick como un 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; }
        }
    

    Y luego definiremos los contratos del servicio. No me gusta usar clases de configuración y proxy-clases generadas, luego no incluiremos estas características aquí.

    Vamos a definir el primer contrato de servidos según los requisitos descritos anteriormente:

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

    Como vemos, hay un esquema estándar para suscribirse y anular la subscripción de las notificaciones de servidor. A continuación se describen los detalles resumidos de las operaciones:

    OperaciónDescripción
    Subscribe()Subscribirse a la exportación de ticks
    Unsubscribe()Anular la suscripción a la exportación de ticks
    GetActiveSymbols()Devuelve la lista de símbolos exportados


    Y la siguiente información debe enviarse para la rellamada: la propia cotización y la notificación sobre los cambios de las listas de símbolos exportados. Vamos a definir las operaciones requeridas como "operaciones one way" para incrementar el rendimiento:

        [ServiceContract]
        public interface IExportClient
        {
            [OperationContract(IsOneWay = true)]
            void SendTick(String symbol, MqlTick tick);
    
            [OperationContract(IsOneWay = true)]
            void ReportSymbolsChanged();
        }
    
    OperaciónDescripción
    SendTick(String, MqlTick)Envía el tick
    ReportSymbolsChanged()Notifica al cliente los cambios en la lista de símbolos exportados

    2. Implementación del servidor

    Vamos a crear una nueva construcción con el nombre Qexport.Service.dll para el servicio con la implementación del contrato de servidor.

    Vamos a elegir NetNamedPipesBinding para una consolidación ya que tiene el mayor rendimiento comparado con las consolidaciones estándar. Si necesitamos comunicar cotizaciones, en una red por ejemplo, debe usarse NetTcpBinding.

    Estos son algunos detalles de la implementación del contrato de servidor:

    La definición de la clase. En primer lugar debe marcarse con el atributo de ServiceBehavior con los siguientes modificadores:

    • InstanceContextMode = InstanceContextMode.Single- para facilitar el uso de una instancia de servicio para todas las solicitudes procesadas, aumentará el rendimiento de la solución. Además, tendremos la posibilidad de servir y gestionar la lista de símbolos exportados;
    • ConcurrencyMode = ConcurrencyMode.Multiple -significa el procesamiento paralelo de todas las solicitudes de clientes;
    • UseSynchronizationContext = false– significa que no adjuntamos al hilo de la GUI para evitar situaciones de parada. No es necesario aquí para nuestra tarea, pero es necesario si queremos alojar el servicio usando aplicaciones Windows.
    • IncludeExceptionDetailInFaults = true– para incluir los detalles de la excepción al objeto FaultExceptioncuando se pasa al cliente.

    El propio ExportService contiene dos interfaces: IExportService, IDisposable. La primera implementa todas las funciones de servicio y la segunda implementa el modelo estándar de la liberación de los recursos .NET.

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

    Vamos a describir las variables del servicio:

            // 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<IExportClient> _Clients = new Collection<IExportClient>();
    
            // active symbols list
            private List<String> _ActiveSymbols = new List<string>();
            
            // object for locking
            private object lockClients = new object();
    

    Vamos a definir los métodos Open() y Close() que abren y cierran nuestro servicio:

            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;
                }
    
                // ...
            }
    

    A continuación la implementación de los métodos IExportService:

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

    Ahora necesitamos añadir métodos para enviar ticks y para registrar y borrar los 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 a resumir la lista de principales funciones de servidor (solo las que necesitamos):

    MétodosDescripción
    Open()Ejecuta el servidor
    Close()Para el servidor
    RegisterSymbol(String)Añade el símbolo a la lista de símbolos exportados
    UnregisterSymbol(String)Borra el símbolo de la lista de símbolos exportados
    GetActiveSymbols()Devuelve el número de símbolos exportados
    SendTick(String, MqlTick)Envía ticks a los clientes

     

    3. Implementación del cliente

    Hemos considerado el servidor, creo que está claro, ahora es el momento de considerar el cliente. Vamos a construir Qexport.Client.dll. El contrato del cliente se implementará aquí. Primero, debe marcarse con el atributo CallbackBehavior, que define su comportamiento. Tiene los siguientes modificadores:

      • ConcurrencyMode = ConcurrencyMode.Multiple - significa el procesado en paralelo para todas las rellamadas y las respuestas del servidor. Este modificador es muy importante. Imagine que el servidor quiere notificar al cliente los cambios en la lista de símbolos exportados llamando a ReportSymbolsChanged(). Y el cliente (en su rellamada) quiere recibir la nueva lista de símbolos exportados llamando al método del servidor GetActiveSymbols(). Resulta que el cliente no puede recibir respuesta del servidor porque hace una rellamada con espera por la respuesta del servidor. Como resultado, el cliente fallará debido a que se agotó el tiempo.
      • UseSynchronizationContext = false  - especifica que no nos adherimos a la GUI para evitar situaciones de parada. Por defecto las rellamadas de wcf se adjuntan al hilo padre. Si el hilo padre tiene GUI la situación es posible cuando la rellamada espera a que se complete el método que la ha llamado, pero el método no puede finalizar porque está esperando a que la rellamada termine. Es algo similar al caso anterior, aunque son dos cosas diferentes.

      En cuanto al servidor, el cliente también implementa dos interfaces: IExportClient and IDisposable:

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

      Vamos a describir las variables de servicio:

              // 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;
                  }
              }
      

      Ahora creamos eventos para los métodos de nuestras rellamadas. Es necesario para que la aplicación del cliente pueda suscribirse a los eventos y obtener notificaciones sobre los cambios del estado del cliente.

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

      También define los métodos Open() y Close() para el cliente:

              public void Open()
              {
                  // creating channel factory
                  var factory = new DuplexChannelFactory<IExportService>(
                      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 la conexión y desconexión de la alimentación se llama cuando se abre o se cierra un cliente, por lo que no es necesario llamarlos directamente.

      Y ahora, vamos a escribir el contrato del cliente. Su implementación lleva a la generación de los eventos siguientes:

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

      Finalmente, se definen las principales propiedades y métodos del cliente de la siguiente forma:

      PropiedadDescripción
      ServicioCanal de comunicación del servicio
      CanalInstancia del contrato de servicio IExportService 


      MétodoDescripción
      Open()Se conecta al servidor
      Close()Se desconecta del servidor

       

      EventoDescripción
      TickRecievedGenerado después de recibir la nueva cotización
      ActiveSymbolsChangedGenerado después de los cambios en la lista de símbolos activos

       

      4. Trasferir la velocidad entre dos aplicaciones .NET

      Me resultó interesante medir la velocidad de transferencia entre dos aplicaciones .NET, de hecho, su rendimiento específico medido en ticks por segundo. Escribí varias aplicaciones para medir el rendimiento del servicio: la primera es para el servidor y la segunda para el cliente. Escribí el siguiente código en la función Main() del 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, el código realiza diez medidas del rendimiento.  Tengo los siguientes resultados de las pruebas en mi 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, creo que es suficiente para exportar cotizaciones para 100 símbolos (por supuesto, virtualmente, porque parece que nadie quiere abrir tantos gráficos y adjuntar expertos =)). Además con el creciente número de clientes, el número máximo de símbolos exportados para cada cliente se reduce.


      5. Crear un "estrato"

      Ahora es el momento de pensar cómo conectarlo con el terminal de cliente. Vamos a ver lo que tenemos en la primera llamada de la función en MetaTrader 5: el entorno de ejecución de .NET (CLR) se carga al proceso y se crea el dominio de la aplicación por defecto. Nos interesa que no se descargue después de la ejecución del código.

      La única forma de descargar CLR del proceso es finalizándolo (cerrar el terminal de cliente), lo que forzará a Windows a liberar todos los recursos del proceso. Podemos pues crear nuestros objetos y estos existirán hasta que se descargue el dominio de la aplicación o hasta que sea destruido por el recogedor de basura.

      Puede parecer bueno, pero si evitamos la destrucción del objeto por el recogedor de basura, no podremos acceder a los objetos desde MQL5. Por suerte, dicho acceso puede organizarse fácilmente. El truco es el siguiente: para cada dominio de la aplicación hay una tabla de controladores del recogedor de basura (tabla de controladores del GC -Garbage Collector), que es usado por la aplicación para seguir la pista de la vida del objeto y permitir su gestión manual.

      La aplicación añade y borra elementos de la tabla usando el tipo System.Runtime.InteropServices.GCHandle. Todo lo que necesitamos es envolver nuestro objeto con dicho descriptor y tendremos acceso a él en toda la propiedad GCHandle.Target. De esta forma, podemos obtener la referencia al objeto GCHandle, que está en la tabla de controladores y es seguro que el recogedor de basura no lo moverá ni lo borrará. El objeto envuelto también evitará el reciclado, debido a la referencia del descriptor.

      Es el momento de probar la teoría en la práctica. Para hacer esto vamos a crear una nueva win32 dll con el nombre QExpertWrapper.dll y añadir el soporte CLR, System.dll, QExport.dll, Qexport.Service.dll a la referencia construida. También creamos una clase auxiliar ServiceManaged con finalidad de gestión - para llevar a cabo la ordenación, para recibir objetos por los controladores, 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 a ver la implementación de estos métodos. El método CreateExportService crea el servicio, lo envuelve dentro de GCHandle usando GCHandle. Lo ubica y devuelve su referencia. Si algo va mal muestra un MessageBox con un error. Lo he usado con finalidad de depuración, por lo que no estoy seguro de que sea realmente necesario, pero lo dejo ahí por si acaso.

      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");
              }
      }
      

      El método DestroyExportService lleva el puntero al GCHandle del servicio, obtiene el servicio de la propiedadTarget y llama a su método Close(). Es importante liberar el objeto del servicio llamando a su método Free(). De no ser así, se quedaría en la memoria, el recogedor de basura no lo eliminaría.

      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");
              }
      }
      

      El métodoRegisterSymbol añade un símbolo a la 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");
              }
      }
      

      El método UnregisterSymbol borra un símbolo de la 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");
              }
      }
      

      Y ahora el método SendTick. Como vemos, el puntero se ha transformado en la estructura MqlTick usando la clase Marshal. Otro punto: no hay ningún código en el bloque de captura, se hace para evitar los retrasos de la cola de tick general en caso de error.

      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 a ver la implementación de funciones que serán llamadas desde nuestros 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));
      }
      

      El código está listo y ahora necesitamos compilarlo y construirlo. Vamos a especificar el directorio de salida en las opciones del proyecto como "C:\Program Files\MetaTrader 5\MQL5\Libraries". Después de la compilación aparecerán tres librerías en la carpeta especificada.

      El programa mql5 solo usa una de ellas, QExportWrapper.dll y las otras dos son usadas por esta. Por esta razón necesitamos poner las librerías Qexport.dll y Qexport.Service.dll en la carpeta raíz de MetaTrader. No es aconsejable.

      La solución es crear el archivo de configuración y especificar la ruta para las librerías aquí. Vamos a crear al archivo con el nombre terminal.exe.config en la carpeta raíz de MetaTrader y a escribir ahí los siguientes strings:

      <?xml version="1.0" encoding="UTF-8" ?>
      <configuration>
         <runtime>
            <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
               <probing privatePath="mql5\libraries" />
            </assemblyBinding>
         </runtime>
      </configuration>
      

      Está listo. Ahora, CLR buscará las librerías en la carpeta que hemos especificado.

       

      6. Implementación de la parte del servidor en MQL5

      Finalmente, hemos llegado a la programación de la parte del servidor en mql5. Vamos a crear un nuevo archivo QService.mqh y a definir las funciones importadas de 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
       
      

      Menos mal que mql5 tiene clases porque es una característica ideal para encapsular dentro toda la lógica que simplifica enormemente el trabajo y la comprensión del código. Por tanto, vamos a diseñar una clase que será una envolvente para los métodos de la librería.

      Además, para evitar tener que crear el servicio para cada símbolo, vamos a organizar la comprobación del servicio con dicho nombre y trabajaremos con él en dicho caso. Un método ideal para obtener esta información son las variables globales, por las siguientes razones:

      • las variables globales desaparecen después de que se cierre el terminal de cliente. Lo mismo para el servicio;
      • podemos servir el número de objetos Qservice que usan el servicio. Permite cerrar el servicio físico solo después de que el último objeto se haya cerrado.

      Vamos a crear una clase 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");
      }

      La clase contiene los siguientes métodos:

      MétodoDescripción
      Create(const string)Inicia el servicio
      Close()Cierra el servicio
      SendTick(const string, MqlTick&)Envía la cotización

       

      Observe también que los métodos privados EnterCriticalSection() y LeaveCriticalSection() le permiten ejecutar las secciones de código crítico en ellos.

      Nos evitará los casos de llamadas simultáneas a la función Create() y la creación de nuevos servicios para cada QService.

      Hemos descrito la clase para trabajar con el servicio, ahora vamos a escribir un asesor experto para la transmisión de las cotizaciones. Se ha elegido el asesor experto por su capacidad para procesar todos los ticks que han llegado.

      //+------------------------------------------------------------------+
      //|                                                    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. Probando el funcionamiento de la comunicación entre ex5 y el cliente .NET

      Es evidente que el rendimiento total del servicio disminuirá si las cotizaciones llegan directamente desde el terminal de cliente, luego me interesa medirlo. Estaba seguro de que iba a disminuir por la pérdida inevitable de tiempo de CPU para la organización y el encasillamiento.

      Con esta finalidad escribí un script simple que es el mismo que para la primera prueba. La función Start() es la siguiente:

         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;
      

      Tengo los siguientes 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 1.900 ticks/seg. 25% es el precio que debe pagarse por el uso de los servicios de MT5, pero de todas formas es suficiente. Es interesante ver que el rendimiento puede aumentarse usando el conjunto de hilos y el método estático System.Threading.ThreadPool.QueueUserWorkItem.

      Usando este método obtengo una velocidad de transferencia de hasta 10.000 ticks por segundo. Pero su funcionamiento en una prueba fue inestable debido al hecho de que el recogedor de basura no tenía tiempo de borrar objetos y, como consecuencia, la memoria asignada por MetaTrader 5 creció rápidamente y finalmente se colapsó. Pero fue una prueba dura, muy alejada de la realidad, por lo que no hay nada peligroso en usar el conjunto de hilos.


      8. Pruebas en tiempo real.

      He creado un ejemplo de tabla de ticks usando el servicio. El proyecto se adjunta al archivo y se le da el nombre de WindowsClient. El resultado de su funcionamiento se muestra a continuación:

      Fig 1. Ventana principal de la aplicación WindowsClient con la tabla de cotizaciones

      Conclusión

      En este artículo he descrito uno de los métodos de exportación de cotizaciones a las aplicaciones .NET. Todo lo necesario se ha implementado y ahora tenemos clases preparadas que pueden usarse en sus propias aplicaciones. Lo único que no es aconsejable es adjuntar scripts a cada uno de los gráficos necesarios.

      En el momento actual creo que este problema puede resolverse usando los perfiles de MetaTrader. Por otro lado, si no necesita todas las cotizaciones puede organizarlas con un script que las transmita a los símbolos necesarios. Como comprenderá, la transmisión de la profundidad del mercado o incluso un acceso desde dos lados puede organizarse del mismo modo.

      Descripción de los archivos:

      Bin.rar - archivo con una solución preparada. Para los usuarios que no quieren ver cómo funciona. Tenga en cuenta que .NET Framework 3.5 (puede que también funcione con la versión 3.0) debe instalarse en su ordenador.

      Src.rar - todo el código fuente del proyecto. Para trabajar con el necesitará MetaEditor y Visual Studio 2008.

      QExportDemoProfile.rar -  perfil de Metatrader que adjunta el script a 10 gráficos como se muestra en la Fig. 1.

      Traducido desde el ruso por MetaQuotes Software Corp.
      Artículo original: https://www.mql5.com/es/articles/27

      Traducción del ruso hecha por MetaQuotes Software Corp.
      Artículo original: https://www.mql5.com/ru/articles/27

      Archivos adjuntos |
      bin.rar (33.23 KB)
      src.rar (137.41 KB)
      Dibujando emisiones de indicador en MQL5 Dibujando emisiones de indicador en MQL5

      En este artículo vamos a considerar la emisión de indicadores, un nuevo enfoque de la investigación de mercados. El cálculo de la emisión se basa en la intersección de indicadores distintos: aparecen más y más puntos con diferentes colores y formas después de cada tick. Forman numerosas agrupaciones como nebulosas, nubes, rastros, líneas, arcos, etc. Estas formas ayudan a detectar los resortes ocultos y las fuerzas que afectan al movimiento de los precios del mercado.

      Intercambio de datos entre indicadores. Es fácil Intercambio de datos entre indicadores. Es fácil

      Queremos crear un entorno que proporcione acceso a los datos de los indicadores adjuntos a un gráfico y que tenga las siguientes propiedades: ausencia de copiado de datos; modificación mínima del código de métodos disponibles si necesitamos usarlo; es preferible el código de MQL (por supuesto, tenemos que usar DLL pero usaremos una docena de strings de código de C++). El artículo describe un método sencillo para desarrollar un entorno de programa para el terminal de MetaTrader que proporcione medios para acceder a los buffers del indicador desde otros programas MQL.

      Implementación práctica de filtros digitales en MQL5 para principiantes Implementación práctica de filtros digitales en MQL5 para principiantes

      La idea del filtrado de señales digitales ha sido ampliamente discutida en foros sobre el tema de la elaboración de sistemas de trading. Y sería imprudente no crear un código estándar de filtros digitales en MQL5. En este artículo el autor describe la transformación de código simple de indicadores SMA de su artículo "Indicadores personalizados en MQL5 para principiantes", en el código de un filtro digital más complejo y universal. Este artículo es consecuencia del artículo anterior. También trata sobre cómo reemplazar texto en el código y cómo corregir errores de programación.

      MQL5: análisis y procesado de informes de la Comisión de Operaciones del Mercado de Futuros (CFTC) en MetaTrader 5 MQL5: análisis y procesado de informes de la Comisión de Operaciones del Mercado de Futuros (CFTC) en MetaTrader 5

      En este artículo vamos a desarrollar una herramienta para el análisis de informes de la CFTC (Commodity Futures Trading Commission). Vamos a resolver los siguientes problemas: desarrollar un indicador que permita el uso de los datos de los informes de la CFTC directamente de los archivos de datos suministrados por la Comisión sin necesidad de un procesado o conversión intermedia. Además puede usarse para diferentes finalidades: para trazar los datos como un indicador, para proceder con los datos en los demás indicadores, en los scripts para el análisis automatizado y en los Expert Advisors para su uso en las estrategias de trading.