English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Comment exporter des cotations de МetaTrader 5 vers des applications .NET à l'aide des services WCF

Comment exporter des cotations de МetaTrader 5 vers des applications .NET à l'aide des services WCF

MetaTrader 5Exemples | 15 novembre 2021, 14:35
135 0
Alexander
Alexander

Introduction

Les programmeurs qui utilisent le service DDE dans MetaTrader 4 ont probablement entendu dire que dans la cinquième version, n'est plus prise en charge. Et il n'y a pas de solution standard pour exporter des cotations En solution à ce problème, les développeurs MQL5 suggèrent d'utiliser votre propre dll, qui l'implémente. Donc, si nous devons écrire l'implémentation, faisons-le intelligemment !

Pourquoi .NET ?

Pour moi avec ma longue expérience de programmation en .NET, il serait plus raisonnable, intéressant et simple d'implémenter l'export de cotations en utilisant cette plateforme. Malheureusement, il n'y a pas de support natif de .NET dans MQL5 en cinquième version. Je suis sûr que les développeurs ont des raisons à cela. Par conséquent, nous utiliserons la dll win32 comme emballeur pour la prise en charge de .NET.

Pourquoi WCF ?

La Technologie Windows Communication Foundation (WCF) a été choisie par moi pour plusieurs raisons : d'une part, elle est facile à étendre et à adapter, d'autre part, je souhaitais la vérifier sous un travail dur. De plus, selon Microsoft, WCF affiche un peu plus de performances que .NET Remoting.

Exigences du Système

