Download MetaTrader 5

How to Export Quotes from МetaTrader 5 to .NET Applications Using WCF Services

19 February 2010, 13:46
Alexander
17
10 069

Introduction

Programmers who use the DDE service in MetaTrader 4 probably have heard that in the fifth version it is no longer supported. And there is no standard solution for exporting quotes. As a solution of this problem, the MQL5 developers suggest using your own dll, that implements it. So if we have to write the implementation, let's do it smart!

Why .NET?

For me with my long experience of programming in .NET, it would be more reasonable, interesting and simple to implement the export of quotes using this platform. Unfortunately, there isn't any native support of .NET in MQL5 in fifth version. I am sure that developers have some reasons for it. Therefore, we will use the win32 dll as a wrapper for .NET support.

Why WCF?

The Windows Communication Foundation Technology (WCF) has been chosen by me because of several reasons: on the one hand, it's easy-to-extend and adapt, on the other hand, I wanted to check it under a hard work. Moreover, according to Microsoft, WCF has a little more performance as compared to .NET Remoting.

System Requirements

Let's think, what we want from our system. I think, there are two main requirements:

    1. Of course, we need to export ticks, better using the native structure MqlTick;
    2. It's preferable to know the list of currently exported symbols.

    Let's start...

    1. General classes and contracts

    First of all, let's create a new class library and name it QExport.dll. We define the MqlTick structure as a 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; }
        }
    

    Then we'll define the contracts of the service. I don't like to use configuration classes and generated proxy-classes, so you won't meet such features here.

    Let's define the first server contract according to the requirements, described above:

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

    As we see, there is a standard scheme of subscribing and unsubscribing from server notifications. The brief details of operations are described below:

    OpearationDescription
    Subscribe()Subscribe to ticks export
    Unsubscribe()Unsubscribe to ticks export
    GetActiveSymbols()Returns list of exported symbols


    And the following information should be sent to the client callback: the quote itself and notification about changes of the lits of exported symbols. Let's define the operations required as "One Way operations" to increase the performance:

        [ServiceContract]
        public interface IExportClient
        {
            [OperationContract(IsOneWay = true)]
            void SendTick(String symbol, MqlTick tick);
    
            [OperationContract(IsOneWay = true)]
            void ReportSymbolsChanged();
        }
    
    OperationDescription
    SendTick(String, MqlTick)Sends tick
    ReportSymbolsChanged()Notify the client about the changes in the exported symbols list

    2. Server implementation

    Let's create a new build with name Qexport.Service.dll for the service with the server contract implementation.

    Let's choose the NetNamedPipesBinding for a binding, because it has the largest performance as compared to standard bindings. If we need to broadcast quotes, over a network for example, the NetTcpBinding should be used.

    Here are some details of the server contract implementation:

    The class definition. First of all, it should be marked with the ServiceBehavior attribute with the following modifiers:

    • InstanceContextMode = InstanceContextMode.Single - to provide the use of one service instance for all the requests processed, it will increase the performance of solution. In addition, we will get the possibility to serve and manage the list of exported symbols;
    • ConcurrencyMode = ConcurrencyMode.Multiple -means the parallel processing for all of the client's requests;
    • UseSynchronizationContext = false – means that we don't attach to the GUI thread to prevent hang situations. It isn't necessary here for our task, but it's necessary if we want to host the service using the Windows applications.
    • IncludeExceptionDetailInFaults = true – to include the exception details to the object FaultException when passed to the client.

    The ExportService itself contains two interfaces: IExportService, IDisposable. The first one implements all service functions, the second one implements the standard model of .NET resources release.

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

    Let's describe the variables of the 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();
    

    Let's define the Open() and Close() methods, which open and close our 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;
                }
    
                // ...
            }
    

    Next, the implementation of IExportService methods:

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

    Now we need to add methods to send ticks and to register and delete the exported symbols.

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

    Let's summarize the list of main server functions (only those that we need):

    MethodsDescription
    Open()Runs server
    Close()Stops server
    RegisterSymbol(String)Adds symbol to the list of exported symbols
    UnregisterSymbol(String)Deletes symbol from the list of exported symbols
    GetActiveSymbols()Returns number of exported symbols
    SendTick(String, MqlTick)Sends tick to clients

     

    3. Client implementation

    We have considered the server, I think its clear, so it's time to consider the client. Let's build the Qexport.Client.dll. The client contract will be implemented there. First, it should be marked with CallbackBehavior attrubute, that defines its behaviour. It has the following modifiers:

      • ConcurrencyMode = ConcurrencyMode.Multiple - means the parallel processing for all callbacks and server responses. This modifier is very important. Imagine, that server wants to notify the client about the changes in list of exported symbols by calling callback ReportSymbolsChanged(). And the client (in its callback) wants to receive the new list of the exported symbols by calling of server method GetActiveSymbols(). So it turns out that client can't receive response from the server because it proceeding callback with waiting for the server response. As a result the client will fall because of timeout.
      • UseSynchronizationContext = false  - specifies that we don't attach to the GUI to prevent hang situations. By default, the wcf callbacks are attached to the parent thread. If the parent thread has GUI, the situation is possible when callback waits for the completion of the method it has been called by, but the method cannot finish because it waits for the callback finish. It's something similar to the previous case, although these are two different things.

      As for the server case, the client also implements two interfaces: IExportClient and IDisposable:

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

      Let's describe the service variables:

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

      Now we will create events for our callback methods. It's required for the client application to be able to subscribe to the events and get notifications about the changes of the client state.

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

      Also define the Open() and Close() methods for the 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;
                  }
                  // ...
              }
      

      Note that connection and disconnection from feeds are called when a client is opened or closed, so it isn't necessary to call them directly.

      And now, let's write the client contract. Its implementation leads to generation of the following events:

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

      Finally, the main properties and methods of the client are defined as follows:

      PropertyDescription
      ServiceService communication channel
      ChannelInstance of service contract IExportService 


      MethodDescription
      Open()Connects to server
      Close()Disconnects from server

       

      EventDescription
      TickRecievedGenerated after the new quote receiving
      ActiveSymbolsChangedGenerated after the changes in the list of active symbols

       

      4. Transfer speed between two .NET applications

      It was interesting to me to measure the transfer speed between two .NET applications, in fact, it's throughput, which is measured in ticks per second. I wrote several console applications to measure the service performance: the first on is for the server, the second one is for the client. I wrote the following code in the Main() function of the server:

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

      As we see, the code performs ten throughput measurements.  I have got the following test results on my 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 per second - I think it's sufficent to export quotes for 100 symbols (of course, virtually, because it seems that nobody wants to open so many charts and attach experts =)) Moreover, with increasing number of clients, the maximal number of exported symbols for each client is reduced.

      5. Creating a "stratum"

      Now it's time to think how to connect it with the client terminal. Let's see what we have at the first call of the function in MetaTrader 5: the .NET runtime environment (CLR) is loaded to the process and application domain is created by default. It's interesting, that it is not unloaded after the code execution.

      The only way to unload CLR from the process is to terminate it (close the client terminal), that will force Windows to clear all the process resources. So, we can create our objects and they will exist until the application domain is unoaded, or until it is destroyed by Garbage Collector.

      You can say that it seems good, but even if we prevent the object destroying by Garbage Collector, we can't be able to access the objects from MQL5. Fortunately, such access can be organized easily. The trick is the following: for each application domain there is a table of Garbage Collector handles (GC handle table), which is used by application to track the object lifetime and allows to manage it manually.

      The application adds and deletes elements from the table by using the type System.Runtime.InteropServices.GCHandle. All we need is to wrap our object with such a descriptor and we have an access to it throughout the property GCHandle.Target. Thus we can get the reference to the object GCHandle, which is in the table of handles and it's guaranteed that it will not be moved or deleted by Garbage Collector. The wrapped object will also aviod recycling, because of the reference by descriptor.

      Now it's time to test the theory in practice. To do it, let's create a new win32 dll with name QExpertWrapper.dll and add the CLR support, System.dll, QExport.dll, Qexport.Service.dll to the build reference. Also we create an auxiliary class ServiceManaged for management purposes – to carry out marshalling, to receive objects by handles, 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);
      };
      

      Let's consider the implementation of these methods. The CreateExportService method creates the service, wraps it into GCHandle by using GCHandle.Alloc and returns its reference. If something goes wrong, it shows a MessageBox with an error. I have used it for the debug purpose, so I am not sure that it's really necessary, but I've left it here just in case.

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

      The DestroyExportService method gets the pointer to the GCHandle of service, gets the service from the Target property and calls its method Close(). It's important to release the service object by calling its method Free(). Overwise it will remain in memory, the Garbage Collector doesn't remove it.

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

      The RegisterSymbol method adds a symbol to the list of exported symbols:

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

      The UnregisterSymbol method deletes a symbol from the list:

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

      And now the SendTick method. As we see, the pointer is transformed to the MqlTick structure using the Marshal class. Another point: there isn't any code in catch block - it is done to avoid the lags of the general tick queue in the case of 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 (...)
              {
              }
      }
      

      Let's consider the implementation of functions, which will be called from our ex5 programs:

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

      The code is ready, now we need to compile and build it. Let's specify the output directory as "C:\Program Files\MetaTrader 5\MQL5\Libraries" in the project options. After the compilation three libraries will appear in the specified folder.

      The mql5 program uses only one of them, namely QExportWrapper.dll, two other libraries are used by it. Because of this reason we need to put the libraries Qexport.dll and Qexport.Service.dll into root folder of MetaTrader. It isn't convenient.

      The solution is to create configuration file and specify the path for the libraries there. Let's create the file with name terminal.exe.config in the root folder of MetaTrader and write the following strings there:

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

      It's ready. Now CLR will search for the libraries in the folder we have specified.

       6. Server part implementation in MQL5

      Finally, we have reached the programming of server part in mql5. Let's create a new file QService.mqh and define the imported functions of 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
       
      

      It's great that mql5 has classes because it's an ideal feature to incapsulate all the logics inside, that significantly simplifies the work and understanding of code. Therefore let's design a class that will be a shell for the library methods.

      Moreover, to avoid the situation with creation of service for every symbol, let's organize the checking of the working service with such name, and we will work through it in such a case. An ideal method to serve this information are global variables, because of the following reasons:

      • the global variables disappear after the client terminal close. The same is with the service;
      • we can serve the number of objects Qservice, that uses the service. It allows to close the physical service only after the last object is closed.

      So, let's create a class 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");
      }

      The class contains the following methods:

      MethodDescription
      Create(const string)Starts service
      Close()Closes service
      SendTick(const string, MqlTick&)Sends quote

       

      Also note that the private methods EnterCriticalSection() and LeaveCriticalSection() allow you to run the critical code sections between them.

      It will relieve us from the cases of the simultaneous calls of function Create() and creation of new services for each QService.

      So, we have described the class for working with service, now let's write an Expert Advisor for the quotes broadcasting. The Expert Advisor has been chosen because of its possibility to process all the ticks arrived.

      //+------------------------------------------------------------------+
      //|                                                    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. Testing communication performance between ex5 and .NET client

      It's evident, that the total performance of service will decreased if the quotes will arrive directly from the client terminal, so I have interested to measure it. I was sure that it should have decreased because of the inevitable loss of CPU time for marshalling and typecasting.

      For this purpose I wrote a simple script that is the same as for the first test. The Start() function looks as follows:

         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;
      

      I have got the following results:

      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 vs 1900 ticks/sec. 25% is the price that should be paid for the use of services from MT5, but anyway it's sufficient. It's interesting to note that performance can be increased by using the threads pool and static method System.Threading.ThreadPool.QueueUserWorkItem.

      Using this method, I have got the transfer speed up to 10000 ticks per second. But its work in a hard testing was unstable because of the fact that the Garbage Collector has no time to delete objects - as a result the memory, allocated by MetaTrader grows rapidly and finally it crashes. But it was a hard testing, far from the real, so there is nothing dangerous in using the threads pool.

       8. Realtime testing

      I have created an example of ticks table using the service. The project is attached in the archive and named WindowsClient. The result of its work is presented below:

      Fig 1. Main window of the WindowsClient application with quotes table

      Conclusion

      I this article I have described one of the methods of exporting quotes to .NET applications. All the required has been implemented and now we have ready classes that can be used in your own applications. The  only one thing that it isn't convenient to attach scripts to each of the necessary chart.

      At present time I think that this problem can be solved using the MetaTrader profiles. From the other side, if you don't need all quotes, you can organize it with a script that broadcasts quotes for the necessary symbols. As you understand, the market depth broadcasting or even two-side access can be organized the same way.

      Description of archives:

      Bin.rar - archive with a ready solution. For users, who want to see how it works. Still note that .NET Framework 3.5 (maybe it also will work with version 3.0) should be installed on your computer.

      Src.rar - full source code of the project. To work with it you'll need MetaEditor and Visual Studio 2008.

      QExportDemoProfile.rar -  Metatrader profile, that attaches script to 10 charts, as shown at Fig. 1.


      Translated from Russian by MetaQuotes Software Corp.
      Original article: https://www.mql5.com/ru/articles/27

      Attached files |
      bin.rar (33.23 KB)
      src.rar (137.41 KB)
      Last comments | Go to discussion (17)
      Burton
      Burton | 27 Jan 2013 at 17:20
      Burton:

      I'm currently trying to impliment my own c++ wrapper for my c# dll for mql5 interop. I have an issue with adding c# dll reference to my c++ library. Could you please clarify the following sentence "...add the CLR support, System.dll, QExport.dll, Qexport.Service.dll to the build reference". I was able to register my dll with regasm utility, add it to the gac and obtain a .tlb type definition file. My problem is I cannot find any #import or #include statements in the source code you provided. Your project seems to reference your dll libraries in some mysterious way. Could somebody please explain a proper way to add a reference to .NET dll to a c++ library? Even though it's not a purely mql5 question it directly relates to the article. I've been researching the topic for quite a while now and still cannot understand how a c++ library can have .NET dlls in its "External Dependencies" without any #import statements. This is my first encounter with c++ and so far it hasn't been a pleasant one, I even thought to convert the .dll to .lib and add a reference to it, but before I do anything I decided to seek advice from a COM specialist. I'm running Windows 8 Ultimate x64 and compile my c# dlls with VS2012 Ultimate. Please help the novice.
      Quite stupidly I found I can reference c# dlls from c++ project by right clicking it and choosing References...  in the dialog box. There's so much crap out there on the web.
      sabe
      sabe | 28 Jul 2015 at 13:23
      joeykruger:

      I managed to get both this working with both 32bit MT5 and, after recompling for x64, managed to get it working with 64bit MT5....however, when I try to run the EA in the strategy tester they both crash spectacularly.

      I would like to use this to export some price and indicator data into a database table to do some analysis with external software.....any ideas what could be causing this crash?   This is the closest I've come to a working solution so far.  


      Joe

      Hi Joe,

       Was there any special trick to get it working on x64? I've just compiled it for x64, but the dll crashes with weird errors on startup. 

      Douglas Mendes
      Douglas Mendes | 18 Oct 2015 at 17:32

      Just posted a new job based on this article: https://www.mql5.com/en/job/34392 .

      It's not working in my MT5 64 bits environment... 

      Great article!

      Thanks 

      Douglas Mendes
      Douglas Mendes | 20 Oct 2015 at 12:23

      Just for knowledge, I discovered what happened in my 64 bits machine.

      After hours and hours of researching and debugging, discovered that one referenced assembly was not loading, generating the exception "System.IO.FileNotFoundException: Unable to load file or assembly 'QExport.Service, Version=1.0.5771.13857, Culture=neutral, PublicKeyToken=56996a45dd1e337b'".

       Maybe because the dll has no config file, don't know yet, MT 5 did not know where to find the assembly. So it was trying to get it in the base path (path where metaeditor64.exe is located). After changing the output directory of the referenced projects to that path, worked as a charm.

      Douglas Mendes
      Douglas Mendes | 20 Oct 2015 at 12:25
      sabe:

      Hi Joe,

       Was there any special trick to get it working on x64? I've just compiled it for x64, but the dll crashes with weird errors on startup. 

      Sabe, see my answer below.

      []'s 

      Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

      I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

      Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

      This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

      Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

      The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.

      How to create an indicator of non-standard charts for MetaTrader Market How to create an indicator of non-standard charts for MetaTrader Market

      Through offline charts, programming in MQL4, and reasonable willingness, you can get a variety of chart types: "Point & Figure", "Renko", "Kagi", "Range bars", equivolume charts, etc. In this article, we will show how this can be achieved without using DLL, and therefore such "two-for-one" indicators can be published and purchased from the Market.