English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
WCF Hizmetlerini Kullanarak МetaTrader 5'ten .NET Uygulamalarına Fiyat Tekliflerini Dışa Aktarma

WCF Hizmetlerini Kullanarak МetaTrader 5'ten .NET Uygulamalarına Fiyat Tekliflerini Dışa Aktarma

MetaTrader 5Örnekler | 10 Aralık 2021, 17:10
103 0
Alexander
Alexander

Giriş

MetaTrader 4'te DDE hizmetini kullanan programcılar muhtemelen beşinci sürümde artık desteklenmediğini duymuşlardır. Ve fiyat tekliflerini dışa aktarmak için standart bir çözüm yoktur. Bu sorunun bir çözümü olarak, MQL5 geliştiricileri, onu uygulayan kendi dll'nizi kullanmanızı önermektedir. Yani uygulamayı yazmamız gerekiyorsa, bunu akılcı bir şekilde yapalım!

Neden .NET?

.NET'teki uzun programlama deneyimim ile benim için, bu platformu kullanarak fiyat tekliflerinin dışa aktarılmasını uygulamak daha mantıklı, ilginç ve basit olurdu. Ne yazık ki, beşinci sürümde MQL5'te .NET'in yerel desteği yok. Geliştiricilerin bunun için bazı nedenleri olduğundan eminim. Bu nedenle, win32 dll'yi .NET desteği için bir paket olarak kullanacağız.

Neden WCF?

Windows Communication Foundation Teknolojisi'ni (WCF) birkaç nedenden dolayı seçtim: Bir yandan genişletilmesi ve uyarlanması kolay, diğer yandan da çok çalışarak kontrol etmek istedim. Ayrıca, Microsoft'a göre WCF, .NET Remoting'e kıyasla biraz daha fazla performansa sahip.

Sistem Gereklilikleri