Pensons à ce que nous voulons de notre système. Je pense qu'il y a deux exigences principales:

    1. Bien sûr, nous devons exporter les ticks, en utilisant mieux la structure native MqlTick ;
    2. Il est préférable de connaître la liste des symboles actuellement exportés.

    Commençons...

    1. Classes générales et contrats

    Tout d'abord, créons une nouvelle bibliothèque de classes et nommons-la QExport.dll.. Nous définissons la structure MqlTick comme 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; }
        }
    

    Ensuite, nous définirons les contrats de service. Je n'aime pas utiliser les classes de configuration et les classes proxy générées, vous ne rencontrerez donc pas de telles fonctionnalités ici.

    Définissons le premier contrat de serveur en fonction des exigences décrites ci-dessus :

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

    Comme nous le constatons, il existe un schéma standard d'abonnement et de désabonnement des notifications du serveur. Les brefs détails des opérations sont décrits ci-dessous :

    OpérationDescription
    S’abonner()Abonnez-vous à l'exportation des tiques
    Désabonner()Se désabonner de l'export des ticks
    GetActiveSymbols()Renvoie la liste des symboles exportés


    Et les informations suivantes doivent être envoyées au rappel client : la cotation elle-même et la notification portant sur les modifications des lits de symboles exportés. Définissons les opérations requises comme des « opérations à sens unique » pour augmenter les performances :

        [ServiceContract]
        public interface IExportClient
        {
            [OperationContract(IsOneWay = true)]
            void SendTick(String symbol, MqlTick tick);
    
            [OperationContract(IsOneWay = true)]
            void ReportSymbolsChanged();
        }
    
    OpérationDescription
    SendTick(String, MqlTick)Envoie la tique
    ReportSymbolsChanged()Informer le client des modifications apportées à la liste des symboles exportés

    2. Implémentation du serveur

    Créons une nouvelle version avec le nom Qexport.Service.dll pour le service avec l'implémentation du contrat de serveur.

    Choisissons le NetNamedPipesBinding pour une liaison, car il dispose de la plus grande performance par rapport aux fixations standard. Si l'on a besoin de diffuser des cotations, sur un réseau par exemple, le NetTcpBinding Devrait être utilisé.

    Voici quelques détails de l’implémentation du contrat de serveur :

    La définition de classe. Tout d'abord, il doit être marqué avec l'attribut ServiceBehavior avec les modificateurs suivants :

    • InstanceContextMode = InstanceContextMode..Single - de fournir l'utilisation d'une instance de service pour toutes les demandes traitées, cela augmentera les performances de la solution. De plus, nous aurons la possibilité de servir et de gérer la liste des symboles exportés ;
    • ConcurrencyMode = ConcurrencyMode..Multiple - indique le traitement parallèle de toutes les requêtes du client ;
    • UseSynchronizationContext = faux – signifie que nous ne nous attachons pas au thread de l'interface graphique pour éviter les situations de blocage. Ce n'est pas nécessaire ici pour notre tâche, mais c'est nécessaire si nous souhaitons héberger le service à l'aide des applications Windows.
    • IncludeExceptionDetailInFaults = vrai – pour inclure les détails de l'exception à l'objet FaultException lorsqu'il est transmis au client.

    Le service d'exportation lui-même comporte deux interfaces : IExportService, IJetable. Le premier implémente toutes les fonctions de service, le second implémente le modèle standard de libération des ressources .NET.

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

    Décrivons les variables du service :

            // 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();
    

    Définissons les méthodes Open() et Close(), qui ouvrent et clôturent notre service :

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

    Ensuite, l'implémentation des méthodes 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();
            }
    

    Nous devons maintenant ajouter des méthodes pour envoyer des ticks et pour enregistrer et supprimer les symboles exportés.

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

    Récapitulons la liste des principales fonctions du serveur (uniquement celles dont nous avons besoin) :

    MéthodesDescription
    Open()Exécute le serveur
    Close()Arrête le serveur
    RegisterSymbol(String)Ajoute le symbole à la liste des symboles exportés
    UnregisterSymbol(String)Supprime le symbole de la liste des symboles exportés
    GetActiveSymbols()Renvoie le nombre de symboles exportés
    SendTick(String, MqlTick)Envoie la coche aux clients

     

    3. Implémentation client

    Nous avons pris en compte le serveur, je pense que c'est clair, il est donc temps de s’intéresser au client. Créons Qexport.Client.dll. Le contrat client y sera implémenté Tout d'abord, il doit être marqué avec l'attribut CallbackBehavior, qui définit son comportement. Il a les modificateurs suivants :

      • ConcurrencyMode = ConcurrencyMode.Multiple - indique le traitement parallèle de tous les rappels et réponses du serveur. Ce modificateur est très important. Imaginez que ce serveur souhaite informer le client des modifications apportées à la liste des symboles exportés en appelant le rappel ReportSymbolsChanged(). Et le client (dans son rappel) souhaite recevoir la nouvelle liste des symboles exportés en appelant la méthode serveur GetActiveSymbols(). Il s'avère donc que le client ne peut pas recevoir de réponse du serveur car il procède au rappel en attendant la réponse du serveur. En conséquence, le client tombera suite au délai d'attente.
      • UseSynchronizationContext = false - Indique que nous ne nous attachons pas à l'interface graphique pour éviter les situations de blocage. Par défaut, les rappels wcf sont attachés au thread parent. Si le thread parent dispose d’ une interface graphique, la situation est possible lorsque le rappel attend l’accomplissement de la méthode par laquelle il a été appelé, mais la méthode ne peut pas se terminer car elle attend la fin du rappel. C'est quelque chose de similaire au cas précédent, même si ce sont deux choses différentes.

      Comme pour le cas du serveur, le client implémente également deux interfaces : IExportClient et IDisposable :

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

      Décrivons les variables de service :

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

      Nous allons maintenant créer des événements pour nos méthodes de rappel. Il est nécessaire que l'application client puisse s'abonner aux événements et recevoir des notifications sur les changements d'état du client.

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

      Définissez également les méthodes Open() et Close() pour le client :

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

      Notez que la connexion et la déconnexion des flux sont appelées lorsqu'un client est ouvert ou fermé, il n'est donc pas nécessaire de les appeler directement.

      Et maintenant, écrivons le contrat client. Son implémentation conduit à la génération des événements suivants :

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

      Enfin, les principales propriétés et méthodes du client sont définies comme suit :

      propertyDescription
      ServiceCanal de communication des services
      CanalInstance de contrat de service IExportService 


      MéthodeDescription
      Open()Se connecte au serveur
      Close()Se déconnecte du serveur

       

      ÉvénementDescription
      TiqueReçuGénéré après la réception de la nouvelle cotation
      ActiveSymbolsChangedGénéré après les modifications à la liste des symboles actifs

       

      4. Vitesse de transfert entre deux applications .NET

      C'était intéressant pour moi de mesurer la vitesse de transfert entre deux applications .NET, en fait, c'est le débit, qui se mesure en ticks par seconde. J'ai écrit plusieurs applications console pour mesurer les performances du service : la première touche le serveur, la seconde le client. J'ai écrit le code suivant dans la fonction Main() du serveur :

                  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();
      

      Comme nous le constatons, le code effectue dix mesures de débit.  J'ai obtenu les résultats suivants du test sur mon 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 par seconde - je pense qu'il suffit d'exporter des cotations pour 100 symboles (bien sûr, virtuellement, car il semble que personne ne souhaite ouvrir autant de graphiques et attacher des experts =)) De plus, avec un nombre croissant de clients, le nombre maximal de symboles exportés pour chaque client est réduit.

      5. Créer une "strate"

      Il est maintenant temps de réfléchir à la manière de le connecter au terminal client. Voyons ce que nous avons au premier appel de la fonction dans MetaTrader 5 : l'environnement d'exécution .NET (CLR) est chargé au processus et le domaine d'application est créé par défaut. Il est intéressant de noter qu'il n'est pas déchargé après l'exécution du code.

      La seule manière de décharger le CLR du processus est de le terminer (fermer le terminal client), cela forcera Windows à effacer toutes les ressources du processus Ainsi, nous pouvons créer nos objets et ils existeront jusqu'à ce que le domaine d'application soit déchargé, ou détruit par le Garbage Collectionneur.

      On peut dire que ça a l'air bien, mais même si nous empêchons la destruction de l'objet par le Garbage Collector, nous ne pouvons pas être en mesure d'accéder aux objets depuis MQL5. Heureusement, un tel accès peut être organisé facilement. L'astuce est la suivante : pour chaque domaine d'application, il existe une table de poignées Garbage Collector (GC handle table), qui est utilisée par l'application pour suivre la durée de vie de l'objet et permet de la gérer manuellement.

      L'application ajoute et supprime des éléments de la table en utilisant le type System.Runtime.InteropServices.GCHandle. Tout ce dont nous avons besoin est d'emballer notre objet avec un tel descripteur et nous y avons accès à l’aide de la propriété GCHandle.Target. Ainsi, nous pouvons obtenir la référence à l'objet GCHandle, qui est dans la table des poignées et il est garanti qu'il ne sera pas déplacé ou supprimé par Garbage Collector. L'objet emballé évitera également le recyclage, du fait de la référence par descripteur.

      Il est maintenant temps de tester la théorie dans la pratique. Pour ce faire, créons une nouvelle dll win32 dont le nom QExpertWrapper.dll et ajoutons le support CLR, System.dll, QExport.dll, Qexport.Service.dll à la référence de build. Nous créons également une classe auxiliaire ServiceManaged à des fins de gestion - pour effectuer le marshalling, recevoir des objets par des poignées, 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);
      };
      

      Examinons l’implémentation de ces méthodes. La méthode CreateExportService crée le service, l'encapsule dans GCHandle à l'aide de GCHandle..Alloc et renvoie sa référence. Si quelque chose ne va pas, il affiche une MessageBox avec une erreur. Je l'ai utilisé à des fins de débogage, donc je ne suis pas sûr que ce soit vraiment nécessaire, mais je l'ai laissé ici juste au cas 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");
              }
      }
      

      La méthode DestroyExportService obtient le pointeur vers le GCHandle du service, obtient le service de la propriété Target et appelle sa méthode Close(). Il est important de libérer l'objet de service en appelant sa méthode Free(). Autrement, il restera en mémoire, le Garbage Collector ne le supprime pas.

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

      La méthode RegisterSymbol ajoute un symbole à la liste des symboles exportés :

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

      La méthode UnregisterSymbolsupprime un symbole de la liste :

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

      Et maintenant la méthode SendTick.. Comme nous le voyons, le pointeur est transformé en structure MqlTick à l'aide de la classe Marshal. Autre point : il n'y a pas de code dans catch block - c'est fait pour éviter les décalages de la file d'attente générale des ticks en cas d'erreur.

      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 (...)
              {
              }
      }
      

      Examinons l'implémentation de fonctions, qui seront appelées à partir de nos programmes 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));
      }
      

      Le code est prêt, nous devons maintenant le compiler et le construire. Indiquons le répertoire de sortie comme "C:\Program Files\MetaTrader 5\MQL5\Libraries" dans les options du projet. Après la compilation, trois bibliothèques apparaîtront dans le dossier indiqué.

      Le programme mql5 n'en utilise qu'une, à savoir QExportWrapper.dll, deux autres bibliothèques sont utilisées par celui-ci. Pour cette raison, nous devons placer les bibliothèques Qexport.dll et Qexport.Service.dll .dans le dossier racine de MetaTrader. Ce n'est pas pratique.

      La solution consiste à créer un fichier de configuration et à y spécifier le chemin des bibliothèques. Créons le fichier nommé terminal.exe.config dans le dossier racine de MetaTrader et écrivons-y les chaînes suivantes :

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

      C'est prêt. Maintenant, CLR recherchera les bibliothèques dans le dossier que nous avons spécifié.

       6. Implémentation de la partie du serveur en MQL5

      Enfin, nous sommes arrivés à la programmation de la partie serveur en mql5. Créons un nouveau fichier QService..mqh et définissons les fonctions importées 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
       
      

      C'est génial que mql5 ait des classes car c'est une fonctionnalité idéale pour encapsuler toute la logique à l'intérieur, ce qui simplifie considérablement le travail et la compréhension du code. Concevons donc une classe qui sera une coquille pour les méthodes de la bibliothèque.

      De plus, pour éviter la situation de création de service pour chaque symbole, organisons la vérification du service de travail avec un tel nom, et nous nous attèlerons dessus dans un tel cas. Les variables globales constituent une méthode idéale pour fournir ces informations, pour les raisons suivantes :

      • les variables globales disparaissent après la clôture du terminal client. La même chose est avec le service;
      • nous pouvons servir le nombre d'objets Qservice, qui utilise le service. Il permet de clôturer le service physique uniquement après la clôture du dernier objet.

      Créons donc une 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");
      }

      La classe comporte les méthodes suivantes :

      MéthodeDescription
      Créer (chaîne const)Commence le service
      Close()Clôture le service
      SendTick(chaîne const, MqlTick&)Envoie une cotation

       

      Notez également que les méthodes privées EnterCriticalSection() et LeaveCriticalSection() vous permettent d'exécuter les sections critiques du code entre elles.

      Cela nous soulagera des cas des appels simultanés de fonction Create() et création de nouveaux services pour chacun QService.

      Donc, nous avons décrit la classe pour travailler avec le service, écrivons maintenant un Expert Advisor pour la diffusion des cotations. L'Expert Advisor a été choisi en raison de sa possibilité de traiter tous les ticks arrivés.

      //+------------------------------------------------------------------+
      //|                                                    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. Test des performances de communication entre ex5 et le client .NET

      Il est évident que la performance totale du service diminuera si les cotations arrivent directement depuis le terminal client, c'est pourquoi je me suis intéressé à le mesurer. J'étais sûr qu'il aurait dû diminuer en raison de la perte inévitable de temps CPU pour le triage et le transtypage.

      Pour cela, j'ai écrit un script simple qui est le même que pour le premier test. La fonction Start() se présente comme suit :

         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;
      

      J'ai obtenu les résultats suivants :

      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/sec contre 1900 ticks/sec. 25% est le prix qui devrait être payé pour l'utilisation des services de MT5, mais de toute façon c'est suffisant. Il est intéressant de noter que les performances peuvent être augmentées en utilisant le pool de threads et la méthode statique System.Threading..ThreadPool..QueueUserWorkItem.

      En utilisant cette méthode, j'ai obtenu une vitesse de transfert jusqu'à 10000 ticks par seconde. Mais son travail dans un test difficile était instable en raison du fait que le Garbage Collector n'a pas le temps de supprimer des objets - en conséquence, la mémoire allouée par MetaTrader augmente rapidement et finalement il se bloque. Mais c'était un test difficile, loin d'être réel, il n'y a donc rien de dangereux dans l’utilisation du pool de threads.

       8. Tests en temps réel

      J'ai créé un exemple de table de ticks en utilisant le service. Le projet est attaché dans l'archive et nommé WindowsClient. Le résultat de ses travaux est présenté ci-dessous :

      Fig 1. Fenêtre principale de l'application WindowsClient avec tableau de guillemets

      Conclusion

      Dans cet article, j'ai décrit l'une des méthodes d'exportation de devis vers des applications .NET. Tout le nécessaire a été implémenté et nous avons maintenant des classes prêtes qui peuvent être utilisées dans vos propres applications. La seule chose qu'il n'est pas pratique de joindre des scripts à chacun des graphiques nécessaires.

      À l'heure actuelle, je pense que ce problème peut être résolu en utilisant les profils MetaTrader. De l'autre côté, si vous n'avez pas besoin de toutes les cotations, vous pouvez l'organiser avec un script qui diffuse des cotations pour les symboles nécessaires. Comme vous le comprenez, la diffusion en profondeur du marché ou même l'accès des deux côtés peuvent être organisés de la même manière.

      Description des archives :

      Bin.rar - archive avec une solution prête. Pour les utilisateurs qui souhaitent voir Comment ça marche ? Notez tout de même que .NET Framework 3.5 (peut-être il fonctionnera également avec la version 3.0) doit être installé sur votre ordinateur.

      Src.rar - Le code source complet du programme Pour y travailler avec, vous aurez besoin de MetaEditor et Visual Studio 2008.

      QExportDemoProfile.rar - Profil Metatrader, qui attache un script à 10 graphiques, comme illustré à la Fig. 1.


      Traduit du russe par MetaQuotes Ltd.
      Article original : https://www.mql5.com/ru/articles/27

      Fichiers joints |
      bin.rar (33.23 KB)
      src.rar (137.41 KB)
      Implémentation pratique des Filtres Numériques dans MQL5 pour les débutants Implémentation pratique des Filtres Numériques dans MQL5 pour les débutants
      L'idée du filtrage des signaux numériques a été largement discutée sur des sujets de forum concernant la création de systèmes de trading. Et il serait imprudent de ne pas créer un code standard de filtres numériques en MQL5. Dans cet article, l'auteur décrit la transformation du code d'un simple indicateur SMA de son article "Indicateurs Personnalisés dans MQL5 pour les Débutants" en code de filtre numérique plus compliqué et universel. Cet article est une suite logique de l'article précédent. Il explique également comment remplacer le texte dans le code et comment corriger les erreurs de programmation.
      Dessiner les Émissions de l'Indicateur en MQL5 Dessiner les Émissions de l'Indicateur en MQL5
      Dans cet article, nous allons traiter l'émission d'indicateurs - une nouvelle approche de l'étude de marché. Le calcul de l'émission est basé sur l'intersection de différents indicateurs : de plus en plus de points de couleurs et de formes différentes apparaissent après chaque tick. Ils forment de nombreux groupes comme des nébuleuses, des nuages, des pistes, des lignes, des arcs, etc. Ces formes contribuent à détecter les ressorts et les forces invisibles qui affectent le mouvement des prix du marché.
      MQL5: Analyse et traitement des rapports de la Commodity Futures Trading Commission (CFTC) dans MetaTrader 5 MQL5: Analyse et traitement des rapports de la Commodity Futures Trading Commission (CFTC) dans MetaTrader 5
      Dans cet article, nous allons élaborer un outil d’analyse des rapports CFTC. Nous allons résoudre le problème suivant: élaborer un indicateur, qui permet d’utiliser les données du rapport CFTC directement à partir des fichiers de données fournis par la Commission sans traitement intermédiaire et conversion. En outre, il peut être utilisé à différentes fins: pour tracer les données en tant qu’indicateur, pour procéder avec les données dans les autres indicateurs, dans les scripts pour l’analyse automatisée, dans les Expert Advisors pour l’utilisation dans les stratégies de trading.
      Échange de Données entre les Indicateurs : C'est facile Échange de Données entre les Indicateurs : C'est facile
      Nous souhaitons créer un tel environnement, qui donnerait accès aux données d'indicateurs attachés à un graphique, et aurait les propriétés suivantes : absence de copie de données ; modification minimale du code des méthodes disponibles, si nous devons les utiliser ; Le code MQL est préférable (bien sûr, nous devons utiliser des DLL, mais nous n'utiliserons qu'une douzaine de chaînes de code C++). L'article décrit une méthode simple pour élaborer un environnement de programme pour le terminal MetaTrader, qui fournirait des moyens d'accès aux tampons d'indicateurs d'autres programmes MQL.