English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
preview
一からの取引エキスパートアドバイザーの開発(第16部):Web上のデータにアクセスする(II)

一からの取引エキスパートアドバイザーの開発(第16部):Web上のデータにアクセスする(II)

MetaTrader 5統合 | 24 8月 2022, 08:27
166 0
Daniel Jose
Daniel Jose

はじめに

前回の「一からの取引エキスパートアドバイザーの開発(第15回):Web上のデータにアクセスする(I)」稿では、MetaTrader 5プラットフォームを使って、専門のWebサイトからマークデータにアクセスする方法の全ロジックと考え方を紹介しました。

その中で、これらのサイトにアクセスしてプラットフォームで利用するために、どのように情報を探し出し、取得するかを考えました。ただし、これで全部ではありません。単にデータを取り込むだけではあまり意味がないのです。最も興味深いのは、プラットフォームからこのデータを取り出し、EAで使用する方法を学ぶことです。この方法はあまり明白ではないので、MetaTrader 5で利用可能なすべての機能を知り、理解しなければ、実装することは困難です。


計画と実装

前回の記事をまだお読みになっていない方は、ぜひお読みになって、そこにあるすべての概念を理解されることをお勧めします。なぜなら、今回もこのテーマを続けるからです。膨大な数のことを研究し、一連の問題を解決し、最終的には美しい解決策にたどり着きます。なぜなら、まだ探求されていない方法でMetaTrader 5を使用するからです。というのも、このプラットフォームに存在するいくつかの機能を使用するためのリンクを見つけるのが難しかったからです。ここではそのリソースの1つを使用する方法を説明しようと思います。

では、準備をして、仕事に取りかかりましょう。


1.EAによるインターネットデータへのアクセス

これが、このシステムで実装できる最も面白い部分です。単純なことではありますが、下手をすると圧倒的に危険です。たとえ一瞬だとしてもEAがサーバからの応答を待つことになるために、危険なのです。

ロジックを下図に示します。

EAが、取得したい情報を含むWebサーバと直接やりとりする方法を見てみましょう。下にあるのは、このように動作する完全なコード例です。

#property copyright "Daniel Jose"
#property version "1.00"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
}
//+------------------------------------------------------------------+
string 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) == -1return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+


よく見ると、コードは前回の記事で作成したものと全く同じですが、コードはEAの一部になったのでそのために適応されています。つまり、すでにそこで動作しているものは、ここでも動作します。違いは、EAに、コードが1秒ごとに実行されるという新しい条件が含まれていることです。つまり、EAは1秒ごとに目的のWebサーバーにリクエストをおこない、応答を待ちます。そして、取り込んだデータを提供し、他の内部関数に戻します。このループは、EAのライフタイムを通じて繰り返されます。実行の結果は以下の通りです。


これはまさにこの方法でおこなわれますが、EAが少しの間でもサーバの応答待ちで止まってしまい、プラットフォームの取引システムやEA自体が危険にさらされる可能性があるため、この方法はお勧めしません。その一方、メソッドを学ぶことに興味があれば、このシステムを通じて多くのことを学ぶことができます。

ただし、インターネットとプラットフォーム間の情報をルーティングするローカルサーバがあれば、おそらくこの方法で十分でしょう。この場合、システムがリクエストを出すと、ローカルサーバはまだ何も情報を持っていないのですぐに応答し、次のステップを省くことができます。

次に、このような作業をもう少し安全に行うための別の方法を考えてみましょう。MetaTrader 5のスレッドシステムを使用して、少なくともある程度のセキュリティを実現し、EAがリモートWebサーバの条件に左右されるのを防ぐので、リモートサーバの応答を少しの間待つことができます。Webから情報を収集できるなど、EAが現状を把握するための条件を追加していきます。


2.通信チャネルの構築

オンラインで収集したデータをEAで利用するための、より良い、よりシンプルな方法がチャンネルです。機能的には問題ないのですが、そのようなチャンネルの使用には制限があるため、あまり向いていない場合もあります。ただし、少なくともEAはリモートサーバからの応答を待つことなく、Web上で収集した情報にアクセスすることができるようになります。

