【超入門】MQL5 EA講座 第122回「トレードタイマークラスを実装する-その4-」【EAの作り方】

MQL5でEA作ろう講座

前回トレードタイマークラスに実装するメンバ関数の第3弾として「トレード時間のオンオフをわかりやすく表示する関数」= DisplayTimerStatus関数を実装する様子を解説しました。

DisplayTimerStatus関数を実装する意味は、第120回で作ったDailySessionTimer関数に改良を施すためです。

DailySessionTimer関数で設定したトレード開始時間/トレード終了時間を、エキスパートログだけでなく、MT5のチャート上にも表示し表示文言もよりわかりやすくしました。

ロードマップに従い、CTradeSessionクラスprivate領域に2つのメンバを追加し、そのうちの1つであるDisplayTimerStatus関数について、引数設定~処理実装記述までを解説しました。

※詳しくは前回の記事の下記セクションをご覧ください。

DisplayTimerStatus関数に必要な仮引数を設定する

DisplayTimerStatus関数に処理実装記述をおこなう

そして、完成したDisplayTimerStatus関数を早速、前回作った「毎日同じ時間帯に取引できるようにする関数」=DailySessionTimer関数にも実装し、トレード開始時間/終了時間がMT5チャート上からも確認できるように改良しました。

※詳しくは前回記事の↓

・「DailySessionTimer関数の処理実装記述を変更する」セクションをご覧ください。

今回もトレードタイマークラスの改良についてです。

今回は「複数の取引時間帯を設定する関数」をトレードタイマークラスに実装していきます。

「またトレードタイマークラスか・・・」と、いい加減皆さんも飽きてきたかもしれませんが、それだけEA開発において、時間操作は大事だという事でもあります。

この先どんどん時間操作が簡単になり、自分の作りたいEAを思い通りに作れる想像と期待に心躍らせながら今回も読んでいただければと思います。

スポンサーリンク
スポンサーリンク
  1. はじめに:「複数の取引時間帯を設定する関数」とは?
  2. 「複数の取引時間帯を設定する関数」を実装するまでのロードマップ
  3. 取引時間帯に関する情報を格納する構造体を宣言する
  4. 「CTradeSession」クラス内に新しいメンバを加える
  5. SegmentTimer関数に必要な仮引数を設定する
    1. 第1仮引数「parSegment」には取引時間に関する情報がまとめて格納される想定
    2. 第2仮引数「parLocalTime」にはローカル時間を使うかどうかを格納する想定
  6. SegmentTimer関数に処理実装記述をおこなう
    1. 処理実装記述1:必要な変数・インスタンスを宣言する
    2. 処理実装記述2:各取引時間帯(タイムセグメント)の配列内をfor文でチェックする
      1. for文の中1:「タイムセグメントが有効かどうか」のチェックをおこなう
      2. for文の中2:トレード開始時間と終了時間を設定する
      3. for文の中3:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード開始時間)
      4. for文の中4:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード開始時間)
      5. for文の中5:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード終了時間)
      6. for文の中6:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード終了時間)
      7. for文の中7:IsActive関数を使って、設定された時間内かどうかをチェックする
    3. 処理実装記述3:DisplayTimerStatus関数でタイマーの状態を出力し、戻り値として返す
  7. 完成したSegmentTimer関数をメインプログラムで使う
    1. 手順1:OriginalTimer.mqhファイルをインクルードする
    2. 手順2:各タイムセグメントごとにinput変数を設定する
      1. キーワード「group」について
    3. 手順3:グローバル領域に必要なインスタンスを宣言する
    4. 手順4:OnInit関数内に必要な記述をおこなう
    5. 手順5:OnTick関数内に必要な記述を行う
  8. まとめ

はじめに:「複数の取引時間帯を設定する関数」とは?

第120回で作った「毎日同じ時間帯に取引できるようにする関数」DailySessionTimer関数は開始時刻と終了時刻がそれぞれ指定できるのが1つだけでした。

このままでは、例えば「8時から22時までを取引時間とする」という事はできるのですが、「8時から13時まで、及び16時から22時まで」という処理を行うことはできません。

そこでトレードタイマーにもっと柔軟性を持たせ、「複数の取引時間帯」を設定できるようにしよう!というのが今回やろうとしている事です。

その為にはトレードタイマークラスCTradeSessionクラスに、「複数の取引時間帯を設定する関数」を追加する必要があります。

この「複数の取引時間帯を設定する関数」が完成すれば、例えば米国雇用統計消費者物価指数(CPI)などの重要な経済指標発表時だけトレードを避ける、などの対応をEAに用意に取らせる事もできるようになります。

米国雇用統計についての詳細は↓の記事をご参照ください

消費者物価指数についての詳細は↓の記事をご参照ください

「複数の取引時間帯を設定する関数」を実装するまでのロードマップ

複数の取引時間帯を設定する関数」を実装するには以下の手順を辿ります

取引時間帯に関する情報を格納する構造体を宣言する

「CTradeSession」クラス内に新しいメンバを加える

SegmentTimer関数に必要な仮引数を設定する

SegmentTimer関数に処理実装記述をおこなう

一つ一つ順を追って見ていきましょう

取引時間帯に関する情報を格納する構造体を宣言する

複数の取引時間帯を設定する関数」の宣言・定義をする前に構造体を1つ作ります。

メインプログラムで複数の取引時間帯(各取引時間帯を「区分・期間」を表す『セグメント』という言葉で以降表します)を設定する事を考えた時、

以下の情報が必要になります。

タイマーセグメントが有効かどうかを示すフラグ

タイマー開始日

タイマー開始時

タイマー開始分

タイマー終了日