Sistemimizden ne istediğimizi düşünelim. Bence iki temel gereklilik var:

    1. Tabii ki, yerel yapıyı MqlTick daha iyi kullanarak tickleri dışa aktarmamız gerekiyor,
    2. Halihazırda dışa aktarılan sembollerin listesini bilmek tercih edilir.

    O halde başlayalım...

    1. Genel sınıflar ve sözleşmeler

    İlk olarak, yeni bir sınıf kitaplığı oluşturalım ve bunu QExport.dll. olarak adlandıralım. MqlTick yapısını DataContract olarak tanımlayalım:

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

    Ardından hizmetin sözleşmelerini tanımlayacağız. Yapılandırma sınıflarını ve oluşturulan proxy-sınıflarını kullanmayı sevmiyorum; bu nedenle burada bu tür özelliklerle karşılaşmayacaksınız.

    İlk sunucu sözleşmesini yukarıda açıklanan gerekliliklere göre tanımlayalım:

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

    Gördüğümüz gibi, sunucu bildirimlerine abone olmanın ve abonelikten çıkmanın standart bir şeması var. İşlemlerin kısa ayrıntıları aşağıda açıklanmıştır:

    İşlemAçıklama
    Subscribe()Tickleri dışa aktarmaya abone olur
    Unsubscribe()Tickleri dışa aktarma aboneliğinden çıkar
    GetActiveSymbols()Dışa aktarılan sembollerin listesini döndürür


    Ve aşağıdaki bilgiler istemci geri aramasına gönderilmelidir: Fiyat teklifinin kendisi ve dışa aktarılan sembollerin listelerindeki değişiklikler hakkında bildirim. Performansı artırmak için yapılması gereken işlemleri "Tek Yönlü işlemler" olarak tanımlayalım:

        [ServiceContract]
        public interface IExportClient
        {
            [OperationContract(IsOneWay = true)]
            void SendTick(String symbol, MqlTick tick);
    
            [OperationContract(IsOneWay = true)]
            void ReportSymbolsChanged();
        }
    
    İşlemAçıklama
    SendTick(Dize, MqlTick)Tick gönderir
    ReportSymbolsChanged()İstemciyi dışa aktarılan semboller listesindeki değişiklikler konusunda bilgilendirir

    2. Sunucu uygulaması

    Sunucu sözleşme uygulaması olan hizmet için Qexport.Service.dll adında yeni bir yapı oluşturalım.

    Standart bağlamalara kıyasla en yüksek performansa sahip olduğu için, bağlama için NetNamedPipesBinding'i seçelim. Örneğin bir ağ üzerinden fiyat tekliflerini yayınlamamız gerekirse, NetTcpBinding kullanılmalıdır.

    Sunucu sözleşmesi uygulamasına ilişkin bazı ayrıntılar şu şekildedir:

    Sınıf tanımı. İlk olarak, aşağıdaki değiştiricilere sahip ServiceBehavior özniteliği ile işaretlenmelidir:

    • InstanceContextMode = InstanceContextMode.Tekli - İşlenen tüm talepler için tek bir hizmet örneğinin kullanımını sağlamak, çözüm performansını artıracaktır. Ayrıca, dışa aktarılan sembollerin listesini sunma ve yönetme olanağına sahip olacağız,
    • ConcurrencyMode = ConcurrencyMode.Çoklu -İstemcinin tüm talepleri için paralel işleme anlamına gelir,
    • UseSynchronizationContext = false – Takılma durumlarını önlemek için GUI ileti dizisine eklemediğimiz anlamına gelir. Burada görevimiz için gerekli değildir, ancak hizmeti Windows uygulamalarını kullanarak barındırmak istiyorsak gereklidir.
    • IncludeExceptionDetailInFaults = true – İstemciye iletildiğinde FaultException nesnesine istisna ayrıntılarını dahil etmek için.

    ExportService'in kendisi iki arayüz içerir: IExportService, IDisposable. Birincisi tüm hizmet işlevlerini uygular, ikincisi .NET kaynakları sürümünün standart modelini uygular.

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

    Hizmetin değişkenlerini tanımlayalım:

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

    Hizmetimizi açan ve kapatan Open() ve Close() yöntemlerini tanımlayalım:

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

    Ardından, IExportService yöntemlerinin uygulanması:

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

    Şimdi tick göndermek ve dışa aktarılan sembolleri kaydetmek ve silmek için yöntemler eklememiz gerekiyor.

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

    Ana sunucu işlevlerinin listesini özetleyelim (yalnızca ihtiyacımız olanları):

    YöntemlerAçıklama
    Open()Sunucuyu çalıştırır
    Close()Sunucuyu durdurur
    RegisterSymbol(Dize)Dışa aktarılan semboller listesine sembol ekler
    UnregisterSymbol(Dize)Dışa aktarılan semboller listesinden sembolü siler
    GetActiveSymbols()Dışa aktarılan sembollerin sayısını döndürür
    SendTick(Dize, MqlTick)İstemcilere tick gönderir

     

    3. İstemci uygulaması

    Bence sunucuyu gayet net bir şekilde göz önünde bulundurduk; şimdi istemciyi ele alma zamanı. Qexport.Client.dll'yi oluşturalım. İstemci sözleşmesi orada uygulanacaktır. İlk olarak, davranışını tanımlayan CallbackBehavior özniteliği ile işaretlenmelidir. Aşağıdaki değiştiricileri içerir:

      • ConcurrencyMode = ConcurrencyMode.Multiple - Tüm geri çağırmalar ve sunucu yanıtları için paralel işleme anlamına gelir. Bu değiştirici çok önemlidir. Bu sunucunun, ReportSymbolsChanged() işlevini geri çağırarak dışa aktarılan semboller listesindeki değişiklikler hakkında istemciyi bilgilendirmek istediğini hayal edin. Ve istemci (geri çağrısında), GetActiveSymbols() sunucu yöntemini çağırarak dışa aktarılan sembollerin yeni listesini almak istiyor. Dolayısıyla, sunucu yanıtını bekleyerek geri çağırmaya devam ettiği için istemcinin sunucudan yanıt alamadığı ortaya çıkıyor. Sonuç olarak, istemci zaman aşımı nedeniyle düşecektir.
      • UseSynchronizationContext = false - Takılma durumlarını önlemek için GUI'ye eklemediğimizi belirtir. Varsayılan olarak, wcf geri çağırmaları ana ileti dizisine eklenir. Ana ileti dizisi GUI'yi içeriyorsa, durum, geri çağırma çağrıldığı yöntemin tamamlanmasını beklediğinde, ancak geri çağırmanın bitmesini beklediği için yöntem tamamlanamadığında mümkündür. Bu, iki farklı şey olmasına rağmen, önceki duruma benzer.

      Sunucu örneğine gelince, istemci ayrıca iki arayüz uygular: IExportClient ve IDisposable:

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

      Hizmet değişkenlerini tanımlayalım:

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

      Şimdi geri çağırma yöntemlerimiz için olaylar oluşturacağız. Bu, istemci uygulamasının olaylara abone olabilmesi ve istemci durumundaki değişiklikler hakkında bildirim alabilmesi için gereklidir.

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

      İstemci için Open() ve Close() yöntemlerini de tanımlayın:

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

      Bir istemci açıldığında veya kapatıldığında akışlara bağlantı ve akışlardan bağlantının kesilmesinin çağrıldığını, dolayısıyla onları doğrudan çağırmanın gerekli olmadığını unutmayın.

      Ve şimdi istemci sözleşmesini yazalım. Uygulanması aşağıdaki olayların oluşturulmasına yol açar:

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

      Son olarak, istemcinin ana özellikleri ve yöntemleri şu şekilde tanımlanır:

      ÖzellikAçıklama
      HizmetHizmet iletişim kanalı
      KanalIExportService hizmet sözleşmesi örneği 


      YöntemAçıklama
      Open()Sunucuya bağlanır
      Close()Sunucuyla bağlantıyı keser

       

      OlayAçıklama
      TickRecievedYeni fiyat teklifi alındıktan sonra oluşturulur
      ActiveSymbolsChangedAktif semboller listesindeki değişikliklerden sonra oluşturulur

       

      4. İki .NET uygulaması arasında aktarım hızı

      İki .NET uygulaması arasındaki aktarım hızını ölçmek benim için ilginçti; aslında verimlilik hızı, saniyedeki tick sayısıyla ölçülür. Hizmet performansını ölçmek için birkaç konsol uygulaması yazdım: İlki sunucu için, ikincisi istemci için. Sunucunun Main() işlevine aşağıdaki kodu yazdım:

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

      Gördüğümüz gibi, kod on adet verimlilik ölçümü gerçekleştirir.  Athlon 3000+'da aşağıdaki test sonuçlarını elde ettim:

      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
      

      Saniyede 2500 tick - 100 sembol için fiyat tekliflerini dışa aktarmanın yeterli olduğunu düşünüyorum (tabii ki sanal olarak; zira kimse bu kadar çok grafik açıp expert eklemek istemiyor gibi görünüyor =)) Ayrıca, artan istemci sayısıyla, her istemci için dışa aktarılan maksimum sembol sayısı azaltılır.

      5. Bir "katman sayısı" oluşturma

      Şimdi onu istemci terminaline nasıl bağlayacağınızı düşünme zamanı. MetaTrader 5'te işlevin ilk çağrısında elimizde ne olduğuna bakalım: .NET çalışma zamanı ortamı (CLR) işleme yüklenir ve varsayılan olarak uygulama etki alanı oluşturulur. İlginçtir ki, kod yürütüldükten sonra kaldırılmaz.

      CLR'yi işlemden kaldırmanın tek yolu, onu sonlandırmaktır (istemci terminalini kapatmak); bu da Windows'u tüm işlem kaynaklarını temizlemeye zorlayacaktır. Böylece, nesnelerimizi oluşturabiliriz; bunlar, uygulama etki alanı kaldırılana veya Garbage Collector tarafından yok edilene kadar var olurlar.

      Bunun iyi göründüğünü söyleyebilirsiniz ama Garbage Collector tarafından nesnenin yok edilmesini engellesek dahi MQL5'ten nesnelere erişemiyoruz. Neyse ki, bu tür erişim kolaylıkla düzenlenebilir. İşin püf noktası şudur: Her uygulama etki alanı için, uygulama tarafından nesne ömrünü izlemek için kullanılan ve onu manuel olarak yönetmeye olanak tanıyan bir Garbage Collector tanıtıcı tablosu (GC tanıtıcı tablosu) vardır.

      Uygulama, System.Runtime.InteropServices.GCHandle. türünü kullanarak tablodan öğeler ekler ve siler. İhtiyacımız olan tek şey, nesnemizi böyle bir tanımlayıcı ile sarmak; ona GCHandle.Target özelliği boyunca erişimimiz var. Böylece, tanıtıcılar tablosunda bulunan GCHandle, nesnesinin referansını alabiliriz; bunun Garbage Collector tarafından taşınmaması veya silinmemesi garanti edilir. Tanımlayıcı tarafından referans verildiği için, sarılmış nesne de geri dönüştürmeden kaçınacaktır.

      Şimdi teoriyi pratikte test etme zamanı. Bunu yapmak için QExpertWrapper.dll adında yeni bir win32 dll oluşturalım ve System.dll, QExport.dll, Qexport.Service.dll CLR desteğini yapı referansına ekleyelim. Ayrıca yönetim amaçları için (sıralama yapmak, nesneleri tanıtıcılarla almak vb.) bir yardımcı sınıf ServiceManaged oluşturuyoruz.

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

      Bu yöntemlerin uygulanmasını ele alalım. CreateExportService yöntemi hizmeti oluşturur, GCHandle öğesini kullanarak GCHandle'a sarar.Referansını ayırır ve döndürür. Herhangi bir sorun oluşması halinde hatalı bir MessageBox gösterir. Bunu, hata ayıklama amacıyla kullandım; bu nedenle gerçekten gerekli olduğundan emin değilim, ancak her ihtimale karşı burada bıraktım.

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

      DestroyExportService yöntemi, işaretçiyi hizmete ait GCHandle'a götürür, hizmeti Target özelliğinden alır ve Close() yöntemini çağırır. Free() yöntemini çağırarak hizmet nesnesini serbest bırakmak önemlidir. Aksi takdirde, bu, hafızada kalacaktır; Garbage Collector onu kaldırmaz.

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

      RegisterSymbol yöntemi, dışa aktarılan semboller listesine bir sembol ekler:

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

      UnregisterSymbol yöntemi listeden bir sembolü siler:

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

      Ve şimdi SendTick yöntemi. Gördüğümüz gibi, işaretçi, Marshal sınıfı kullanılarak MqlTick yapısına dönüştürülür. Diğer bir nokta: catch bloğunda herhangi bir kod yoktur - Bu, hata durumunda genel tick kuyruğunun gecikmesini önlemek için yapılır.

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

      Şimdi ex5 programlarımızdan çağrılacak işlevlerin uygulamasını ele alalım:

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

      Kod hazır; şimdi derlememiz ve oluşturmamız gerekiyor. Çıktı dizinini, proje seçeneklerinde "C:\Program Files\MetaTrader 5\MQL5\Libraries" olarak belirleyelim. Derlemeden sonra belirtilen klasörde üç kitaplık görünecektir.

      Mql5 programı bunlardan yalnızca birini, yani QExportWrapper.dll'yi kullanır, onun tarafından diğer iki kitaplık kullanılır. Bu nedenle, Qexport.dll ve Qexport.Service.dll kitaplıklarını MetaTrader'ın kök klasörüne yerleştirmemiz gerekiyor. Bu, uygun değildir.

      Çözüm, yapılandırma dosyası oluşturmak ve oradaki kitaplıkların yolunu belirtmektir. MetaTrader'ın kök klasöründe terminal.exe.config adlı dosyayı oluşturalım ve buraya aşağıdaki dizeleri yazalım:

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

      Artık hazır. Şimdi CLR, belirttiğimiz klasördeki kitaplıkları arayacaktır.

       6. Sunucu bölümünün MQL5 içinde uygulanması

      Son olarak, sunucu bölümünün mql5 içinde programlanmasına ulaştık. Yeni bir QService.mqh dosyası oluşturalım ve QExpertWrapper.dll öğesinin içe aktarılan işlevlerini tanımlayalım:

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

      Mql5'in sınıflara sahip olması mükemmel; zira içindeki tüm mantığı özetlemek için ideal bir özelliktir; bu da çalışmayı ve kodun anlaşılmasını önemli ölçüde basitleştirir. Bu nedenle, kitaplık yöntemleri için kabuk olacak bir sınıf tasarlayalım.

      Ayrıca, her sembol için hizmet oluşturma durumundan kaçınmak için, bu adla, çalışan hizmetin kontrolünü düzenleyelim; böyle bir durumda bunun üzerinde çalışacağız. Bu bilgiyi sunmak için ideal bir yöntem, aşağıdaki nedenlerden dolayı genel değişkenlerdir:

      • Genel değişkenler, istemci terminali kapandıktan sonra ortadan kaybolur. Aynı şey hizmette de olur; hizmeti
      • kullanan Qservice nesne sayısı kadar hizmet sunabiliriz. Fiziksel hizmeti ancak son nesne kapatıldıktan sonra kapatmaya izin verir.

      Şimdi bir Qservice sınıfı oluşturalım:

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

      Sınıf aşağıdaki yöntemleri içerir:

      YöntemAçıklama
      Create(const dizesi)Hizmeti başlatır
      Close()Hizmeti kapatır
      SendTick(const dizesi, MqlTick&)Fiyat teklifi gönderir

       

      Ayrıca EnterCriticalSection() ve LeaveCriticalSection() özel yöntemlerinin, bunlar arasında kritik kod bölümlerini çalıştırmanıza izin verdiğini unutmayın.

      Bu, bizi, Create() işlevinin eşzamanlı çağrıları ve her QService için yeni hizmetler oluşturma durumlarından kurtaracak.

      Hizmetle çalışma sınıfını ele aldık, şimdi fiyat teklifi yayınlama için bir Expert Advisor yazalım. Expert Advisor, gelen tüm tickleri işleme olasılığı nedeniyle seçilmiştir.

      //+------------------------------------------------------------------+
      //|                                                    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. ex5 ve .NET istemcisi arasındaki iletişim performansını test etme

      Fiyat teklifleri doğrudan istemci terminalinden gelirse hizmetin toplam performansının düşeceği aşikardır; bu nedenle bunu ölçmek istedim. Sıralama ve tür dönüşümü için CPU zamanının kaçınılmaz kaybı nedeniyle azalması gerektiğinden emindim.

      Bu amaçla, ilk testle aynı olan basit bir script dosyası yazdım. Start() işlevi aşağıdaki gibi görünür:

         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;
      

      Aşağıdaki sonuçları elde ettim:

      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 tick/sn ve 1900 tick/sn. %25, MT5 hizmetlerinin kullanımı için ödenmesi gereken bedeldir, ancak yine de yeterlidir. Performansın ileti dizisi havuzu ve System.Threading.ThreadPool.QueueUserWorkItem statik yöntemi kullanılarak artırılabileceğini belirtmek ilginçtir.

      Bu yöntemi kullanarak saniyede 10000 tick'e kadar aktarım hızına sahibim. Ancak, Garbage Collector'ın nesneleri silmek için zamanı olmadığı gerçeği nedeniyle zorlu bir testte çalışması istikrarsızdı; sonuç olarak MetaTrader tarafından tahsis edilen bellek hızla büyür ve sonunda çöker. Ancak bu, gerçek olmaktan uzak, zorlu bir testti; bu nedenle ileti dizisi havuzunu kullanmanın tehlikeli bir yanı yok.

       8. Gerçek zamanlı test

      Hizmeti kullanarak bir tickler tablosu örneği oluşturdum. Proje arşive eklenir ve WindowsClient olarak adlandırılır. Çalışmasının sonucu aşağıda sunulmuştur:

      Şek. 1. Fiyat teklifleri tablosunu içeren WindowsClient uygulamasının ana penceresi

      Sonuç

      Bu makalede, fiyat tekliflerini .NET uygulamalarına dışa aktarma yöntemlerinden birini ele aldım. Gerekli olan her şey uygulandı ve artık kendi uygulamalarınızda kullanabileceğiniz hazır sınıflarımız var. Gerekli grafiklerin her birine script dosyası eklemenin uygun olmadığı tek şey.

      Halihazırda bu sorunun MetaTrader profilleri kullanılarak çözülebileceğini düşünüyorum. Diğer taraftan, tüm fiyat tekliflerine ihtiyacınız yoksa bunu, gerekli semboller için fiyat teklifleri yayınlayan bir script dosyasıyla düzenleyebilirsiniz. Anladığınız gibi, piyasa derinliği yayını veya hatta iki taraflı erişim aynı şekilde düzenlenebilir.

      Arşivlerin açıklaması:

      Bin.rar - Hazır bir çözümle arşivler. Bunun nasıl çalıştığını görmek isteyen kullanıcılara yöneliktir. Yine de .NET Framework 3.5'in (belki 3.0 sürümüyle de çalışacaktır) bilgisayarınızda yüklü olması gerektiğini unutmayın.

      Src.rar - Projenin tam kaynak kodu. Onunla çalışmak için MetaEditor ve Visual Studio 2008'e ihtiyacınız olacak.

      QExportDemoProfile.rar - Şekil 1'de gösterildiği gibi 10 grafiğe script dosyası ekleyen Metatrader profili.


      MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
      Orijinal makale: https://www.mql5.com/ru/articles/27

      Ekli dosyalar |
      bin.rar (33.23 KB)
      src.rar (137.41 KB)
      Yeni Başlayanlar için MQL5'te Dijital Filtrelerin Pratik Uygulaması Yeni Başlayanlar için MQL5'te Dijital Filtrelerin Pratik Uygulaması
      Dijital sinyal filtreleme fikri, alım satım sistemlerinin oluşturulmasıyla ilgili forum konularında kapsamlı olarak tartışıldı. Ve MQL5'te standart bir dijital filtre kodu oluşturmamak mantıksız olacaktır. Bu makalede yazar, "Yeni Başlayanlar için MQL5'te Özel Göstergeler" adlı makalesinden basit SMA gösterge kodunun daha karmaşık ve evrensel dijital filtre koduna dönüşümünü açıklamaktadır. Bu makale, mantıksal olarak önceki makalenin devamı niteliğindedir. Ayrıca koddaki metnin nasıl değiştirileceği ve programlama hatalarının nasıl düzeltileceği de anlatılmaktadır.
      MQL5'te Gösterge Emisyonlarının Çizimi MQL5'te Gösterge Emisyonlarının Çizimi
      Bu makalede, piyasa araştırmasına yeni bir yaklaşım olan göstergelerin emisyonunu ele alacağız. Emisyon hesaplaması, farklı göstergelerin kesişimine dayanmaktadır: Her tick'ten sonra farklı renk ve şekillerde daha fazla nokta belirir. Bulutsular, bulutlar, parçalar, çizgiler, kavisler vb. gibi çok sayıda küme oluştururlar. Bu şekiller, piyasa fiyatlarının hareketini etkileyen görünmez kavisleri ve güçleri tespit etmeye yardımcı olur.
      MQL5: MetaTrader 5'te Emtia Vadeli İşlemler Ticaret Komisyonu (CFTC) Raporlarının Analizi ve İşlenmesi MQL5: MetaTrader 5'te Emtia Vadeli İşlemler Ticaret Komisyonu (CFTC) Raporlarının Analizi ve İşlenmesi
      Bu makalemizde, CFTC rapor analizi için bir araç geliştireceğiz. Şu sorunu çözeceğiz: CFTC rapor verilerinin, Komisyon tarafından sağlanan veri dosyalarından bir ara işleme ve dönüştürme olmadan doğrudan kullanılmasına olanak tanıyan bir gösterge geliştirmek. Ayrıca, bu, farklı amaçlar için kullanılabilir: Verileri bir gösterge olarak çizmek, diğer göstergelerdeki verilerle devam etmek, otomatik analiz için script dosyalarında, alım satım stratejilerinde kullanılmak üzere Expert Advisor'larda.
      Göstergeler Arası Veri Değişimi: Bu Kolaydır Göstergeler Arası Veri Değişimi: Bu Kolaydır
      Bir grafiğe eklenmiş göstergelerin verilerine erişim sağlayacak ve şu özelliklere sahip olacak şöyle bir ortam oluşturmak istiyoruz: Veri kopyalama olmaması, kullanmamız gerekirse mevcut yöntemlerin kodunun minimum düzeyde değiştirilmesi, MQL kodunun tercih edilebilir olması (elbette DLL kullanmak zorundayız, ancak yalnızca bir düzine C++ kodu dizesi kullanacağız). Makalede, MetaTrader terminali için diğer MQL programlarından gösterge arabelleklerine erişmek için araçlar sağlayacak bir program ortamı geliştirmek için kolay bir yöntem açıklanmaktadır.