問題を解決する最も簡単な方法は、データをダウンロードしてMetaTrader 5プラットフォームに提供するローカルサーバを作成する「データルーティング」であることは既に述べたとおりですが、これには一定の知識と計算能力が必要で、ほとんどすべての場合に複雑になってしまいます。ただし、MetaTrader 5の機能を使えば、非常によく似たチャンネルを作成することができ、ローカルサーバを経由するよりもはるかにシンプルになります。

下図は、そのようなチャンネルをどのように推進していくかを示しています。

作成にはオブジェクトを使用します。EAは、スクリプトが配置した情報をオブジェクトの内部で確認します。この仕組みを理解するために、以下に3つの完全なコードを見てみましょう。1つはEA、もう1つはオブジェクトを含むヘッダ、3つ目はスクリプトになります。

#property copyright "Daniel Jose"
#property description "Testing Inner Channel"
#property version "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetInfoInnerChannel());
}
//+------------------------------------------------------------------+


次のコードは、使用するヘッダです。なお、ここでのオブジェクトは、EAとスクリプトの間で共有されるように宣言されています。

//+------------------------------------------------------------------+
//|                                          Canal Intra Process.mqh |
//|                                                      Daniel Jose |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_NameObjectChannel   "Inner Channel Info WEB"
//+------------------------------------------------------------------+
void CreateInnerChannel(void)
{
        long id;
        
        ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE);
}
//+------------------------------------------------------------------+
void RemoveInnerChannel(void)
{
        ObjectDelete(ChartID(), def_NameObjectChannel);
}
//+------------------------------------------------------------------+
inline void SetInfoInnerChannel(string szArg)
{
        ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg);
}
//+------------------------------------------------------------------+
inline string GetInfoInnerChannel(void)
{
        return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT);
}
//+------------------------------------------------------------------+


そして最後に、次がスクリプトです。これはローカルサーバの作成に代わるもので、実際にサーバの仕事をすることになります。

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        CreateInnerChannel();
        while (!IsStopped())
        {
                SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
                Sleep(200);
        }
        RemoveInnerChannel();
}
//+------------------------------------------------------------------+
string 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 "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++);
        for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return (_StopFlag ? "" : szInfo);
}
//+------------------------------------------------------------------+


ここには、EAが見ることができる、スクリプトによって作成されたオブジェクトがあります。EAとスクリプトを同じチャートに共存させ、このオブジェクトがEAとスクリプトの通信チャンネルになるという考え方です。EAがクライアント、スクリプトがサーバ、オブジェクトがその間の通信路となるのです。スクリプトは、リモートWebサーバー上の値をキャプチャして目的の値をオブジェクトに入れることになります。EAは、オブジェクトにどんな値があるか(オブジェクトが存在する場合)随時確認します。スクリプトが実行されていない場合、オブジェクトは利用できないはずだからです。とにかく、EAがオブジェクトの値を見る時間は、何らスクリプトに違反するものではありません。リモートサーバからの応答待ちのためにスクリプトがブロックされても、EAはスクリプトの動作に関係なく動作を継続するので、影響はありません。

これは素晴らしい解決策ですが、完璧ではありません。問題はスクリプトにあります。

このことを理解するために、次のビデオを細部まで注意してご覧ください。


すべてにおいて素晴らしい出来栄えです。このような解決策は、クライアント・サーバ型のプログラムを開発する際に一方が他方をブロックしないようにするためにプログラミングで広く使われているため、予想されたことでした。つまり、プロセス間の通信にチャンネルを使用するのです。しばしば、同じ環境にある場合、チャンネルはメモリを使用して作成されます。このために特別に隔離された領域が割り当てられますが、これはクライアントとサーバの両方から見えるように共有されます。サーバはそこにデータを追加し、クライアントは同じ領域にアクセスして既存のデータを取得します。つまり、一方が他方に依存するのではなく、両者はつながっているのです。

アイデアは、同じ原理を使用することです。ただし、このスクリプトが動作する方法によって問題が発生します。時間枠を切り替えるとスクリプトが閉じ、無限ループを使用してもMetaTrader 5によって閉じられるということです。そのため、スクリプトを再初期化して、チャートに戻すことになります。常に時間枠を切り替える必要がある場合、これは問題になります。言うまでもなく、毎回チャート上に戻ってスクリプトを起動する必要があります。

