一からの取引エキスパートアドバイザーの開発(第17部):Web上のデータにアクセスする(III)
はじめに
前回の「一からの取引エキスパートアドバイザーの開発(第16回):Web上のデータにアクセスする(II)では、Webからのデータ取り込みの問題点とその結果について話しました。また、EAでの使用方法についても検討し、それぞれ長所と短所を持つ3つの解決策について議論しました。
最初の解決策ではEAから直接データを取得するため、サーバのレスポンスの遅さに関連する問題が発生する可能性があると考えました。また、これが取引システムに与える影響についても言及しました。
2つ目の解決策では、EAがクライアント、スクリプトがサーバ、オブジェクトがチャンネルとなる、クライアント・サーバーモデルに基づいたチャンネルを実装しました。このモデルは、時間枠を変えないかぎりよくできていますが、変えることになると不便です。ただし、このシステムが最も優れていると言えます。クライアント・サーバーモデルを採用することで、EAがリモートサーバーを待つことがないからです。情報がどこから来たのかにかかわらずオブジェクトに含まれるデータが読み込まれるだけです。
最後の3つ目の解決策では、クライアント・サーバーシステムをサービス化することで改善しました。そこで、あまり研究されていないMetaTrader 5プラットフォームのリソース、すなわちターミナルのグローバル変数を使い始めました。この解決策により、スクリプトを活用したモデルの最大の欠点であった時間枠の変化の問題が解決されました。しかし、新たな問題が発生しました。ターミナルのグローバル変数のシステムでは、double型しか使用できないということです。多くの人はこれを回避する方法を知らないため、MetaTrader 5が提供するチャンネルを通じて、テキストの一部などさまざまな情報を渡しています。
今回は、この制限を回避する方法について説明しますが、奇跡を期待してはいけません。システムを思い通りに動かすには、多くの作業が必要だからです。
今回は、代用できるシステムの開発に進みます。
1.計画
ご存知のように、MetaTrader 5が提供するチャンネルシステムでは、double型の変数しか使用できません。この型は8バイトで構成されます。あまり有益な情報ではないと思われるかもしれません。しかし、次の瞬間には把握できるとおもいます。
コンピュータのシステムはバイトで動いているのですが、この概念を忘れている人が多いようです。この仕組みを理解することは非常に重要です。1バイトは8ビットで構成されています。1ビットは、計算機システムにおいて可能な限り小さな数字です。この言語で最も小さく単純な型はブール型であり、1つのビットから構成されています。これは基本中の基本です。
つまり、どんなに複雑な情報でも、1バイトに収まるということです。繰り返しになりますが、どんなに複雑な情報でも、8ビットで構成される1バイトの中に収まってしまいます。2バイトを結合すると、システムで最初の複合セットが得られます。この最初のセットをWORD、2番目のセットをDWORD(2WORD)、3番目のセットをQWORD(2 DWORD)と呼びます。これは、すべての現代言語の母体であるアセンブリで使われている命名法なので、ほとんどのシステムで同じ型が使われています。ただ、これらの型は名前の付け方が違うだけです。
ここまでの説明をご理解いただけたでしょうか。これから始められる方のために、以下の図をご覧ください。
上の画像は現在使用できる主な型で、1ビットから64ビットまでをカバーしています。なぜ、このような説明が必要なのかと思われるかもしれません。さまざまな内部プロパティを持つ情報を渡すことができるようにこれらの型を操作するため、この記事全体でおこなうことを理解するには、この情報を知っておくことが重要です。
これらの型はそれぞれ使用する言語によって異なる名前を持つが、MQL5の場合、以下の表に示すようになります。
名前 | バイト数 | アセンブリ言語に基づく名称(上記画像) |
---|---|---|
bool | 1ビットしか使用しない(1バイトで8個のbool値を持つことができる) | 1ビットしか使用しない(1バイトで8個のbool値を持つことができる) |
char | 1 | Byte |
short | 2 | Word |
int | 4 | DWord |
long | 8 | QWord |
この表は符号付き整数を対象としています。MQL5の詳細については、整数型を参照してください。次に、実数型は整数型と似ているところがありますが、独自の内部形式とスタイルを持っています。形式とモデリングの例は、倍精度浮動小数点数で見ることができますが、基本的には下の表と一致することになります。
名前 | バイト数 | アセンブリ言語に基づく名称(上記画像) |
---|---|---|
Float | 4 | DWord |
Doble | 8 | QWord |
面白いのは、浮動小数点型と整数型が同じデータベースを使うが、長さが違うことです。さて、いよいよ本題に入ります。このロジックを理解すれば、最終的には下の画像のような結論に達することができます。
QWORDは8バイトなので、doubleには8バイトの情報を入れることができます。例えば、ターミナルグローバル変数に印字可能な8文字を渡すと、以下のようにサービスとEAとの接続結果を得ることができます。
データはOK、アイデア自体は理解できると思います。大きな特徴は、メッセージが印字可能な8文字以上である場合、より多くの部分に分割されなければならないことです。しかし、非常に高速に、つまり1サイクルで情報を伝達しようとすると、1サイクルで伝達するために必要な数だけグローバルターミナル変数を使用しなければならなくなります。そして、元のメッセージを復元するために接着する必要があります。しかし、パケットで配信できるのであれば、クライアント(この場合はEA)がメッセージを読んで次のブロックを待つことをサービスが知っているように、サーバ用のフォームを作らなければならないでしょう。
この種の問題には、複数の解決策があります。これらの解決策を理解したり、実装したりする場合、すべてを一から作る必要はありません。TCP/IPまたはUDPなどのネットワーク通信プロトコルと同じモデリングを使用し、グローバルターミナル変数を使ってアイデアを情報転送システムに適応させればいいのです。プロトコルの仕組みを理解すれば、この作業はもはや複雑なものではなく、使用する言語に関するスキルと知識の問題になるのです。これは非常に幅広いテーマであり、それぞれの状況や問題に応じて個別に検討する必要があります。
2.実装
これから使うアイデアが理解できたので、サービスとEAの間でどのように情報をやり取りするのか、システムの初期実装をテストしてみましょう。ただし、印字可能な文字のみを渡します。
2.1.基本モデル
前回までのシステムを利用し、ヘッダーファイルから順に修正していきます。その新しい内容は、以下のコードに全文が示されています。
//+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalNameChannel "InnerChannel" //+------------------------------------------------------------------+ union uDataServer { double value; char Info[sizeof(double)]; }; //+------------------------------------------------------------------+
このヘッダーは基本的なものです。グローバルターミナル値の宣言が含まれています。また、共用体という新しい仕組みも持っています。構造体と異なる点は、構造体がインターリーブなしでデータを結合するのに対し、共用体は小さいデータが大きいデータの中にある場合、必ずインターリーブを使用する点です。先ほどの場合、基礎となる値はdoubleで、その内部は8バイトですが、長さsizeofをキャプチャするシステムを使用しているので、将来、より大きなdoubleに出くわした場合(可能性は低いですが)、このコードは自動的にそれに適応します。
その結果、次が得られます。
上の図と似ていますが、これは共用体がやることです。
次に修正するのは、クライアントに対応するEAコードです。コードの全文は下にあります。
#property copyright "Daniel Jose" #property description "Testing internal channel\nvia terminal global variable" #property version "1.04" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { uDataServer loc; if (GlobalVariableCheck(def_GlobalNameChannel)) { GlobalVariableGet(def_GlobalNameChannel, loc.value); Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer))); } } //+------------------------------------------------------------------+
ここでは、CharArrayToString関数を使用して、uchar 配列を文字列に変換しています。しかし、ターミナルのグローバル変数から受け取ることができる唯一の値はdouble値であるため、依然としてdouble値を受け取っています。それに対して、MQL5のstringはC/C++の原則に従っているので、任意の文字を使うことはできず、独自に作成するしかありません。しかし、それはまた別の話です。8バイトの制限を超えるためには、モデリングデータ圧縮を使用するとよいでしょうが、
やはりサーバとして機能するプログラムが必要です。ここでの場合、サーバはサービスです。以下は、このシステムをテストするためのコードです。
//+------------------------------------------------------------------+ #property service #property copyright "Daniel Jose" #property version "1.03" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { uDataServer loc; char car = 33; while (!IsStopped()) { if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel); for (char c0 = 0; c0 < sizeof(uDataServer); c0++) { loc.Info[c0] = car; car = (car >= 127 ? 33 : car + 1); } GlobalVariableSet(def_GlobalNameChannel, loc.value); Sleep(1000); } } //+------------------------------------------------------------------+
これは単純なものですが、非常に効果的で機能的です。
プラットフォームでプログラムを起動することで、次のような結果が得られます。
ばからしく意味がないと思われるかもしれませんが、少し工夫すれば、このシステムを十分に便利にして、他の人が想像もつかないようなことをさせることができるのです。
これを実証するために、システムを改造して、好奇心と興味を喚起するために、非常に単純なものを示してみましょう。このような通信システムには、どのようなとても変わった機能があるか考えてみてください。
2.2.ステッカーの交換
ステッカーの交換は、クライアントとサーバー間の情報交換です。サーバーはクライアントが受け取りたい情報を知っているため、その情報の作成または検索を開始することができます。
概念は至って単純です。しかし、その実装は、特にデータモデリングに関しては、チャンネルをデータ転送に使用しながら8バイトしか利用できない場合、かなり困難なものになる可能性があります。
2.2.1.クライアント・サーバー通信テスト
以下に、サービスのコードの全体をご覧ください。
#property service #property copyright "Daniel Jose" #property version "1.03" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { uDataServer loc, loc1, loc2; char car = 33; while (!IsStopped()) { if (!GlobalVariableCheck(def_GlobalValueInChannel)) { GlobalVariableTemp(def_GlobalValueInChannel); GlobalVariableTemp(def_GlobalMaskInfo); GlobalVariableTemp(def_GlobalPositionInfos); } for (char c0 = 0; c0 < sizeof(uDataServer); c0++) { loc.Info[c0] = car; car = (car >= 127 ? 33 : car + 1); } GlobalVariableSet(def_GlobalValueInChannel, loc.value); GlobalVariableGet(def_GlobalMaskInfo, loc1.value); GlobalVariableGet(def_GlobalPositionInfos, loc2.value); Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), " ",loc2.Position[0], " ", loc2.Position[1]); Sleep(1000); } } //+------------------------------------------------------------------+
新しいサービスコード(サーバとして機能する)の中で、特に興味深い部分に注目してください。変数が1つから3つになりました。クライアント(この場合はEA)とサーバ(サービス)の間の通信を可能にするのに十分な大きさのチャンネルを作る働きをします。以下にご注目ください。
Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), " ",loc2.Position[0], " ", loc2.Position[1]);
クライアントから公開されたデータです。2つの変数を使って、3つの異なる情報を渡していることに注意してください。しかし、どうしてそんなことが可能なのでしょうか。これを理解するためには、以下でヘッダーコードを見る必要があります。全体が記載されています。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalValueInChannel "Inner Channel" #define def_GlobalMaskInfo "Mask Info" #define def_GlobalPositionInfos "Positions Infos" //+------------------------------------------------------------------+ union uDataServer { double value; uint Position[2]; char Info[sizeof(double)]; }; //+------------------------------------------------------------------+
この共用体の中の各変数は、他の変数から分離されていると思われるかもしれません。この記事の冒頭をご覧になることをお勧めします。異なる名前の変数がありますが、ここでは8バイト幅の1つの変数として扱われます。下の画像は、その様子を正確に映し出しています。
このスキームは、uDataServerの中身を表しています。
共用体はプログラミングをする上でとても便利なものなので、もし難しそうに見えたら、実際に共用体を使って実験して、どのような仕組みになっているのかを理解するとよいでしょう。
しかし、システムの話に戻りましょう。次にやるべきことは、クライアントであるEA用のコードを作ることです。以下はコード全体です。
#property copyright "Daniel Jose" #property description "Testing internal channel\nvia terminal global variable" #property version "1.04" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ enum eWhat {DOW_JONES, SP500}; input eWhat user01 = DOW_JONES; //Search //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { uDataServer loc; SetFind(); if (GlobalVariableCheck(def_GlobalValueInChannel)) { GlobalVariableGet(def_GlobalMaskInfo, loc.value); Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer)), " ", GlobalVariableGet(def_GlobalValueInChannel)); } } //+------------------------------------------------------------------+ inline void SetFind(void) { static int b = -1; uDataServer loc1, loc2; if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01)) { b = user01; switch (user01) { case DOW_JONES : StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer)); loc2.Position[0] = 172783; loc2.Position[1] = 173474; break; case SP500 : StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer)); loc2.Position[0] = 175484; loc2.Position[1] = 176156; break; } GlobalVariableSet(def_GlobalMaskInfo, loc1.value); GlobalVariableSet(def_GlobalPositionInfos, loc2.value); } }; //+------------------------------------------------------------------+
このEAでは、情報を送受信する、つまり、サービスのあり方を制御できます。1つの変数には、サービスが探すべきものを示す小さな文字列を渡し、もう1つの変数には2つのアドレスポイントを渡しています。
これに対して、サービスは情報を返します。この最初のポイントを理解するために、下のビデオで結果をご覧ください。
3.1.2.2 - 実用版の作成
システムの仕組みがわかったので、今度は本当に機能するものを作ってみましょう。今回はWebサーバーから情報を収集するため、完璧な理解を得るためにさまざまな工夫が必要です。また、解析にゴミを使っているにもかかわらず、あたかも更新されたデータを受け取っているように錯覚してしまうことがあります。プログラミングの段階では、このリスクにさらされないように細心の注意を払う必要があります。できることは、できる限り多くのテストを追加し、実行中に検出されるかもしれない奇妙な動作をシステムに報告させることです。
覚えておいてください。情報は、あなたがそれを信頼してこそ、あなたの役に立つのです。
まず、ヘッダーファイルを編集して、次のようにします。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalValueInChannel "Inner Channel" #define def_GlobalMaskInfo "Mask Info" #define def_GlobalPositionInfos "Positions Infos" //+------------------------------------------------------------------+ #define def_MSG_FailedConnection "BAD" #define def_MSG_FailedReturn "FAILED" #define def_MSG_FailedMask "ERROR" #define def_MSG_FinishServer "FINISH" //+------------------------------------------------------------------+ union uDataServer { double value; uint Position[2]; char Info[sizeof(double)]; }; //+------------------------------------------------------------------+
ハイライトされた部分は、奇妙な動作を報告するために使用するコードを表しています。最大8文字まで使用できますが、市場が作り出しそうもないシーケンスを作る必要もあります。これは簡単なことではありません。すべてがうまくいっているように見えても、サーバのエラーメッセージとして使用するシーケンスに対応する値が市場で生成されるリスクは常に存在します。とにかく、この目的のためにターミナルのグローバル変数を使用することもできます。そうすれば、可能な組み合わせの数が増えるので、より多くのものを作ることができますが、グローバルターミナル変数はできるだけ使いたくありませんでした。しかし、実際のケースでは、考えた末、おそらくエラーや異常な動作の表示と報告のためだけに変数を使用することになるでしょう。
次は、EAの完全なコードです。
#property copyright "Daniel Jose" #property description "Testing internal channel\nvia terminal global variable" #property version "1.04" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ enum eWhat {DOW_JONES, SP500}; input eWhat user01 = DOW_JONES; //Search //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { ClientServer(); } //+------------------------------------------------------------------+ inline void ClientServer(void) { uDataServer loc1, loc2; string sz0; SetFind(); if (GlobalVariableCheck(def_GlobalValueInChannel)) { GlobalVariableGet(def_GlobalMaskInfo, loc1.value); loc2.value = GlobalVariableGet(def_GlobalValueInChannel); sz0 = CharArrayToString(loc2.Info, 0, sizeof(uDataServer)); if (sz0 == def_MSG_FailedConnection) Print("Failed in connection."); else if (sz0 == def_MSG_FailedReturn) Print("Error in Server Web."); else if (sz0 == def_MSG_FailedMask) Print("Bad Mask or position."); else if (sz0 == def_MSG_FinishServer) Print("Service Stop."); else Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), " ", loc2.value); } } //+------------------------------------------------------------------+ inline void SetFind(void) { static int b = -1; uDataServer loc1, loc2; if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01)) { b = user01; switch (user01) { case DOW_JONES : StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer)); loc2.Position[0] = 172783; loc2.Position[1] = 173474; break; case SP500 : StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer)); loc2.Position[0] = 175487; loc2.Position[1] = 176159; break; } GlobalVariableSet(def_GlobalMaskInfo, loc1.value); GlobalVariableSet(def_GlobalPositionInfos, loc2.value); } }; //+------------------------------------------------------------------+
ハイライトされた行はとても重要で、何が起こっているのか本当に知りたいので、よく考えて作られるべきです。このように、ヘッダーファイルで作成されたシーケンスよりも詳細な情報をユーザーに伝えることができるため、プログラミングや解決策の維持が容易になります。それ以外のコードはあまり変わっていません。以下のサービスコードをご覧ください。
#property service #property copyright "Daniel Jose" #property version "1.03" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { uDataServer loc1, loc2; while (!IsStopped()) { if (!GlobalVariableCheck(def_GlobalValueInChannel)) { GlobalVariableTemp(def_GlobalValueInChannel); GlobalVariableTemp(def_GlobalMaskInfo); GlobalVariableTemp(def_GlobalPositionInfos); } GlobalVariableGet(def_GlobalMaskInfo, loc1.value); GlobalVariableGet(def_GlobalPositionInfos, loc2.value); if (!_StopFlag) { GlobalVariableSet(def_GlobalValueInChannel, GetDataURL( "https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), loc2.Position[0], loc2.Position[1], 0x0D ) ); Sleep(1000); } } GlobalVariableSet(def_GlobalValueInChannel, Codification(def_MSG_FinishServer)); } //+------------------------------------------------------------------+ double GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit) { string headers, szInfo = ""; char post[], charResultPage[]; int counter; if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return Codification(def_MSG_FailedConnection); for (int c0 = 0, c1 = StringLen(szTest); (c0 < c1) && (!_StopFlag); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return Codification(def_MSG_FailedReturn); for (int c0 = 0, c1 = StringLen(szFind); (c0 < c1) && (!_StopFlag); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return Codification(def_MSG_FailedMask); if (_StopFlag) return Codification(def_MSG_FinishServer); for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++); for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]); return StringToDouble(szInfo); } //+------------------------------------------------------------------+ inline double Codification(const string arg) { uDataServer loc; StringToCharArray(arg, loc.Info, 0, sizeof(uDataServer)); return loc.value; } //+------------------------------------------------------------------+
ハイライトされた行はサービスがもう実行されていないことを警告するもので、重要です。
このシステムを実行すると、次のような結果が得られます。
結論
MetaTrader 5プラットフォームでWebデータを調査、検索、利用することに関連する考え方を説明できたと思います。特にプログラミングの知識があまりない方は、最初はよくわからないかもしれませんが、時間をかければ、鍛錬と学習によっていずれはこの教材のほとんどをマスターすることができるようになると思います。ここでは、私が知っていることを少しだけでもお伝えしようと思いました。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10447
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索