MQL5-RPC. Remote Procedure Calls von MQL5, mit Web Service Access und XML-RPC-ATC-Analysator
Einleitung
In diesem Artikel wird das MQL5-RPC-System beschrieben, das ich in den letzten Wochen gebaut habe. Er enthält die Grundlagen von XML-RPC access, eine Beschreibung der MQL5-Implementierung und zwei Beispiele der Verwendung von MQL5-RPC aus dem echten Leben. Das erste Beispiel wird ein Remote Procedure Call zum Webdienst einer externen Forex-Webseite sein, und das zweite wird ein Client von unserem eigenen XML-RPC-Server sein, der verwendet wird, um Ergebnisse von der Automated Trading Championship 2011 zu gliedern, zu analysieren und zu bieten. Wenn Sie sich für Implementierungen und Analysen von verschiedenen Statistiken des ATC 2011 in Echtzeit interessieren, dann ist dieser Artikel das Richtige für Sie.
XML-RPC Grundlagen
Fangen wir mit den Grundlagen von XML-RPC an. XML-RPC steht für XML Remote Procedure Call. Dies ist ein Netzwerk-Protokoll, das XML verwendet um Parameter zu enkodieren und zu dekodieren um einen externen Service zu rufen. Als Transportmechanismus für den Datenaustausch werden HTTP-Protokolle verwendet. Mit externer Methode meine ich ein anderes Computerprogramm oder einen Webdienst, das von fern Prozesse aufrufen kann.
Die externe Methode kann mit jeder Computersprache von jedem Rechner angewandt werden, der mit dem Netzwerk verbunden ist, jedoch unter der Voraussetzung, dass er auch XML-RPC-Protokolle verwendet und Netzwerkzugang zum Server hat. Das heißt auch, dass XML-RPC verwendet werden kann, um eine Methode auf dem gleichen Rechner aufzurufen, die in einer anderen Programmiersprache geschrieben ist. Auf dies wird im zweiten Teil des Artikels noch näher eingegangen
XML-RPC Datenmodell
In einer XML-RPC-Beschreibung kommen sechs verschiedene Datentypen vor: Int, Double, Boolean, String, Datetime, Base64 und zwei verbundene Datentypen: Array und Struct. Array kann aus ein paar Grundelementen bestehen, Struct bietet Namen-Werte-Paare, zum Beispiel assoziative Arrays oder Objekteigenschaften.
Grundlegende Datentypen in XML-RPC | ||
---|---|---|
Art | Wert | Beispiele |
int or i4 | 32-bit Ganze Zahlen zwischen- 2.147.483.648 und 2.147.483.647. | <int>11<int> <i4>12345<i4> |
double | 64-bit floating-point numbers | <double>30.02354</double> <double>-1.53525</double> |
Boolean | true (1) or false (0) | <boolean>1</boolean> <boolean>0</boolean> |
string | ASCII Text, viele Implementierungen unterstützen Unicode | <string>Hello</string> <string>MQL5</string> |
dateTime.iso8601 | Datum in ISO8601-Format CCYYMMDDTHH:MM:SS | <dateTime.iso8601> 20111125T02:20:04 </dateTime.iso8601> <dateTime.iso8601> 20101104T17:27:30 </dateTime.iso8601> |
base64 | Binärinformation enkodiert wie in RFC 2045 definiert | <base64> TDVsbG8sIFdvdwxkIE== </base64> |
Tabelle 1 Grundlegende Datentypen in XML-RPC
Array kann jeden der Grundtypen beinhalten, nicht unbedingt den gleichen Typ. Array-Element muss im Werte-Element vorhanden sein. Ein Datenelement und einen oder mehrere Werte-Elemente sind in Daten-Elementen enthalten. Das untere Beispiel zeigt ein Array mit vier ganzzahligen Werten.
<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>
Das zweite Beispiel zeigt ein Array mit fünf String Values.
<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>
Ich bin davon überzeugt, dass Sie Ähnlichkeiten in diesen zwei Beispielen erkennen und dann einen weiteren XML-RPC-Array bauen können.
Structs haben ein Struct-Element im Wert-Element und Member Sections in diesem Struct-Element. Jedes Member besteht aus seinem Namen und seinem Wert. Daher ist es einfach, Werte eines Associative Array oder Members eines Objektes anhand von Structs auszutauschen.
Bitte sehen Sie sich das Beispiel unten an.
<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>
Nachdem wir uns nun mit dem XML-RPC-Datenmodell vertraut gemacht haben, gehen wir über zu Anfragen- und Antwort-Strukturen. Dies wird die Grundlage für eine Implementierung von XML-RPC-Clients in MQL5.
XML-RPC-Anfragenstruktur
Die XML-RPC-Anfrage besteht aus Header und Nutzdaten (payload). Der Header gibt die HTTP-Sendemethode (POST), den relativen Pfad zu den XML-RPC-Diensten, die HTTP-Protokollversion, den User-Agent-Namen, die Host-IP-Adresse, den Inhaltstyp (text/xml) und die Inhaltslänge in Bytes an.
POST /xmlrpc HTTP 1.1 User-Agent: mql5-rpc/1.0 Host: 10.12.10.10 Content-Type: text/xml Content-Length: 188
Die Nutzdaten einer XML-RPC-Anfrage sind XML-Dokumente. Wurzelelemente des XML-Baumes müssen methodCall genannt werden. Ein methodCall beinhaltet ein einzelnes methodName-Element, dessen Inhalt ausgeführt wird. Das MethodName-Element beinhaltet keine oder ein params-Element.
Das params-Element beinhaltet einen oder mehr Werte, Array- oder Struct-Elemente. Alle Werte sind entsprechend mit Datentypen enkodiert (siehe Tabelle unten). Beachten Sie hier das Beispiel der Nutzdaten unten, das die Anfrage in der Ausführung "Multiply"-Methode mit zwei Werten für die Funktion zeigt.
<?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>
Der Header und die Nutzdaten werden durch HTTP zum Server geschickt, der den Input akzeptiert. Wenn der Server verfügbar ist, überprüft er den Namen der Methode und die Parameter-Liste und führt die gewünschte Methode aus. Nachdem der Prozess abgeschlossen ist, bereitet er die Antwort-Struktur XML-RPC vor, die vom Client gelesen werden kann.
XML-RPC-Anfragenstruktur
Ähnlich wie die XML-RPC-Anfrage besteht die XML-RPC-Antwort aus einem Header und den Nutzdaten. Der Header ist Text und die Nutzdaten sind ein XML-Dokument. Wenn die Anfrage korrekt war, sagt die erste Zeile des Headers, dass der Server gefunden wurden (Code 200) und spezifiziert die Protokollversion. Der Header muss auch den Inhaltstyp text/xml enthalten und die Inhaltslänge, die die Länge der Nutzdaten in Bytes ist.
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
Die Nutzdaten der Antwort sind ebenfalls ein XML-Dokument. Wurzelelemente des XML-Baumes müssen methodResponse genannt werden. Das methodResponse-Element enthält ein params pro erfolgreichem Element oder ein fault Element bei Versagen. Das params-Element enthält genau ein param-Element. Das param-Element enthält genau ein Werte-Element.
Das Beispiel einer erfolgreichen Antwort wird unten präsentiert:
<?xml version="1.0"?> <methodResponse> <params> <param> <value><double>62606001.94</double></value> </param> </params> </methodResponse>
Die Stör-Antwort wird gesendet wenn ein Problem beim Prozess der XML-RPC-Anfrage auftritt.
Das Stör-Element hat, wie das params-Element, nur einen einzelnen Output-Wert.
<?xml version="1.0"?> <methodResponse> <fault> <value><string>No such method!</string></value> </fault> </methodResponse>
Da Error-Codes unter XML-RPC nicht standardisiert werden, sind Error-Nachrichten abhängig von der Implementierung.
Einführung MQL5-RPC
Ich fand zwei Artikel von Alex Sergeev "Using WinInet.dll for Data Exchange between Terminals via the Internet" und "Using WinInet in MQL5. Teil 2: POST Requests and Files" und mir wurde klar, dass ich einen XML-RPC-Client für den MetaTrader 5 implementieren konnte. Nachdem ich mir die Spezifizierungen angeschaut habe, habe ich meine eigenen von Grund auf implementiert. Dies ist ein laufendes Projekt und deckt nicht alle Spezifizierungen ab (base64-Unterstützung wird in naher Zukunft dazukommen), aber man kann es bereits verwenden um eine große Teilmenge von XML-RPC-Calls mit MetaTrader 5 zu machen.
MQL5-RPC-Datenmodell
Das schwerste in Bezug auf die Implementierung war für mich ein korrektes Datenmodell für MQL5 zu finden. Ich beschloss, dass es für den User so einfach wie möglich sein sollte, deswegen baute ich einige Klassen, die die Funktionalität des Systems einrahmen. Zuerst entschied ich mich dafür, die Anfragen-param-Daten als einzelnen COject*-Zeiger zu machen. Dieser Zeiger hat einige Zeiger auf Arrays, die von der CObject-Klasse abstammen.
Es gibt Standard-Klassen die CObject-Arrays enthalten: CArrayInt, CArrayDouble, CArrayString, auf die habe ich mich bezogen und CArrayBool, CArrayDatetime implementiert, um grundlegende Datentypen zu vollenden und CArrayMqlRates um Array von Structs hinzuzufügen. Nur der Typ base64 fehlt bis jetzt, aber er wird in naher Zukunft unterstützt. Wenn ein Array nur ein Element enthält, ist es in XML als ein einzelnes Werte-Element vorhanden.
Ich habe geschrieben, wie man verschiedene Arrays zum CObject*-Array hinzufügen kann und alle Arrays verschiedener Typen zeigen kann. Man kann es unten lesen.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Das Ergebnis sollte klar sein: es gibt 6 Subarrays: array of integer values, array of double values, array of strings, array of datetimes, array of boolean values and array of MqlRates.
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
Vielleicht interessiert es sie, wie ich Arrays anderer Datentypen implementiert habe. In CArrayBool und CArrayDatetime habe ich einfach CArrayInt als Grundlage benutzt, aber in CArrayMqlRates war es ein bisschen anders, weil Strukturen als Referenzen behandelt werden und kein TYPE_MQLRATES definiert ist.
Unten sehen Sie Teile eines Quellcodes der CArrayMqlRates-Klasse. Andere Klassen sind als Anhang zum Artikel erhältlich.
//+------------------------------------------------------------------+ //| 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); } ...
Alle MQL5-Daten müssen in XML-Werte konvertiert werden, bevor sie als RPC-Anfragen gesendet werden, daher habe ich eine CXMLRPC-Encoder-Hilfklasse entworfen die einen Wert nimmt und ihn als XML-String enkodiert.
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); };
Ich habe drei der implementierten Methoden unten eingefügt. Alle haben einen Parameter (bool. string, datetime) und einen Return-String, ein XML-gültiger Datentyp für XML-RPC-Protokoll.
//+------------------------------------------------------------------+ //| 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(); }
Tags, die auf ein _B-Suffix enden, bedeuten 'tag begin', und Tags, die auf ein _E-Suffix enden, bedeuten 'tag end'.
Ich habe mich dazu entschlossen, eine Header-Datei zu machen, die die Namen der XML-Tags und -Headers beibehält, denn das macht die Implementierung viel transparenter.
//+------------------------------------------------------------------+ //| 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>" //+------------------------------------------------------------------+
Nachdem wir nun das Datenmodell MQL5-RPC definiert haben, können wir mit dem Bau einer vollständigen XML-RPC-Anfrage fortfahren.
MQL5-RPC-Anfrage
Wie zuvor erwähnt, bestehen XML5-Anfragen aus einem Anfragen-Header und den XML-Nutzdaten. Ich habe die CXMLRPC-Query-Klasse entworfen, die automatisch ein Query-Objekt aus MQL5-Datenarrays generiert. Diese Klasse verwendet den CXMLRPC-Encoder, um Daten in XML einzubetten und fügt den Methodennamen im methodName-Tag an.
class CXMLRPCQuery { private: CString s_query; void addValueElement(bool start,bool array); public: CXMLRPCQuery() {}; CXMLRPCQuery(string method="",CArrayObj *param_array=NULL); string toString(); };
Der Konstruktor dieser Klasse hat zwei Parameter: Method-Name und Zeiger zu CArrayObj tragende Parametern, um die Methode aufzurufen. Alle Parameter sind in XML eingebettet, wie in den vorigen Abschnitten beschrieben, und ein Query-Header wird hinzugefügt. Die gesamte XML-Query kann mit der toString()-Methode angezeigt werden.
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); }
Beachten Sie hier bitte das Query-Testbeispiel unten. Dies ist nicht das einfachste, da ich zeigen will, dass es möglich ist, ziemlich komplexe Methoden zu nennen.
Input-Parameter sind folgende: Array aus double Werten, Array aus integer Werten, Array aus string Werten, Array aus bool Werten, ein einzelner datetime Wert und Array aus MqlRates Structs.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Die resultierende Anfrage lautet wie folgt. Es gibt normalerweise keinen One-Line-String-Wert, aber ich präsentiere ihn als Text um die Unterscheidung zwischen XML-RPC-Abschnitten und Einzelwerten einfacher zu machen.
<?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>
Dies ist ein gut entwickelter XML-Baum und das Protokoll ist flexibel genug um sogar komplexere Methoden aufzurufen.
MQL5-RPC-Anfrage
Genauso wie XML-RPC-Anfragen sind XML-RPC-Antworten aus Headern und XML-Nutzdaten zusammengebaut, aber dieses Mal muss die Verarbeitungsreihenfolge verkehrt werden, das heißt, die XML-Antwort muss in MQL5-Daten konvertiert werden. CXMLRPC-Result-Klasse wurde entwickelt, um diese Aufgabe zu bewältigen.
Ergebnisse können als String gemacht werden, das zum Klassen-Konstruktor geht, oder sie können automatisch von der CXML-Server-Proxy-Klasse abgerufen werden nachdem die gewünschte Methode ausgeführt wird. Wenn das Resultat Structs enthält, die nicht davor bekannt sind, kann man mit der Methode parseXMLResponseRAW() alle <value>-Tags suchen und gibt den CArrayObj-Zeiger, der Arrays mit allen gefunden Werte-Elementen enthält, zurück.
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(); };
Der Konstruktor der Klasse untersucht alle Header der Antwort-XML und ruft die private Methode parseXMLValuesToMQLArray() auf, die die schwierige Aufgabe hat, XML in MQL5-Daten hinter den Szenen zu konvertieren.
Es erkennt ob param ein Array ist oder ein einzelnes Element und es befüllt die passenden Arrays, die zu dem CArrayObj hinzugefügt werden.
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; }
Die Umwandlung findet bei überladenen parseXMLValuesToMQLArray-Methoden statt. Der String-Wert wird von XML extrahiert und in einfache MQL5 Variablen umgewandelt.
Drei der Methoden, die für die Umwandlung genommen wurden, sind unten eingefügt.
//+------------------------------------------------------------------+ //| 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; }
Wie Sie sehen, werden String-Werte von XML-RPC-Tags extrahiert mit den Methoden, die in der CString-Klasse zur Verfügung stehen; Assign(), Mid() und Find() werden weiter in MQL5 umgewandelt mittels StringToTime(), StringToInteger(), StringToDouble() oder einer benutzerdefinierten Methode, wie im Fall der Bool-Variablen.
Nachdem alle Werte gegliedert sind, könne alle zur Verfügung stehenden Daten mit der String()-Methode angezeigt werden. Ich verwende einfach Doppelpunkte, um Werte zu trennen.
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(); }
Ich habe einen Ergebnistest für Sie implementiert, damit Sie genau sehen, wie XML-RPC-Ergebnisparameter in MQL5 umgewandelt werden.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Sie können das Ergebnis unten sehen.
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:
Dies ist eine Auswahl an Arrays mit verschiedenen MQL5-Werten, die von XML umgewandelt wurden. Wie Sie sehen, gibt es hier strings, double-Werte, boolean Werte, integer Werte und datetime Werte die mit einem einfachen CArrayObj Zeiger abgerufen werden können.
MQL5-RPC Proxy Klasse
CXMLRPC-Proxy Server ist eine Kernklasse der HTTP-Kommunikation. Ich habe "Using WinInet in MQL5 verwendet Teil 2: Der Artikel POST Requests and Files beschreibt die Implementierung von HTTP-Funktionen und dem hinzugefügten, benutzerdefinierten Header, der zu den XML-RPC-Spezifikationen passt.
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."; }
Das CXMLRPC-Query-Objekt muss von der CXMLRPCServerProxy execute()-Methode übermittelt werden um XML-RPC-Calls auszulösen.
Die Methode gibt Zeiger zurück zu CXMLRPC-Result-Objekt das weiterhin in dem Skript verwendet wird, das XML-RPC-Call genannt wird.
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; }
Beispiel 1 - Webdienst-Zugriff
Das erste Arbeitsbeispiel von MQL5-RPC ist ein Call zu einem externen Webdienst. Bei dem gefundenen Beispiel werden aktuelle Wechselkurse verwendet, um einen bestimmten Betrag von einer Währung in eine andere zu wechseln. Die exakte Beschreibung von Methoden-Parametern ist online erhältlich.
Der Webdienst zeigt die foxrate.currencyConvert-Methode, die drei Parameter akzeptiert, zwei String- und einen Float-Wert:
- von der Währung (z.B: USD) = string;
- in die Währung (z.B: GBP) = string;
- zu wechselnder Betrag (z.B: 100,0) = float
Die Implementierung ist nur wenige Zeilen lang.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Da durch die Methode komplexe Structs zurückgegeben werden, habe ich die Ergebnisse mit der parseXMLResponseRAW()-Methode gegliedert.
Die XML-RPC-Anfrage in diesem Beispiel schaut wie folgt aus:
<?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>
Und die Antwort wird als Struct in ein XML eingebettet zurückgegeben.
<?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>
Der Output von toString() zeigt, dass drei Werte in den Ergebnissen zur Verfügung stehen: 0 - kein Error, der zweite Wert ist die Anzahl der grundlegenden Währung für den Wechsel, und der dritte Parameter ist die Zeit des letzten Wechselkurses (wurde im Cache gespeichert).
0:15773.00000000:cached:
Fahren wir mit interessanteren Anwendungsfällen fort.
Beispiel 2 - XML-RPC-ATC 2011 Analysator
Stellen Sie sich vor, sie wollen Statistiken von der Automated Trading Championship 2011 haben (ich hab das gemacht) und diese Information im MetaTrader 5-Terminal verwenden. Wenn Sie wissen wollen, wie Sie das am besten erreichen können, lesen Sie weiter.
Da MetaQuotes Software Corp. einen Dienst für die Verfolgung von Signalen vorbereitet, der es möglich macht, gewisse Signale von Expert Advisors zu abonnieren, denke ich, dass die Kombination von Diensten mit dem Analysator eine sehr mächtige Möglichkeit wäre, eine gute Analyse zu machen und neue Vorteile des ATC zu finden. Tatsächlich können Sie Daten von wenigen verschiedenen Quellen mit dieser Methode ergattern und einen komplexen, darauf basierenden Expert Advisor schreiben.
XML-RPC ATC 2011 Analysator Dienst
Der erste Schritt beim Schreiben eines ATC-Analysators ist die Vorbereitung der Output-Analyse. Gehen wir davon aus, dass wir uns alle Teilnehmer schnappen, deren Eigenkapital auf dem Konto über einem bestimmten Limit liegt, die an ihrer derzeitigen Position interessiert sind und die die Anzahl an Statistiken von Positionen kaufen/verkaufen des Währungspaares, an dem wir interessiert sind, anzeigen.
Ich habe Python als Programmiersprache und die BeautifulSoup-Bibliothek verwendet, um Daten anzufragen und aufzuteilen. Wenn Sie noch nie Python benutzt haben, kann ich nur schwer empfehlen, auf http:/Python.org zu gehen und die Angebote dieser Sprache zu lesen, Sie werden es nicht bereuen.
Wenn Sie stattdessen lieber den Analysator hätten, empfehle ich, Python 2.7.1 installer und setup_tools-Pakete herunterzuladen und beides zu installieren. Öffnen Sie danach die Windows-Konsole (console) und gehen sie auf C:\Python27\Scripts, oder auf den Dateipfad, unter dem Sie Python installiert haben. Dann geben Sie den Befehl 'easy_install BeautifulSoup'. Das wird das BeautifulSoup-Paket installieren.
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>
Danach sollten Sie dazu in der Lage sein, die Python-Konsole laufen zu lassen und den 'import BeautifulSoup'-Befehl ohne Fehler zu geben.
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 >>>
Und lassen Sie den angehängten Analysator mit dem Befehl 'python ContestantParser.py' laufen. Der Analysator ruft die Webseite auf, schlägt einige Schlüsselwörter nach und überträgt den Output auf die Konsole.
import re import urllib from BeautifulSoup import BeautifulSoup class ContestantParser(object): URL_PREFIX = 'https://championship.mql5.com/2011/de/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/de/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
Dies wurde erreicht durch simples Betrachten des HTML-Quellcodes und Finden der Beziehungen zwischen Tags. Wenn dieser Code direkt von der Python-Konsole ausgeführt wird, gibt er die Teilnehmernamen aus, deren Bilanzen und Eigenkapital und alle aktuell offenen Positionen.
Betrachten Sie den Output einer einfachen Anfrage:
* 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 ------------------------------------------------------------ ...
Schaut gut aus, oder? Mit diesen Daten können wir interessante Statistiken sammeln und einen XML-RPC-Dienst implementieren, der sie auf Nachfrage bieten kann. Der XMLRPC-Client von MetaTrader 5 könnte diese Statistiken bei Bedarf abrufen.
Um einen XMLRPC-Server zu machen, habe ich Pythons SimpleXMLRPC Server-Bibliothek verwendet. Der Server zeigt der Außenwelt zwei Methoden: listContestants and getStats. Ersterer zeigt nur die Namen der Teilnehmer, deren Eigenkapital über einem bestimmten Limit liegen, Letzterer zeigt, wie viele von ihnen eine offene Position auf einem bestimmten Währungspaar haben und was das Verhältnis von Kaufen und Verkaufen auf diesen Positionen ist.
Mit dem Signaldienst und/oder Handelskopierer des Artikels könnten sie bestätigen, dass das Setup, mit dem Sie handeln, hat größere Chancen, ein rentables zu sein.
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()
Auf diesen Server kann direkt von der Python-Konsole zugegriffen werden. Wenn Sie dieses Skript laufen lassen, denken Sie daran, dass Sie die IP-Adresse in die ändern müssen, die Ihr System verwendet.
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'] >>>
Wahrscheinlich haben sich die Teilnehmerergebnisse verändert, wenn Sie dies lesen, aber Sie sollten eine ungefähre Idee bekommen haben, was ich erreichen wollte.
Der Aufruf der getStats()-Methode mit dem "eurusd"-param gibt drei Zahlen aus:
In : u.getStats("eurusd") Out: [23, 12, 9]
Die erste ist die Zahl der Teilnehmer, die Eigenkapital über dem Limit haben, die zweite Zahl ist die Anzahl der Teilnehmer mit einer offenen EURUSD-Position und die dritte ist die Anzahl der Teilnehmer, die eine lange EURUSD-Position haben. In diesem Fall haben zwei Drittel der Gewinner lange EURUSD-Positionen, deswegen kann dies als ein Bestätigungssignal dienen wenn Ihr Roboter oder Ihre Indikatoren ein 'open eurusd long'-Signal senden.
MQL5-RPC ATC 2011 Analysator-Client
Es ist Zeit, das MQL5-RPC-System zu benützen, um die Daten mit MetaTrader 5 zu holen. Tatsächlich ist die Verwendung unmittelbar verständlich, wenn man dem Quellcode folgt.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Das passiert, wenn das Skript aufgerufen wird. Die Query wird in XML eingebettet:
<?xml version="1.0" ?> <methodCall> <methodName>listContestants</methodName> <params> <param> <value> <double>20000.00000000</double> </value> </param> </params> </methodCall>
und nach einer Weile erhält der XML-RPC-Server eine Antwort. In diesem Fall habe ich den Linux Host verwendet, der den Python-XML-RPC-Dienst laufen lässt und habe ihn von der Windows VirtualBox-Gastinstallation aufgerufen, die MetaTrader 5 läuft.
<?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>
Das Ergebnis der ersten Anfrage wird mit der toString()-Methode wie folgt dargestellt:
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:
Die zweite Anfrage wird verwendet, um die getStats()-Methode aufzurufen.
<?xml version="1.0" ?> <methodCall> <methodName>getStats</methodName> <params> <param> <value> <string>eurusd</string> </value> </param> </params> </methodCall>
Die XML-RPC-Antwort ist einfach, sie enthält nur drei ganzzahlige Werte.
<?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>
Diesmal bin ich von anderen Gegebenheiten ausgegangen und habe Rückwerte als MQL5-Variablen aufgerufen.
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
Wie Sie sehen können, ist der Output eine einfach verständliche Form.
Fazit
Ich habe hier ein neues MQL5-RPC-System vorgestellt, das MetaTrader 5 dazu befähigt, Remote Procedure Calls mit XML-RPC protocol auszuführen. Zwei Anwendungsfälle wurden hier vorgestellt: erstens Zugang zu einem Webdienst, zweitens ein benutzerdefinierter Analysator für die Automated Trading Championship 2011. Diese zwei Beispiele sollen die Grundlage für weitere Experimente sein.
Ich glaube, MQL5-RPC ist ein sehr mächtiges Werkzeug, das auf vielerlei Weise verwendet werden kann. Ich habe beschlossen, das System Open Source zu machen und es unter GPL Licence auf der Seite http://code.google.com/p/mql5-rpc/ zu veröffentlichen. Wenn jemand seinen Beitrag leisten will, den Code unerschütterlich zu machen, ihn zu überarbeiten oder bugfixes zu machen, ist herzlich eingeladen, sich diesem Projekt anzuschließen. Der Quellcode mit allen Beispielen ist auch als Anhang zu diesem Artikel erhältlich.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/342
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.