さらに、スクリプトがチャート上にあるかどうかを確認するのを忘れてしまい、EAのコード化によって、スクリプトがチャート上にあるかどうかがわからなくなってしまい、間違った情報を使ってしまうことになるかもしれません。これは、スクリプトがチャート上にあるかどうかを確認することで解決できます。この作業は難しくありません。オブジェクトに最後のスクリプト公開時刻のチェックを書き込むだけです。そうすれば、問題は解決します。

ただし、(少なくとも一部の場合には)もっと良い解決策を作ることは可能で、正直なところ、これがほぼ理想的な解決策です。上で紹介したのと同じ概念で、ただスクリプトの代わりにサービスを使うことにしましょう。


3.サービスの作成

これは極端な解決策ですが、時間枠が変わるたびにスクリプトが終了してしまうという問題があるため、別の方法を使う必要があります。ただし、ある問題を解決することで、別の問題を生み出してしまうのです。とにかく、どのような解決策が可能で、どのような使い方ができるかを知っておくとよいでしょう。一番重要なのは、それぞれの解決策が示す限界を知り、可能な限り最善の方法で問題を解決できるような中間的なものを見つけようとすることです。

プログラミングというのは、1つの問題を解決しようとすると、新たな問題を生み出してしまうものです。

目指すのは、下図のようなものです。

これは簡単なことのように思えるかもしれませんが、関係するリソースは一般的にほとんど探求されません。そこで、これらのリソースを使った仕事についてもっと知りたいと思う読者のために、詳細に触れてみようと思います。


3.1.グローバル変数へのアクセス

この部分はほとんど勉強しておらず、最初はこの機能をサポートするためだけにDLLを作ろうかと思ったくらいですが、MQL5のドキュメントを調べたら、ありました。問題は、サービスとEAとの共通ポイントにアクセスするか作成する必要があることです。スクリプトを使っていたときは、このポイントはオブジェクトだったのですが、サービスを使うとなると、同じことはできません。解決策としては外部変数を使えばいいのですが、それをやろうとすると、期待したようなパフォーマンスが得られません。詳しくは、外部変数に関連するドキュメントをご覧ください。どうすればいいかが解説されています。

そこで、この発想はまずいということで、DLLを使うことにしました。しかし、やはりMetaTrader 5とMQL5を学びたいと思ってターミナルを覗いてみると、下の画像のようなものがありました。

         

これが私たちが探していたものです。この手順をどのように構成できるかを確認できる変数を追加しました。ただし、double値のみを使用できます。これが問題だと思うかもしれませんが (これは実際には制限です)、ここでの場合のように短いメッセージを送信したい場合はこれで十分です。実は、double型は8文字の短い文字列なので、プログラム間で値や短いメッセージを伝達することができます。

問題の第一部は解決しました。MetaTrader 5は、DLLを作成することなくチャンネルを作成する方法を備えていますが、今度は別の問題が発生しました。プログラムを通じてどうやってこれらの変数にアクセスするかということです。EA、スクリプト、指標、サービスなど、プログラム内部でグローバル変数を作成することは可能でしょうか。それとも、ターミナルで宣言されたものだけを使うべきなのでしょうか。

これらの質問は、この解決策を本当に使いたいのであれば、非常に重要なことです。プログラムで使えないとすれば、DLLを使うしかありませんが、使えます。詳しくは、「ターミナルのグローバル変数」をご覧ください。


3.2.ターミナル変数を使った情報交換

さて、基本的なことを考えたところで、ターミナル変数を使った処理が実際にどのように機能するかをテストして理解するために、簡単なものを作ってみましょう。

この目的で、次のコードを作成しました。まず、ヘッダファイルです。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+

ここでは、ただグラフィカルターミナル上で実行される2つのプロセスで同じになるグローバルターミナル変数の名前を定義しています。

以下は、サービスを実行することを表すコードです。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        double count = 0;
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                GlobalVariableSet(def_GlobalNameChannel, count);
                count += 1.0;
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+