タイマー終了時

タイマー終了分

上記の情報すべてを保持できるような構造体を「OriginalTimer.mqh」ファイル内に宣言します。

//タイマーセグメントを管理するための構造体
struct TimerSegment
{
	bool enabled; // タイマーセグメントが有効かどうかを示すフラグ
	int start_day; // タイマー開始日(0=日曜、1=月曜、...、6=土曜)
	int start_hour; // タイマー開始時(0~23の間で指定)
	int start_min; // タイマー開始分(0~59の間で指定)
	int end_day; // タイマー終了日(0=日曜、1=月曜、...、6=土曜)
	int end_hour; // タイマー終了時(0~23の間で指定)
	int end_min; // タイマー終了分(0~59の間で指定)
};

構造体名を「TimerSegment」としました。

構造体についての詳細は↓の記事をご参照ください

「CTradeSession」クラス内に新しいメンバを加える

もはやお決まりの流れになってきましたが、

今回も「CTradeSessionクラスに新しいメンバを追加します。

前回までの「CTradeSessionクラスメンバは以下のようになっていました↓

class CTradeSession
  {
private:
   //トレード開始時間と終了時間を格納する
   datetime          SessionStartTime, SessionEndTime;
   //トレードタイマーのオンオフ状態を格納
   bool timerStartedFlag ;
   //トレードタイマーのオンオフ状態をチャートとエキスパートログに表示する関数
   void DisplayTimerStatus(bool parTimerState);

public:
   // 取引セッションがアクティブかどうかをチェックする関数
   bool              IsActive(datetime openTime, datetime closeTime, bool useLocalTime = false);
   //毎日のトレードタイマーを作成する関数
   bool DailySessionTimer(int parStartHour, int parStartMinute, int parEndHour, int parEndMinute, bool parLocalTime = false);
  };

ここから今回さらにメンバを追加して以下のような形に変わります↓

class CTradeSession
  {
private:
   //トレード開始時間と終了時間を格納する
   datetime          SessionStartTime, SessionEndTime;
   //トレードタイマーのオンオフ状態を格納
   bool timerStartedFlag ;
   //トレードタイマーのオンオフ状態をチャートとエキスパートログに表示する関数
   void              DisplayTimerStatus(bool parTimerState);

public:
   // 取引セッションがアクティブかどうかをチェックする関数
   bool              IsActive(datetime openTime, datetime closeTime, bool useLocalTime = false);
   //毎日のトレードタイマーを作成する関数
   bool              DailySessionTimer(int parStartHour, int parStartMinute, int parEndHour, int parEndMinute, bool parLocalTime = false);
   // 複数の取引時間帯を設定する関数
bool SegmentTimer(TimerSegment &parSegment[], bool parLocalTime=false);
  };

アクセスレベルpublicの領域に複数の取引時間帯を設定するメンバ関数を追加しました。

関数名を「SegmentTimer」としました。

SegmentTimer関数に必要な仮引数を設定する

続いてSegmentTimer関数に必要な引数を設定します。

SegmentTimer関数には2つの引数を設定します。

SegmentTimer(TimerSegment &parSegment[], bool parLocalTime=false);

第1仮引数「parSegment」には取引時間に関する情報がまとめて格納される想定

第1引数データ型は、「取引時間帯に関する情報を格納する構造体を宣言する」セクションで作ったTimerSegment構造体です。引数は「parSegment」としています。

「parSegment」の前についている&は、この引数部分に記述された値が参照渡しで扱われることを意味します。

参照渡しについては大丈夫でしょうか?参照渡しとはオリジナルのデータの位置を関数に渡す処理の事を指し、配列構造体引数に指定する際は、MQL5では参照渡しを使う決まりになっています。

参照渡しについての詳細は↓の記事をご参照ください

「parSegment」の後ろについている[]は、この引数には配列情報が格納されることを意味しています。

構造体は、端的に言えば変数を集めてワンセットにしたものですから、それぞれのメンバ配列としてデータを格納する事も出来る訳です。

TimerSegment構造体には7つのメンバを定義しました。このメンバそれぞれに配列が付与されると考えてください。仮に2つの取引時間帯(タイムセグメント)を設定する場合、

TimerSegment構造体1つ目の取引時間帯
配列[0])
2つ目の取引時間帯
配列[1])
メンバenabled
メンバstart_day
メンバstart_hour
メンバstart_min
メンバend_day
メンバend_hour
メンバend_min

上記のような、イメージを持っていただければと思います。

構造体配列も既にこれまでの講座で使ってきた文法要素ですが、組み合わさると最初少し混乱するかもしれません。

配列についての講座記事群は以下の通りですので、よくわからなくなってきた方は一度配列の内容をおさらいしてみてください。

この第1引数をメインプログラムでどのように使っていくのか?という部分については、この後「完成したSegmentTimer関数をメインプログラムで使う」セクションで具体的に解説するので、今は使い方にはピンとこなくても大丈夫です。

第2仮引数「parLocalTime」にはローカル時間を使うかどうかを格納する想定

第2引数「parLocalTime」にはには、メインプログラムにてローカル時間を基準として使うかが格納される想定となっています。

この引数については第119回で作ったIsActive関数や、第120回で作ったDailySessionTimer関数でも使ったものなので、見覚えがあるかと思います。

基本的にはサーバー時間を基準とすることが多いと思いますので初期値はfalseとします。

SegmentTimer関数に処理実装記述をおこなう

引数の設定が終わったので、今度は処理実装記述をほどこしていきます。

SegmentTimer関数の処理実装記述は以下のようになっています。

