前回はトレードタイマークラスに実装するメンバ関数の第3弾として「トレード時間のオンオフをわかりやすく表示する関数」= DisplayTimerStatus関数を実装する様子を解説しました。
DisplayTimerStatus関数を実装する意味は、第120回で作ったDailySessionTimer関数に改良を施すためです。
DailySessionTimer関数で設定したトレード開始時間/トレード終了時間を、エキスパートログだけでなく、MT5のチャート上にも表示し表示文言もよりわかりやすくしました。
ロードマップに従い、CTradeSessionクラスのprivate領域に2つのメンバを追加し、そのうちの1つであるDisplayTimerStatus関数について、仮引数設定~処理実装記述までを解説しました。
※詳しくは前回の記事の下記セクションをご覧ください。
・DisplayTimerStatus関数に必要な仮引数を設定する
・DisplayTimerStatus関数に処理実装記述をおこなう
そして、完成したDisplayTimerStatus関数を早速、前回作った「毎日同じ時間帯に取引できるようにする関数」=DailySessionTimer関数にも実装し、トレード開始時間/終了時間がMT5チャート上からも確認できるように改良しました。
※詳しくは前回記事の↓
・「DailySessionTimer関数の処理実装記述を変更する」セクションをご覧ください。
今回もトレードタイマークラスの改良についてです。
今回は「複数の取引時間帯を設定する関数」をトレードタイマークラスに実装していきます。
「またトレードタイマークラスか・・・」と、いい加減皆さんも飽きてきたかもしれませんが、それだけEA開発において、時間操作は大事だという事でもあります。
この先どんどん時間操作が簡単になり、自分の作りたいEAを思い通りに作れる想像と期待に心躍らせながら今回も読んでいただければと思います。
- はじめに:「複数の取引時間帯を設定する関数」とは?
- 「複数の取引時間帯を設定する関数」を実装するまでのロードマップ
- 取引時間帯に関する情報を格納する構造体を宣言する
- 「CTradeSession」クラス内に新しいメンバを加える
- SegmentTimer関数に必要な仮引数を設定する
- SegmentTimer関数に処理実装記述をおこなう
- 処理実装記述1:必要な変数・インスタンスを宣言する
- 処理実装記述2:各取引時間帯(タイムセグメント)の配列内をfor文でチェックする
- for文の中1:「タイムセグメントが有効かどうか」のチェックをおこなう
- for文の中2:トレード開始時間と終了時間を設定する
- for文の中3:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード開始時間)
- for文の中4:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード開始時間)
- for文の中5:TimeToStruct関数を使って、日時情報をMqlDateTime構造体型に格納する(トレード終了時間)
- for文の中6:開始曜日と現在の曜日との差を計算し、適切な曜日に調整をする(トレード終了時間)
- for文の中7:IsActive関数を使って、設定された時間内かどうかをチェックする
- 処理実装記述3:DisplayTimerStatus関数でタイマーの状態を出力し、戻り値として返す
- 完成したSegmentTimer関数をメインプログラムで使う
- まとめ
はじめに:「複数の取引時間帯を設定する関数」とは?
第120回で作った「毎日同じ時間帯に取引できるようにする関数」=DailySessionTimer関数は開始時刻と終了時刻がそれぞれ指定できるのが1つだけでした。
このままでは、例えば「8時から22時までを取引時間とする」という事はできるのですが、「8時から13時まで、及び16時から22時まで」という処理を行うことはできません。
そこでトレードタイマーにもっと柔軟性を持たせ、「複数の取引時間帯」を設定できるようにしよう!というのが今回やろうとしている事です。
その為にはトレードタイマークラス=CTradeSessionクラスに、「複数の取引時間帯を設定する関数」を追加する必要があります。
この「複数の取引時間帯を設定する関数」が完成すれば、例えば米国雇用統計や消費者物価指数(CPI)などの重要な経済指標発表時だけトレードを避ける、などの対応をEAに用意に取らせる事もできるようになります。
※米国雇用統計についての詳細は↓の記事をご参照ください
※消費者物価指数についての詳細は↓の記事をご参照ください
「複数の取引時間帯を設定する関数」を実装するまでのロードマップ
「複数の取引時間帯を設定する関数」を実装するには以下の手順を辿ります
・「CTradeSession」クラス内に新しいメンバを加える
一つ一つ順を追って見ていきましょう
取引時間帯に関する情報を格納する構造体を宣言する
「複数の取引時間帯を設定する関数」の宣言・定義をする前に構造体を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);
// 終了時間を設定
続いて各タイムセグメントのトレード開始時間と終了時間を設定します。
SessionStartTimeはCTradeSessionクラスのprivate領域に作ったメンバ変数です。トレードの開始時間情報を格納する事を想定しています。
※詳細は講座記事第120回の「メンバ「SessionStartTime」について」セクションを参照。
GenerateTimestamp関数は、講座記事第118回で作った、「日時を生成する独立関数」です。
第1引数に開始時間の「時」情報、第2引数に開始時間の「分」情報を記述します。ここではTimerSegment構造体のメンバである「start_hour」を第1引数に、同じくTimerSegment構造体のメンバである「start_min」を第2引数に指定します。これでGenerateTimestamp関数が生成した日時情報が,SessionStartTimeに格納されました。
同じ要領でトレード終了時間も設定していきます。
SessionEndTimeもCTradeSessionクラスの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変数の固まりを「名前付きブロック」に分割できる機能を持ちます。このプログラムを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関数をメインプログラムで使う」セクションをご覧ください。
現時点での「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関数/夏時間
コメント