その動作は簡単で、変数がすでに宣言されているかどうかとGlobalVariableCheckが何をしているかを確認します。変数が存在しない場合、GlobalVariableTemp関数で変数が一時的に作成され、その後GlobalVariableSet関数から値を受け取ることになります。つまり、私たちはテストをおこない、スクリプトと同じように情報を作成し、書き込み、サービスはサーバとして動作していますが、まだWebサイトにアクセスしていないだけです。まず、システムの仕組みを理解するべきです。

次の手順では、EAとなるクライアントを作成します。

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        double value;
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, value);
                Print(value);           
        }
}
//+------------------------------------------------------------------+


コードは単純で、1秒ごとにEAが変数が存在するかどうかを確認します。もし存在すれば、EAはGlobalVariableGetを使ってその値を読み込み、ターミナルに出力します。

では、このプロセスをどのように実装するか見てみましょう。まず、サービスを実行します。これは、以下のようにおこなわれます。

しかし、もう1つのシナリオが可能です。サービスが停止しているときに再起動することです。この場合、次のように進めていきます。

その後、ターミナル変数を確認すると、以下のような結果が得られます。

システムは明らかに動作していますが、今度はEAをチャート上に配置し、値を取得し、チャンネル上の接続を確認する必要があります。そこで、EAをチャートに配置すると、以下のような結果になりました。

以上、システムは思い通りに動きます。実際に下図のようなモデルがあります。これは、典型的なクライアント・サーバ形式であり、これこそ私たちがやりたいことなのです。既に述べたようなメリットがあるため、まさにこのフォーマットを実現しようとしているのです。

あとは、Webから値を読み書きするシステムを追加するだけです。その後、最終的なモデルを作ってテストすることになります。この部分はとても簡単で、最初から使っているコードをサービスに追加します。テストを実行するには、Webサイトから値を読み取り、クライアントが読めるようにその値を公開するようにサーバファイルを変更するだけです。新しいサービスコードは以下の通りです。

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        string szRet;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D);
                GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet));
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+
string 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 "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+


これで、下の画像のような仕組みが出来上がりました。

準備が整い、以下が結果です。さらに、時間枠の変更も問題なくおこなえるようになります。



結論

今日は、これまであまり検討されてこなかったMetaTrader 5の機能をいくつか考えてみました。その1つが通信チャンネルです。ただし、まだこの機能を十分に活用できていないのが現状です。さらに踏み込むことができるので、次回以降にご紹介します。この連載でこれまで見てきたことは、MetaTrader 5プラットフォームでどれだけのことができるかを示しています。可能性のあるそれぞれの道の限界、利点、リスクを知っておくことは有用ですが、道を選び、望む結果を得るまで続けることです。


MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10442

添付されたファイル |
Script_e_EA.zip (3.03 KB)
Serviso_e_EA.zip (2.39 KB)
ニューラルネットワークが簡単に(第15部):MQL5によるデータクラスタリング ニューラルネットワークが簡単に(第15部):MQL5によるデータクラスタリング
クラスタリング法について引き続き検討します。今回は、最も一般的なk-meansクラスタリング手法の1つを実装するために、新しいCKmeansクラスを作成します。テスト中には約500のパターンを識別することができました。
OBVによる取引システムの設計方法を学ぶ OBVによる取引システムの設計方法を学ぶ
今回は、初心者向けのシリーズとして、人気のあるいくつかの指標をもとに取引システムを設計する方法について、新しい記事をお届けします。今回は、新しい指標であるOBV (On Balance Volume)を学び、その使い方とそれに基づいた取引システムの設計を学びます。
ニューラルネットワークが簡単に(第16部):クラスタリングの実用化 ニューラルネットワークが簡単に(第16部):クラスタリングの実用化
前回は、データのクラスタリングをおこなうためのクラスを作成しました。今回は、得られた結果を実際の取引に応用するためのバリエーションを紹介したいと思います。
DoEasy - コントロール(第6部):パネルコントロール、内部コンテンツに合わせたコンテナサイズの自動変更 DoEasy - コントロール(第6部):パネルコントロール、内部コンテンツに合わせたコンテナサイズの自動変更
本稿では、Panel WinFormsオブジェクトの作業を続け、パネル内にあるDockオブジェクトの一般的なサイズに合わせた自動サイズ変更を実装します。さらに、銘柄ライブラリオブジェクトに新しいプロパティを追加します。