このページではWinAPIを使用してWeb上の特定ページを文字列として読み込み、EAやインジケーター内で使用できるようにします。
難しそうなタイトル!!どういうときに使うんですか?
上級クラスだけど、難しい言葉は使わないように解説するねー
主にゴトービ(5と10の付く日)や、日本時間でトレードを行うEAを作成する際に、祝日はトレードを回避するフィルター機能を搭載したい場合があるよね?
その時に、祝日かどうかを判定する為に使うよ
祝日の情報データを取得する
祝日の情報は、政府の以下のページにてWeb上に、ご丁寧にCSVで公開されておりますので、こちらを使用します。(「国民の祝日」で検索)
※このページのリンクがなくなった時点で、使用不可なコードになります。
リンクの取得方法
リンク(文字の色が青)となっている場所を右クリック→リンクのアドレスをコピー
Webページを取得するオリジナル関数(ReadWebPage関数)を作成
Webページを読み取る便利な関数として、WebRequest関数がありますが、この関数はインジケーターやテスターで使えないというデメリットがあります。なので、基本的にはインターネット関係のWindowsAPI(Windowsの関数)を使用します。
以下は、引数で指定したURLのWebページを、テキストで取得するオリジナル関数です。
//+------------------------------------------------------------------+
//| Webページを読み込んでstringに代入する関数 ワンダフルFXライフ |
//+------------------------------------------------------------------+
#import "wininet.dll"
int InternetOpenW(string agent, int accessType, string proxyName, string proxyByPass, int flags);
int InternetOpenUrlW(int internetSession, string url, string headers, int headersLength, int flags, int context);
int InternetReadFile(int handle, uchar &buffer[], int numberOfBytesToRead, int &numberOfBytesRead);
int InternetCloseHandle(int handle);
#import
string ReadWebPage(string url)
{
string text = "";
if(TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) == false)
{
Comment("DLLの使用を許可してください");
return(text);
}
int inet = InternetOpenW("MetaTrader 5 Terminal", 0, NULL, NULL, 0);
if(inet == 0)
{
Comment("InternetOpenW failed.");
return(text);
}
int handle = InternetOpenUrlW(inet, url, NULL, 0, 0, 0);
if(handle == 0)
{
Comment("InternetOpenUrlW failed.");
InternetCloseHandle(inet);
return(text);
}
int byteSize = 0;
uchar receive[1024];//1キロバイトずつが最適
while(InternetReadFile(handle, receive, ArraySize(receive), byteSize))
{
if(byteSize <= 0)
break;
text += CharArrayToString(receive, 0, byteSize);
}
InternetCloseHandle(handle);
InternetCloseHandle(inet);
return(text);
}
こんなコード覚えられません・・・
これを理解するには、超上級だから覚えなくても良いよ!実際ワンもよくわからんよ!w
コード忘れたときはChatGPT4に以下の内容で聞いてみて
MQL4または、MQL5で以下の関数を作成してください。
①特定のURLページの内容をテキストとして取得する
②WebRequestではなく、WinAPI関数をインポートして使用
③引数URL、戻り値は取得したテキスト
MQL4とMQL5では、Windows APIを使用してインターネットからテキストデータを取得する関数を作成することは少し複雑ですが、以下にその方法を示します。まず、WinAPI関数をインポートし、それを使って特定のURLの内容を取得する関数を作成します。
MQL4/MQL5でのWinAPI関数のインポート
// WinAPI関数の宣言
#import "Wininet.dll"
int InternetOpenW(string agent, int accessType, string proxyName, string proxyBypass, int flags);
int InternetConnectW(int handle, string serverName, int serverPort, string userName, string password, int service, int flags, int context);
int HttpOpenRequestW(int connectHandle, string verb, string objectName, string version, string referer, string acceptTypes, int flags, int context);
bool HttpSendRequestW(int requestHandle, string headers, int headersLength, string optional, int optionalLength);
int InternetReadFile(int fileHandle, string &data, int numberOfBytesToRead, int &numberOfBytesRead);
bool InternetCloseHandle(int handle);
#import
URLの内容を取得する関数
string GetUrlContent(string url) {
int inet, connection, request;
string data = "";
string result = "";
int bytesRead;
int flags = 0; // ここで必要なフラグを設定
// インターネット接続を開く
inet = InternetOpenW("Mozilla/5.0", 1, NULL, NULL, 0);
if (inet == 0) return "";
// サーバーに接続
connection = InternetConnectW(inet, "www.example.com", 80, NULL, NULL, 3, 0, 0);
if (connection == 0) {
InternetCloseHandle(inet);
return "";
}
// HTTPリクエストを開く
request = HttpOpenRequestW(connection, "GET", url, NULL, NULL, NULL, flags, 0);
if (request == 0) {
InternetCloseHandle(connection);
InternetCloseHandle(inet);
return "";
}
// リクエストを送信
if (!HttpSendRequestW(request, NULL, 0, NULL, 0)) {
InternetCloseHandle(request);
InternetCloseHandle(connection);
InternetCloseHandle(inet);
return "";
}
// 応答を読み取る
while (InternetReadFile(request, data, 256, bytesRead) && bytesRead > 0) {
result += data;
ZeroMemory(&data, 256);
}
// ハンドルを閉じる
InternetCloseHandle(request);
InternetCloseHandle(connection);
InternetCloseHandle(inet);
return result;
}
注意事項
- このコードはMQL4とMQL5の両方で動作しますが、MQL5ではUnicodeを扱う必要があるため、関数名に
W
(Wide文字列用)が付いているAPIを使用しています。 InternetConnectW
関数で使用するサーバー名は実際のURLから取得してください。- エラーハンドリングは基本的なものだけを含んでいます。より詳細なエラーハンドリングが必要な場合は、追加のコードを記述する必要があります。
※上記コードは、コンパイルエラーになるため
ZeroMemory(&data,256);
を削除する必要があります。
Webから祝日CSVを取得して変数に代入
const string HolidayURL = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
まずは、政府のページでコピーした祝日CSVのURLを変数に代入しています。変数の宣言前に、constをつけると上書きできない変数となります。うっかり変更してしまわないように、念のためつけておきます。
string holiday_txt = ReadWebPage(HolidayURL);
先ほどのオリジナル関数(ReadWebPage)にURLを引数として渡し、返ってきた値をstring(文字列)型の変数(holiday_txt)で受け取りました。
どのように取得できたかをイメージするには、実際にメモ帳で国民の祝日CSVを開いてみるとわかります。以下のようになっています。
CSVはカンマと改行で、それぞれのデータが区切られているのがわかります。
取得したデータを格納して、使用するための配列を宣言する
まずは、配列名HolidayArrayを宣言して作成します。
string HolidayArray[][2];//行数不明、列は日付、名称の2つ
この配列変数の値を使用する際に
HolidayArray[0][0] と指定すると(0行目の0列目) → 例)2020.1.1
HolidayArray[0][1] と指定すると(0行目の1列目) → 例)元旦
となっていれば使いやすいかなと思います。
ここでMQLの基本的な重要ポイント
国民の祝日CSVは、日付がスラッシュ(/)で区切られていますが、MQLはドット(.)で日付を区切らないと認識しない仕様です。こういう基本的な情報は、落とし穴になりやすいので、リファレンスにはザックリと目を通しておきましょう。
取得したCSVデータを配列に格納する
ここからが今回のメインです。
CSVの行は改行で区切られていますので、取得したテキストholiday_txt(StringSplit関数の第一引数)を、StringSplit関数で改行(StringSplit関数の第二引数)によって区切り、result配列(StringSplit関数の第三引数)に格納して、戻り値をrows(int型の変数)で受け取ります。戻り値は格納後のresultの配列サイズなので、rowsが全体の行数となります。
string result[];
int rows = StringSplit(holiday_txt, '\n', result);
\nってどういう意味ですか?
わからない時はとりあえずリファレンスを見るのだよ
今回はstring関係の不明点だから、stringにカーソルを当ててF1を押すとリファレンスがでてくるよ
\nは特別文字定数っていうリンクに発見したから見てみて(とか言いながらこれはワンも始めて見た、、、)
配列のサイズを設定する
次は、最終的に格納する配列のサイズを、ArrayResizeで再設定します。
多次元配列の場合、ArrayResizeは1次元目の配列のサイズを設定することになります。
-1しているのは、最初の行が不要なためです。完璧を求めないのであれば-1しなくても大丈夫です。
ArrayResize(HolidayArray, rows - 1); // ヘッダー行を除く行数で配列をリサイズ
1次元ってなんでしたっけ??
配列名[①]→これが1次元配列
配列名[①][②]→これが2次元配列
①の部分を1次元目、②の部分を2次元目というんだよ
配列は、4次元まで扱うことができるよ
配列に格納されている取得済みデータを整理し、新たな配列に格納する
今度は、result配列のすべての行をカンマで区切って日付と名称を分断し、行ごとに日付と名称を配列columnsに格納していきます。
この作業はfor文を使用し、すべての行で実行するため、rows行繰り返します。
※iが1から始まっているのは、最初の項目の行(休日月日・休日名称)を飛ばしているためです。
カンマで区切った、columnsのサイズは2個以上になるはずなので、サイズが2以上ならHolidayArray配列に区切った日付と名称を代入します。
この時、日付はスラッシュで区切られているため、StringReplace関数でスラッシュをドットに置換しています。
for(int i = 1; i < rows; i++)
{
string columns[];
StringSplit(result[i], ',', columns); // カンマで列を分割
if(ArraySize(columns) >= 2)
{
string holiday = columns[0];
StringReplace(holiday, "/", "."); // 日付のフォーマットを変更
HolidayArray[i - 1][0] = holiday; // 日付
HolidayArray[i - 1][1] = columns[1]; // 名称
}
}
完成したコードは以下です。
//+------------------------------------------------------------------+
//| 国民の祝日CSVを読み込んで配列に格納する関数 ワンダフルFXライフ |
//+------------------------------------------------------------------+
string HolidayArray[][2];//日付、名称
const string HolidayURL = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
void HolidayRead()
{
string holiday_txt = ReadWebPage(HolidayURL);
string result[];
int rows = StringSplit(holiday_txt, '\n', result);
ArrayResize(HolidayArray, rows - 1); // ヘッダー行を除く行数で配列をリサイズ
for(int i = 1; i < rows; i++)
{
string columns[];
StringSplit(result[i], ',', columns); // カンマで列を分割
if(ArraySize(columns) >= 2)
{
string holiday = columns[0];
StringReplace(holiday, "/", "."); // 日付のフォーマットを変更
HolidayArray[i - 1][0] = holiday; // 日付
HolidayArray[i - 1][1] = columns[1]; // 名称
}
}
}
長ったらしく解説したけど、意外とコードは短いでしょう?
確かに短いコードですね!
でも、実際に自分でコーディングできるか自信が無いです
まずは読めることが大事!
このページではCSVのカンマと改行で出来ている仕組みと、特定文字で区切って配列に格納するStringSplit、特定文字を別の文字に置換するStringReplace関数の意味が分かれば十分だよ!
実際に使うときは、このページからコピペして使ってね!
まとめ
- Webページのデータを取得するための、オリジナル関数を作成する(コピペOK)
- 取得したデータを、配列に格納する(今回の例では2次元配列)
- 配列に格納したデータを細分化し、使いやすいようにする(別の配列を用意し、適宜振り分ける)
以下は完成したコードです。EAとして、動作を確認することができます。
//+------------------------------------------------------------------+
//| テストコード ワンダフルFXライフ
//+------------------------------------------------------------------+
int OnInit()
{
HolidayRead();
Comment(HolidayArray[5][0], " ", HolidayArray[5][1]);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Webページを読み込んでstringに代入する関数 ワンダフルFXライフ
//+------------------------------------------------------------------+
#import "wininet.dll"
int InternetOpenW(string agent, int accessType, string proxyName, string proxyByPass, int flags);
int InternetOpenUrlW(int internetSession, string url, string headers, int headersLength, int flags, int context);
int InternetReadFile(int handle, uchar &buffer[], int numberOfBytesToRead, int &numberOfBytesRead);
int InternetCloseHandle(int handle);
#import
string ReadWebPage(string url)
{
string text = "";
if(TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) == false)
{
Comment("DLLの使用を許可してください");
return(text);
}
int inet = InternetOpenW("MetaTrader 5 Terminal", 0, NULL, NULL, 0);
if(inet == 0)
{
Comment("InternetOpenW failed.");
return(text);
}
int handle = InternetOpenUrlW(inet, url, NULL, 0, 0, 0);
if(handle == 0)
{
Comment("InternetOpenUrlW failed.");
InternetCloseHandle(inet);
return(text);
}
int byteSize = 0;
uchar receive[1024];//1キロバイトずつが最適
while(InternetReadFile(handle, receive, ArraySize(receive), byteSize))
{
if(byteSize <= 0)
break;
text += CharArrayToString(receive, 0, byteSize);
}
InternetCloseHandle(handle);
InternetCloseHandle(inet);
return(text);
}
//+------------------------------------------------------------------+
//| 国民の祝日CSVを読み込んで配列に格納する関数 ワンダフルFXライフ
//+------------------------------------------------------------------+
string HolidayArray[][2];//日付、名称
const string HolidayURL = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
void HolidayRead()
{
string holiday_txt = ReadWebPage(HolidayURL);
string result[];
int rows = StringSplit(holiday_txt, '\n', result);
ArrayResize(HolidayArray, rows - 1); // ヘッダー行を除く行数で配列をリサイズ
for(int i = 1; i < rows; i++)
{
string columns[];
StringSplit(result[i], ',', columns); // カンマで列を分割
if(ArraySize(columns) >= 2)
{
string holiday = columns[0];
StringReplace(holiday, "/", "."); // 日付のフォーマットを変更
HolidayArray[i - 1][0] = holiday; // 日付
HolidayArray[i - 1][1] = columns[1]; // 名称
}
}
}
//+------------------------------------------------------------------+
チャートにセットした場合の出力結果は、以下になります。