MQL5-RPC. MQL5'ten Uzaktan Prosedür Çağrıları: Eğlence ve Kar için Web Hizmeti Erişimi ve XML-RPC ATC Çözümleyici
Giriş
Bu makale, son haftalarda oluşturduğum MQL5-RPC çerçevesini açıklayacaktır. XML-RPC erişim temelleri, MQL5 uygulamasının açıklamasını ve iki gerçek dünya MQL5-RPC kullanım örneğini kapsar. Birincisi, harici bir forex web sitesinin web servisi üzerinde bir uzaktan prosedür çağrısı olacak ve ikincisi, Automated Trading Championship 2011'den toplanan sonuçları ayrıştırmak, analiz etmek ve sağlamak için kullanılan kendi XML-RPC sunucumuzun bir istemcisi olacak. ATC 2011'den farklı istatistikleri gerçek zamanlı olarak nasıl uygulayacağınızı ve analiz edeceğinizi merak ediyorsanız bu makale tam size göre.
XML-RPC temelleri
XML-RPC temelleri ile başlayalım. XML-RPC, XML Uzaktan Yordam Çağrısı anlamına gelir. Bu, harici bir yöntemi çağırmak için geçirilen parametreleri kodlamak ve kodunu çözmek için XML kullanan bir ağ protokolüdür. Veri alışverişi için aktarım mekanizması olarak HTTP protokolünü kullanır. Harici yöntemle, uzak prosedürleri ortaya çıkaran başka bir bilgisayar programını veya web hizmetini kastediyorum.
Açığa çıkan yöntem, XML-RPC protokol yığınını kullanması ve sunucuya ağ erişimi olması koşuluyla, ağa bağlı herhangi bir makineden herhangi bir bilgisayar dili tarafından çağrılabilir. Bu aynı zamanda XML-RPC'nin aynı makinede başka bir programlama dilinde yazılmış bir yöntemi çağırmak için kullanılabileceği anlamına gelir. Bu, makalenin ikinci bölümünde gösterilecektir.
XML-RPC veri modeli
XML-RPC belirtimi altı temel veri türü kullanır: int, double, boole değeri, dize, tarih saat, base64 ve iki bileşik veri türü: dizi ve yapı. Dizi herhangi bir temel öğeden oluşabilir ve yapı, ilişkisel diziler veya nesne özellikleri gibi ad-değer çiftleri sağlar.
XML-RPC'deki temel veri türleri | ||
---|---|---|
Tür | Değer | Örnekler |
int veya i4 | 2.147.483.648 ve 2.147.483.647 arasındaki 32 bit tamsayılar. | <int>11<int> <i4>12345<i4> |
çift | 64 bit kayan noktalı sayılar | <double>30.02354</double> <double>-1.53525</double> |
Boole değeri | doğru (1) veya yanlış (0) | <boolean>1</boolean> <boolean>0</boolean> |
dize | ASCII metni, birçok uygulama Unicode'u destekler | <string>Merhaba</string> <string>MQL5</string> |
dateTime.iso8601 | ISO8601 formatındaki tarihler: CCYYMMDDTHH:MM:SS | <dateTime.iso8601> 20111125T02:20:04 </dateTime.iso8601> <dateTime.iso8601> 20101104T17:27:30 </dateTime.iso8601> |
base64 | RFC 2045'te tanımlandığı gibi kodlanmış ikili bilgi | <base64> TDVsbG8sIFdvdwxkIE== </base64> |
Tablo 1. XML-RPC'deki temel veri türleri
Dizi, aynı türden olması gerekmeyen temel türlerden herhangi birini tutabilir. Dizi öğesi, değer öğesinin içine yerleştirilmelidir. Veri öğesinde bir veri öğesi ve bir veya daha fazla değer öğesi içerir. Aşağıdaki örnek, dört tamsayı değerinden oluşan bir diziyi göstermektedir.
<value> <array> <data> <value><int>111</int></value> <value><int>222</int></value> <value><int>-3456</int></value> <value><int>666</int></value> </data> </array> </value>
İkinci örnek, beş dize değerinden oluşan bir diziyi gösterir.
<value> <array> <data> <value><string>MQL5</string></value> <value><string>is </string></value> <value><string>a</string></value> <value><string>great</string></value> <value><string>language.</string></value> </data> </array> </value>
Başka bir XML-RPC dizisi oluşturmak için bu iki örnekteki benzerlikleri tespit edebileceğinize inanıyorum.
Yapıların, değer öğesinin içinde bir yapı öğesi ve yapı öğesi içinde üye bölümleri vardır. Her üye kendi adından ve sahip olduğu değerden oluşur. Bu nedenle, yapılar kullanarak ilişkisel bir dizinin veya bir nesnenin üyelerinin değerlerini iletmek kolaydır.
Lütfen aşağıdaki örneğe bakın.
<value> <struct> <member> <name>AccountHolder</name> <value><string>John Doe</string></value> </member> <member> <name>Age</name> <value><int>77</int></value> </member> <member> <name>Equity</name> <value><double>1000000.0</double></value> </member> </struct> </value>
XML-RPC veri modeli ile tanıştıktan sonra, istek ve yanıt yapılarına geçiyoruz. Bu, MQL5'de XML-RPC istemcisinin uygulanması için bir temel oluşturacaktır.
XML-RPC talep yapıları
XML-RPC isteği, mesaj başlığı ve mesaj yükünden oluşur. İleti başlığı, HTTP gönderme yöntemini (POST), XML-RPC hizmetine göreli yolu, HTTP protokol sürümünü, kullanıcı aracı adını, ana bilgisayar IP adresini, içerik türünü (metin/xml) ve bayt cinsinden içerik uzunluğunu belirtir.
POST /xmlrpc HTTP 1.1 User-Agent: mql5-rpc/1.0 Host: 10.12.10.10 Content-Type: text/xml Content-Length: 188
XML-RPC isteğinin yükü XML belgesidir. XML ağacının kök öğesi methodCall olarak adlandırılmalıdır. Bir methodCall, içeriği yöntem adı olarak yürütülen tek bir methodName öğesi içerir. MethodName öğesi, sıfır veya bir parametre öğesi içeriyor.
params öğesi bir veya daha fazla değer, dizi veya yapı öğesi içerir. Tüm değerler, veri türüne göre kodlanmıştır (yukarıdaki tabloya bakın). Lütfen, işleve iletilecek iki çift değerle "çarpma" yöntemi yürütme isteğini gösteren aşağıdaki örnek yüke bakın.
<?xml version="1.0"?> <methodCall> <methodName>multiply</methodName> <params> <param> <value><double>8654.41</double></value> </param> <param> <value><double>7234.00</double></value> </param> </params> </methodCall>
Başlık ve yük, girişi kabul eden sunucuya HTTP aracılığıyla gönderilir. Sunucu varsa yöntem adını ve parametre listesini kontrol eder ve istenen yöntemi yürütür. İşlemi bitirdikten sonra, istemci tarafından okunabilen XML-RPC yanıt yapısını hazırlar.
XML-RPC yanıt yapıları
XML-RPC Talebine benzer şekilde, XML-RPC Yanıtı bir başlık ve bir yükten oluşur. Başlık metindir ve yük XML belgesidir. İstek doğruysa başlığın ilk satırı sunucunun bulunduğunu (200 kod) bildirir ve protokol sürümünü belirtir. Başlık ayrıca, bayt cinsinden yükün uzunluğu olan Content-Type text/xml ve Content-Length içermelidir.
HTTP/1.1 200 OK Date: Tue, 08 Nov 2011 23:00:01 GMT Server: Unix Connection: close Content-Type: text/xml Content-Length: 124
Şaşırtıcı olmayan bir şekilde yanıtın yükü de XML belgesidir. XML ağacının kök öğesi methodResponse olarak adlandırılmalıdır. methodResponse öğesi, başarı öğesinde bir paragraf veya başarısızlık durumunda bir hata öğesi içerir. Params öğesi tam olarak bir param öğesi içerir. Param öğesi tam olarak bir değer öğesi içerir.
Bir başarılı yanıt örneği aşağıda sunulmuştur:
<?xml version="1.0"?> <methodResponse> <params> <param> <value><double>62606001.94</double></value> </param> </params> </methodResponse>
XML-RPC isteğinin işlenmesinde bir sorun olması durumunda hata yanıtı hazırlanır.
Arıza elemanı, params elemanı gibi, sadece tek bir çıkış değerine sahiptir.
<?xml version="1.0"?> <methodResponse> <fault> <value><string>No such method!</string></value> </fault> </methodResponse>
XML-RPC hata kodlarını standartlaştırmadığından, hata mesajları uygulamaya bağlıdır.
MQL5-RPC ile tanışın
Alex Sergeev'in "İnternet Üzerinden Terminaller Arası Veri Alışverişi için WinInet.dll'yi Kullanımı" ve "MQL5'te WinInet'i Kullanma" başlıklı iki makalesine rastladım. Bölüm 2: POST İstekleri ve Dosyaları" başlıklı iki makalesine rastladım ve MetaTrader 5 için bir XML-RPC istemcisi uygulayabileceğimi fark ettim. Spesifikasyonları inceledikten sonra sıfırdan kendiminkini uyguladım. Bu devam eden bir projedir ve henüz tüm özellikleri kapsamamaktadır (yakın gelecekte base64 desteği eklenecektir), ancak bunu MetaTrader 5'ten büyük bir XML-RPC araması alt kümesi yapmak için zaten kullanabilirsiniz.
MQL5-RPC veri modeli
Benim için uygulamanın en zor kısmı MQL5 için doğru bir veri modeli bulmaktı. Çerçeve kullanıcısı için olabildiğince basit olması gerektiğine karar verdim, bu nedenle işlevselliğini içine alan birkaç sınıf oluşturdum. İlk karar, tek bir CObject* işaretçisi olarak bir istek parametresi verisi yapmaktı. Bu işaretçi, CObject sınıfından türetilen dizilere yönelik bir işaretçi dizisini tutar.
CObject dizilerini tutan standart sınıflar vardır; CARrayInt, CARrayDouble, CArrayString. Bu nedenle buna dayandım ve temel veri türlerini tamamlamak için CArrayBool, CArrayDatetime ve yapı dizisi eklemek için CArrayMqlRates uyguladım. Şimdilik sadece base64 türü eksik, ancak bu da yakın gelecekte desteklenecek. Dizi yalnızca bir öğe içeriyorsa XML'de tek bir değer öğesi olarak kapsüllenir.
CObject* dizisine farklı dizilerin nasıl ekleneceğine ve farklı türlerdeki dizilerin tamamının nasıl görüntüleneceğine dair bir örnek yazdım. Aşağıda mevcuttur.
//+------------------------------------------------------------------+ //| ArrayObjTest.mq5 | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://Investeo.pl" #property version "1.00" //--- #include <Arrays\ArrayObj.mqh> #include <Arrays\ArrayInt.mqh> #include <Arrays\ArrayDouble.mqh> #include <Arrays\ArrayString.mqh> #include <Arrays\ArrayBool.mqh> #include <Arrays\ArrayMqlRates.mqh> #include <Arrays\ArrayDatetime.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- CArrayObj* params = new CArrayObj; CArrayInt* arrInt = new CArrayInt; arrInt.Add(1001); arrInt.Add(1002); arrInt.Add(1003); arrInt.Add(1004); CArrayDouble* arrDouble = new CArrayDouble; arrDouble.Add(1001.0); arrDouble.Add(1002.0); arrDouble.Add(1003.0); arrDouble.Add(1004.0); CArrayString* arrString = new CArrayString; arrString.Add("s1001.0"); arrString.Add("s1002.0"); arrString.Add("s1003.0"); arrString.Add("s1004.0"); CArrayDatetime* arrDatetime = new CArrayDatetime; arrDatetime.Add(TimeCurrent()); arrDatetime.Add(TimeTradeServer()+3600); arrDatetime.Add(TimeCurrent()+3600*24); arrDatetime.Add(TimeTradeServer()+3600*24*7); CArrayBool* arrBool = new CArrayBool; arrBool.Add(false); arrBool.Add(true); arrBool.Add(true); arrBool.Add(false); CArrayMqlRates* arrRates = new CArrayMqlRates; MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(Symbol(),0,0,4,rates); arrRates.Add(rates[0]); arrRates.Add(rates[1]); arrRates.Add(rates[2]); arrRates.Add(rates[3]); params.Add(arrInt); params.Add(arrDouble); params.Add(arrString); params.Add(arrDatetime); params.Add(arrBool); params.Add(arrRates); Print("params has " + IntegerToString(params.Total()) + " arrays."); for (int p=0; p<params.Total(); p++) { int type = params.At(p).Type(); switch (type) { case TYPE_INT: { CArrayInt *arr = params.At(p); for (int i=0; i<arr.Total(); i++) PrintFormat("%d %d %d", p, i, arr.At(i)); break; } case TYPE_DOUBLE: { CArrayDouble *arr = params.At(p); for (int i=0; i<arr.Total(); i++) PrintFormat("%d %d %f", p, i, arr.At(i)); break; } case TYPE_STRING: { CArrayString *arr = params.At(p); for (int i=0; i<arr.Total(); i++) PrintFormat("%d %d %s", p, i, arr.At(i)); break; } case TYPE_BOOL: { CArrayBool *arr = params.At(p); for (int i=0; i<arr.Total(); i++) if (arr.At(i) == true) PrintFormat("%d %d true", p, i); else PrintFormat("%d %d false", p, i); break; } case TYPE_DATETIME: { CArrayDatetime *arr = params.At(p); for (int i=0; i<arr.Total(); i++) PrintFormat("%d %d %s", p, i, TimeToString(arr.At(i), TIME_DATE|TIME_MINUTES)); break; } case TYPE_MQLRATES: { // CArrayMqlRates *arr = params.At(p); for (int i=0; i<arr.Total(); i++) PrintFormat("%d %d %f %f %f %f", p, i, arr.At(i).open, arr.At(i).high, arr.At(i).low, arr.At(i).close); break; } }; }; delete params; } //+------------------------------------------------------------------+
Sonuç açık olmalıdır: 6 alt dizi vardır: tamsayı değerleri dizisi, çift değerler dizisi, dize dizisi, tarihsaat dizisi, boole değerleri dizisi ve MqlRates dizisi.
ArrayObjTest (EURUSD,H1) 23:01:54 params has 6 arrays. ArrayObjTest (EURUSD,H1) 23:01:54 0 0 1001 ArrayObjTest (EURUSD,H1) 23:01:54 0 1 1002 ArrayObjTest (EURUSD,H1) 23:01:54 0 2 1003 ArrayObjTest (EURUSD,H1) 23:01:54 0 3 1004 ArrayObjTest (EURUSD,H1) 23:01:54 1 0 1001.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 1 1002.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 2 1003.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 3 1004.000000 ArrayObjTest (EURUSD,H1) 23:01:54 2 0 s1001.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 1 s1002.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 2 s1003.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 3 s1004.0 ArrayObjTest (EURUSD,H1) 23:01:54 3 0 2011.11.11 23:00 ArrayObjTest (EURUSD,H1) 23:01:54 3 1 2011.11.12 00:01 ArrayObjTest (EURUSD,H1) 23:01:54 3 2 2011.11.12 23:00 ArrayObjTest (EURUSD,H1) 23:01:54 3 3 2011.11.18 23:01 ArrayObjTest (EURUSD,H1) 23:01:54 4 0 false ArrayObjTest (EURUSD,H1) 23:01:54 4 1 true ArrayObjTest (EURUSD,H1) 23:01:54 4 2 true ArrayObjTest (EURUSD,H1) 23:01:54 4 3 false ArrayObjTest (EURUSD,H1) 23:01:54 5 0 1.374980 1.374980 1.374730 1.374730 ArrayObjTest (EURUSD,H1) 23:01:54 5 1 1.375350 1.375580 1.373710 1.375030 ArrayObjTest (EURUSD,H1) 23:01:54 5 2 1.374680 1.375380 1.373660 1.375370 ArrayObjTest (EURUSD,H1) 23:01:54 5 3 1.375270 1.377530 1.374360 1.374690
Diğer veri türlerinin dizilerini nasıl uyguladığımla ilgilenebilirsiniz. CArrayBool ve CArrayDatetime'da ben sadece CarrayInt'i temel aldım ama CArrayMqlRates'de yapı referans olarak iletilmesi gerektiğinden ve TYPE_MQLRATES tanımlı olmadığından biraz farklıydı.
Aşağıda, CArrayMqlRates sınıfının kısmi kaynak kodunu bulabilirsiniz. Diğer sınıflar makalenin eki olarak mevcuttur.
//+------------------------------------------------------------------+ //| ArrayMqlRates.mqh | //| Copyright 2011, Investeo.pl | //| http://Investeo.pl | //| Revision 2011.03.03 | //+------------------------------------------------------------------+ #include "Array.mqh" //+------------------------------------------------------------------+ //| Class CArrayMqlRates. | //| Purpose: Class of dynamic array of structs | //| of MqlRates type. | //| Derived from CArray class. | //+------------------------------------------------------------------+ #define TYPE_MQLRATES 7654 class CArrayMqlRates : public CArray { protected: MqlRates m_data[]; // data array public: CArrayMqlRates(); ~CArrayMqlRates(); //--- method of identifying the object virtual int Type() const { return(TYPE_MQLRATES); } //--- methods for working with files virtual bool Save(int file_handle); virtual bool Load(int file_handle); //--- methods of managing dynamic memory bool Reserve(int size); bool Resize(int size); bool Shutdown(); //--- methods of filling the array bool Add(MqlRates& element); bool AddArray(const MqlRates &src[]); bool AddArray(const CArrayMqlRates *src); bool Insert(MqlRates& element,int pos); bool InsertArray(const MqlRates &src[],int pos); bool InsertArray(const CArrayMqlRates *src,int pos); bool AssignArray(const MqlRates &src[]); bool AssignArray(const CArrayMqlRates *src); //--- method of access to the array MqlRates At(int index) const; //--- methods of changing bool Update(int index,MqlRates& element); bool Shift(int index,int shift); //--- methods of deleting bool Delete(int index); bool DeleteRange(int from,int to); protected: int MemMove(int dest,int src,int count); }; //+------------------------------------------------------------------+ //| Constructor CArrayMqlRates. | //| INPUT: no. | //| OUTPUT: no. | //| REMARK: no. | //+------------------------------------------------------------------+ void CArrayMqlRates::CArrayMqlRates() { //--- initialize protected data m_data_max=ArraySize(m_data); } //+------------------------------------------------------------------+ //| Destructor CArrayMqlRates. | //| INPUT: no. | //| OUTPUT: no. | //| REMARK: no. | //+------------------------------------------------------------------+ void CArrayMqlRates::~CArrayMqlRates() { if(m_data_max!=0) Shutdown(); } ... //+------------------------------------------------------------------+ //| Adding an element to the end of the array. | //| INPUT: element - variable to be added. | //| OUTPUT: true if successful, false if not. | //| REMARK: no. | //+------------------------------------------------------------------+ bool CArrayMqlRates::Add(MqlRates& element) { //--- checking/reserve elements of array if(!Reserve(1)) return(false); //--- adding m_data[m_data_total++]=element; m_sort_mode=-1; //--- return(true); } //+------------------------------------------------------------------+ //| Adding an element to the end of the array from another array. | //| INPUT: src - source array. | //| OUTPUT: true if successful, false if not. | //| REMARK: no. | //+------------------------------------------------------------------+ bool CArrayMqlRates::AddArray(const MqlRates &src[]) { int num=ArraySize(src); //--- checking/reserving elements of array if(!Reserve(num)) return(false); //--- adding for(int i=0;i<num;i++) m_data[m_data_total++]=src[i]; m_sort_mode=-1; //--- return(true); } ...
Tüm MQL5 verileri, RPC isteği olarak gönderilmeden önce XML değerlerine dönüştürülmelidir, bu nedenle bir değer alan ve onu XML dizesi olarak kodlayan bir CXMLRPCEncoder yardımcı sınıfı tasarladım.
class CXMLRPCEncoder { public: CXMLRPCEncoder(){}; string header(string path,int contentLength); string fromInt(int param); string fromDouble(double param); string fromBool(bool param); string fromString(string param); string fromDateTime(datetime param); string fromMqlRates(MqlRates ¶m); };
Uygulanan yöntemlerden üçünü aşağıya yapıştırdım. Hepsi bir parametre (bool, dize, tarihsaat) alır ve XML-RPC protokolü için XML geçerli veri tipi olan dizeyi döndürür.
//+------------------------------------------------------------------+ //| fromBool | //+------------------------------------------------------------------+ string CXMLRPCEncoder::fromBool(bool param) { CString s_bool; s_bool.Clear(); s_bool.Append(VALUE_B); s_bool.Append(BOOL_B); if(param==true) s_bool.Append("1"); else s_bool.Append("0"); s_bool.Append(BOOL_E); s_bool.Append(VALUE_E); return s_bool.Str(); } //+------------------------------------------------------------------+ //| fromString | //+------------------------------------------------------------------+ string CXMLRPCEncoder::fromString(string param) { CString s_string; s_string.Clear(); s_string.Append(VALUE_B); s_string.Append(STRING_B); s_string.Append(param); s_string.Append(STRING_E); s_string.Append(VALUE_E); return s_string.Str(); } //+------------------------------------------------------------------+ //| fromDateTime | //+------------------------------------------------------------------+ string CXMLRPCEncoder::fromDateTime(datetime param) { CString s_datetime; s_datetime.Clear(); s_datetime.Append(VALUE_B); s_datetime.Append(DATETIME_B); CString s_iso8601; s_iso8601.Assign(TimeToString(param, TIME_DATE|TIME_MINUTES)); s_iso8601.Replace(" ", "T"); s_iso8601.Remove(":"); s_iso8601.Remove("."); s_datetime.Append(s_iso8601.Str()); s_datetime.Append(DATETIME_E); s_datetime.Append(VALUE_E); return s_datetime.Str(); }
'Etiket başlangıcı' anlamına gelen _B son ekine ve 'etiket sonu' anlamına gelen _E son ekine sahip bazı etiketler olduğunu fark edebilirsiniz.
Uygulamayı çok daha şeffaf hale getirdiğinden, XML etiketlerinin ve başlıklarının adlarını tutan bir başlık dosyası kullanmaya karar verdim
//+------------------------------------------------------------------+ //| xmlrpctags.mqh | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #define HEADER_1a "POST" #define HEADER_1b "HTTP/1.1" #define HEADER_2 "User-Agent: MQL5RPC/1.1" #define HEADER_3 "Host: host.com" #define HEADER_4 "Content-Type: text/xml" #define HEADER_5 "Content-Length: " #define HEADER_6 "<?xml version='1.0'?>" #define METHOD_B "<methodCall>" #define METHOD_E "</methodCall>" #define METHOD_NAME_B "<methodName>" #define METHOD_NAME_E "</methodName>" #define RESPONSE_B "<methodResponse>" #define RESPONSE_E "</methodResponse>" #define PARAMS_B "<params>" #define PARAMS_E "</params>" #define PARAM_B "<param>" #define PARAM_E "</param>" #define VALUE_B "<value>" #define VALUE_E "</value>" #define INT_B "<int>" #define INT_E "</int>" #define I4_B "<i4>" #define I4_E "</i4>" #define BOOL_B "<boolean>" #define BOOL_E "</boolean>" #define DOUBLE_B "<double>" #define DOUBLE_E "</double>" #define STRING_B "<string>" #define STRING_E "</string>" #define DATETIME_B "<dateTime.iso8601>" #define DATETIME_E "</dateTime.iso8601>" #define BASE64_B "<base64>" #define BASE64_E "</base64>" #define ARRAY_B "<array>" #define ARRAY_E "</array>" #define DATA_B "<data>" #define DATA_E "</data>" #define STRUCT_B "<struct>" #define STRUCT_E "</struct>" #define MEMBER_B "<member>" #define MEMBER_E "</member>" #define NAME_B "<name>" #define NAME_E "</name>" //+------------------------------------------------------------------+
MQL5-RPC'nin veri modelini tanımladıktan sonra, tam bir XML-RPC talebi oluşturmaya başlayabiliriz.
MQL5-RPC talebi
Daha önce belirtildiği gibi, XML-RPC isteği, bir istek başlığından ve XML yükünden oluşur. MQL5 veri dizilerinden otomatik olarak bir sorgu nesnesi oluşturan CXMLRPCQuery sınıfını tasarladım. Sınıf, verileri XML'de kapsüllemek için CXMLRPCEncoder'ı kullanır ve methodName etiketinin içine yöntem adını ekler.
class CXMLRPCQuery { private: CString s_query; void addValueElement(bool start,bool array); public: CXMLRPCQuery() {}; CXMLRPCQuery(string method="",CArrayObj *param_array=NULL); string toString(); };
Sınıfın yapıcısının iki parametresi vardır: yöntem adı ve yöntemi çağırmak için CArrayObj parametresini tutan işaretçi. Tüm parametreler, önceki bölümde açıklandığı gibi XML'e sarılır ve bir sorgu başlığı eklenir. Tüm XML sorgusu, toString() yöntemi kullanılarak görüntülenebilir.
CXMLRPCQuery::CXMLRPCQuery(string method="",CArrayObj *param_array=NULL) { //--- constructs a single XMLRPC Query this.s_query.Clear(); CXMLRPCEncoder encoder; this.s_query.Append(HEADER_6); this.s_query.Append(METHOD_B); this.s_query.Append(METHOD_NAME_B); this.s_query.Append(method); this.s_query.Append(METHOD_NAME_E); this.s_query.Append(PARAMS_B); for(int i=0; i<param_array.Total(); i++) { int j=0; this.s_query.Append(PARAM_B); int type=param_array.At(i).Type(); int elements=0; switch(type) { case TYPE_INT: { CArrayInt *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) this.s_query.Append(encoder.fromInt(arr.At(j))); break; } case TYPE_DOUBLE: { CArrayDouble *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDouble(arr.At(j))); break; } case TYPE_STRING: { CArrayString *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) this.s_query.Append(encoder.fromString(arr.At(j))); break; } case TYPE_BOOL: { CArrayBool *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) this.s_query.Append(encoder.fromBool(arr.At(j))); break; } case TYPE_DATETIME: { CArrayDatetime *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDateTime(arr.At(j))); break; } case TYPE_MQLRATES: { CArrayMqlRates *arr=param_array.At(i); elements=arr.Total(); if(elements==1) addValueElement(true,false); else addValueElement(true,true); for(j=0; j<elements; j++) { MqlRates tmp=arr.At(j); this.s_query.Append(encoder.fromMqlRates(tmp)); } break; } }; if(elements==1) addValueElement(false,false); else addValueElement(false,true); this.s_query.Append(PARAM_E); } this.s_query.Append(PARAMS_E); this.s_query.Append(METHOD_E); }
Lütfen aşağıdaki sorgu testi örneğine bakın. Bu en basiti değildir, çünkü oldukça karmaşık yöntemler çağırmanın mümkün olduğunu göstermek istiyorum.
Girdi parametreleri şunlardır: çift değerler dizisi, tamsayı değerleri dizisi, string değerleri dizisi, bool değerleri dizisi, tek bir tarihsaat değeri ve MqlRates yapıları dizisi.
//+------------------------------------------------------------------+ //| MQL5-RPC_query_test.mq5 | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #property version "1.00" #include <MQL5-RPC.mqh> #include <Arrays\ArrayObj.mqh> #include <Arrays\ArrayInt.mqh> #include <Arrays\ArrayDouble.mqh> #include <Arrays\ArrayString.mqh> #include <Arrays\ArrayBool.mqh> #include <Arrays\ArrayDatetime.mqh> #include <Arrays\ArrayMqlRates.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- query test CArrayObj* params = new CArrayObj; CArrayDouble* param1 = new CArrayDouble; CArrayInt* param2 = new CArrayInt; CArrayString* param3 = new CArrayString; CArrayBool* param4 = new CArrayBool; CArrayDatetime* param5 = new CArrayDatetime; CArrayMqlRates* param6 = new CArrayMqlRates; for (int i=0; i<4; i++) param1.Add(20000.0 + i*100.0); params.Add(param1); for (int i=0; i<4; i++) param2.Add(i); params.Add(param2); param3.Add("first_string"); param3.Add("second_string"); param3.Add("third_string"); params.Add(param3); param4.Add(false); param4.Add(true); param4.Add(false); params.Add(param4); param5.Add(TimeCurrent()); params.Add(param5); const int nRates = 3; MqlRates rates[3]; ArraySetAsSeries(rates,true); int copied=CopyRates(Symbol(),0,0,nRates,rates); if (copied==nRates) { param6.AddArray(rates); params.Add(param6); } CXMLRPCQuery query("sampleMethodname", params); Print(query.toString()); delete params; } //+------------------------------------------------------------------+
Ortaya çıkan sorgu aşağıdaki gibidir. Normalde bu tek satırlık bir dize değeridir, ancak XML-RPC bölümlerini ve tek değerleri ayırt etmeyi kolaylaştırmak için sekmeli bir metin olarak sunuyorum.
<?xml version="1.0" ?> <methodCall> <methodName>sampleMethodname</methodName> <params> <param> <value> <array> <data> <value> <double>20000.0000000000000000</double> </value> <value> <double>20100.0000000000000000</double> </value> <value> <double>20200.0000000000000000</double> </value> <value> <double>20300.0000000000000000</double> </value> </data> </array> </value> </param> <param> <value> <array> <data> <value> <int>0</int> </value> <value> <int>1</int> </value> <value> <int>2</int> </value> <value> <int>3</int> </value> </data> </array> </value> </param> <param> <value> <array> <data> <value> <string>first_string</string> </value> <value> <string>second_string</string> </value> <value> <string>third_string</string> </value> </data> </array> </value> </param> <param> <value> <array> <data> <value> <boolean>0</boolean> </value> <value> <boolean>1</boolean> </value> <value> <boolean>0</boolean> </value> </data> </array> </value> </param> <param> <value> <dateTime.iso8601>20111111T2042</dateTime.iso8601> </value> </param> <param> <value> <array> <data> <value> <struct> <member> <name>open</name> <value> <double>1.02902000</double> </value> </member> <member> <name>high</name> <value> <double>1.03032000</double> </value> </member> <member> <name>low</name> <value> <double>1.02842000</double> </value> </member> <member> <name>close</name> <value> <double>1.02867000</double> </value> </member> <member> <name>time</name> <value> <dateTime.iso8601> <value> <dateTime.iso8601>20111111T1800</dateTime.iso8601> </value> </dateTime.iso8601> </value> </member> <member> <name>tick_volume</name> <value> <double>4154.00000000</double> </value> </member> <member> <name>real_volume</name> <value> <double>0.00000000</double> </value> </member> <member> <name>spread</name> <value> <double>30.00000000</double> </value> </member> </struct> </value> <value> <struct> <member> <name>open</name> <value> <double>1.02865000</double> </value> </member> <member> <name>high</name> <value> <double>1.02936000</double> </value> </member> <member> <name>low</name> <value> <double>1.02719000</double> </value> </member> <member> <name>close</name> <value> <double>1.02755000</double> </value> </member> <member> <name>time</name> <value> <dateTime.iso8601> <value> <dateTime.iso8601>20111111T1900</dateTime.iso8601> </value> </dateTime.iso8601> </value> </member> <member> <name>tick_volume</name> <value> <double>3415.00000000</double> </value> </member> <member> <name>real_volume</name> <value> <double>0.00000000</double> </value> </member> <member> <name>spread</name> <value> <double>30.00000000</double> </value> </member> </struct> </value> <value> <struct> <member> <name>open</name> <value> <double>1.02760000</double> </value> </member> <member> <name>high</name> <value> <double>1.02901000</double> </value> </member> <member> <name>low</name> <value> <double>1.02756000</double> </value> </member> <member> <name>close</name> <value> <double>1.02861000</double> </value> </member> <member> <name>time</name> <value> <dateTime.iso8601> <value> <dateTime.iso8601>20111111T2000</dateTime.iso8601> </value> </dateTime.iso8601> </value> </member> <member> <name>tick_volume</name> <value> <double>1845.00000000</double> </value> </member> <member> <name>real_volume</name> <value> <double>0.00000000</double> </value> </member> <member> <name>spread</name> <value> <double>30.00000000</double> </value> </member> </struct> </value> </data> </array> </value> </param> </params> </methodCall>
Bu oldukça gelişmiş bir XML ağacıdır ve protokol daha da karmaşık yöntemleri çağırmak için esnektir.
MQL5-RPC yanıtı
XML-RPC isteğinde olduğu gibi, XML-RPC yanıtı başlıklardan ve XML yükünden oluşturulur, ancak bu sefer işlem sırası tersine çevrilmelidir, yani XML yanıtı MQL5 verilerine dönüştürülmelidir. CXMLRPCResult sınıfı bu görevle başa çıkmak için tasarlanmıştır.
Sonuç, sınıf yapıcısına iletilen bir dize olarak alınabilir veya istenen yöntem yürütüldükten sonra CXMLServerProxy sınıfından otomatik olarak alınabilir. Sonucun önceden bilinmeyen yapılar içerdiği durumlarda parseXMLResponseRAW() yöntemi tüm <value> etiketlerini arayabilir ve bulunan tüm değer öğelerinin dizisini içeren CARrayObj işaretçisini döndürür.
class CXMLRPCResult { private: CArrayObj *m_resultsArr; CString m_cstrResponse; CArrayString m_params; bool isValidXMLResponse(); bool parseXMLValuesToMQLArray(CArrayString *subArr,CString &val); bool parseXMLValuesToMQLArray(CArrayDouble *subArr,CString &val); bool parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val); bool parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val); bool parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val); bool parseXMLValuesToMQLArray(CArrayMqlRates *subArr,CString &val); bool parseXMLResponse(); public: CXMLRPCResult() {}; ~CXMLRPCResult(); CXMLRPCResult(string resultXml); CArrayObj *getResults(); bool parseXMLResponseRAW(); string toString(); };
Sınıfın kurucusu, yanıt XML'indeki her üstbilgiyi tarar ve sahne arkasında XML'i MQL5 verilerine dönüştürmenin zor işini yapan parseXMLValuesToMQLArray() özel yöntemini çağırır.
Paramın bir dizi mi yoksa tek bir öğe mi olduğunu tanır ve CArrayObj dizi sonucuna eklenen uygun dizileri doldurur.
bool CXMLRPCResult::parseXMLResponse() { CArrayObj *results=new CArrayObj; m_params.Clear(); //--- find params and put them in m_params array int tagStartIdx= 0; int tagStopIdx = 0; while((tagStartIdx!=-1) && (tagStopIdx!=-1)) { tagStartIdx= m_cstrResponse.Find(tagStartIdx,PARAM_B); tagStopIdx = m_cstrResponse.Find(tagStopIdx,PARAM_E); if((tagStartIdx!=-1) && (tagStopIdx!=-1)) { m_params.Add(m_cstrResponse.Mid(tagStartIdx+StringLen(PARAM_B),tagStopIdx-tagStartIdx-StringLen(PARAM_B))); tagStartIdx++; tagStopIdx++; }; }; for(int i=0; i<m_params.Total(); i++) { CString val; val.Assign(m_params.At(i)); //--- parse value tag val.Assign(val.Mid(StringLen(VALUE_B),val.Len()-StringLen(VALUE_B)-StringLen(VALUE_E))); //--- now check first tag and handle it approprietaly string param_type=val.Mid(0,val.Find(0,">")+1); if(param_type==INT_B || param_type==I4_B) { val.Assign(m_params.At(i)); CArrayInt *subArr=new CArrayInt; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(param_type==BOOL_B) { val.Assign(m_params.At(i)); CArrayBool *subArr=new CArrayBool; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(param_type==DOUBLE_B) { val.Assign(m_params.At(i)); CArrayDouble *subArr=new CArrayDouble; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(param_type==STRING_B) { val.Assign(m_params.At(i)); CArrayString *subArr=new CArrayString; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(param_type==DATETIME_B) { val.Assign(m_params.At(i)); CArrayDatetime *subArr=new CArrayDatetime; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(param_type==ARRAY_B) { val.Assign(val.Mid(StringLen(ARRAY_B)+StringLen(DATA_B),val.Len()-StringLen(ARRAY_B)-StringLen(DATA_E))); //--- find first type and define array string array_type=val.Mid(StringLen(VALUE_B),val.Find(StringLen(VALUE_B)+1,">")-StringLen(VALUE_B)+1); if(array_type==INT_B || array_type==I4_B) { CArrayInt *subArr=new CArrayInt; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(array_type==BOOL_B) { CArrayBool *subArr=new CArrayBool; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(array_type==DOUBLE_B) { CArrayDouble *subArr=new CArrayDouble; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(array_type==STRING_B) { CArrayString *subArr=new CArrayString; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } else if(array_type==DATETIME_B) { CArrayDatetime *subArr=new CArrayDatetime; bool isValid=parseXMLValuesToMQLArray(subArr,val); if(isValid==true) results.Add(subArr); } } }; m_resultsArr=results; return true; }
Dönüştürme, aşırı yüklenmiş parseXMLValuesToMQLArray yöntemleri içinde gerçekleşir. Dize değeri, XML'den çıkarılır ve temel MQL5 değişkenlerine dönüştürülür.
Dönüştürme için kullanılan yöntemlerden üçü referans için aşağıya yapıştırılmıştır.
//+------------------------------------------------------------------+ //| parseXMLValuesToMQLArray | //+------------------------------------------------------------------+ bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val) { //--- parse XML values and populate MQL array int tagStartIdx=0; int tagStopIdx=0; while((tagStartIdx!=-1) && (tagStopIdx!=-1)) { tagStartIdx= val.Find(tagStartIdx,VALUE_B); tagStopIdx = val.Find(tagStopIdx,VALUE_E); if((tagStartIdx!=-1) && (tagStopIdx!=-1)) { CString e; e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(BOOL_B), tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(BOOL_B)-StringLen(BOOL_E))); if(e.Str()=="0") subArr.Add(false); if(e.Str()=="1") subArr.Add(true); tagStartIdx++; tagStopIdx++; }; } if(subArr.Total()<1) return false; return true; } //+------------------------------------------------------------------+ //| parseXMLValuesToMQLArray | //+------------------------------------------------------------------+ bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val) { //--- parse XML values and populate MQL array int tagStartIdx=0; int tagStopIdx=0; while((tagStartIdx!=-1) && (tagStopIdx!=-1)) { tagStartIdx= val.Find(tagStartIdx,VALUE_B); tagStopIdx = val.Find(tagStopIdx,VALUE_E); if((tagStartIdx!=-1) && (tagStopIdx!=-1)) { CString e; e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(INT_B), tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(INT_B)-StringLen(INT_E))); subArr.Add((int)StringToInteger(e.Str())); tagStartIdx++; tagStopIdx++; }; } if(subArr.Total()<1) return false; return true; } //+------------------------------------------------------------------+ //| parseXMLValuesToMQLArray | //+------------------------------------------------------------------+ bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val) { // parse XML values and populate MQL array int tagStartIdx=0; int tagStopIdx=0; while((tagStartIdx!=-1) && (tagStopIdx!=-1)) { tagStartIdx= val.Find(tagStartIdx,VALUE_B); tagStopIdx = val.Find(tagStopIdx,VALUE_E); if((tagStartIdx!=-1) && (tagStopIdx!=-1)) { CString e; e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(DATETIME_B), tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(DATETIME_B)-StringLen(DATETIME_E))); e.Replace("T"," "); e.Insert(4,"."); e.Insert(7,"."); subArr.Add(StringToTime(e.Str())); tagStartIdx++; tagStopIdx++; }; } if(subArr.Total()<1) return false; return true; }
Gördüğünüz gibi, CString sınıfında bulunan yöntemler kullanılarak XML-RPC etiketlerinden dize değerleri çıkarılır; Assign(), Mid(), Find() ve ayrıca bool değişkenlerinde olduğu gibi StringToTime(), StringToInteger(), StringToDouble() veya özel bir yöntem kullanılarak MQL5'e dönüştürülür.
Tüm değerler ayrıştırıldıktan sonra, mevcut tüm veriler toString() yöntemi kullanılarak görüntülenebilir. Değerleri ayırmak için iki nokta üst üste kullanıyorum.
string CXMLRPCResult::toString(void) { // returns results array of arrays as a string CString r; for(int i=0; i<m_resultsArr.Total(); i++) { int rtype=m_resultsArr.At(i).Type(); switch(rtype) { case(TYPE_STRING) : { CArrayString *subArr=m_resultsArr.At(i); for(int j=0; j<subArr.Total(); j++) { r.Append(subArr.At(j)+":"); } break; }; case(TYPE_DOUBLE) : { CArrayDouble *subArr=m_resultsArr.At(i); for(int j=0; j<subArr.Total(); j++) { r.Append(DoubleToString(NormalizeDouble(subArr.At(j),8))+":"); } break; }; case(TYPE_INT) : { CArrayInt *subArr=m_resultsArr.At(i); for(int j=0; j<subArr.Total(); j++) { r.Append(IntegerToString(subArr.At(j))+":"); } break; }; case(TYPE_BOOL) : { CArrayBool *subArr=m_resultsArr.At(i); for(int j=0; j<subArr.Total(); j++) { if(subArr.At(j)==false) r.Append("false:"); else r.Append("true:"); } break; }; case(TYPE_DATETIME) : { CArrayDatetime *subArr=m_resultsArr.At(i); for(int j=0; j<subArr.Total(); j++) { r.Append(TimeToString(subArr.At(j),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" : "); } break; }; }; } return r.Str(); }
XML-RPC sonuç parametrelerinin MQL5'e nasıl dönüştürüldüğünü açıkça görmeniz için bir sonuç testi uyguladım.
//+------------------------------------------------------------------+ //| MQL5-RPC_result_test.mq5 | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #property version "1.00" //--- #include <MQL5-RPC.mqh> #include <Arrays\ArrayObj.mqh> #include <Arrays\ArrayInt.mqh> #include <Arrays\ArrayDouble.mqh> #include <Arrays\ArrayString.mqh> #include <Arrays\ArrayBool.mqh> #include <Arrays\ArrayDatetime.mqh> #include <Arrays\ArrayMqlRates.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- string sampleXMLResult = "<?xml version='1.0'?>" "<methodResponse>" + "<params>" + "<param>" + "<value><array><data>" + "<value><string>Xupypr</string></value>" + "<value><string>anuta</string></value>" + "<value><string>plencing</string></value>" + "<value><string>aharata</string></value>" + "<value><string>beast</string></value>" + "<value><string>west100</string></value>" + "<value><string>ias</string></value>" + "<value><string>Tim</string></value>" + "<value><string>gery18</string></value>" + "<value><string>ronnielee</string></value>" + "<value><string>investeo</string></value>" + "<value><string>droslan</string></value>" + "<value><string>Better</string></value>" + "</data></array></value>" + "</param>" + "<param>" + "<value><array><data>" + "<value><double>1.222</double></value>" + "<value><double>0.456</double></value>" + "<value><double>1000000000.10101</double></value>" + "</data></array></value>" + "</param>" + "<param>" + "<value><array><data>" + "<value><boolean>1</boolean></value>" + "<value><boolean>0</boolean></value>" + "<value><boolean>1</boolean></value>" + "</data></array></value>" + "</param>" + "<param>" + "<value><array><data>" + "<value><int>-1</int></value>" + "<value><int>0</int></value>" + "<value><int>1</int></value>" + "</data></array></value>" + "</param>" + "<param>" + "<value><array><data>" + "<value><dateTime.iso8601>20021125T02:20:04</dateTime.iso8601></value>" + "<value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value>" + "<value><dateTime.iso8601>20121221T00:00:00</dateTime.iso8601></value>" + "</data></array></value>" + "</param>" + "<param><value><string>Single string value</string></value></param>" + "<param><value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value></param>" + "<param><value><int>-111</int></value></param>" + "<param><value><boolean>1</boolean></value></param>" + "</params>" + "</methodResponse>"; CXMLRPCResult* testResult = new CXMLRPCResult(sampleXMLResult); Print(testResult.toString()); delete testResult; } //+------------------------------------------------------------------+
Sonucu aşağıda bulabilirsiniz:
MQL5-RPC__result_test (EURUSD,H1) 23:16:57 Xupypr:anuta:plencing:aharata:beast:west100:ias:Tim:gery18:ronnielee:investeo: droslan:Better:1.22200000:0.45600000:1000000000.10099995:true:false:true:-1:0: 1:2002.11.25 02:20:04 : 2011.11.15 00:00:00 : 2012.12.21 00:00:00 : Single string value:2011.11.15 00:00:00 :-111:true:
Bu, XML'den dönüştürülmüş farklı MQL5 değerlerinin görüntülenen dizileri dizisidir. Gördüğünüz gibi tek bir CArrayObj işaretçisinden erişilebilir dizeler, çift değerleri, boole değerleri, tamsayı değeri, tarih saat değerleri
MQL5-RPC proxy sınıfı
CXMLRPCServer proxy, HTTP iletişimi için bir çekirdek sınıftır. HTTP işlevselliğini uygulamak için "MQL5'te WinInet'i kullanma. Bölüm 2: POST İstekleri ve Dosyaları" makalesini kullandım ve XML-RPC belirtimi ile tutarlı özel başlık eklendi.
CXMLRPCServerProxy::CXMLRPCServerProxy(string s_proxy,int timeout=0) { CString proxy; proxy.Assign(s_proxy); //--- find query path int sIdx = proxy.Find(0,"/"); if (sIdx == -1) m_query_path = "/"; else { m_query_path = proxy.Mid(sIdx, StringLen(s_proxy) - sIdx) + "/"; s_proxy = proxy.Mid(0, sIdx); }; //--- find query port. 80 is default int query_port = 80; int pIdx = proxy.Find(0,":"); if (pIdx != -1) { query_port = (int)StringToInteger(proxy.Mid(pIdx+1, sIdx-pIdx)); s_proxy = proxy.Mid(0, pIdx); }; //Print(query_port); //Print(proxy.Mid(pIdx+1, sIdx-pIdx)); if(InternetAttemptConnect(0)!=0) { this.m_connectionStatus="InternetAttemptConnect failed."; this.m_session=-1; this.m_isConnected=false; return; } string agent = "Mozilla"; string empty = ""; this.m_session=InternetOpenW(agent,OPEN_TYPE_PRECONFIG,empty,empty,0); if(this.m_session<=0) { this.m_connectionStatus="InternetOpenW failed."; this.m_session=-2; this.m_isConnected=true; return; } this.m_connection=InternetConnectW(this.m_session,s_proxy,query_port,empty,empty,SERVICE_HTTP,0,0); if(this.m_connection<=0) { this.m_connectionStatus="InternetConnectW failed."; return; } this.m_connectionStatus="Connected."; }
XML-RPC çağrısını tetiklemek için CXMLRPCQuery nesnesi CXMLRPCServerProxy execute() yöntemine geçirilmelidir.
Yöntemler, XML-RPC çağrısı olarak adlandırılan komut dosyasında daha fazla kullanılabilecek CXMLRPCResult nesnesine işaretçi döndürür.
CXMLRPCResult *CXMLRPCServerProxy::execute(CXMLRPCQuery &query) { //--- creating descriptor of the request string empty_string = ""; string query_string = query.toString(); string query_method = HEADER_1a; string http_version = HEADER_1b; uchar data[]; StringToCharArray(query.toString(),data); int ivar=0; int hRequest=HttpOpenRequestW(this.m_connection,query_method,m_query_path,http_version, empty_string,0,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0); if(hRequest<=0) { Print("-Err OpenRequest"); InternetCloseHandle(this.m_connection); return(new CXMLRPCResult); } //-- sending the request CXMLRPCEncoder encoder; string header=encoder.header(m_query_path,ArraySize(data)); int aH=HttpAddRequestHeadersW(hRequest,header,StringLen(header),HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE); bool hSend=HttpSendRequestW(hRequest,empty_string,0,data,ArraySize(data)-1); if(hSend!=true) { int err=0; err=GetLastError(); Print("-Err SendRequest= ",err); } string res; ReadPage(hRequest,res,false); CString out; out.Assign(res); out.Remove("\n"); //--- closing all handles InternetCloseHandle(hRequest); InternetCloseHandle(hSend); CXMLRPCResult* result = new CXMLRPCResult(out.Str()); return result; }
Örnek 1 - Web Hizmeti erişimi
MQL5-RPC'nin çalışan ilk örneği, harici web hizmetine yapılan bir çağrıdır. Bulduğum örnek, bir para biriminin belirli bir miktarını başka bir para birimine dönüştürmek için geçerli döviz kurunu kullanıyor. Yöntem parametrelerinin tam belirtimi çevrimiçi olarak mevcuttur.
Web hizmeti, iki dize ve bir kayan değer olmak üzere üç parametreyi kabul eden foxrate.currencyConvert yöntemini sunar:
- para biriminden (ör: USD) = dize;
- para birimine (ör: GBP) = dize;
- dönüştürülecek miktar (örneğin: 100.0) = kayan nokta.
Uygulama oldukça kısadır ve sadece birkaç satır sürer.
//+------------------------------------------------------------------+ //| MQL5-RPC_ex1_ExternalWebService.mq5 | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #property version "1.00" //--- #include <MQL5-RPC.mqh> #include <Arrays\ArrayObj.mqh> #include <Arrays\ArrayDouble.mqh> #include <Arrays\ArrayString.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- //--- web service test CArrayObj* params = new CArrayObj; CArrayString* from = new CArrayString; CArrayString* to = new CArrayString; CArrayDouble* amount = new CArrayDouble; from.Add("GBP"); to.Add("USD"); amount.Add(10000.0); params.Add(from); params.Add(to); params.Add(amount); CXMLRPCQuery query("foxrate.currencyConvert", params); Print(query.toString()); CXMLRPCServerProxy s("foxrate.org/rpc"); CXMLRPCResult* result; result = s.execute(query); result.parseXMLResponseRAW(); Print(result.toString()); delete params; delete result; } //+------------------------------------------------------------------+
Yöntem karmaşık yapı döndürdüğü için sonucu parseXMLResponseRAW() yöntemini kullanarak ayrıştırdım.
Bu örnekteki XML-RPC sorgusu aşağıdaki gibidir:
<?xml version="1.0" ?> <methodCall> <methodName>foxrate.currencyConvert</methodName> <params> <param> <value> <string>GBP</string> </value> </param> <param> <value> <string>USD</string> </value> </param> <param> <value> <double>10000.00000000</double> </value> </param> </params> </methodCall>
Yanıt, XML'e sarılmış bir yapı olarak döndürülür.
<?xml version="1.0" ?> <methodResponse> <params> <param> <value> <struct> <member> <name>flerror</name> <value> <int>0</int> </value> </member> <member> <name>amount</name> <value> <double>15773</double> </value> </member> <member> <name>message</name> <value> <string>cached</string> </value> </member> </struct> </value> </param> </params> </methodResponse>
toString() çıktısı, sonuçta üç değerin mevcut olduğunu gösterir: 0 - hata yok, ikinci değer değişim için gereken temel para birimi miktarı ve üçüncü parametre en son döviz kurunun zamanı (önbelleğe alındı).
0:15773.00000000:cached:
Daha ilginç kullanım durumuyla devam edelim.
Örnek 2 - XML-RPC ATC 2011 Çözümleyici
Automated Trading Championship 2011'den istatistik almak istediğinizi (yaptım) ve bu bilgiyi MetaTrader 5 terminalinde kullanmak istediğinizi hayal edin. Bunu nasıl başaracağınızı bilmek istiyorsanız okumaya devam edin.
MetaQuotes Software Corp., belirli Expert Advisor'lardan gelen sinyallerin abone olmasını sağlayacak bir Sinyal takip hizmeti hazırladığından, bu hizmetin çözümleyiciyle birleşiminin, karmaşık bir analiz gerçekleştirmenin ve ATC'den yararlanmanın yeni yollarını sağlamanın çok güçlü bir yolu olacağını düşünüyorum. Aslında bu yöntemi kullanarak birkaç farklı kaynaktan veri alabilir ve buna dayalı karmaşık bir Expert Advisor yapabilirsiniz.
XML-RPC ATC 2011 Çözümleyici sunucusu
Bir ATC Çözümleyici sunucusu oluşturmanın ilk adımı, çıktı analizi hazırlamaktır. Örneğin hesapları belirli bir eşiğin üzerinde olan ve şu anda sahip oldukları pozisyonlarla ilgilenen tüm katılımcıları almak ve ilgilendiğimiz döviz çiftinin alım satım pozisyonu istatistiklerini görüntülemek istiyoruz.
Verileri almak ve ayrıştırmak için Python dilini ve BeautifulSoup kitaplığını kullandım. Python'u daha önce hiç kullanmadıysanız tarayıcınızı http:/Python.org adresine yönlendirmenizi ve bu dilin ne anlama geldiğini okumanızı şiddetle tavsiye ederim, pişman olmayacaksınız.
Elinizi çabuk bir şekilde çözümleyiciye koymak isterseniz download Python 2.7.1 installer ve setup_tools paketlerinin her ikisine de kurun. Bunun ardından Windows konsolunu çalıştırın ve dizini C:\Python27\Scripts olarak veya Python'u yüklediğiniz klasör olarak değiştirin. Ardından 'easy_install BeautifulSoup' komutunu verin. Bu işlem BeautifulSoup paketini kuracaktır:
C:\Python27\Scripts>easy_install BeautifulSoup Searching for BeautifulSoup Reading http://pypi.python.org/simple/BeautifulSoup/ Reading http://www.crummy.com/software/BeautifulSoup/ Reading http://www.crummy.com/software/BeautifulSoup/download/ Best match: BeautifulSoup 3.2.0 Downloading http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulS oup-3.2.0.tar.gz Processing BeautifulSoup-3.2.0.tar.gz Running BeautifulSoup-3.2.0\setup.py -q bdist_egg --dist-dir c:\users\przemek\ap pdata\local\temp\easy_install-zc1s2v\BeautifulSoup-3.2.0\egg-dist-tmp-hnpwoo zip_safe flag not set; analyzing archive contents... C:\Python27\lib\site-packages\setuptools\command\bdist_egg.py:422: UnicodeWarnin g: Unicode equal comparison failed to convert both arguments to Unicode - interp reting them as being unequal symbols = dict.fromkeys(iter_symbols(code)) Adding beautifulsoup 3.2.0 to easy-install.pth file Installed c:\python27\lib\site-packages\beautifulsoup-3.2.0-py2.7.egg Processing dependencies for BeautifulSoup Finished processing dependencies for BeautifulSoup C:\Python27\Scripts>
Bundan sonra Python konsolunu çalıştırabilmeli ve "BeautifulSoup'u içe aktar" komutunu hatasız verebilmelisiniz.
Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on win32 Type "copyright", "credits" or "license()" for more information. >>> import BeautifulSoup >>>
Ayrıca ekli analizörü "python ContestantParser.py" komutuyla çalıştırın. Analizör web sitesini getirir, bazı anahtar kelime araması yapar ve çıktıyı konsola koyar.
import re import urllib from BeautifulSoup import BeautifulSoup class ContestantParser(object): URL_PREFIX = 'https://championship.mql5.com/2011/en/users/' def __init__(self, name): print "User:", name url = ContestantParser.URL_PREFIX + name feed = urllib.urlopen(url) s = BeautifulSoup(feed.read()) account = s.findAll('td', attrs={'class' : 'alignRight'}) self.balance = float(account[0].contents[0].replace(' ', '')) self.equity = float(account[2].contents[0].replace(' ', '')) terminals = s.findAll('table', attrs={'class': 'terminal'}) terminal = terminals[0] trs = terminal.findAll('tr') pairs = terminal.findAll('span', attrs={'class':'stateText'}) self.stats = {} for i in range(len(pairs)-1): if len(pairs[i].string)> 6: break self.stats[str(pairs[i].string)] = str(trs[i+1].contents[7].string) def __str__(self): for k in self.stats.keys(): print k, self.stats[k] return "Bal " + str(self.balance) + " Equ " + str(self.equity) def position(self, pair): if pair in self.stats.keys(): return self.stats[pair] else: return "none" class ContestantsFinder(object): URL_PREFIX = "https://championship.mql5.com/2011/en/users/index/page" URL_SUFFIX = "?orderby=equity&dir=down" def __init__(self, min_equity): self.min_equity = min_equity self.user_list = [] self.__find() def __find(self): isLastPage = False pageCnt = 1 while isLastPage == False: url = ContestantsFinder.URL_PREFIX + str(pageCnt) + \ ContestantsFinder.URL_SUFFIX feed = urllib.urlopen(url) s = BeautifulSoup(feed.read()) urows = s.findAll('tr', attrs={'class' : re.compile('row.*')}) for row in urows: user_name = row.contents[5].a.string equity = float(row.contents[19].string.replace(' ','')) if equity <= self.min_equity: isLastPage = True break self.user_list.append(str(user_name)) print user_name, equity pageCnt += 1 def list(self): return self.user_list if __name__ == "__main__": # find all contestants with equity larger than threshold contestants = ContestantsFinder(20000.0) print contestants.list() # display statistics print "* Statistics *" for contestant in contestants.list(): u = ContestantParser(contestant) print u print u.position('eurusd') print '-' * 60
Bu, yalnızca HTML kaynak kodunun içine bakarak ve etiketler arasındaki ilişkileri bularak sağlandı. Bu kod doğrudan Python konsolundan çalıştırılırsa yarışmacıların adlarını, bakiyelerini, özkaynaklarını ve mevcut tüm açık pozisyonları verir.
Lütfen örnek bir sorgunun çıktısını gözlemleyin:
* Statistics * User: Tim Bal 31459.2 Equ 31459.2 none ------------------------------------------------------------ User: enivid eurusd sell euraud sell Bal 26179.98 Equ 29779.89 sell ------------------------------------------------------------ User: ias eurjpy sell usdchf sell gbpusd buy eurgbp buy eurchf sell audusd buy gbpjpy sell usdjpy buy usdcad buy euraud buy Bal 15670.0 Equ 29345.66 none ------------------------------------------------------------ User: plencing eurusd buy Bal 30233.2 Equ 29273.2 buy ------------------------------------------------------------ User: anuta audusd buy usdcad sell gbpusd buy Bal 28329.85 Equ 28359.05 none ------------------------------------------------------------ User: gery18 Bal 27846.7 Equ 27846.7 none ------------------------------------------------------------ User: tornhill Bal 27402.4 Equ 27402.4 none ------------------------------------------------------------ User: rikko eurusd sell Bal 25574.8 Equ 26852.8 sell ------------------------------------------------------------ User: west100 eurusd buy Bal 27980.5 Equ 26255.5 buy ------------------------------------------------------------ ...
İyi görünüyor, değil mi? Bu verilerle ilginç istatistikler toplayabilir ve bunları talep üzerine sağlayacak bir XML-RPC hizmeti uygulayabiliriz. MetaTrader 5 adresindeki XMLRPC istemcisi, gerektiğinde bu istatistikleri getirebilir.
Bir XMLRPC sunucusu yapmak için python'un SimpleXMLRPCserver kitaplığını kullandım. Sunucu, dış dünyaya iki yöntem sunar: listContestants ve getStats. İlki yalnızca belirli eşiğin üzerinde özsermayeye sahip yarışmacı isimlerini görüntülerken, ikincisi belirli bir döviz çiftinde kaç tanesinin açık pozisyonu olduğunu ve bu pozisyonlarda alım/satım oranının ne olduğunu gösterir.
Makaledeki sinyal hizmeti ve/veya alım satım kopyalayıcı ile birlikte, gerçekten alım satım yaptığınız kurulumun karlı olma şansının daha yüksek olduğunu doğrulamak isteyebilirsiniz.
from SimpleXMLRPCServer import SimpleXMLRPCServer from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler from PositionGrabberTest import ContestantParser, ContestantsFinder class RequestHandler( SimpleXMLRPCRequestHandler ): rpc_path = ( '/RPC2', ) class ATC2011XMLRPCServer(SimpleXMLRPCServer): def __init__(self): SimpleXMLRPCServer.__init__( self, ("192.168.235.168", 6666), requestHandler=RequestHandler, logRequests = False) self.register_introspection_functions() self.register_function( self.xmlrpc_contestants, "listContestants" ) self.register_function( self.xmlrpc_get_position_stats, "getStats" ) def xmlrpc_contestants(self, min_equity): try: min_equity = float(min_equity) except ValueError as error: print error return [] self.contestants = ContestantsFinder(min_equity) return self.contestants.list() def xmlrpc_get_position_stats(self, pair): total_users = len(self.contestants.list()) holding_pair = 0 buy_positions = 0 for username in self.contestants.list(): u = ContestantParser(username) position = u.position(pair) if position != "none": holding_pair += 1 if position == "buy": buy_positions += 1 return [ total_users, holding_pair, buy_positions ] if __name__ == '__main__': server = ATC2011XMLRPCServer() server.serve_forever()
Bu sunucuya doğrudan Python konsolundan erişilebilir. Bu script dosyasını çalıştırırken, IP adresini sisteminizin kullandığı ile değiştirmeyi unutmayın.
C:\Program Files\MetaTrader 5\MQL5>python Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win 32 Type "help", "copyright", "credits" or "license" for more information. >>> from xmlrpclib import ServerProxy >>> u = ServerProxy('http://192.168.235.168:6666') >>> u.listContestants(20000.0) ['lf8749', 'ias', 'aharata', 'Xupypr', 'beast', 'Tim', 'tmt0086', 'yyy999', 'bob sley', 'Diubakin', 'Pirat', 'notused', 'AAA777', 'ronnielee', 'samclider-2010', 'gery18', 'p96900', 'Integer', 'GradyLee', 'skarkalakos', 'zioliberato', 'kgo', 'enivid', 'Loky', 'Gans-deGlucker', 'zephyrrr', 'InvestProm', 'QuarkSpark', 'ld7 3', 'rho2011', 'tornhill', 'botaxuper'] >>>
Muhtemelen bunu okurken yarışmacıların sonuçları tamamen değişti ama burada neyi başarmaya çalıştığımı anlamalısınız.
"eurusd" parametresiyle getStats() yöntemini çağırmak üç sayı döndürür:
In : u.getStats("eurusd") Out: [23, 12, 9]
Birincisi eşik üzerinde hisseye sahip yarışmacı sayısı, ikincisi EURUSD açık pozisyonuna sahip yarışmacı sayısı ve üçüncüsü uzun eurusd pozisyonuna sahip yarışmacı sayısı. Bu durumda, kazanan yarışmacıların üçte ikisi uzun EURUSD açmışlardır, bu nedenle robotunuz veya göstergeleriniz 'açık eurusd uzun' sinyali ürettiğinde bu bir onay sinyali işlevi görebilir.
MQL5-RPC ATC 2011 Çözümleyici istemcisi
MetaTrader 5'te bu verileri talep üzerine getirmek için MQL5-RPC çerçevesini kullanmanın zamanı geldi. Aslında kaynak kodunu izlerseniz kullanımı açıklayıcıdır.
//+------------------------------------------------------------------+ //| MQL5-RPC_ex2_ATC2011AnalyzerClient.mq5 | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #property version "1.00" #include <MQL5-RPC.mqh> #include <Arrays\ArrayObj.mqh> #include <Arrays\ArrayInt.mqh> #include <Arrays\ArrayDouble.mqh> #include <Arrays\ArrayString.mqh> //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- //--- ATC 2011 analyzer test CXMLRPCServerProxy s("192.168.235.168:6666"); CXMLRPCResult* result; //--- Get list of contestants CArrayObj* params1 = new CArrayObj; CArrayDouble* amount = new CArrayDouble; amount.Add(20000.0); params1.Add(amount); CXMLRPCQuery query("listContestants", params1); Print(query.toString()); result = s.execute(query); Print(result.toString()); delete result; //--- Get position statistics CArrayObj* params2 = new CArrayObj; CArrayString* pair = new CArrayString; pair.Add("eurusd"); params2.Add(pair); CXMLRPCQuery query2("getStats", params2); Print(query2.toString()); result = s.execute(query2); CArrayObj* resultArray = result.getResults(); CArrayInt* stats = resultArray.At(0); Print("Contestants = " + IntegerToString(stats.At(0)) + ". EURUSD positions = " + IntegerToString(stats.At(1)) + ". BUY positions = " + IntegerToString(stats.At(2))); delete params1; delete params2; delete result; } //+------------------------------------------------------------------+
Komut dosyası çağrıldığında olan budur. Sorgu XML'e sarılır:
<?xml version="1.0" ?> <methodCall> <methodName>listContestants</methodName> <params> <param> <value> <double>20000.00000000</double> </value> </param> </params> </methodCall>
ve bir süre sonra XML-RPC sunucusundan yanıt alınır. Benim durumumda, python XML-RPC hizmetini çalıştıran ve MetaTrader 5 çalıştıran Windows VirtualBox konuk kurulumundan çağıran Linux ana bilgisayarını kullandım.
<?xml version="1.0" ?> <methodResponse> <params> <param> <value> <array> <data> <value> <string>lf8749</string> </value> <value> <string>ias</string> </value> <value> <string>Xupypr</string> </value> <value> <string>aharata</string> </value> <value> <string>beast</string> </value> <value> <string>Tim</string> </value> <value> <string>tmt0086</string> </value> <value> <string>yyy999</string> </value> <value> <string>bobsley</string> </value> <value> <string>Diubakin</string> </value> <value> <string>Pirat</string> </value> <value> <string>AAA777</string> </value> <value> <string>notused</string> </value> <value> <string>ronnielee</string> </value> <value> <string>samclider-2010</string> </value> <value> <string>gery18</string> </value> <value> <string>Integer</string> </value> <value> <string>GradyLee</string> </value> <value> <string>p96900</string> </value> <value> <string>skarkalakos</string> </value> <value> <string>Loky</string> </value> <value> <string>zephyrrr</string> </value> <value> <string>Medvedev</string> </value> <value> <string>Gans-deGlucker</string> </value> <value> <string>InvestProm</string> </value> <value> <string>zioliberato</string> </value> <value> <string>QuarkSpark</string> </value> <value> <string>rho2011</string> </value> <value> <string>ld73</string> </value> <value> <string>enivid</string> </value> <value> <string>botaxuper</string> </value> </data> </array> </value> </param> </params> </methodResponse>
toString() yöntemi kullanılarak görüntülenen ilk sorgunun sonucu aşağıdaki gibidir:
lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat:
AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900:
skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato:
QuarkSpark:rho2011:ld73:enivid:botaxuper:
İkinci sorgu getStats() yöntemini çağırmak için kullanılır.
<?xml version="1.0" ?> <methodCall> <methodName>getStats</methodName> <params> <param> <value> <string>eurusd</string> </value> </param> </params> </methodCall>
XML-RPC yanıtı basittir, yalnızca üç tamsayı değeri içerir.
<?xml version="1.0" ?> <methodResponse> <params> <param> <value> <array> <data> <value> <int>31</int> </value> <value> <int>10</int> </value> <value> <int>3</int> </value> </data> </array> </value> </param> </params> </methodResponse>
Bu sefer başka bir yaklaşım benimsedim ve döndürülen değerlere MQL5 değişkenleri olarak eriştim.
MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1) 12:39:23 lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat: AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900: skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato: QuarkSpark:rho2011:ld73:enivid:botaxuper: MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1) 12:39:29 Contestants = 31. EURUSD positions = 10. BUY positions = 3
Gördüğünüz gibi çıktı kolayca anlaşılabilir bir biçimde.
Sonuç
MetaTrader 5'in XML-RPC protokolünü kullanarak uzaktan prosedür çağrıları yürütmesini sağlayan yeni bir MQL5-RPC çerçevesi sundum. İlki bir web hizmetine erişim ve ikincisi Automated Trading Championship 2011 için özel bir çözümleyici olmak üzere iki kullanım örneği sunuldu. . Bu iki örnek, daha sonraki deneyler için bir temel teşkil etmelidir.
MQL5-RPC'nin birçok farklı şekilde kullanılabilen çok güçlü bir özellik olduğuna inanıyorum. Çerçeveyi Açık Kaynak yapmaya karar verdim ve onu GPL licence altında http://code.google.com/p/mql5-rpc/. adresinde yayınladım. Herhangi biri kodu kurşun geçirmez hale getirmek, yeniden düzenlemek veya hata düzeltmeleri yapmak için yardım etmek isterse, projeye katılması için kapılar açıktır. Tüm örneklerle birlikte kaynak kodu makalenin ekinde de mevcuttur.
MetaQuotes Ltd tarafından İngilizceden çevrilmiştir.
Orijinal makale: https://www.mql5.com/en/articles/342
- Ücretsiz ticaret 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