bool CTradeSession::SegmentTimer(TimerSegment &parSegment[], bool parLocalTime=false)
  {
   MqlDateTime today; // 現在の日時を格納するためのインスタンス
   bool timerOn = false; // タイマーがオンかどうかを保持するフラグ
   int timerCount = ArraySize(parSegment); // 渡されたタイマーセグメントの配列サイズを取得

   for(int i = 0; i < timerCount; i++) // 各タイマーセグメントについてループ
     {
      if(parSegment[i].enabled == false)
         continue; // タイマーセグメントが有効でなければ、次のループへ

      SessionStartTime = GenerateTimestamp(parSegment[i].start_hour, parSegment[i].start_min); // 開始時間を設定
      SessionEndTime = GenerateTimestamp(parSegment[i].end_hour, parSegment[i].end_min); // 終了時間を設定

      TimeToStruct(SessionStartTime,today); // 開始時間を構造体に変換
      int dayShift = parSegment[i].start_day - today.day_of_week; // 開始曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionStartTime += DAYS_TO_SECONDS * dayShift; // 開始曜日が今日と異なれば、適切な曜日に調整

      TimeToStruct(SessionEndTime,today); // 終了時間を構造体に変換
      dayShift = parSegment[i].end_day - today.day_of_week; // 終了曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionEndTime +=DAYS_TO_SECONDS * dayShift; // 終了曜日が今日と異なれば、適切な曜日に調整

      timerOn = IsActive(SessionStartTime,SessionEndTime,parLocalTime); // 設定された時間内かどうかをチェック
      if(timerOn == true)
         break; // 一つでも条件を満たすタイマーセグメントがあればループを抜ける
     }

   DisplayTimerStatus(timerOn); // タイマーの状態に応じてメッセージを出力

   return(timerOn); // タイマーがオンかどうかの結果を返す
  }

1つ1つ順を追って見ていきましょう。

処理実装記述1:必要な変数・インスタンスを宣言する

まずはSegmentTimer関数内で使うローカル変数インスタンスを宣言していきます。


   MqlDateTime today; // 現在の日時を格納するためのインスタンス
   bool timerOn = false; // タイマーがオンかどうかを保持するフラグ
   int timerCount = ArraySize(parSegment); // 渡されたタイマーセグメントの配列サイズを取得

一番上の「today」はデータ型MqlDateTime構造体インスタンスです。

インスタンスについての詳細は↓の記事をご参照ください

MqlDateTime構造体は日付と時刻に関連する情報を格納する構造体です。

「年、月、日、時、分、秒」などの情報を格納する各メンバが用意されており、プログラムがこれらののメンバにアクセスする事によって日付と時刻の情報の部分的な要素を容易に取り出したり加工したりすることが出来るようになっています。

MqlDateTime構造体については講座記事第117回で解説していますので詳細は↓の記事をご参照ください。

・MQL5 EA講座 第117回「MqlDateTime構造体について」

bool型ローカル変数「 timerOn」は、トレードタイマーがオンかどうかを保持するフラグです。

「 timerOn」の値を最終的に戻り値として返します。

int型ローカル変数「 timerCount」 には配列のサイズが格納されます。 ArraySize関数を使い、引数引数である「parSegment」を記述します。

「parSegment」にはメインプログラムで作られたTimerSegment構造体インスタンスが記述される想定です。

メインプログラムで指定した取引時間帯(=トレードセクション)の数だけ、「 timerCount」に格納されます(今は何を言っているかわからないかもしれません。後で説明します)

処理実装記述2:各取引時間帯(タイムセグメント)の配列内をfor文でチェックする

続いて各取引時間帯(タイムセグメント)の配列内をfor文でチェックします。

 for(int i = 0; i < timerCount; i++) // 各タイマーセグメントについてループ
     {

ローカル変数「timerCount」にはメインプログラムで設定した、取引時間帯(セグメント)の数が格納されています。

仮に8-12時、16-22時、といったように2つの時間帯をメインプログラムで設定しているのであれば、for文内で0と1をループチェックします。

for文の中1:「タイムセグメントが有効かどうか」のチェックをおこなう

if(parSegment[i].enabled == false)
         continue; // タイマーセグメントが有効でなければ、次のループへ

parSegmentはTimerSegment構造体インスタンスです。([i]にはfor文でチェック中の数字が入ります)

TimerSegment構造体メンバである「enabled」には「そのタイムセグメントが有効かどうか」の情報が格納されています。

例えば「8-12時」というタイムセグメントをメインプログラムで作っていたとして、そのタイムセグメントを使わない、としている場合はcontinueで次のタイムセグメントのチェックに移ります。

trueであれば、for文中の次の処理に移ります。

for文の中2:トレード開始時間と終了時間を設定する

SessionStartTime = GenerateTimestamp(parSegment[i].start_hour, parSegment[i].start_min); 
// 開始時間を設定
SessionEndTime = GenerateTimestamp(parSegment[i].end_hour, parSegment[i].end_min); 
// 終了時間を設定

続いて各タイムセグメントのトレード開始時間と終了時間を設定します。

SessionStartTimeCTradeSessionクラスprivate領域に作ったメンバ変数です。トレードの開始時間情報を格納する事を想定しています。

※詳細は講座記事第120回の「メンバ「SessionStartTime」について」セクションを参照。

GenerateTimestamp関数は、講座記事第118回で作った、「日時を生成する独立関数です。

第1引数に開始時間の「時」情報、第2引数に開始時間の「分」情報を記述します。ここではTimerSegment構造体メンバである「start_hour」を第1引数に、同じくTimerSegment構造体メンバである「start_min」を第2引数に指定します。これでGenerateTimestamp関数が生成した日時情報が,SessionStartTimeに格納されました。

同じ要領でトレード終了時間も設定していきます。

SessionEndTimeCTradeSessionクラスprivate領域に作ったメンバ変数です。SessionEndTimeはトレードの終了時間情報を格納する事を想定しています。

※詳細は講座記事第120回の「メンバ「SessionEndTime」について」セクションを参照

開始時間の時と同じように、GenerateTimestamp関数のを第1引数には、TimerSegment構造体メンバである「end_hour」を、第2引数には同じくTimerSegment構造体メンバである「end_min」を指定します。

これでGenerateTimestamp関数が生成した日時情報が,SessionEndTimeに格納されました。

【スポンサーリンク】

for文の中3:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード開始時間)

