時間と日付の操作 P237
この章では、MQL5で時間と日付を扱う方法を学びます。新しいバーが開いたときにのみ注文できるようにするクラスを作成します。また、EAに完全な機能を備えたトレードタイマーを実装できるクラスを作成します。また、定義済みの間隔でアクションを実行できるタイマーイベントについても学習します。
新しいBerが出たら取引する P237
デフォルトではEAは、新しい着信ティックまたは、価格の変化ごとに実行されます。これは、別の方法で取引するようにEAをプログラムしない限り、注文がイントラバーで開かれることを意味します。現在の足のタイムスタンプを静的変数、グローバル変数、またはクラス変数に保存することで、各新しい足の始値でのみ取引するようにEAをプログラムできます。現在のバーのタイムスタンプが変わると、新しいバーが開いたことがわかります。この時点で、注文の開閉条件を確認できます。
新しいバーの始値で取引する場合、取引決定を行う際に前のバーの終値と指標値を使用します。過去の取引のチャートを見ている場合、取引基準は取引が開始されたバーではなく、前のバーに基づいていることに注意してください!
ストラテジーテスターには、この動作を模倣する始値のみの実行モードがあることに注意してください。ストラテジーが新しいバーの開始時にのみ注文をオープンおよびクローズする場合、始値のみを使用してEAの迅速かつ正確なテストを実行できます。
CNewBarクラス P237
現在のバーのタイムスタンプを追跡し、新しいバーがいつ開いたかを判断できるようにするクラスを作成しましょう。\MQL5\Include\Mql5Book\Timer.mqhという名前の新しいインクルードファイルにこのクラスを作成します。この章のすべてのクラスと関数は、このファイルに入ります。
CNewBerクラスのクラス宣言は次のとおりです。
class CNewBer
{
private:
datetime Time[], LastTime;
public:
void CNewBar();
bool CheckNewBar(string pSymbol, ENUM_TIMEFRAMES pTimeframe);
};
クラスには2つのプライベート変数があります。Time[]配列は現在のバーのタイムスタンプを格納し、LastTime変数は最後にチェックされたバーのタイムスタンプを含みます。同じ型を共有しているため、両方の変数を同じ行で宣言したことに注意してください。これら2つの値を比較して、新しいバーが開いたかどうかを判断します。このクラスには、コンストラクターとCheckNewBar()関数も含まれています。クラスコンストラクターは、単にTime[]配列を系列配列として設定します。
void CNewBar : : CNewBar(void)
{
ArraySetAsSeries(Time, true);
}
CheckNewBar()関数は、新しいバーのオープンをチェックするために使用されます。関数宣言は次のとおりです。
bool CNewBar :: CheckNewBar(string pSymbol, ENUM_TIMEFRAMES pTimeframe)
{
bool firstRun = false, newBar = false;
CopyTime(pSymbol, pTimeframe, 0, 2, Time);
if(LastTime == 0) firstRun = true;
if(Time[0] > LastTime
{
if(firstRun == false) newBar = true;
LastTime = Time[0];
}
return(newBar);
}
この関数は、使用しているチャートのシンボルと期間の2つのパラメータを取ります。firstRunとnewBarという2つのブール変数を初期化し、明示的にfalseに設定します。CopyTime()関数はTime[]配列を最新バーのタイムスタンプで更新します。
LastTimeクラス変数をチェックして、値が割り当てられているかどうかを確認します。この関数を始めて実行すると、LastTimeは0になり、firstRun変数をtrueに設定します。これはEAをチャートに添付したことを示しています。EAがイントラバーを取引することを望まないため、これはすぐに注文を開始しようとしないことを確認するために必要なチェックです。
次に、Time[0] > LastTimeかどうかを確認します。この関数を始めて実行する場合、この式はtureになります。この式は、新しいバーが開くたびにtrueと評価されます。現在のバーのタイムスタンプが変更された場合、firstRunがfalseに設定されているかどうかを確認します。その場合、newBarをtureに設定します。LastTime変数を現在のバーのタイムスタンプで更新し、newBarの値をプログラムに返します。
CheckNewBar()の実行は次のように機能します。EAが最初にチャートにアタッチされると、firstRunがtrueに設定されるため、関数はfalseの値を返します。関数の後続の各チェックでは、firstRunは常にfalseになります。現在のバーのタイムスタンプ(Time[0])がLastTimeより大きい場合、newBarをtureに設定し、LastTimeの値を更新して、trueの値をプログラムに返します。これは、新しいバーが開いたことを示しており、取引条件を確認できます。
EAでCNewBarクラスを使用する方法を次に示します。
//ファイルとオブジェクトの宣言を含める
#include <Mql5Book\Time.mqh>
CNewBar NewBar;
//入力変数
input bool TradeOnNewBar = true;
//OnTick()イベントハンドラ
bool newBar = true;
int barShift = 0;
if(TrandeOnNewBar == true)
{
newBar = NewBar. CheckNewBar(_Symbol, _Period);
barShift = 1;
}
if(newBar == true)
{
//発注コード
}
NewBarという名前のCNewBarクラスに基づいてオブジェクトを宣言します。TradeOnNewBar入力変数を使用すると、新しい足で取引するか、すべてのティックで取引するかを選択できます。OnTick()イベントハンドラで、newBarという名前のローカルbool変数を宣言してtrueに初期化し、barShiftという名前のint変数を宣言します。
TradeOnNewBarがtrueに設定されている場合、CheckNewBar()関数を呼び出し、戻り値をnewBar変数に保存します。ほとんどの場合、この値はfalseになります。また、barShiftの値を1に設定します。TradeOnNewBarがfalseに設定されている場合、newBarはtrueになり、barShiftは0になります。
newBarがtrueの場合、先に進み、発注条件を確認します。新しいバーの開始時にのみ実行したいコードは、これらの括弧内に入ります。これには、すべての注文開始条件と、場合によっては終了条件も含まれます。
CNewBarクラスを使用して新しいバーのオープンを確認する場合、バーのShift変数を使用して、価格またはインジケータ値のバーインデックスを設定します。例えば、前の章で定義した移動平均関数と終値関数を使用します。
doble close = Price. Close(barShift);
double ma = MA.Main(barShift);
TradeOnNewBarがfalseに設定されている場合、close変数とma変数には、現在の足の値(barShift = 0)が割り当てられ、TradeOnNewBarがtrueに設定されている場合、前の足の値(barShift =1)が割り当てられます。
日時型 P240
前の16ページでdatetime型について説明しました。datetime型は、日付と時刻の値を保持するために使用されます。Datetime変数では、日付と時刻は1970年1月1日の午前0時からの経過秒数として表されます。これにより、2つの日時値を簡単に比較し、数学的に操作できます。
例えば、data1とdate2という2つの日時変数があるとします。date1は2012年7月12日3:00に等しく、date2は2012年7月14日22:00に等しいことがわかっています。どちらの日付が早いか知りたい場合は、2つを比較できます。
datetime date1 = D’2012.07.12 03:00’;
datetime date2 = D’2012.07.14 22:00’;
if(date1 < date2)
{
Print(“date1の方が早い”) ; //真実
}
else if(date1 > date2)
{
Print(“date2の方が早い”);
}
正しい結果は「date1の方が早い」です。日時値を操作するもう1つの利点は、時間、日、または任意の期間を加算また減算する場合、適切な秒数を日時値に単純に加算または減算できることです。たとえば、上記のdate1変数に24時間を追加します。
datetime addDay = date1 + 86400; //結果 : 2012.07.13 03:00
24時間は86,400秒です。datetime値に86400を追加すると、日付が正確に24時間進みます。date1変数の値は、2012年7月13日3:00に等しくなりました。
日時値は人間が読めるものではありませんが、MetaTrader5ターミナルは日時値をログに出力する際に読み取り可能な文字列定数に自動的に変換します。他の目的で日時値を文字列に変換する必要がある場合、TimeToStirng()関数は日時変数をyyy.mm.dd.hh:mm形式の文字列に変換します。この例では、TimeToString()関数を使用してdate2を変換します。
string convert = TimeToString(date2);
Print(convert); //結果 : 2012.07.14 22:00
TimeToString()関数は、date2変数を文字列に変換し、結果をconvert変数に保存します。convertの値を出力すると、2012.07.14 22:00になります。
yyy.mm.dd hh:mm:ssの形式で文字列を作成し、StringToTime()関数を使用してそれをdatetime変数に変換することで、datetime値を作成できます。例えば、文字列2012.07.14 22:00を日時値に変換する場合、文字列をStringToTime()関数に渡します。
string dtConst = “2012.07.14 22:00”;
datetime dtDate = StringToTime(dtConst);
dtDate変数には、2012.07.14 22:00に相当する日時値が割り当てられます。文字列を一重引用符で囲み、先頭に大文字のDを付けることで、datetime定数を作成することもできます。このdatetime定数は、datetime変数に直接割り当てることができます。
datatime dtDate = D’2012.07.14 22:00’;
このメソッドにより、datetime定数文字列を構築する際の柔軟性が向上します。たとえば、日付形式としてdd.mm.yyyyを使用し、時刻を省略できます。言語の基本 > データ型 > 整数型 > 日時型の下のMQL5リファレンスの日時定数のトピックには、日時定数のフォーマットに関する詳細情報があります。
最後に、MqlDateTime構造体を使用できます。この構造により、日時変数から時間や曜日などの特定の情報を取得できます。
MQLDateTime構造体 P241
MqlDateTime構造体には、日時値に関する情報を保持する変数が含まれています。MQL5リファレンスからの構造定義は次のとおりです。
Struct MqlDateTime
{
int yaer; // 年
int mon; // 月
int day; // 日
int hour; // 時間
int min; // 分
int sec; // 秒
int day_of_week; // 曜日(0-日曜日、1-月曜日、・・・6-土曜日)
int day_of_year; // 年間通算日(1月1日 = 0)
};
MqlDataTime構造体を使用すると、datetime値から任意の日付または時刻要素を取得できます。時間、分、または日を取得して、その値を整数変数に割り当てることができます。曜日や年間通算日を取得することもできます。これらの変数に値を割り当てて、datetime値を作成することもできます。
TimeToStruct()関数は、datetime値をMqlDateTime型のオブジェクトに変換するために使用されます。関数の最初のパラメータは、変換する日時値です。2番目のパラメータは、値をロードするMqlDateTimeオブジェクトです。次に例を示します。
datetime dtTime = D’2012.10.15 16:36:23’;
MqlDateTime timeStruct;
TimeToStruct(dtTime,timeStruct);
int day = timeStruct.day;
int hour = timeStruct.hour;
int dayOfWeek = timeStruct.day_of_week;
まず、日時定数を使用して日時変数dtTimeを作成します。次に、timeStructという名前のMqlDataTimeのオブジェクトを宣言します。TimeToStruct()関数を使用して、dtTimeの日時値をMqlDataTime構造体に変換します。最後に、timeStructオブジェクトから曜日、時間、曜日を取得する方法を示します。日は15、時間は16、dayOfWeekは月曜日の1です。
MqlDateTimeオブジェクトを作成し、そのメンバ変数に値を割り当て、StructToTime()関数を使用して日時値を構築することもできます。MqlDateTimeオブジェクトを使用して日時値を作成する場合、day_of_weekおよびday_of_year変数は使用されません。StructToTime()関数を使用して日時値を作成する方法を次に示します。
MqlDateTime timeStruct;
timeStruct.year = 2012;
timeStruct.mon = 10;
timeStruct.day = 20;
timeStruct.hour = 2;
timeStruct.min = 30;
timeStruct.sec = 0;
datetime dtTime = StructToTime(timeStruct);
MqlDateTimeオブジェクトのすべての変数に値を明示的に割り当ててください。そうしないと、期待する結果が得られない可能性があります!StructToTime()関数は、timeStructオブジェクトを日時値に変換し、結果をdtTime変数に格納します。dtTime変数を出力すると、timeStructオブジェクトに割り当てた値を確認できます。
Print(dtTime); // 結果 : 2012.10.20 02:30:00
MqlDateTimeオブジェクト変数に値を割り当てなかった場合、または無効な値を割り当てた場合はどうなりますか?
MqlDateTime timeStruct;
//月と日のゼロ
timeStruct.year = 2012;
timeStruct.mon = 0;
timeStruct.day = 0;
timeStruct.hour = 0;
timeStruct.min = 0;
timeStruct.sec =0;
datetime dtTime = StructToTime(timeStruct);
Print(TimeToString(dtTime)); // 結果 : 2012.01.01 00:00
//無効な月と日
timeStruct.mon = 13;
timeStruct.day = 32;
dtTime = StructToTime(timeStruct);
Print(TimeToStruct(dtTime)); // 結果 : 2012.12.31 00:00
年を指定する必要があります。そうしないと、StructToTime()関数はなにも返しません。月または日が1未満の場合、デフォルト値は1になります。月が最大値の12より大きい場合、デフォルトは12月(12)になります。日がその月の日数よりも大きい場合、その月に31日ある場合はデフォルトで31になります。そうでない場合は、超過した値が良く月にオーバーフローします。いうまでもなく、day変数をオーバーフローさせないでください。そうしないと、日付が数日ズレてします可能性があります。無効な時間または分の値はデフォルトで最小値と最大値がそれぞれ0と59になります。
TreadTimerクラスの作成 P244
Datetime値を作成および変換する方法を学習したので、この情報を使用してEAのトレードタイマークラスを作成できます。開始時間と終了時間の日時値を作成する必要があります。通常、現在の日または週に含まれる時間を扱うため、現在の日付の指定された時間の日時値を計算する関数を作成します。その後、必要に応じてその値を操作して特定の日付を取得できます。
CreateDateTime()関数 P244
現在の日付の日時値を作成する関数を作成しましょう。時間と分を指定すると、関数は現在の日付のその時間の値を返します。MqlDateTimeオブジェクトを使用して日時値を作成します。
この章のすべてのコードは\MQL5\Include\Mql5Book\Timer.mqhインクルードファイルに入ります。CreateDateTime()関数のコードは次のとおりです。
datetime CreateDateTime(int pHour = 0, int pMinute = 0)
{
MqlDateTime timeStruct;
TimeToStruct(TimeCurrent(), timeStruct);
timeStruct.hour = pHour;
timeStruct.min = pMinute;
datetime useTime = StructToTime(timeStruct);
return(useTime);
}
まず、timeStructという名前のMqlDateTimeオブジェクトを作成します。TimeToStruct()関数を使用して、TimeCurrent()関数を使用してメンバー変数に現在の日付と時刻を入力します。次に、pHourおよびpMinuteパラメータ値をtimeStructオブジェクトのhourおよびmin変数に割り当てます。最後に、StructToTime()を使用して新しい日時値を作成し、結果をuseTime変数に割り当てます。useTimeの値がプログラムに返されます。
指定された時刻の日時値を取得したら、必要に応じてこの値を簡単に操作できます。例えば、取引タイマーを毎日20:00に開始したいとします。翌日8:00にその日の取引を停止します。まず、CreateDateTime()関数を使用して、開始時刻と終了時刻の日時値を作成します。
datetime startTime = CreateDateTime(22, 0);
datetime endTime = CreateDateTime(8, 0);
Print(“Start: ”, startTime,”, End: ”, endTime);
// 結果 : Start: 2012.10.22 22:00:00, End: 2012.10.22 08:00:00
ここまでの問題は、終了時刻が開始時刻よりも早いことです。今日が10月22日で、タイマーを20:00に開始すると仮定すると、正しい時刻を取得するには、終了時刻を24時間進める必要があります。この章の前半で、24時間は86400秒に等しいことを思い出してください。この値をendTimeに追加するだけで、正しい時刻を取得できます。
86400秒が1日であることを覚えておく代わりに、datetime変数から時間を加算または減算する必要がある場合に簡単に参照できるいくつかの定数を定義しましょう。これらは、Timer.mqhインクルードファイルの先頭に配置されます。
#define TIME_ADD_MINUTE 60
#define TIME_ADD_HOUR 3600
#define TIME_ADD_DAY 86400
#define TIME_ADD_WEEK 604800
これらの定数を使用すると、指定した期間を簡単に加算または減算できます。endTime変数に24時間を追加しましょう。
endTime +=TIME_ADD_DAY;
Print(“Start: ”, startTime, ” End: ”, endTime);
// 結果 : 開始: 2012.10.22 22:00:00, 終了: 2012.10.23 08:00:00
これで、開始時刻と終了時刻が互いに正解になりました。
CTimeクラス P245
もっとも単純なタイプのトレードタイマーを調べてみましょう。このタイマーは2つのdatetime値を取り、現在の時刻がそれらの値の間にあるかどうかを判断します。もしそうなら、私たちの取引タイマーがアクティブであり、取引を開始することができます。現在の時間がこれらの値の外にある場合、取引タイマーは非アクティブであり、取引は行われません。
CTimerクラスは、タイマー関連の関数を保持します。取引が許可されているかどうかを確認するために2つの日時変数をチェックするCheckTimer()関数の1つの関数から始めます。後で他のクラスメンバーを追加します。
class CTimer
{
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
};
CheckTimer()関数の本体は次の通りです。
bool CTimer::CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
{
if(pStartTime >= pEndTime)
{
Alert(“Error: Invalid start or end time”);
return(false);
}
datetime currentTime;
if(pLocalTime == true) currentTime = TimeLocal();
else currentTime = TimeCurrent();
bool timeOn = false;
if(currentTime >= pStartTime && currentTime < pEndTime)
{
timerOn = true;
}
Return(timerOn);
}
pStartTimeパラメータは取引開始時刻を保持し、pEndTimeパラメータは取引終了時刻を保持します。pLocalTimeパラメータは、ローカル時刻とサーバー時刻のどちらかを使用するかを決定するブール値です。
まず、pStartTimeがpEndTime以上かどうかを確認します。その場合、開始時刻と終了時刻が無効であることがわかっているため、エラーメッセージを表示してfalseの値で終了します。それ以外の場合は、タイマーの状態をチェックし続けます。
現在の時刻を保持するcurrentTimeという名前のdatetime変数を宣言します。pLocalTimeがtureの場合、ローカルコンピュータの時刻が使用されます。falseの場合は、サーバー時間を使用します。TimeLocal()およびTimeCurrent()関数はそれぞれローカルコンピュータまたはサーバーから現在の時刻を取得します。通常はサーバー時刻を使用しますが、ユーザーがローカル時刻(現地時間)を使用できるようにするための単純な追加であるため、オプションを追加しました。
currentTime変数に現在の時間が割り当てられると、それを取引の開始時間と終了時間と比較します。currentTime >= pStartTimeかつcurrentTime < pEndTimeの場合、トレードタイマーはアクティブです。timeOn変数をtureに設定し、その値をプログラムに返します。
CheckTimer()を使用して簡単なトレードタイマーを作成する方法を示しましょう。2つの日時入力変数を使用します。MetaTrader5インターフェースにより、有効な日時値を簡単に入力できます。開始時刻と終了時刻を設定し、CheckTimer()関数を使用して有効な取引条件を確認します。
#include <Mql5Book/Timer.mqh>
CTimer Timer;
input dateime StartTime = D’2012.10.15 08:00’;
input dateime EndTime = D’2012.10.19 20:00’;
//OnTick()イベントハンドラ
bool timerOn = Timer.CheckTimer(StartTime, EndTime, false);
if(timerOn == true)
{
//発注コード
}
まず、Timer.mqhファイルをインクルードし、CTimerクラスに基づいてTimerオブジェクトを宣言します。入力変数StartTimeとEndTimeには、有効な開始時刻と終了時刻が入力されています。OnTick()イベントハンドラで、CheckTimer()関数を呼び出し、StartTimeおよびEndTime入力変数をそれに渡します。(pLocalTimeパラメータにfalseの値を渡しました。これは、現在の時刻を決定するためにサーバー時刻を使用することを意味します)。
現在の時刻が開始時刻と終了時刻の間にある場合、ブール値のtimerOn変数がtureに設定されます。注文開始条件を確認するときは、timerOnがtrueかどうかを確認します。その場合は、注文条件の確認に進みます。
datetime入力変数を使用して開始時刻と終了時刻を設定する際の問題は、常に更新する必要があることです。たとえば、毎日同じ時間に取引したい場合はどうしますか?
DailyTimer()関数 P247
経験豊富な外国為替トレーダのほとんどは、1日の特定の時間帯が取引に有利であることを知っています。ロンドン市場の開始からニューヨークセッションの終わりまでが1日の中で最も活発な取引期間帯です。取引をこれらの時間に制限することで、EAのパフォーマンスを向上させることができます。
毎日同じ時間に取引できるタイマーを作成します。開始と終了の時間と分を設定します。取引時間を変更しない限り、再度編集する必要はありません。デイリータイマーは、前のセクションの単純なタイマーと同じ原理で動作します。CreatDateTime()関数を使用して2つの日時値を作成します。開始時刻と終了時刻を比較し、必要に応じて1日ずつ増減させます。現在の時間が開始時間と終了時間の間にある場合、取引は有効です。
毎日のタイマー関数をCTimerクラスに追加しましょう。また、2つのプライベートdatetime変数(StartTimeとEndTime)をCTimerクラスに追加します。これらは、プログラムのほかの場所で取得する必要がある場合に備えて、現在の開始時刻と終了時刻を保存するために使用されます。
class CTimer
{
private:
datetime StartTime, EndTime;
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
bool DailyTimer(int pStartHour, int pStartMinute, int pEndHour, int pEndMinute,
bool pLocalTime = false);
};
DailyTimer()関数の本体は次のとおりです。
bool CTimer :: DailyTimer(int pStartHour, int pStartMinute, int pEndHour, int pEndMinute,
bool pLocalTime = false)
{
datetime currentTime;
if(pLocalTime == true) currentTime = TimeLocal();
else currentTime = TimeCurrent();
StartTime = CreateDateTime(pStartHour, pStartMinute);
EndTime = CreateDateTime(pEndHour, pEndMinute);
if(EndTime <= StartTime)
{
StartTime -= TIME_ADD_DAY;
if(currentTime > EndTime)
{
StartTime += TIME_ADD_DAY;
EndTime += TIME_ADD_DAY;
}
}
bool timerOn = CheckTimer(StartTime, EndTime, pLocalTime);
return(timeOn);
}
この関数では、int型の変数pStartHour、pStartMinute、pEndHourおよびpEndMinuteを使用して、開始および終了の時間と分を入力します。また、サーバー時間とローカル時間を選択するためのpLocalTimeパラメータも含まれています。現在の時刻をcurrentTime変数に割り当てた後、CreateDateTime()関数を呼び出し、pStartHour、pStartMinute、pEndHourおよびpEndMinuteを渡します。結果の日時値は、それぞれStartTimeとEndTimeに格納されます。
次に、開始時間と終了時間の値を増減する必要があるかどうかを判断します。まず、EndTime値がStartTime値より小さいかどうかを確認します。その場合、StartTimeの値がEndTimeの値よりも早くなるように、開始時刻から1日を減算します。(TIME_ADD_DAY定数を使用)。currentTimeの値がEndTimeより大きい場合、タイマーはすでに期限切れになっているため、次の日のために設定する必要があります。タイマーが翌日に設定されるように、StartTimeとEndTimeの両方を1日増やします。最後に、pLocalTimeパラメータとともに、StartTimeとEndTimeの値をCheckTimer()関数に渡します。結果はtimerOn変数に保存され、プログラムに返されます。
EAプログラムで毎日のタイマーを使用する方法を示しましょう。
#include <Mql5Book/Timer.mqh>
CTimer Timer;
input bool UseTimer = false;
input int StartHour = 0;
input int StartMinute = 0;
input int EndHour = 0;
input int EndMinute = 0;
int bool UseLocalTime = false;
//OnTick()イベントハンドラー
bool timerOn = true;
if(UseTimer == true)
{
timerOn = Timer.DailyTimer(StartHour, StartMinute, EndHour, EndMinute, UseTime);
}
if(timerOn == true)
{
//発注コード
}
タイマーをオンとオフに切り替えるためのUseTimerという名前の入力変数を追加しました。開始と終了の時間と分、およびローカル/サーバーの時間のための入力もあります。
OnTick()イベントハンドラでは、timerOnというブール型の変数を宣言します(DailyTimer()関数の同盟のローカル変数と混同しないように注意してください)。この変数の値によって、取引が可能かどうかが決まります。最初はtrueに初期化します。UseTimer入力変数がtrueに設定されている場合、DailyTimer()関数に保存されます。timerOnがtrueの場合は取引が開始され、falseの場合は次の取引日まで待機します。
PrintTimerMessage()関数 P250
ユーザーの観点からは、タイマーによって取引が有効になっているかどうかが重要です。タイマーがいつ開始および停止したかをユーザーに通知するために、チャートとログにコメントを書き込む短い関数PrintTimerMessage()を作成しましょう。この関数はCTimerクラスのプライベートメンバーにします。またTimerStartedという名前のプライベート変数を宣言し、オン/オフ状態を保持します。
class CTimer
{
private:
datetime StartTime, EndTime;
bool TimerStarted;
void PrintTimerMessage(bool pTimerOn);
public:
bool CheckTimer(datetime pStartTime, datetime pEndTime, bool pLocalTime = false);
bool DailyTimer(int pStartHour, int pStartMinute, int pEndHour, int pEndMinute,
bool pLocalTime = false);
};
PrintTimerMessage()関数のコードは次のとおりです。
void CTimer : : PrintTimerMessage(bool pTimerOn)
{
if(pTimerOn == true && TimerStarted == false)
{
string message = “Timer started”;
Print(message);
Comment(message);
TimeStarted = true;
}
else if(pTimerOn == false && TimerStarted == true)
{
string message = “Timer stopped”;
Print(message);
Comment(message);
TimeStarted = false;
}
}
この関数は値を返す必要がないため、関数の型はvoidです。pTimerOnパラメータは、DailyTimer()関数からtimerOn変数の値を取得します。pTimerOnがtrueの場合、タイマーがアクティブかどうかを確認し、ログと画面にメッセージを出力します。
図18.1-PrintTimerMessage()関数によって生成されたチャート
TimerStarted変数には、タイマーの起動状態が含まれます。タイマーが最初にアクティブ化され、pTimerOnがtrueの場合、TimerStartedはfalseになります「Timer started」というメッセージをチャートとログに出力し、TimerStartedをtrueに設定します。pTimerOnがfalseで、TimerStartedがtrueの場合、「タイマーが停止しました」というメッセージをチャートとログに出力し、TimerStartedをfalseに設定します。
後は、これをDailyTimer()関数の最後に追加するだけです。
bool timerOn = CheckTimer(StartTime, EndTime, pLocalTime);
printTimerMessage(timerOn);
return(timerOn);
その結果、タイマーがアクティブになったときに1つのメッセージがログに出力され、タイマーが非アクティブになったときに別のメッセージが出力されます。メッセージはチャートのコメント領域にも出力され、別のコメントで上書きされるまでそこに残ります。
BlockTimer()関数 P251
これまでに作成した取引タイマーは、単一の開始時間と終了時間を取ります。ほとんどのトレーダーにとってはデイリータイマーで十分ですが、トレードタイマーをより柔軟に設定する必要がある場合は、BlockTimer()関数を作成しました。これを「ブロックタイマー」と呼びます。これは、EAが毎週取引を許可される時間ブロックをユーザーがいくつか設定するためです。これにより、ユーザーは非農業部門の給与レポートなどの不安定な市場イベントを回避できます。
ENUM_DAY_OF_WEEK列挙を使用して、取引を開始および終了する日を指定します。ユーザーは曜日を選択し、EAの入力で開始と終了の時間と分を指定します。時間ブロックごとに、開始日、開始時間、開始分、終了日、終了時間、終了分が必要です。そのタイマーブロックを使用するかどうかを示すブール変数も必要です。
トレーダーのニーズに応じて、一定のタイマーブロックを入力で指定する必要があります。ほとんどのトレーダーにとって、5ブロックで十分です。各タイマーブロックに必要な変数の数のため、それらすべてを保持する構造体を作成します。この構造体に基づいて、各タイマーブロックのすべての変数を保持する配列を作成します。この配列はBlockTimer()関数に渡され、取引を有効にするかどうかを決定します。
これは、Timer.mqlインクルードファイルで定義する構造です。これは、ファイルの先頭で、グローバルスコープで宣言されます。
struct TimerBlock
{
bool enabled; //タイマーブロックを有効または無効にしました
int start_day; //開始日(1:月曜日…5:金曜日)
int start_hour; //開始時間
int start_min; //開始分
int end_day; //終了日
int end_hour; //終了時間
int end_min; //終了分
};
この構造を使用して、配列オブジェクトにタイマー情報を入力する方法を示しましょう。まず、EAで入力変数を宣言します。それぞれ必要な入力を持つ2つのタイマーブロックを追加します。
sinput string Block1; //タイマーブロック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;
sinput string Block2; //タイマーブロック2
input bool UseTimer2 = true;
input ENUM_DAY_OF_WEEK StartDay2 = 5;
input int StartHour2 = 13;
input int StartMinute2 = 0;
input ENUM_DAY_OF_WEEK EndDay2 = 5;
input int EndHour2 = 18;
input int EndMinute2 = 0;
input bool UseLocalTime = false;
各タイマーブロックには、タイマーブロックのオンとオフを切り替えるブール変数(UseTimer)、ENUM_DAY_OF_WEEK型のStartDay変数とEndDay変数、およびStartHour、StartMinute、EndHour、EndMinute変数があります。sinput修飾子を含むBlock1変数とBlock2変数に注意してください。これらは、情報提供および書式設定の目的で使用される静的入力変数です。変数識別子に続くコメントは、MetaTraderの入力ウィンドウに表示されます。
図18.2-エキスパートアドバイザーの入力タブに表示されるブロックタイマー入力
入力変数を宣言したので、TimerBlock構造体オブジェクトをグローバルスコープで宣言します。
TimerBlock[2];
これは、2つの要素を持つ、blockという名前の配列オブジェクトを宣言します。次に、入力値をこの配列にロードする必要があります。これにはかなりのコードが必要ですが、これを回避する簡単な方法はありません。入力変数をOninit()イベントハンドラ内の配列にコピーします。
//OnInit()イベントハンドラ
block[0].enabled = UseTimer;
block[0].start_day = StartDay;
block[0].start_hour = StartHour;
block[0]. start_min = StartMinute;
block[0]. end_day = EndDay;
block[0].end_hour = EndHour;
block[0].end_min = EndMinute;
block[1].enabled = UseTimer2;
block[1].start_day = StartDay2;
block[1].start_hour = StartHour2;
block[1]. start_min = StartMinute2;
block[1]. end_day = EndDay2;
block[1].end_hour = EndHour2;
block[1].end_min = EndMinute2;
最初のタイマーブロックからのすべての入力変数は、要素0のblock[]配列のメンバー変数にコピーされます。要素1の2番目のタイマーブロックに対しても同じことが行われます。入力値がblock[]配列にコピーされたので、それらをタイマー関数に渡しましょう。
//OnTick()イベントハンドラ
bool timerOn = true;
if(UseTimer == true)
{
timerOn = Timer.BlockTimer(block, UseLocalTime);
}
OnTick()イベントハンドラ内で、注文条件を確認する前にタイマーの状態を確認します。現在の時間がいずれかのタイマーブロック内にある場合、timerOnの値はtrueに設定されます。次に、注文開始条件を確認し、タイマーがアクティブな時に発生するアクションを実行できます。
BlockTimer()関数を調べてみましょう。
bool CTimer : : BlockTimer(TimerBlock &pBlock[], bool pLocalTime = false)
{
MqlDateTime today;
bool timerOn = false;
int timerCount = ArraySize(pBlock);
for(int i = 0; i < timerCount; i++)
{
if(pBlock[i].enabled = false) continue;
StartTime = CreateDateTime(pBlock[i].start_hour, pBlock[i].start_min);
EndTime = CreateDateTime(pBlock[i].end_hour, pBlock[i].end_min);
TimeToStruct(StartTime, today);
int dayShift = pBlock[i].start_day – today.day_of_week;
if(dayshift ! = 0) StartTime +=TIME_ADD_DAY * dayshift;
TimeToStruct(EndTime, today);
dayShift = pBlock[i].end_day – today.day_of_week;
if(dayshift ! = 0) EndTime +=TIME_ADD_DAY * dayshift;
timerOn = CheckTimer(StartTime, EndTime, pLocalTime);
if(timerOn == true) break;
}
PrintTimerMessage(timerOn);
return(timerOn);
}
BlockTimer()関数には2つのパラメータがあります。pBlock[]パラメータは、参照によって渡されるタイマーブロック構造体型の配列オブジェクトを受け取り、pLocalTimeパラメータは、サーバー時刻とローカル時刻のどちらかを決定するブール変数です。
まず、関数で使用する変数を宣言します。todayという名前のMqlDateTimeオブジェクトは、現在の日付の曜日を取得するために使用されます。timerCount変数は、pBlock[]配列のサイズを保持します。これは、ArraySize()関数を使用して取得します。タイマーブロックの処理にはforループが使用されます。タイマーブロックの数は、タイマーのCount変数によって決まります。各タイマーブロックの開始時刻と終了時刻を計算し、現在の時刻がそれらの時刻内にあるかどうかを判断します。時間内にあれば、ループから抜け出し、呼び出しプログラムにtrueの値を返します。
forループ内で最初にチェックするのは、現在のタイマーブロックの有効な変数です。trueの場合、開始時間と終了時間の計算を続行します。CreateDateTime()関数を使用して、pBlock[]オブジェクトのstart_hour、start_min、end_hour、およびend_min変数を使用して、StartTimeおよびEndTime変数を計算します。
次に、開始時間と終了時間に指定された曜日に基づいて、正しい日付を計算します。TimeToStruct()を使用して、StartTimeとEndTimeの値をMqlDateTime構造体に変換します。結果はtodayオブジェクトに保存されます。today.day_of_week変数は、現在の曜日を保持します。現在の曜日がpBlock[]オブジェクトのstart_dayまたはend_dayの値と異なる場合、差を計算してdayShift変数に格納します。dayShift変数はTIME_ADD_DAY定数で乗算され、StartTimeまたはEndTime変数から加算または減算されます。その結果、StartTimeとEndTimeは現在の週を基準とした正しい時刻と日付に設定されます。
現在のタイマーブロックの開始時刻と終了時刻が決定されたら、CheckTimer()関数を使用して、現在の時刻が現在のタイマーブロックの開始時刻と終了時刻の範囲内にあるかどうかを確認します。関数がtrueを返す場合、forループから抜け出します。PrintTimerMessage()関数は、有益なメッセージをチャートとログに出力し、timerOnの値がプログラムに返されます。
柔軟な取引タイマーをプログラミングすることは、これまでに行った中で最も複雑なことです。この章で定義したタイマー関数を使用することで、必要に応じて十分に柔軟な取引タイマーを実装できるはずです。
開始時刻と終了時刻の取得 P255
追加する最後のコード:何らかの理由で、トレードタイマーが使用している現在の開始時刻または終了時刻を確認する必要がある場合は、GetStartTime()およびGetEndTime()関数を使用します。これらの関数は、適切なdatetime変数を取得し、それをプログラムに返します。これらは非常に短い関数なので、クラス宣言で関数全体を宣言できます。
class CTimer
{
private:
datetime StartTime, EndTime;
public:
datetime GetStartTime() {return(StartTime);};
datetime GetEndTime() {return(EndTime);};
};
わかりやすくするために、この例ではCTimerクラスから他の関数と変数を削除しました。関数名の後の括弧内のreturnステートメントに注意してください。これが関数本体です。GetStartTime()およびGetEndTime()の唯一の目的は、StartTimeまたはEndTime変数の値をプログラムに返すことです。値を返すことのみを目的とする短い関数がある場合は、本体をクラス宣言に配置します。
OnTimer()イベントハンドラ P256
これまでのところ、EAではOnInit()およびOnTick()イベントハンドラのみを使用してきました。特定の目的に使用できるオプションのイベントハンドラがいくつかあります。それらの1つは、定義済みの時間間隔で実行されるOnTimer()イベントハンドラです。
OnTick()イベントハンドラは、端末から価格の変更を受信した場合にのみ実行されます。一方、OnTimer()イベントはX秒ごとに実行できます。取引戦略で一定の間隔でアクションを実行する場合は、そのコードをOnTimer()イベントハンドラに配置します。
OnTimer()イベントハンドラを使用するには、EventSetTimer()関数を使用してタイマー間隔を設定する必要があります。この関数は通常、OnInit()イベントハンドラ内またはクラスコンストラクタ内で宣言されます。たとえば、EAに60秒ごとに1回アクションを実行するようにしたい場合、イベントタイマーを次のように設定します。
int OnInit()
{
EventSetTimer(60);
}
これは、プログラムにOnTimer()イベントハンドラが存在する場合、60秒ごとに実行します。MQL5ウイザードを使用して、ソースファイルの作成時にOnTimer()イベントをソースファイルに追加できますが、後でいつでも追加できます。OnTimer()イベントハンドラは、パラメータのないvoid型です。
void OnTimer()
{
}
何かしらの理由でイベントタイマーを停止する必要がある場合、EventKillTimer()はイベントタイマーを削除し、OnTimer()イベントハンドラは実行されなくなります。これは通常、OnDeinit()イベントハンドラまたはクラスデストラクタで宣言されますが、プログラム内のどこでも動作するはずです。
EventKillTimer();
EventKillTimer()関数はパラメータを取らず、値を返しません。