MQL5-RPC. MQL5からのリモートプロシージャコール:ウェブサービスアクセスと、利益のためのXML-RPC ATC アナライザー
investeo | 28 10月, 2015
はじめに
この記事は、私が先週開発したMQL5-RPCを紹介します。XML-RPC アクセス基礎、MQL5実装方法、二つの実際のMQL5-RPC使用例を扱っています。一つ目は、外部のFOREXウェブサイトのサービスでのリモートプロシージャコールで、二つ目は、Automated Trading Championship 2011から集められた結果の解析・分析・提供のために使用されるXML-RPCサーバー のクライアントになります。もしATC 2011からの異なる統計を実装、分析方法に興味がある際は、この記事をご覧ください。
XML-RPC 基礎
XML-RPC 基礎から始めましょう。XML-RPCは XML リモートプロシージャコールを意味しています。これは、外部のメソッドを呼び出すために渡されるパラメーターのエンコード・デコードするためのXMLを使用するネットワークプロトコルです。それは、データ交換の移送システムとして、HTTPプロトコルを使用します。外部のメソッドとは、その他のコンピューターのプログラムや、リモートプロシージャを公開するウェブサービスのことを意味します。
公開されたメソッドは、XML-RPCプロトコルスタックを使用し、サーバーへのネットワークアクセスを所有する場合に限り、ネットワークに接続されたどの機器からでも、その他のコンピューターにより呼び出されます。これは、XML-RPCがその他のプログラミング言語で記述された同じ機器のメソッドを呼び出すためにも使用できることを意味します。これについては、記事の二部で紹介します。
XML-RPC データモデル
XML-RPCの仕様では、6つの基礎的なデータ型を使用します:int、double、boolean、string、datetime、base64と二つの複合データ型:array、strcutです。Arrayは、すべての基礎的な要素で構成され、structは、連想配列やオブジェクト属性などのネーム・バリュー型のペアを提供します。
XML-RPCでの基礎データ型 | ||
---|---|---|
種類 | 値 | 例 |
intかi4 | - 2,147,483,648と2,147,483,647間での32-bitインテジャー | <int>11<int> <i4>12345<i4> |
double | 64-bit 浮動小数点数 | <double>30.02354</double> <double>-1.53525</double> |
Boolean | true (1)、もしくは、 false (0) | <boolean>1</boolean> <boolean>0</boolean> |
string | ASCII テキスト、多くの実装がUnicodeをサポートしています。 | <string>Hello</string> <string>MQL5</string> |
dateTime.iso8601 | ISO8601フォーマットでのDate: CCYYMMDDTHH:MM:SS | <dateTime.iso8601> 20111125T02:20:04 </dateTime.iso8601> <dateTime.iso8601> 20101104T17:27:30 </dateTime.iso8601> |
base64 | RFC 2045にて定義されたように、エンコードされるバイナリ型情報 | <base64> TDVsbG8sIFdvdwxkIE== </base64> |
図表1. XML-RPCでの基礎データ型
配列は、同じ型のものでなくても、いかなる基礎的なデータがを保持することができます。配列の要素は、必ず値要素の中に組み込まれている必要が有ります。それは、一つのデータ要素と、一つかそれ以上の値要素をデータ要素中に含みます。以下の例は、4つのインテジャーの配列を示しています。
<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>
二つ目の例は、5つのString型の値の配列を示しています。
<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>
XML-RPC配列を作成するための二つの例の中に類似点を見つけることができると思います。
Structは、値要素の中にStrcut要素を持ち、Struct要素の中にメンバーセクションを持ちます。個々のメンバーは、名前や値からなります。従って、連想配列の値やオブジェクトのメンバーをStructを使用し渡しやすいのです。
以下の例をご覧ください。
<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データモデルを知った後は、次にリクエスト・レスポンス構造に移ります。これは、MQL5でのXML-RPCの実装のための基礎を形成します。
XML-RPCリクエスト構造
XML-RPCリクエストは、メッセージヘッダーとメッセージメッセージペイロードの二つからなります。メッセージヘッダーは、HTTP送信メソッド(POST)、XML-RPCへの相対パス、HTTPプロトコルバージョン、ユーザーエージェント名、ホストIPアドレス、コンテンツ種類(text/xml)、バイト値におけるコンテンツ量を明記しています。
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リクエストのペイロードは、XMLドキュメントです。XMLツリーのルートエレメントは、methodCallと呼ばれます。methodCallは、methodName要素を持ち、その内容は実行されたメソッド名です。MethodName要素は、0か1のパラメーター要素を持ちます。
そのパラメーターは、1かそれ以上の値、配列、Struct要素を持っています。すべての値は、データ型に従ってエンコードされます。関数に渡す二つのDouble型の値を持つ「multiply」メソッド実行リクエストを示す以下のペイロード例をご覧ください。
<methodCall> <methodName>multiply</methodName> <params> <param> <value><double>8654.41</double></value> </param> <param> <value><double>7234.00</double></value> </param> </params> </methodCall>
そのヘッダーとペイロードは、HTTPを通して、入力を受け入れるサーバーに送信されます。もしサーバーが使用可能であれば、メソッド名とパラメーターリストをチェックし、希望のメソッドを実行します。処理を終了した後は、クライアントにより読み込まれるXML-RPCレスポンス構造を準備してください。
XML-RPCレスポンス構造
XML-RPC リクエストと類似し、XML-RPC レスポンスは、ヘッダーとペイロードから成り立ちます。ヘッダーは、テキストであり、ペイロードはXMLドキュメントです。もしリクエストが正しければ、ヘッダーの最初の行は、そのサーバが見つかったことを知らせ(200コード)、プロコルのバージョンを明記します。そのヘッダーは、Content-Type text/xmlと、バイトでのペイロードの量を示すContent-Lengthからなります。
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
そのレスポンスのペイロードは、XMLドキュメントです。XMLツリーのルート要素は、methodResponseと呼ばれなければなりません。methodResponse要素は、成功hした要素か、失敗した要素における一つのパラメーターを含んでいます。そのパラムは、正しく一つのパラメータ要素を持ち、そのパラメーター要素は、一つの値要素を所有します。
成功したレスポンスの例は以下に記載されています。
<methodResponse> <params> <param> <value><double>62606001.94</double></value> </param> </params> </methodResponse>
もしXML-RPCリクエストの処理に問題があれば、失敗用レスポンスが準備されます。
パラメーターの要素のような欠陥の要素は、シングルアウトプット値を持ちます。
<methodResponse> <fault> <value><string>No such method!</string></value> </fault> </methodResponse>
XML-RPCはエラーコードを標準化しないため、エラーメッセージが実装に依存しています。
MQL5-RPCの導入
Alex Sergeevによる二つの記事"Using WinInet.dll for Data Exchange between Terminals via the Internet"と"Using WinInet in MQL5Part 2: POST Requests and Files" を見つけ、MetaTrader5にXML-RPCクライアントを実装できることに気づきました。明細事項を確認補、自分のものを1から実装しました。これは、継続中のプロジェクトであり、まだすべての項目を扱っていません。(base64のサポートは近いうちに追加されます。)しかし、MetaTrader5からのXML-RPCの呼び出しのサブセットの作成のために使用できます。
MQL5-RPCデータモデル
実装における最も困難な部分は、MQL5における正しいデータモデルを特定することでした。フレームワーク使用者にとって最もシンプルである必要があると考え、その機能をカプセル化するいくつかのクラスを実装しました。最初の決定は、CObject* ポインターとしてリクエストパラメーターを作成することです。このポインターは、CObjectクラスから派生した配列へのポインター要素を保持しています。
CObject配列を保持する標準クラスがあります; CArrayInt、CArrayDouble, CArrayStringです。それに基づき、CArrayBool、CArrayDatetimeを完全な基礎的データタイプ、CArrayMqlRatesに実装し、structの配列を追加します。base64型のみ現在ありませんが、近いうちにサポートされます。配列がただ一つの要素のみ保持している場合、シングル値要素としてXMLにカプセル化されています。
異なる配列のCObject*配列への追加方法についての例を喜寿sつし、異なる型の配列を表示しています。以下に示されています。
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
結果は明確です:6のサブ配列があります;インテジャー値、Double値、Strings、Datetime、Boolean、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
その他のデータ型の配列をどのように実装したか興味があるかもしれません。CArrayBoolとCArrayDatetimeでは、単純にCArrayIntに基づいていますが、CArrayMqlRatesでは、少々異なります、というのも、ストラクチャー型は、リファレンスとして渡される必要があり、TYPE_MQLRATESがないためです。
以下に、CArrayMqlRatesクラスのソースコードの一部があります。その他のクラスはこの記事に添付されています。
//+------------------------------------------------------------------+ //| 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); } ...
MQL5データは、すべてXML値に、RCリクエストとして送信する前に変換されなければならず、そのため、値を取得し、XMLStringにエンコードしてくれるCXMLRPCEncoderヘルパークラスを設計しました。
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); };
以下に実装されたメソッド3つを貼り付けています。すべて一つのパラメーター(bool、String、Datetime)を取得し、XML-RPCプロトコルに有効なXMLデータ型のStringとして返します。
//+------------------------------------------------------------------+ //| 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(); }
「タグ開始」を意味する接尾辞_Bの特定のタグと、「タグ終了」を意味する_Eという接尾辞があることに気づくでしょう。
実装をより分かりやすくできるため、XMLタグの名前とヘッダーを保持するヘッダーファイルを使用することに決めました。
//+------------------------------------------------------------------+ //| 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 "" #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のデータモデルを定義し終えれば、完全なXML-RPCリクエストの作成に進むことができます。
MQL5-RPCリクエスト
以前述べられた通り、XML-RPCリクエストは、リクエストヘッダーとXMLペイロードから成り立ちます。MQL5データ配列からクエリオブジェクトを自動的に構成するCXMLRPCQueryを設計しました。このクラスは、XMLにデータをカプセル化し、メソッド名を追加するためCXMLRPCEncodeを使用します。
class CXMLRPCQuery { private: CString s_query; void addValueElement(bool start,bool array); public: CXMLRPCQuery() {}; CXMLRPCQuery(string method="",CArrayObj *param_array=NULL); string toString(); };
このクラスのコンストラクタは、二つのパラメータを持ちます;メソッド名とメソッドを呼ぶためのパラメーターを保持するCArrayObjへのポインターです。すべてのパラメーターは、以前のセクションにて記載された通りXMLにて保護されており、クエリヘッダーが追加されます。XMLクエリは、toString()メソッドを使用して表示されます。
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); }
以下のクエリテストの例をご覧ください。複雑なメソッドを呼ぶことができることを示したいので、これは最もシンプルなものではありません。
入力パラメーターは: double値、 integer値、 string bool値、datetime 値の配列、MqlRates struct型配列などです。
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
結果として生じるクエリは以下です。普通は、これは1行のString値ですが、XML-RPCセクションとシングル値を区別しやすいようにタブ化されたテキストとして表示しています。
<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>
これは、開発されたXMLツリーであり、そのプロトコルは、より複雑なメソッドを呼ぶこともできるほど柔軟です。
MQL5-RPC レスポンス
XML-RPCリクエストと同様、XML-RPCレスポンスは、ヘッダーとXMLペイロードで構成されています。しかし、今回は処理順序は逆である必要があり、つまり、XML-RPCレスポンスは、MQL5データに転換される必要があります。CXMLRPCResultクラスは、このタスクを処理するよう設計されました。
結果は、Stringとして受け取られ、それは、クラスコンストラクターに渡されるか、自動的にCXMLServerProxy classクラスから希望のメソッドが実行された後に取得されます。その結果が前もって知られていないStructを不空場合、parseXMLResponseRAW()メソッドは、すべての<value>タグを探し、すべての見つけられた値要素を含むCArrayObjポインターを返します。
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(); };
このクラスのコンストラクターは、XMLレスポンスのすべてのヘッダーを探索し、XMLをMQL5データに変換することを行うparseXMLValuesToMQLArray()プライベートメソッドを呼び出します。
パラメーターが配列かシングル要素か認識し、 CArrayObj配列結果に追加された適切な配列を格納します。
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; }
その変換は、parseXMLValuesToMQLArrayメソッド内にて行われます。そのString値は、XMLから抽出され、基礎MQL5変数に転換されます。
変換に使用されるその3つのメソッドは、以下に参考として載せてあります。
//+------------------------------------------------------------------+ //| 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; }
ご覧の通り、String値は、XML-RPCタグからCStringクラスにて使用可能なメソッドを用い抽出されます; Assign()、Mid()、Find()などです。そして、 StringToTime(), StringToInteger()、StringToDouble()か、Book変数の場合、カスタムメソッドを用い、MQL5に変換されます。
すべての値が解析されれば、データは、toString()メソッドにより表示されます。値を分けるため、コロンを用いています。
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の結果パラメーターがMQL5にいかに転換されているのかを示すためにテスト結果を実装しました。
//+------------------------------------------------------------------+ //| 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 = "" "<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; } //+------------------------------------------------------------------+
以下に結果を見ることができます;
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:
これは、XMLより変換された異なるMQL5値の表示された配列です。ご覧の通り、シングルCArrayObjポインターからアクセスできるstrings, double 値、 boolean値、integer値、 datetime値があります。
MQL5-RPC プロキシクラス
CXMLRPCServer プロキシは、HTTPコミュニケーションのためのコアクラスです。"Using WinInet in MQL5. Part 2: POST Requests and Files"(MQL5でWinlnetを使用する パート2:POSTリクエストとファイル)という記事を使用し、HTTPの機能を実装し、XML-RPCの明記事項と一致するカスタムヘッダーを追加しました。
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."; }
CXMLRPCQueryオブジェクトは、 CXMLRPCServerProxy execute()メソッドに渡され、XML-RPCの呼び出しを行う必要があります。
そのメソッドは、XML−RPCコールを呼び出したスクリプトにて使用されるCXMLRPCResultオブジェクへのポインターを返します。
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; }
例 1 - ウェブサービスへのアクセス
MQL5-RPCの最初の例は、外部ウェブサービスの呼び出しです。見つけた例は、現在の通貨交換レートを使用し、ある特定の量の通貨を別の通貨に変換します。メソッドパラメーターの正確な明記事項はオンラインで得ることができます。
そのウェブサービスは3つのパラメーター、二つのString型、Float値を取得するfoxrate.currencyConvertメソッドを公開しています。
- currency (eg: USD) = stringから;
- currency (eg: GBP) = stringへ;
- (eg:100.0) = float:変換量.
その実装は、かなり短く、数行ですみます。
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
そのメソッドは複雑なStrcut型を返しますので、parseXMLResponseRAW()メソッドを使用し結果を解析します。
この例でのXML-RPCクエリは以下のようになります。
<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>
そして、そのレスポンスは、XMLのStruct型で返されます。
<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()からのアウトプットは、3つの値がresult:0-no errorにて使用可能であり、二つ目の値は、外貨交換に必要な通貨の量であり、三番目のパラメーターは、最新の交換比率の時間です。
0:15773.00000000:cached:
より面白い使用例に進みましょう。
例2 - XML-RPC ATC 2011アナライザー
Automated Trading Championship 2011から統計を取得し、MetaTrader 5ターミナルの情報を使用したいとしましょう。それを実行する際は、こちらをお読みください。
MetaQuotes Software Corp. は、特定のエキスパートアドバイザーからのシグナルを登録できるシグナルフォローサービスを準備しているため、そのサービスとアナライザーを組み合わせると、より洗練された分析を実行し、ATCから利益を得る新しい方法を提供すると思います。実際、このメソッドを用い、異なるソースからデータを取得し、複雑なエキスパートアドバイザーを作成できます。
XML-RPC ATC 2011 アナライザーサーバー
ATC アナライザーサーバーを作成する最初のステップは、アウトプットの分析の準備です。資産を保有し、特定の出発点よりも上にあり、ポジションに興味のあるすべての参加者を集めたいとし、枯れたはすべて現在、現在の通貨ペアの買いと売りのポジションの統計の量を表示しているとします。
PythonとBeautifulSoup ライブラリを、データを取得し解析するために使用しました。Pythonを以前使用したことのない場合は、http:/Python.org にアクセスし、この言語がどのようなものか読んでおくと後悔しません。
アナライザーにいち早く手をつけたい場合は、Python 2.7.1 installerとsetup_toolsパッケージをインストールしてください。その後、Windousコンソールを稼働し、ディレクトリをC:\Python27\Scriptsか、Pythonをインストールしたフォルダーに変えてください。'easy_install BeautifulSoup' コマンドを売ってください。BeautifulSoupパッケージをインストールしてください;
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>
そのあと、Pythonコンソールを稼働することができ、「import BeautifulSoup」コマンドをエラーなしで発行できます。
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 >>>
そして、添付されたアナライザーを「python ContestantParser.py」コマンドとともに稼働してください。そのアナライザーは、ウェブサイトへ行き、いくつかのキーワードを探し、コンソールに結果を出力します。
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
これは、HTMLソースコードを見て、タグの関係を見つけることで達成されます。もしこのコードがPythonコンソールから直接実行される場合、定数名、バランス、資産、現在のオープンポジションなどを出力します。
サンプルクエリのアウトプットを見てください:
* 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 ------------------------------------------------------------ ...
よくないですか?そのデータで、興味深い統計を得ることができ、XML-RPCサービスを実装できます。MetaTrader 5からXMLRPC は必要な時にこれらの統計を取得してくることができます。
XMLRPCサーバーを作成するために、pythonのSimpleXMLRPCサーバーライブラリを使用しました。そのサーバーは、外部に二つのメソッドを公開しています;listCOntenstantsとgetStatsです。前者が特定のスタート地点よりも上の資産を持つ定数名を表示し、後者が特定の通貨ペアにていくつのオープンポジションを持ち、これらのポジションにて売りと買いの比率を表示します。
記事のシグナルサービルやトレードコピーと同様、セットアップはより利益のあるものになる機会があります。
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()
このサーバーは直接Pythonコンソールからアクセスされます。このスクリプトを実行する際は、システムが使用しているものにIPアドレスを変更してください。
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'] >>>
これを読んでいる際に、定数の結果が完全に変わるかもしれませんが、どのように実行しようとしているかは理解してください。
getStats()メソッドを「eurusd」パラメーターで呼び出すと、3つの数字を返します:
In : u.getStats("eurusd") Out: [23, 12, 9]
一番目は、特定の開始地点よりも上の資産を持つ参加者の数であり、二番目は、EURUSDポジションを持つ参加者数、三番目は、eurusdポジションを持つ参加者の数です。この場合、勝利している参加者の3分の2は、EURUSDをオープンしており、「open eurusd long」シグナルをインジケーターやシステムが生成した際、これは確認シグナルとして稼働します。
MQL5-RPC ATC 2011 Analyzer クライアント
MQL5-RPCフレームワークを使用し、MetaTrader5の需要に基づきデータを取得します。実際、その使用方法は、以下のソースコードをたどると明白です。
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
これがそのスクリプトが呼ばれた際に発生するものです。このクエリは、XMLに内包されます。
<methodCall> <methodName>listContestants</methodName> <params> <param> <value> <double>20000.00000000</double> </value> </param> </params> </methodCall>
そして、レスポンスは、XML-RPCサーバーから取得されます。私の場合、Python XML-RPCサービスを稼働するLinuxホストを使用し、Windows VirtualBoxゲストインストールから呼び出しています。
<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メソッドを用いて表示された最初のクエリの結果は以下です;
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:
二番目のクエリは、getStats()メソッドを呼ぶために使用されます。
<methodCall> <methodName>getStats</methodName> <params> <param> <value> <string>eurusd</string> </value> </param> </params> </methodCall>
XML-RPCレスポンスはシンプルで、3つのInteger値のみを持ちます。
<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>
今回は、別のアプローチを取り、MQL5変数として値を返しました。
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
ご覧の通り、アウトプットは、簡単に理解できる形式です。
結論
XML-RPC プロトコルを使用し、MetatTrader5がリモートプロシージャーコールを行えるようにするMQL5-RPCフレームワークを紹介しました。二つの使用ケースはが紹介され、一番目は、ウェブサービスへのアクセス、二つ目は、Automated Trading Championship 2011.のためのカスタムアナライザーです。これら二つの例は、さらなる実験の基礎となります。
MQL5-RPCはとても強力な木のうで、様々な方法で使用できます。オープンソースのフレームワークを作成することとし、GPL licence at http://code.google.com/p/mql5-rpc/のもと公開しました。もしどなたかコードバレット証明を作成し、リファクタリングし、バグ修正したければ、そのプロジェクトの参加への扉は開かれています。この例のソースコードは、記事に添付されています。