TimeToStruct(SessionStartTime,today); // 開始時間を構造体に変換

SessionStartTime及びSessionEndTimeに格納されている日時情報はdateTime型です。この情報を自由に加工できるようにMqlDateTime構造体型に変換し、MqlDateTime構造体インスタンスである「today」に格納します。

dateTime型MqlDateTime構造体型への変換・格納はTimeToStruct関数を使います。

TimeToStruct関数の第1引数にトレード開始時間情報を格納している「SessionStartTime」を記述します。第2引数にはMqlDateTime構造体インスタンスである「today」を記述します。

これで「today」の各メンバに「SessionStartTime」が保持していたトレード開始時間情報が分解して格納されました。

TimeToStruct関数についての詳細は↓の記事をご参照ください

for文の中4:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード開始時間)

      int dayShift = parSegment[i].start_day - today.day_of_week; 
      // 開始曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionStartTime += DAYS_TO_SECONDS * dayShift; 
      // 開始曜日が今日と異なれば、適切な曜日に調整

TimerSegment構造体メンバ「start_day」には、タイマー開始日の曜日が格納される想定になっています。MQL5にはENUM_DAY_OF_WEEKという事前に曜日について定義されたenum列挙型があり、0 から 6 の値で、0 は日曜日、1 は月曜日、…、6 は土曜日という形で曜日を扱っています。

メンバ「start_day」もそれに準じる形で宣言時、データ型int型にしています。

ENUM_DAY_OF_WEEKについての詳細は↓の記事をご参照ください

parSegment[i].start_day – today.day_of_week;

↑の表記が何をやっているかというと、「事前に設定した開始時間の曜日情報」と「現在日時における曜日情報」との差分を計算しています。

・・・「全然何を言っているのかわからない・・・」という方もいらっしゃるかと思うので、具体例を挙げて説明します。

仮に皆さんが「金曜日にトレードをしたい」と思ったとします。その場合、メインプログラムのinput変数で金曜=5と入力します。

それがメンバstart_day」に格納され、「start_day」=5となります。

一方で今日が月曜日だとします。月曜日=1ですから、

parSegment[i].start_day – today.day_of_week

は5-1=4となります。ローカル変数「dayShift」には4が代入されることになります。

その下のif(dayShift != 0) は「dayShift」が4なので条件を満たすことになり、その下の処理を実行する事になります。すなわち、

SessionStartTime += DAYS_TO_SECONDS * dayShift;

↑を実行する事になるのですが、これは一体何をしているのでしょうか?

「DAYS_TO_SECONDS」という定数は、第118回の「「1分」「1時間」「1日」「1週間」に相当する経過秒数をdefine命令により定数として設定する」セクションで解説した定数です。

1日を秒に換算した値→「86400」秒という値を格納しています。

「dayShift」が4なので、右辺の計算は86400(1日)×4となります。

つまり4日分の値を「SessionStartTime 」に足し合わせているのです。

これによって、月曜から4日後の金曜日(の指定した時刻)にトレードするような日時情報の設定が完了したことになります。

ところで、「事前に設定した開始時間の曜日情報」と「現在日時における曜日情報」との差分は、マイナスになる事もあります。その場合はどうなるでしょうか?考えてみましょう

今度は皆さんが「月曜日にトレードをしたい」と思ったとします。その場合、メインプログラムのinput変数で月曜日=1と入力します。

それがメンバstart_day」に格納され、「start_day」=1となります。

一方で今日が水曜日だとします。月曜日=3ですから、

parSegment[i].start_day – today.day_of_week

は1-3=-2となります。ローカル変数「dayShift」には-2が代入されることになります。

その下のif(dayShift != 0) は「dayShift」が-2なので条件を満たすことになり、その下の処理を実行する事になります。この場合、

SessionStartTime+= DAYS_TO_SECONDS * dayShift

「dayShift」が-2なので、右辺の計算は86400(1日)×(-2)となります。

今度は2日分の値をSessionStartTimeからマイナスすることになります。つまり、SessionStartTime には過去の日時が格納されることになります。

設定が月曜=1なのに対し、現在が水曜=3なのでこの週は(対象となるタイムセグメントに関しては)トレードをしない、という事になります。

このように差分がマイナスの場合は、次の週に変わって差分が0になるかプラスになった時、改めて正しい取引日時に設定されることになります。

for文の中5:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード終了時間)

TimeToStruct(SessionEndTime,today); // 終了時間を構造体に変換

トレード開始時間に対して行った処理を、トレード終了時間に対しても行います。

TimeToStruct関数の第1引数にトレード終了時間情報を格納している「SessionEndTime」を記述します。第2引数にはMqlDateTime構造体インスタンスである「today」を記述します。

第2引数は現在の日時情報を格納しているので、この第2引数はトレード開始時間の時と変わりません。

for文の中6:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード終了時間)

dayShift = parSegment[i].end_day - today.day_of_week; 
// 終了曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionEndTime +=DAYS_TO_SECONDS * dayShift; 
         // 終了曜日が今日と異なれば、適切な曜日に調整

