WCF Hizmetlerini Kullanarak МetaTrader 5'ten .NET Uygulamalarına Fiyat Tekliflerini Dışa Aktarma
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:
- Tabii ki, yerel yapıyı MqlTick daha iyi kullanarak tickleri dışa aktarmamız gerekiyor,
- 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:
İşlem | Açı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(); }
İşlem | Açı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öntemler | Açı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:
Özellik | Açıklama |
---|---|
Hizmet | Hizmet iletişim kanalı |
Kanal | IExportService hizmet sözleşmesi örneği |
Yöntem | Açıklama |
---|---|
Open() | Sunucuya bağlanır |
Close() | Sunucuyla bağlantıyı keser |
Olay | Açıklama |
---|---|
TickRecieved | Yeni fiyat teklifi alındıktan sonra oluşturulur |
ActiveSymbolsChanged | Aktif 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öntem | Açı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
- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz