PR

【MQL4&MQL5】日本の祝日を取得する方法【ソースコード有】

オリジナル関数

このページではWinAPIを使用してWeb上の特定ページを文字列として読み込み、EAやインジケーター内で使用できるようにします。


You
You

難しそうなタイトル!!どういうときに使うんですか?

WAN
WAN

上級クラスだけど、難しい言葉は使わないように解説するねー

主にゴトービ(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);
  }
You
You

こんなコード覚えられません・・・

WAN
WAN

これを理解するには、超上級だから覚えなくても良いよ!実際ワンもよくわからんよ!w

コード忘れたときはChatGPT4に以下の内容で聞いてみて

ChatGPTとのやり取り
You
You

MQL4または、MQL5で以下の関数を作成してください。

①特定のURLページの内容をテキストとして取得する

②WebRequestではなく、WinAPI関数をインポートして使用

③引数URL、戻り値は取得したテキスト

ChatGPT
ChatGPT

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);
You
You

\nってどういう意味ですか?

WAN
WAN

わからない時はとりあえずリファレンスを見るのだよ

今回はstring関係の不明点だから、stringにカーソルを当ててF1を押すとリファレンスがでてくる

\nは特別文字定数っていうリンクに発見したから見てみて(とか言いながらこれはワンも始めて見た、、、)

配列のサイズを設定する

次は、最終的に格納する配列のサイズを、ArrayResizeで再設定します。
多次元配列の場合、ArrayResizeは1次元目の配列のサイズを設定することになります。
-1しているのは、最初の行が不要なためです。完璧を求めないのであれば-1しなくても大丈夫です。

ArrayResize(HolidayArray, rows - 1); // ヘッダー行を除く行数で配列をリサイズ
You
You

1次元ってなんでしたっけ??

WAN
WAN

配列名[①]→これが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]; // 名称
        }
     }
  }
WAN
WAN

長ったらしく解説したけど、意外とコードは短いでしょう?

You
You

確かに短いコードですね!
でも、実際に自分でコーディングできるか自信が無いです

WAN
WAN

まずは読めることが大事!
このページでは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]; // 名称
        }
     }
  }
//+------------------------------------------------------------------+

チャートにセットした場合の出力結果は、以下になります。


次へ
タイトルとURLをコピーしました