開始時間の時と同様に、「事前に設定した終了時間の曜日情報」と「現在日時における曜日情報」との差分を計算します。

もし差分が生じていれば(=dayShiftが0じゃなければ)、dayShiftに格納されている数にDAYS_TO_SECONDS(1日分の経過秒数)をかけたものを、SessionEndTimeにプラス、あるいはマイナスして曜日調整を行います。

for文の中7:IsActive関数を使って、設定された時間内かどうかをチェックする

timerOn = IsActive(SessionStartTime,SessionEndTime,parLocalTime); // 設定された時間内かどうかをチェック
      if(timerOn == true)
         break; // 一つでも条件を満たすタイマーセグメントがあればループを抜ける

IsActive関数は講座記事第119回で作った「トレード時間かどうかをチェックする」メンバ関数です。

曜日調整等の処理を加えた「SessionStartTime」「SessionEndTime」を引数に記述し、戻り値ローカル変数「timerOn」に代入します。

「timerOn」の値がtrueであれば、チェック中の取引時間帯(タイムセグメント)に突入している事を意味します。その場合、その時点で他のタイムセグメントをチェックする必要がないのでfor文breakで抜けます。

【スポンサーリンク】

処理実装記述3:DisplayTimerStatus関数でタイマーの状態を出力し、戻り値として返す

  DisplayTimerStatus(timerOn); // タイマーの状態に応じてメッセージを出力

   return(timerOn); // タイマーがオンかどうかの結果を返す
  }

DisplayTimerStatus関数前回解説したばかりの「トレード時間のオンオフをわかりやすく表示する関数」です。ローカル変数「timerOn」を引数に記述し、トレードタイマーの状態をエキスパートログとチャート上に表示させます。

最後にreturnで「timerOn」の値を戻り値として返してすべての記述は終了です。

完成したSegmentTimer関数をメインプログラムで使う

処理実装記述を終え、完成したSegmentTimer関数を実際にメインプログラムで使う手順を今度は見ていきましょう。

手順1:OriginalTimer.mqhファイルをインクルードする

もう恒例となった、include命令によるOriginalTimer.mqhファイルの読み込みを行います。

//OriginalTimer.mqhファイルをインクルード
#include <OriginalTimer.mqh>

include命令については↓の記事をご参照ください

MQL5 EA講座 第56回「#include命令(#include directive)」

手順2:各タイムセグメントごとにinput変数を設定する

まずは、input変数を設定していくのですが、TimerSegment構造体には7つのメンバを用意しました。※「取引時間帯に関する情報を格納する構造体を宣言する」セクション参照

それを埋める為のinput変数は当然7つです。

ただし、これは取引時間帯(タイムセグメント)が1つだけの場合です。

取引時間帯(タイムセグメント)を複数設ける場合は、7つ×必要なタイムセグメント分だけinput変数を用意する、という事になります。

いくつかのタイムセグメントを用意するか?は開発するEAの戦略や思想にも関わってくる部分になりますが、今回はとりあえず2つのタイムセグメントを用意する、という前提で設定していきます。

input group           "取引時間(タイムセグメント)1" // タイムセグメント1の設定開始
input bool UseTimer=true; // タイマーを使用するかどうか
input ENUM_DAY_OF_WEEK StartDay=5; // 取引開始日
input int StartHour=8; // 取引開始時間
input int StartMinute=0; // 取引開始分
input ENUM_DAY_OF_WEEK EndDay=5; // 取引終了日
input int EndHour=12; // 取引終了時間
input int EndMinute=25; // 取引終了分

input group           "取引時間(タイムセグメント)2" // タイムセグメント2の設定開始
sinput string Block2; // タイムセグメント2の説明
input bool UseTimer2=true; // タイマー2を使用するかどうか
input ENUM_DAY_OF_WEEK	 StartDay2=5; // 取引開始日2
input int StartHour2=13; // 取引開始時間2
input int StartMinute2=0; // 取引開始分2
input ENUM_DAY_OF_WEEK EndDay2=5; // 取引終了日2
input int EndHour2=18; // 取引終了時間2
input int EndMinute2=0; // 取引終了分2

input bool UseLocalTime=false; // ローカル時間を使用するかどうか

2つのタイムセグメントを作る事を想定して、上記のようにTimerSegment構造体の各メンバに値を入れるinput変数を2セット用意しました。

input変数についての詳細は↓の記事をご参照ください

キーワード「group」について

サンプルコードの冒頭で「group」というキーワードが出てきました。

input group           "取引時間(タイムセグメント)1" // タイムセグメント1の設定開始

これまでの講座記事では出てこなかったのですが、上記のように

input group “任意の文字列”

の順番で記述することによってinput変数の固まりを「名前付きブロック」に分割できる機能を持ちます。このプログラムをMT5のチャートに挿入すると、プロパティ画面は以下のように表示されます。

また、バックテストや最適化を行うストラテジーテスターのパラメーター画面でも・・・↓

グループ名をダブルクリックして入力ブロックを折りたたみ/展開などができるようになります。

groupキーワードによる指定は、新しいgroup指定がされるまで有効となっています。

groupキーワードを使う事によって、よりパラメーターを視覚的にわかりやすく分離できるようになりますので、是非積極的に利用して頂ければと思います。

手順3:グローバル領域に必要なインスタンスを宣言する

続いてグローバル領域にCTradeSessionクラスTimerSegment構造体インスタンスを宣言します。

//CTradeSessionクラスのインスタンスを宣言
CTradeSession TradeSesson;

//TimerSegment構造体のインスタンスを宣言
TimerSegment Segment[2];

CTradeSessionクラスインスタンス名を「TradeSesson」、TimerSegment構造体インスタンス名を「Segment」としました。

用意する取引時間帯(タイムセグメント)は今回2つなので、TimerSegment構造体インスタンス「Segment」が確保する配列サイズも2としています。

【スポンサーリンク】

手順4:OnInit関数内に必要な記述をおこなう

今まではこの後OnTick関数の中に処理記述をしていましたが、今回はその前に、長らく登場していなかったイベントハンドラーであるOnInit関数内に必要な記述をおこなっていきます。

OnInit関数プログラムファイルをチャートに挿入した直後に1回だけ、指定した処理をする関数です。※OnInit関数についての詳細は↓の記事をご参照ください

時間帯の指定という処理は、通常EAをチャートに挿入する際におこなうものであり、値動きがあるたびに処理を行うOnTick関数内よりもOnInit関数内でおこなう方が妥当でしょう。

void OnInit()
  {
// タイムセグメント1に関する設定をSegment配列の最初の要素に代入
   Segment[0].enabled= UseTimer; // タイマー1を使用するかどうか
   Segment[0].start_day=StartDay; // タイマー1の取引開始日
   Segment[0].start_hour=StartHour; // タイマー1の取引開始時間
   Segment[0].start_min=StartMinute; // タイマー1の取引開始分
   Segment[0].end_day=EndDay; // タイマー1の取引終了日
   Segment[0].end_hour=EndHour; // タイマー1の取引終了時間
   Segment[0].end_min=EndMinute; // タイマー1の取引終了分

// タイムセグメント2に関する設定をSegment配列の2番目の要素に代入
   Segment[1].enabled= UseTimer2; // タイマー2を使用するかどうか
   Segment[1].start_day=StartDay2; // タイマー2の取引開始日
   Segment[1].start_hour=StartHour2; // タイマー2の取引開始時間
   Segment[1].start_min=StartMinute2; // タイマー2の取引開始分
   Segment[1].end_day=EndDay2; // タイマー2の取引終了日
   Segment[1].end_hour=EndHour2; // タイマー2の取引終了時間
   Segment[1].end_min=EndMinute2; // タイマー2の取引終了分
// タイマー2の取引終了時間と分の設定は省略されています
  }

タイムセグメント1を作るのに必要な情報は、TimerSegment構造体インスタンス「Segment」の配列[0]に格納されます。

手順2:各タイムセグメントごとにinput変数を設定する」セクションで作ったinput変数のうち、group1にまとめられているものをを代入していきます。

タイムセグメント2を作るのに必要な情報は、TimerSegment構造体インスタンス「Segment」の配列[1]に格納されます。

input変数のうち、group2にまとめられているものをを代入していきます。

手順5:OnTick関数内に必要な記述を行う

最後にOnTick関数内に必要な記述を行います。

void OnTick()
  {//取引時間帯かどうかを判定するローカル変数の宣言
   bool TradeOn=true;


// 現在の時刻が取引時間帯(タイムセグメント)かどうかをチェック(取引可能時間かを確認)
   TradeOn=TradeSesson.SegmentTimer(Segment,UseLocalTime);



// 取引時間内であれば
   if(TradeOn==true)
     {
      // 注文を発注する処理をここに書く(取引時間内なら注文を出す)
     }
  }

この部分は講座記事第120回DailySessionTimer関数について解説した時と殆ど変わりません。

bool型ローカル変数「 timerOn」を宣言します。「 timerOn」は現在時刻が取引時間帯(トレードセッション)かどうかを判定する結果を代入する変数となっています。

初期値はtrueとなっています。

タイマーを使用するかどうかを決めるinput変数である「UseTimer」「UseTimer2」はOnInit関数内にある、TimerSegment構造体インスタンス「Segment」のメンバであるenabledに紐づけられています。

従って、もしいずれのタイマーも使わない(メンバ「enabled」がfalseになっている)場合は、ローカル変数「 timerOn」は常時trueとなっている事になります。

TradeOn=TradeSesson.SegmentTimer(Segment,UseLocalTime);

の記述によってSegmentTimer関数を呼び出し、戻り値を「 timerOn」に返します。

SegmentTimer関数の第1引数にはTimerSegment構造体インスタンス「Segment」を、

第2引数には時間判定にローカル時間を使うかどうかを設定するinput変数「UseLocalTime」をそれぞれ記述します。

if(TradeOn==true)を満たす時は、取引時間帯(タイムセグメント)内という事になりますので{}内に具体的な発注記述を行います。

まとめ

今回はトレードタイマークラスに実装するメンバ関数の第4弾として、「複数の取引時間帯を設定する関数」=SegmentTimer関数を実装しました。

まずは、「複数の時間帯を設定する」という事の根本的な意味とそれによって、EA開発にもたらされるメリットについて簡単に説明をしました。※詳細は当記事の「はじめに:「複数の取引時間帯を設定する関数」とは?」セクションをご覧ください。

そして、「複数の取引時間帯を設定する関数」を実装するまでのロードマップに示した手順に従って、

OriginalTimer.mqh」ファイル内にTimerSegment構造体を作り、CTradeSessionクラスにはSegmentTimer関数メンバとして追加しました。

その後は、SegmentTimer関数引数設定~処理実装記述までを解説しました。

詳しくは当記事の下記セクションをご確認ください。

取引時間帯に関する情報を格納する構造体を宣言する

「CTradeSession」クラス内に新しいメンバを加える

SegmentTimer関数に必要な仮引数を設定する

SegmentTimer関数に処理実装記述をおこなう

そして、最後に実装を終えたSegmentTimer関数をメインプログラムでどのように使っていくのか、という記述例の紹介をしました。詳しくは当記事の↓

完成したSegmentTimer関数をメインプログラムで使う」セクションをご覧ください。

現時点での「OriginalTimer.mqh」ファイルに記述した内容は以下の通りです↓

//+------------------------------------------------------------------+
//|                                                OriginalTimer.mqh |
//|                                                                  |
//|                                                                  |
//+------------------------------------------------------------------+

#define MINUTES_TO_SECONDS 60 // 1分を秒に変換する定数(60秒)
#define HOURS_TO_SECONDS 3600 // 1時間を秒に変換する定数(3600秒)
#define DAYS_TO_SECONDS 86400 // 1日を秒に変換する定数(86400秒)
#define WEEKS_TO_SECONDS 604800 // 1週間を秒に変換する定数(604800秒)

//タイマーセグメントを管理するための構造体
struct TimerSegment
  {
   bool              enabled; // タイマーセグメントが有効かどうかを示すフラグ
   int               start_day; // タイマー開始日(0=日曜、1=月曜、...、6=土曜)
   int               start_hour; // タイマー開始時(0~23の間で指定)
   int               start_min; // タイマー開始分(0~59の間で指定)
   int               end_day; // タイマー終了日(0=日曜、1=月曜、...、6=土曜)
   int               end_hour; // タイマー終了時(0~23の間で指定)
   int               end_min; // タイマー終了分(0~59の間で指定)
  };




//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class OriginalCNewBar
  {
private:
   datetime TimeArray[], //現在の時間を格納
            LastTimeVar;//直近にチェックした時間

public:

   void              OriginalCNewBar();//配列「TimeArray」に時系列セットを施すコンストラクタ
   //新しいバーがオープンした瞬間かどうかをチェックする
   bool              CheckNewBar(string parSymbol, ENUM_TIMEFRAMES parTimeframe);
  };
//+------------------------------------------------------------------+

//コンストラクタOriginalCNewBarの処理実装記述
void OriginalCNewBar::OriginalCNewBar(void)
  {
   ArraySetAsSeries(TimeArray,true);//配列「TimeArray」を時系列にセットする
  }

// 新しいバーがオープンしたかどうかをチェックするメンバ関数の実装
bool OriginalCNewBar::CheckNewBar(string parSymbol, ENUM_TIMEFRAMES parTimeframe)
  {
// 初回実行かどうかを判定するフラグ
   bool firstExe = false;
// 新しいバーがオープンしたかどうかを示すフラグ
   bool newBarOpen = false;

// 指定されたシンボルと時間枠の現在と一つ前のバーの時間をTimeArrayにコピー
   CopyTime(parSymbol, parTimeframe, 0, 2, TimeArray);

// LastTimeVarが0の場合、これが初回実行と判断
   if(LastTimeVar == 0)
      firstExe = true;

// TimeArrayの最新の時間がLastTimeVarより大きい場合、新しいバーと判断
   if(TimeArray[0] > LastTimeVar)
     {
      // 初回実行ではない場合、newBarフラグをtrueに設定
      if(firstExe == false)
         newBarOpen = true;
      // 最後にチェックしたバーのオープン時間を更新
      LastTimeVar = TimeArray[0];
     }

// 新しいバーの有無を返す
   return(newBarOpen);
  }


// 日付時間値を生成する関数
datetime GenerateTimestamp(int newHour = 0, int newMinute = 0)
  {
   MqlDateTime dateTimeDetails; // MqlDateTime構造体を使用して日時データを保存するための変数
   TimeToStruct(TimeCurrent(), dateTimeDetails); // 現在のサーバーの時間を取得し、dateTimeDetails構造体に代入

   dateTimeDetails.hour = newHour; // 時間を新しい値に設定
   dateTimeDetails.min = newMinute; // 分を新しい値に設定

   datetime constructedTime = StructToTime(dateTimeDetails); // MqlDateTime構造体値をdatetime値に変換

   return(constructedTime); // 変換したdatetime値を返す
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CTradeSession
  {
private:
   //トレード開始時間と終了時間を格納する
   datetime          SessionStartTime, SessionEndTime;
   //トレードタイマーのオンオフ状態を格納
   bool timerStartedFlag ;
   //トレードタイマーのオンオフ状態をチャートとエキスパートログに表示する関数
   void              DisplayTimerStatus(bool parTimerState);

public:
   // 取引セッションがアクティブかどうかをチェックする関数
   bool              IsActive(datetime openTime, datetime closeTime, bool useLocalTime = false);
   //毎日のトレードタイマーを作成する関数
   bool              DailySessionTimer(int parStartHour, int parStartMinute, int parEndHour, int parEndMinute, bool parLocalTime = false);
   // 複数の取引時間帯を設定する関数
   bool              SegmentTimer(TimerSegment &parSegment[], bool parLocalTime=false);
  };


// SegmentTimer
bool CTradeSession::SegmentTimer(TimerSegment &parSegment[], bool parLocalTime=false)
  {
   MqlDateTime today; // 現在の日時を格納するための変数
   bool timerOn = false; // タイマーがオンかどうかを保持するフラグ
   int timerCount = ArraySize(parSegment); // 渡されたタイマーセグメントの配列サイズを取得

   for(int i = 0; i < timerCount; i++) // 各タイマーセグメントについてループ
     {
      if(parSegment[i].enabled == false)
         continue; // タイマーセグメントが有効でなければ、次のループへ

      SessionStartTime = GenerateTimestamp(parSegment[i].start_hour, parSegment[i].start_min); // 開始時間を設定
      SessionEndTime = GenerateTimestamp(parSegment[i].end_hour, parSegment[i].end_min); // 終了時間を設定

      TimeToStruct(SessionStartTime,today); // 開始時間を構造体に変換
      int dayShift = parSegment[i].start_day - today.day_of_week; 
      // 開始曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionStartTime += DAYS_TO_SECONDS * dayShift; 
         // 開始曜日が今日と異なれば、適切な曜日に調整

      TimeToStruct(SessionEndTime,today); // 終了時間を構造体に変換
      dayShift = parSegment[i].end_day - today.day_of_week; 
      // 終了曜日と現在の曜日との差を計算
      if(dayShift != 0)
         SessionEndTime +=DAYS_TO_SECONDS * dayShift; 
         // 終了曜日が今日と異なれば、適切な曜日に調整

      timerOn = IsActive(SessionStartTime,SessionEndTime,parLocalTime); // 設定された時間内かどうかをチェック
      if(timerOn == true)
         break; // 一つでも条件を満たすタイマーセグメントがあればループを抜ける
     }

   DisplayTimerStatus(timerOn); // タイマーの状態に応じてメッセージを出力

   return(timerOn); // タイマーがオンかどうかの結果を返す
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CTradeSession::DisplayTimerStatus(bool parTimerState)
  {
// parTimerStateがtrueで、かつtimerStartedFlagがfalseの場合、タイマーが開始されたことを示す。
   if(parTimerState == true && timerStartedFlag == false)
     {
      string message = "タイマー開始"; // "タイマー開始"を代入
      Print(message); // メッセージをエキスパートログに出力
      Comment(message); // メッセージをチャートに表示
      timerStartedFlag = true; // timerStartedFlagをtrueに設定して、タイマーが開始されたことを記録
     }
   else
      if(parTimerState == false && timerStartedFlag == true) // parTimerStateがfalseで、かつtimerStartedFlagがtrueの場合、タイマーが停止したことを示す。
        {
         string message = "タイマー停止"; // "タイマー停止"を代入
         Print(message); // メッセージをエキスパートログに出力
         Comment(message); // メッセージをチャートに表示
         timerStartedFlag = false; // timerStartedFlagをfalseに設定して、タイマーが停止されたことを記録
        }
  }

// 取引セッションがアクティブかどうかをチェックする関数の実装
bool CTradeSession::IsActive(datetime openTime, datetime closeTime, bool useLocalTime = false)
  {
// 開始時間が終了時間より後であるかどうかを確認
   if(openTime >= closeTime)
     {
      // 開始時間と終了時間が不正である場合、警告メッセージを出力して処理を中止
      Alert("エラー:開始時間または終了時間が不正です");
      return false; // 不正な時間設定のため、falseを返す
     }

// 現在時刻を取得する
   datetime currentTime;
//ローカル時間を使うかどうかのチェック
   if(useLocalTime == true)
      currentTime = TimeLocal();
   else
      currentTime = TimeCurrent();

// 現在時刻が開始時間と終了時間の範囲内かどうかをチェック
   bool timerOn = false; // タイマーがオンかどうかのフラグを初期化
   if(currentTime >= openTime && currentTime < closeTime)
     {
      // 現在時刻が指定された時間範囲内にある場合、タイマーをオンにする
      timerOn = true;
     }

   return timerOn; // タイマーの状態を返す
  }

// Daily timer
bool CTradeSession::DailySessionTimer(int parStartHour, int parStartMinute, int parEndHour, int parEndMinute, bool parLocalTime=false)
  {
// 現在の時間を取得
   datetime currentTime;
   if(parLocalTime == true)
      currentTime = TimeLocal(); // ローカル時間を使用
   else
      currentTime = TimeCurrent(); // サーバー時間を使用

// セッションの開始時間を設定
   SessionStartTime = GenerateTimestamp(parStartHour,parStartMinute);

// セッションの終了時間を設定
   SessionEndTime = GenerateTimestamp(parEndHour,parEndMinute);

// セッションの終了時間が開始時間より前に設定されている場合、終了時間を調整する
   if(SessionEndTime <= SessionStartTime)
     {
      SessionStartTime -= DAYS_TO_SECONDS; // 開始時間を1日分マイナスする

      // 現在時刻が終了時間を過ぎていた場合、開始時間と終了時間を次の日にずらす
      if(currentTime > SessionEndTime)
        {
         SessionStartTime += DAYS_TO_SECONDS;
         SessionEndTime += DAYS_TO_SECONDS;
        }
     }

// セッションがアクティブかどうかを判断
   bool timerOn = IsActive(SessionStartTime, SessionEndTime, parLocalTime);

// タイマーの状態を出力
   DisplayTimerStatus(timerOn);

// タイマーがオンかオフかの状態を返す
   return(timerOn);
  }

//+------------------------------------------------------------------+

今回はいつも以上にヘビーな内容だったかもしれません。ここまで読んでいただいた皆様、大変お疲れ様でした。次回はいよいよトレードタイマークラスに関する最終回となります。

今回は以上とさせて頂きます。

最後までお読みいただきありがとうございました。

MQL5 EA講座 第121回「トレードタイマークラスを実装する-その3-」

        →MQL5 EA講座 第123回「トレードタイマークラスを実装する-その5-」

<参照>

ENUM_DAY_OF_WEEK/TimeCurrent関数/TimeToString関数/UNIXタイム/タイムスタンプ/datetime型/iTime関数/StringToTime関数/ENUM_TIMEFRAMES/TimeToStruct関数/GetTickCount関数/MqlDateTime構造体/タイムゾーン/TimeDaylightSavings関数/TimeGMT関数/TimeGMTOffset関数/TimeLocal関数/夏時間

コメント

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