English Deutsch
preview
ニュース取引が簡単に(第4回):パフォーマンス向上

ニュース取引が簡単に(第4回):パフォーマンス向上

MetaTrader 5トレーディングシステム | 14 1月 2025, 10:27
91 0
Kabelo Frans Mampa
Kabelo Frans Mampa

はじめに

前回の記事では、ニュースの影響に基づいて取引を実行するプロセスについて説明しました。このミッションは成功しましたが、記事で使用したコードには重要な欠点がありました。それは、バックテストの速度が比較的遅いことです。この原因は主に、戦略のバックテスト中にメモリ内のデータベースへ頻繁にアクセスしていたことにあります。この問題を解決するため、バックテスト中のデータベースアクセス回数を減らします。必要な情報をその日の分だけメモリ内に取得し、理想的には1日に1回だけデータベースにアクセスするようにします。

さらにパフォーマンスを向上させるために、ニュースイベントを発生時間に基づいてクラスタ化する方法を採用します。具体的には、1日の各時間帯ごとにイベント情報を格納する配列を用意し、それぞれの時間帯専用の情報を保存します。現在の時間帯に関連するイベント情報が必要な場合、switch文を使って該当する配列にアクセスします。この方法により、特定の日や時間帯に多数のニュースイベントが発生する場合でも、EAの実行時間を大幅に短縮できます。この記事では、1つの記事が長くなりすぎないように、これらの解決策を実装するための基盤をコーディングします。


時間変数クラス

このクラスはもともと、ローソク足の時刻を格納する2000個の固定サイズのインデックスを持つ配列を宣言するために使用されていました。この配列は、「ローソク足プロパティクラス」で使用され、格納された時刻と現在のローソク足の時刻を比較することで、新しいローソク足が形成されたかどうかを判別する役割を果たしていました。今回、このクラスを拡張し、時間に関する列挙型を宣言し、整数値を列挙型に変換する関数を導入します。これにより、他のクラスで秒、分、時間を扱う際に制御された方法で操作できるようになります。

クラスのレイアウトは以下の通りです。

//+------------------------------------------------------------------+
//|                                                      NewsTrading |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                            https://www.mql5.com/en/users/kaaiblo |
//+------------------------------------------------------------------+
//--- Enumeration For Hours in a Day
enum HOURLY
  {
   H1=1,//01
   H2=2,//02
   H3=3,//03
   H4=4,//04
   H5=5,//05
   H6=6,//06
   H7=7,//07
   H8=8,//08
   H9=9,//09
   H10=10,//10
   H11=11,//11
   H12=12,//12
   H13=13,//13
   H14=14,//14
   H15=15,//15
   H16=16,//16
   H17=17,//17
   H18=18,//18
   H19=19,//19
   H20=20,//20
   H21=21,//21
   H22=22,//22
   H23=23,//23
   H24=0//00
  };

//--- Enumeration For Minutes in an Hour
enum MINUTELY
  {
   M0,//00
   M1,//01
   M2,//02
   M3,//03
   M4,//04
   M5,//05
   M6,//06
   M7,//07
   M8,//08
   M9,//09
   M10,//10
   M11,//11
   M12,//12
   M13,//13
   M14,//14
   M15,//15
   M16,//16
   M17,//17
   M18,//18
   M19,//19
   M20,//20
   M21,//21
   M22,//22
   M23,//23
   M24,//24
   M25,//25
   M26,//26
   M27,//27
   M28,//28
   M29,//29
   M30,//30
   M31,//31
   M32,//32
   M33,//33
   M34,//34
   M35,//35
   M36,//36
   M37,//37
   M38,//38
   M39,//39
   M40,//40
   M41,//41
   M42,//42
   M43,//43
   M44,//44
   M45,//45
   M46,//46
   M47,//47
   M48,//48
   M49,//49
   M50,//50
   M51,//51
   M52,//52
   M53,//53
   M54,//54
   M55,//55
   M56,//56
   M57,//57
   M58,//58
   M59//59
  };

//--- Enumeration For Seconds Pre-event datetime
enum PRESECONDLY
  {
   Pre_S30=30//30
  };

//--- Enumeration For Seconds in a Minute
enum SECONDLY
  {
   S0,//00
   S1,//01
   S2,//02
   S3,//03
   S4,//04
   S5,//05
   S6,//06
   S7,//07
   S8,//08
   S9,//09
   S10,//10
   S11,//11
   S12,//12
   S13,//13
   S14,//14
   S15,//15
   S16,//16
   S17,//17
   S18,//18
   S19,//19
   S20,//20
   S21,//21
   S22,//22
   S23,//23
   S24,//24
   S25,//25
   S26,//26
   S27,//27
   S28,//28
   S29,//29
   S30,//30
   S31,//31
   S32,//32
   S33,//33
   S34,//34
   S35,//35
   S36,//36
   S37,//37
   S38,//38
   S39,//39
   S40,//40
   S41,//41
   S42,//42
   S43,//43
   S44,//44
   S45,//45
   S46,//46
   S47,//47
   S48,//48
   S49,//49
   S50,//50
   S51,//51
   S52,//52
   S53,//53
   S54,//54
   S55,//55
   S56,//56
   S57,//57
   S58,//58
   S59//59
  };
//+------------------------------------------------------------------+
//|TimeVariables class                                               |
//+------------------------------------------------------------------+
class CTimeVariables
  {
private:
   //--- Array to store candlestick times
   datetime          CandleTime[2000];
public:
                     CTimeVariables(void);
   //--- Set datetime value for an array index
   void              SetTime(uint index,datetime time);
   //--- Get datetime value for an array index
   datetime          GetTime(uint index);
   //--- Convert Integer to the Enumeration HOURLY
   HOURLY            Hourly(uint Hour);
   //--- Convert Integer to the Enumeration MINUTELY
   MINUTELY          Minutely(uint Minute);
   //--- Convert Integer to the Enumeration SECONDLY
   SECONDLY          Secondly(uint Second);
  };
//+------------------------------------------------------------------+
//|Constructor                                                       |
//+------------------------------------------------------------------+
CTimeVariables::CTimeVariables()
  {
//--- Set default datetime values for all indexes in array CandleTime
   for(uint i=0; i<CandleTime.Size(); i++)
     {
      CandleTime[i]=D'1970.01.01';
     }
  }

//+------------------------------------------------------------------+
//|Set datetime value for an array index in array CandleTime         |
//+------------------------------------------------------------------+
void CTimeVariables::SetTime(uint index,datetime time)
  {
   if(index>=0&&index<CandleTime.Size())
     {
      CandleTime[index] = time;
     }
  }

//+------------------------------------------------------------------+
//|Get the datetime value for an array index in array CandleTime     |
//+------------------------------------------------------------------+
datetime CTimeVariables::GetTime(uint index)
  {
   return (index>=0&&index<CandleTime.Size())?CandleTime[index]:datetime(0);
  }

//+------------------------------------------------------------------+
//|Convert Integer to the Enumeration HOURLY                         |
//+------------------------------------------------------------------+
HOURLY CTimeVariables::Hourly(uint Hour)
  {
   return (Hour>23)?HOURLY(0):HOURLY(Hour);
  }

//+------------------------------------------------------------------+
//|Convert Integer to the Enumeration MINUTELY                       |
//+------------------------------------------------------------------+
MINUTELY CTimeVariables::Minutely(uint Minute)
  {
   return (Minute>59)?MINUTELY(0):MINUTELY(Minute);
  }

//+------------------------------------------------------------------+
//|Convert Integer to the Enumeration SECONDLY                       |
//+------------------------------------------------------------------+
SECONDLY CTimeVariables::Secondly(uint Second)
  {
   return (Second>59)?SECONDLY(0):SECONDLY(Second);
  }
//+------------------------------------------------------------------+

以下のコードは、HOURLYという列挙型(enum)を定義しています。列挙型とは、ユーザーが定義するデータ型の一種で、名前が付けられた整数定数の集合から構成されます。特定の値の集合を意味のある名前で表すことで、コードの可読性を向上させる際によく使用されます。

enum HOURLY
  {
   H1=1,  // Represents Hour 01
   H2=2,  // Represents Hour 02
   H3=3,  // Represents Hour 03
   H4=4,  // Represents Hour 04
   H5=5,  // Represents Hour 05
   H6=6,  // Represents Hour 06
   H7=7,  // Represents Hour 07
   H8=8,  // Represents Hour 08
   H9=9,  // Represents Hour 09
   H10=10,  // Represents Hour 10
   H11=11,  // Represents Hour 11
   H12=12,  // Represents Hour 12
   H13=13,  // Represents Hour 13
   H14=14,  // Represents Hour 14
   H15=15,  // Represents Hour 15
   H16=16,  // Represents Hour 16
   H17=17,  // Represents Hour 17
   H18=18,  // Represents Hour 18
   H19=19,  // Represents Hour 19
   H20=20,  // Represents Hour 20
   H21=21,  // Represents Hour 21
   H22=22,  // Represents Hour 22
   H23=23,  // Represents Hour 23
   H24=0    // Represents Hour 00 (Midnight)
  };

値:

列挙内の各値は1日の特定の時間を表しており、最初の時間を示すH1=1から始まり、H2=2、...と続いて、23時間目を示すH23=23まで対応します。また、H24=0は真夜中(00:00)を表します。コード内でH1、H2などの名前を使用することで、時間関連のデータを扱う際により直感的で分かりやすくなります。時間を生の数値で扱うのではなく、私はHOURLYの値を使用することを推奨します。

使用例

HOURLY current_hour = H10; // Setting the current hour to 10:00 AM
if (current_hour == H10) 
{
   Print("The current hour is 10:00 AM");
}

この例では、current_hourにH10という値が代入されており、現在の時刻がH10かどうかを確認し、それに応じてメッセージを表示します。このようにすることで、特に数多くの時間ベースの操作を扱う場合でも、コードがより読みやすく、理解しやすくなります。

MINUTELY(分単位の列挙型)

この列挙型では、1時間に59分があることから、M0 (00)からM59 (59)までの1時間内の各分に対応する定数を定義します。

enum MINUTELY
{
   M0, M1, M2, M3, ..., M59
};

生の整数(例: 0, 1, 2)を直接扱う代わりに、M0やM1といった意味のあるラベルを使用します。

例:

  • M0は分「00」を表す
  • M30は分「30」を表す

PRESECONDLY(イベント前の秒数)

この列挙型では、イベント発生前の時刻に対する相対的な秒数を定義します。たとえば、Pre_S30は特にニュースイベントの30秒前を指します。これは、ニュースイベントを事前にエントリーするための固定値として使用されます。たとえば、14:00にニュースイベントが発生する場合、13:59:30にエントリーを行います。この設定により、イベントの5秒前に取引を開始する従来のオプションは利用できなくなります。

この変更には賛否両論あります。

主な短所

  • カスタマイズ性の低下::この変更のデメリットは、ユーザーやトレーダーが高インパクトなイベント発生前の短期的なボラティリティを考慮し、イベントの5秒前だけ取引を開始したい場合に不便となる点です。エントリータイミングが早すぎると、そのわずかな時間差でストップロスが作動するリスクが生じる可能性があります。このように、ニュース取引では数秒の違いが収益性に大きな影響を与えることがあります。

主な長所

  • 固定スケジュール:この利点は、EAが30秒間隔でのみ取引エントリーを確認するよう設定できる点にあります。これにより、バックテストの実行スピードが大幅に向上します。バックテストの時間が短縮される理由は、コンピュータのリソースを頻繁に使用しなくても済むからです。たとえば、イベントの5秒前に取引を開始したい場合、EAは5秒または1秒ごとに適切なタイミングかどうかをチェックする必要があり、それによってコンピュータのリソースが消費され、バックテスト中のパフォーマンスが低下します。

考慮すべきもう一つの要素は流動性です。インパクトの大きいニュースイベントが近づくにつれ、関連する資産は流動性が低下(illiquid)していきます。その結果、取引のエントリー自体が一般的に不利になることがあります。

流動性

これらは、流動性の低い資産市場での取引を不利にする要因です。

  • スリッページ:資産や銘柄の価格が非常に頻繁に変動するため、ユーザーやトレーダーが希望した価格で市場に参入または退出できなくなり、より不利または予想外の価格で取引が行われてしまいます。スリッページの主な欠点は、その予測不可能性にあります。
  • スプレッド:流動性が低い場合、広いスプレッドが一般的になり、これがトレーダーの収益性を大きく制限します。
  • オフクォート/取引不能:取引が完全に実行されない場合があります。これにより、トレーダーは資産や市場での潜在的に有利な動きから利益を得る機会を失ってしまいます。

ニュースイベントの30秒前にエントリーすることで、5秒前にエントリーしたり、30秒以降にエントリーしたりする場合に比べて、非流動性を回避できる可能性が一貫して高くなります。

enum PRESECONDLY
{
   Pre_S30 = 30 // 30 seconds before an event
};

SECONDLY(秒単位の列挙型)

MINUTELYと同様に、S0(00秒)からS59(59秒)までの範囲で、1分間の各秒を表します。

enum SECONDLY
{
   S0, S1, S2, ..., S59
};

例:

  • S0は秒「00」を表す
  • S30は秒「30」を表す

以下の関数は、整数(Hour)をHOURLY列挙型の対応する値に変換します。この関数は、Hour変数に渡された値が有効(0~23の範囲内)であることを確認し、その後、対応するHOURLY列挙値を返します。

  • 戻り型:HOURLY:この関数は、先ほど説明したHOURLY列挙型の値を返します。この列挙型には、1日の24時間に対応する値が含まれています(例:H1=1、H2=2、...、H23=23、H24=0)。
  • 関数名:Hourly:これは、スコープ解決演算子(::)で示されているように、CTimeVariablesクラスに属するメソッドです。つまり、HourlyはCTimeVariablesクラスの一部です。
  • パラメータ

  • uint Hour:符号なし整数で1時間を表します。この値は関数にパラメータとして渡されます。

HOURLY CTimeVariables::Hourly(uint Hour)
{
   return (Hour>23)?HOURLY(0):HOURLY(Hour);
}

関数内のロジック:

この関数では、三項演算子(? :)が使用されています。これは、if-else文を簡略化した構文で、条件を確認し、その結果に基づいて2つの値のいずれかを返します。

条件:(Hour > 23)

  • Hourの値が23を超えているかどうかを確認します。1日の有効な時間は0~23(24時間)なので、23を超える値は無効です。

trueの場合:HOURLY(0)
  • Hour > 23(無効な時間)の場合、関数はHOURLY(0)を返します。これはH24=0(午前0時または00:00)に対応します。

falseの場合:HOURLY(Hour)

  • Hourが有効範囲(0~23)の場合、その値をHOURLY列挙型の対応する値に変換して返します。たとえば、Hour = 10の場合、関数はHOURLY(10)を返します。これはH10(午前10時)に対応します。

return (Hour>23)?HOURLY(0):HOURLY(Hour);

例:

  • Hourly(10)を呼び出した場合、関数はHOURLY(10)(午前10時の列挙値)を返します。
  • Hourly(25)を呼び出した場合、25は無効な時間なので、関数はHOURLY(0)(00:00または午前0時)を返します。

重要なポイント

  • 無効な時間の処理:この関数は、Hourの値が有効範囲外(23より大きい)であった場合、デフォルトでHOURLY(0)を返します。これは、真夜中(00:00)を意味します。
  • 変換:この関数は、整数の時間をHOURLY列挙型の値に効率的に変換します。これにより、時間に基づくロジックを扱う際のコードがより使いやすくなります。

MINUTELYへの整数変換ユーティリティ関数

この関数は、整数(0~59)をMINUTELY列挙型の値に変換します。入力された整数が59を超える(無効な分の場合)場合、関数はデフォルトでM0(分0)を返します。

MINUTELY CTimeVariables::Minutely(uint Minute)
{
   return (Minute>59)?MINUTELY(0):MINUTELY(Minute);
}

分が59を超える場合は、エラーを防ぐためにフォールバックとしてM0を返します。

整数を秒に変換するユーティリティ関数

この関数は、秒単位で同様の処理をおこないます。整数(0から59まで)をSECONDLY列挙型の値に変換します。整数が59を超える場合、デフォルトはS0(秒0)となります。

SECONDLY CTimeVariables::Secondly(uint Second)
{
   return (Second>59)?SECONDLY(0):SECONDLY(Second);
}

秒が59を超えた場合、無効な入力を適切に処理するためにS0を返します。

重要なポイント

時間精度:TimeVariablesクラスは、取引システムにおける時間管理を簡素化し、ニュース取引戦略に不可欠な分単位や秒単位の精度を扱いやすくします。

柔軟性:列挙型を使用することで、開発者はニュースイベントに基づいた取引実行時間を、各シナリオのために手動で時間をコーディングせずに簡単に調整できます。

実世界での応用:ニュース取引では、経済発表などによる市場のボラティリティという一刻を争うチャンスに迅速に対応することで、取引パフォーマンスが飛躍的に向上します。

予測と洞察:

市場環境が異なることで、TimeVariablesクラスの有用性にも差が生じます。以下の点が例です。

  • ボラティリティの高い市場、特に主要なニュースイベント(例:NFPや中央銀行の発表など)の場合、大きな価格変動がミリ秒単位で発生するため、秒単位の精度が非常に重要です。
  • ボラティリティの低い市場では、価格変動がゆっくり進行するため、分単位の精度でも取引は十分におこなえる可能性があります。


DBアクセス削減

前回の記事では、取引のタイミングを判断するために、イベント情報をメモリ内のデータベースに保存していました。以下にそのデータベースへのアクセス手順を示します。

  • 毎日の開始時に、その日に発生予定のニュースイベントを全て読み込みます。理想的には、このデータは構造体の配列に格納され、プログラムはイベントオブジェクトをチャート上に表示し、イベントの時刻や名称を示します。
  • イベントが発生した、または発生中である場合、EAはメモリ内のデータベースに再度アクセスし、その日中の次の発生するイベント情報を取得します。

しかし、この方法には問題があります。もし同じ日に異なる時間のイベントが複数存在すれば、次に発生するイベント情報を取得するためにデータベースに頻繁にアクセスしなければならなくなります。簡単な解決策は、毎日更新される構造体の配列を使用し、その配列を反復処理して、イベント日付と現在の日付が一致するまでチェックを行う方法です。これにより、適切なタイミングで取引を開始でき、次のイベント情報を取得するためにデータベースを何度もアクセスする必要がなくなります。

このシンプルな解決策で、EAの処理速度は確実に向上します。しかし、同じ構造体配列を反復処理している際、もう一つの問題が発生します。それは、すでに発生したイベントの日付や、現在の日付よりも大きく後のイベントの日付をチェックすることです。この冗長なチェックがEAの速度を制限します。 

例えば、以下のような配列の中に次のような時刻があるとしましょう。

time[0] = '14:00'
time[1] = '14:15'
time[2] = '14:30'
time[3] = '14:45'
time[4] = '14:50'
time[5] = '14:55'
time[6] = '15:00'
time[7] = '15:05'
time[8] = '15:10'
time[9] = '15:15'
time[10] = '15:30'
time[11] = '15:45'
time[12] = '15:55'

現在時刻が午前11時である場合、午後2時という時刻が一致するかどうかを確認する意味はなく、イベント時刻がまだ到達できないときに、チャートの新しいティックごとにこの配列を常に反復することは、EAの動作速度にさらに影響します。これを念頭に置くと、この問題に対する簡単な解決策は、少なくとも配列を異なる時間帯に分け、EAが現在時刻に一致する時間帯の配列だけを繰り返し処理することでしょう。こうすることで、EAは、イベントの時間が少なくとも現在の日付と同じ時間内にある場合のみ確認することで、時間を節約することができます。 

ケーススタディ

この最適化がどれほどパフォーマンス向上に寄与するかを実際の例を通して見ていきましょう。

  • シナリオ:トレーダーは、米国の非農業部門雇用者数(NFP)などの主要ニュースリリースを監視するEAを使用しています。NFPの発表は14:30に予定されています。EAは、ニュース結果に基づいて買い逆指値注文または売り逆指値注文を発注するようにプログラムされています。最適化がおこなわれていない場合、EAは一日中毎秒データベースにアクセスし続け、次のイベントの確認をおこないます。NFP発表の際には、過剰なデータベースクエリによる遅延が発生し、ニュースに迅速に反応できず、最適な取引タイミングを逃すリスクが高まります。
  • DBアクセス削減の解決策:継続的なデータベースアクセスを避けるため、EAは00:00にその日のすべてのイベント情報をロードし、それを時間ごとにセグメント化します。時刻が14:00に近づくと、EAは14時台のイベントのみを確認し、それ以外の時間帯のイベント確認はスキップします。14:30にNFPが発表されると、EAは遅れなく即座に反応し、適切なタイミングで取引をおこなう準備が整います。

TimeSeriesフォルダをプロジェクト内に新規作成し、このフォルダに「1日の各時間ごとの配列を作成する」2つのクラスを格納します。このクラスは、イベント情報を時間ごとに整理し、EAがその時点で関係のあるイベントのみを確認できるようにします。

時系列フォルダ



TimeByHourクラス

以下のコードでは、CTimeByHourクラスを定義しています。このクラスは、1日の各時間の時刻とイベントデータを管理し、取得することを目的としています。このクラスでは、構造体、配列、オブジェクト指向プログラミング(OOP)の概念など、いくつかの要素を使用しています。このクラスの目的は、1日が24時間であるため、各時間に対応する配列オブジェクトを作成することです。つまり、24個の配列オブジェクトを宣言します。これらの配列オブジェクトには、整数型の変数Hour、整数型の変数Minute、およびカレンダー構造体型の変数myEData(この変数には、1日のうちで発生する特定の時間と分のすべてのイベント情報が格納されます)が格納されます。宣言された構造体TimeDateはl日付の時と分を格納し、配列myTimeDataは構造体配列myEventsと並行してデータを格納します。

//+------------------------------------------------------------------+
//|                                                   TimeByHour.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include <Object.mqh>
#include <Arrays\ArrayObj.mqh>
#include "../TimeVariables.mqh"
#include "../CommonVariables.mqh"
#include "../TimeManagement.mqh"
//--- Structure to store time data in Hour and Minute
struct TimeDate
  {
   int               Hour;
   int               Minute;
  } myTimeData[];
//--- Structure array to store event data in parallel with myTimeData array
Calendar myEvents[];
//+------------------------------------------------------------------+
//|TimeByHour class                                                  |
//+------------------------------------------------------------------+
class CTimeByHour:public CObject
  {
private:
   //--- classes' object declarations
   CTimeManagement   CTime;
   CTimeVariables    CTV;

protected:
   //--- class constructor with parameters
                     CTimeByHour(HOURLY myHour,MINUTELY myMinute,Calendar &myEventData):
      //--- Assign integer variables Hour and Minute with time data from myHour and myMinute respectively
                     Hour(int(myHour)),Minute(int(myMinute))
     {
      //--- Assign variable myEData with event info from variable myEventData
      myEData = myEventData;
     }

   virtual void      myTime(Calendar &myNews[]);
   //--- Array object declarations for each hour of the day
   CArrayObj         *myH1,*myH2,*myH3,...,*myH24;
   //--- Integer variables to store time data in hour and minute format
   int               Hour;
   int               Minute;
   //--- Calendar structure variable to store event info
   Calendar          myEData;

public:
   //--- class constructor without parameters
                     CTimeByHour(void)
     {
     }
   //--- Array object variable
   CArrayObj         *getmyTime;
   //--- Retrieve array object for an individual hour
   CObject           *getTime(HOURLY myHour)
     {
      switch(myHour)
        {
         case  H1:
            //--- retrieve array obj for 01 Hour
            return myH1;
            break;
         case H2:
            //--- retrieve array obj for 02 Hour
            return myH2;
            break;
         case H3:
            //--- retrieve array obj for 03 Hour
            return myH3;
            break;
         // ...
         default:
            //--- retrieve array obj for 24|00 Hour
            return myH24;
            break;
        }
     }
   //--- class pointer variable
   CTimeByHour        *myClass;
   //--- class destructor
                    ~CTimeByHour(void)
     {
      //--- delete all pointer variables
      delete getmyTime;
      delete myClass;
      delete myH1;
      delete myH2;
      delete myH3;
      // ...
     }
   //--- Function to retrieve timedata and calendar info for a specific hour of the day via parameters passed by reference
   void              GetDataForHour(HOURLY myHour,TimeDate &TimeData[],Calendar &Events[])
     {
      //--- Clean arrays
      ArrayRemove(TimeData,0,WHOLE_ARRAY);
      ArrayRemove(Events,0,WHOLE_ARRAY);
      //--- retrieve array object for the specific hour
      getmyTime = getTime(myHour);
      // Iterate through all the items in the list.
      for(int i=0; i<getmyTime.Total(); i++)
        {
         // Access class obj via array obj index
         myClass = getmyTime.At(i);
         //Re-adjust arrays' sizes
         ArrayResize(TimeData,i+1,i+2);
         ArrayResize(Events,i+1,i+2);
         //--- Assign values to arrays' index
         TimeData[i].Hour = myClass.Hour;
         TimeData[i].Minute = myClass.Minute;
         Events[i] = myClass.myEData;
        }
     }
  };
//+------------------------------------------------------------------+
    

構造体

  • TimeDate:HourとMinuteを整数フィールドとして持つ、時刻データを格納する構造体
  • myTimeData[]:複数時間分の時刻データを保持するTimeDate構造体の配列

struct TimeDate
{
   int Hour;
   int Minute;
} myTimeData[];

  • myEvents[]:イベントデータをmyTimeDataと並列に格納するためのCalendar型の配列

Calendar myEvents[];

クラスCTimeByHourの宣言

  • CTimeByHour:CObjectを継承したクラス。このクラスは、時間とイベントのデータを時間ごとに管理します。

class CTimeByHour:public CObject

Privateメンバー

  • CTimeManagementとCTimeVariables:これらは、TimeManagement.mqhとTimeVariables.mqhに含まれるカスタムクラス(CTimeManagement、CTimeVariables)のオブジェクトで、時間関連のデータと変数を管理します。

private:
   CTimeManagement   CTime;
   CTimeVariables    CTV;

コンストラクタ

  • これはクラスのパラメータ化されたコンストラクタです。HOURLY列挙型とMINUTELY列挙型を使って2つの整数変数(HourとMinute)を初期化し、イベント情報(myEventData)をmyEDataに代入します。

protected:
   CTimeByHour(HOURLY myHour, MINUTELY myMinute, Calendar &myEventData):
      Hour(int(myHour)), Minute(int(myMinute))
   {
      myEData = myEventData;
   }

データメンバー

  • myH1 ... myH24:CArrayObjオブジェクトへのポインタで、それぞれがその日の特定の時間(01から24まで)に対応します。各CArrayObjは、特定の時間のオブジェクトの配列を保持します。
  • HourおよびMinute:時間を格納するための整数変数
  • myEData:イベント情報を格納するCalendarオブジェクト

   CArrayObj *myH1, *myH2, ..., *myH24;
   int Hour;
   int Minute;
   Calendar myEData;

Publicメソッド

  • CTimeByHour(void):何も初期化しないデフォルトのコンストラクタ
  • getmyTime:特定の時間の時刻データを保持する配列オブジェクトへのポインタ

public:
   CTimeByHour(void) {}
   CArrayObj *getmyTime;

時間分の配列オブジェクトを取得します。

  • getTime(HOURLY myHour):switchステートメントを使用して、HOURLY列挙型に基づく特定の時間帯の適切なCArrayObjオブジェクトを取得するメソッド。各ケースは1時間に対応します(例:H1、H2、...H24)。

CObject *getTime(HOURLY myHour)
{
   switch(myHour)
   {
      case H1: return myH1;
      case H2: return myH2;
      ...
      case H24: return myH24;
   }
}

デストラクタ

  • ~CTimeByHour(void):デストラクタは、CArrayObjポインタ(myH1 ... myH24)と他のクラスポインタに対してdeleteを呼び出すことで、動的に割り当てられたメモリをクリーンアップします。

~CTimeByHour(void)
{
   delete getmyTime;
   delete myClass;
   delete myH1, myH2, ..., myH24;
}

特定の時間のデータを取得します。

  • GetDataForHour:このメソッドは、特定の時間(myHour)の時間とイベントデータを取得します。
  • ArrayRemove:配列(TimeData[]、Events[])をクリアします。
  • getmyTime = getTime(myHour):指定された時間の配列オブジェクトを取得します。
  • forループ:取得されたCArrayObjのすべての項目(すなわち、各エントリーの時間とイベントデータ)を繰り返し処理します。
  • ArrayResize:新しいデータに合わせて、配列(TimeData[]、Events[])のサイズを動的に変更します。
  • myClass:配列内の、現在処理中のオブジェクトを指します。

各オブジェクトに対して、このメソッドはHour、Minute、myEDataをTimeData[]配列とEvents[]配列の対応するインデックスに代入します。

void GetDataForHour(HOURLY myHour, TimeDate &TimeData[], Calendar &Events[])
{
   ArrayRemove(TimeData, 0, WHOLE_ARRAY);
   ArrayRemove(Events, 0, WHOLE_ARRAY);

   getmyTime = getTime(myHour);
   
   for(int i = 0; i < getmyTime.Total(); i++)
   {
      myClass = getmyTime.At(i);
      ArrayResize(TimeData, i + 1);
      ArrayResize(Events, i + 1);

      TimeData[i].Hour = myClass.Hour;
      TimeData[i].Minute = myClass.Minute;
      Events[i] = myClass.myEData;
   }
}



TimeByDayクラス

このクラスは、TimeByHourヘッダーファイルの中で先に宣言された配列オブジェクトに値を代入したり、これらの値を取得したり、関連する配列オブジェクトに格納されている特定の時間と分についてソートしたりする役割を担います。コードは他のファイルをインポートすることから始まります。時間レベルの時間データを扱うTimeByHour.mqhと、共有定数と変数を含むCommonVariables.mqh。 CTimeByDayクラスはCTimeByHourを継承しています。このクラスは日別の時間データを扱い、CTimeByHourによって管理される時間別の時間データとのインタラクションを可能にします。

//+------------------------------------------------------------------+
//|                                                    TimeByDay.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "TimeByHour.mqh"
#include "../CommonVariables.mqh"
//+------------------------------------------------------------------+
//|TimeByDay class                                                   |
//+------------------------------------------------------------------+
class CTimeByDay:private CTimeByHour
  {
private:
   //--- Function to clear all array objects
   void              Clear();
   //--- Function to clean array dates in accordance to the current minute
   void              DatePerMinute(TimeDate &TData[],Calendar &EData[],MINUTELY min,TimeDate &TimeData[],Calendar &EventData[])
     {
      //--- Iterate through all the idexes in TData array
      for(uint i=0;i<TData.Size();i++)
        {
         //--- Check if Minutes match
         if(TData[i].Minute==int(min))
           {
            //--- Resize arrays
            ArrayResize(TimeData,TimeData.Size()+1,TimeData.Size()+2);
            ArrayResize(EventData,EventData.Size()+1,EventData.Size()+2);
            //--- Assign data from each array to the other
            TimeData[TimeData.Size()-1] = TData[i];
            EventData[EventData.Size()-1] = EData[i];
           }
        }
     }
public:
   //--- Function to set time for array objects based on calendar array myNews
   void              SetmyTime(Calendar &myNews[])
     {
      //--- Clear previous data stored in array objects
      Clear();
      //--- clean arrays in parallel declared in TimeByHour header file
      ArrayRemove(myTimeData,0,WHOLE_ARRAY);
      ArrayRemove(myEvents,0,WHOLE_ARRAY);
      //--- Set new values to each array object accordingly
      myTime(myNews);
     }
   //--- Function to get time for the specific hour and minute for news events
   void              GetmyTime(HOURLY myHour,MINUTELY myMinute,TimeDate &TimeData[],Calendar &Events[])
     {
      //--- clean arrays in parallel declared in TimeByHour header file
      ArrayRemove(myTimeData,0,WHOLE_ARRAY);
      ArrayRemove(myEvents,0,WHOLE_ARRAY);
      //--- Declare temporary arrays to get news data for a specific hour
      TimeDate myTData[];
      Calendar myData[];
      //--- Get Data for the specific hour of the day
      GetDataForHour(myHour,myTData,myData);
      //--- Filter the Data for a specific Minute of the hour
      DatePerMinute(myTData,myData,myMinute,TimeData,Events);
      //--- Clear data from the temporary array variables
      ArrayRemove(myTData,0,WHOLE_ARRAY);
      ArrayRemove(myData,0,WHOLE_ARRAY);
     }
public:
   //--- Class constructor
                     CTimeByDay(void)
     {
      //--- Initialize array objects
      myH1 = new CArrayObj();
      myH2 = new CArrayObj();
      myH3 = new CArrayObj();
      //...
     }
   //--- Class destructor
                    ~CTimeByDay(void)
     {
     }
  };

//+------------------------------------------------------------------+
//|Add data to Array Objects for each Hour of the day                |
//+------------------------------------------------------------------+
void              CTimeByHour::myTime(Calendar &myNews[])
  {
//--- Iterate through myNews calendar array
   for(uint i=0;i<myNews.Size();i++)
     {
      //--- Assign datetime from myNews calendar array
      datetime Date = datetime(myNews[i].EventDate);
      //--- Assign HOURLY Enumeration value from datetime variable Date
      HOURLY myHour = CTV.Hourly(CTime.ReturnHour(Date));
      //--- Assign MINUTELY Enumeration value from datetime variable Date
      MINUTELY myMinute = CTV.Minutely(CTime.ReturnMinute(Date));
      //--- Switch statement to identify each value scenario for myHour
      switch(myHour)
        {
         case  H1:
            //--- add array obj values for 01 Hour
            myH1.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
            break;
         case H2:
            //--- add array obj values for 02 Hour
            myH2.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
            break;
         case H3:
            //--- add array obj values for 03 Hour
            myH3.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
            break;
         //...
         default:
            //--- add array obj values for 24|00 Hour
            myH24.Add(new CTimeByHour(myHour,myMinute,myNews[i]));
            break;
        }
     }
  }

//+------------------------------------------------------------------+
//|Clear Data in Array Objects                                       |
//+------------------------------------------------------------------+
void CTimeByDay::Clear(void)
  {
//--- Empty all array objects
   myH1.Clear();
   myH2.Clear();
   myH3.Clear();
   //...
  }
//+------------------------------------------------------------------+

Private関数

  • この関数は、1時間ごとの時刻データを格納するすべての配列オブジェクトをクリア(またはリセット)するために使用されます。

void Clear();

以下の関数は、時刻データ(TData[])とカレンダーイベントデータ(EData[])をフィルタリングし、特定の分(min引数で表される)に一致するエントリのみを保持します。

  • TData[]を繰り返し処理し、各要素について分がminに一致するかどうかを確認します。もし一致すれば、配列TimeData[]とEventData[]のサイズが変更され、TData[]とEData[]から対応するデータがコピーされます。

void DatePerMinute(TimeDate &TData[], Calendar &EData[], MINUTELY min, TimeDate &TimeData[], Calendar &EventData[]);

Public関数

以下の関数は、その日の新しい時刻とイベントデータをリセットして割り当てます。これはCTimeByHourのmyTimeメソッドを使用し、myNews[]で渡されたニュースイベントに基づいて時間レベルの時間データを処理します。

  • まず、Clear()を使用して、以前に保存された時間とイベントのデータをクリアします。
  • 次に、並列配列(myTimeDataとmyEvents)からすべてのデータを削除し、CTimeByHourから継承したmyTime()関数を使用して新しい値を設定します。

void SetmyTime(Calendar &myNews[]);

以下の関数は、特定の時間(myHour)と分(myMinute)の時刻データを取得します。

  • まず、配列myTimeDataとmyEventsをクリアします。
  • 一時的な配列myTData[]とmyData[]が、時間とイベントのデータを保持するために宣言されています。
  • GetDataForHour()が呼び出され、一時配列に指定された時間のデータが格納されます。
  • データはさらに、DatePerMinute()を使用して特定の分についてフィルタリングされます。

void GetmyTime(HOURLY myHour, MINUTELY myMinute, TimeDate &TimeData[], Calendar &Events[]);

コンストラクタ

  • コンストラクタは、1日の各時間(1~24)の配列オブジェクトを初期化します。これらの配列オブジェクトには、特定の時間帯の時刻とイベントデータが格納されます。

CTimeByDay(void)
{
   // Initialize array objects for each hour
   myH1 = new CArrayObj();
   myH2 = new CArrayObj();
   // (Initializes for all 24 hours)
}

CTimeByHour::myTime()

この関数はCTimeByHourで定義され、CTimeByDayで継承されます。myNews[]配列を処理し、イベントを特定の時間帯に関連付けます。

  • myNews[]内の各イベントについて、そのイベントの時間と分を抽出します。
  • switchステートメントを使用して、どの時系列配列オブジェクト(例えば、myH1、myH2など)に時間とイベントデータを格納するかを決定します。
  • 各時間イベントはCTimeByHourオブジェクトとして対応する配列オブジェクトに追加されます。

for(uint i=0; i<myNews.Size(); i++)
{
   datetime Date = datetime(myNews[i].EventDate);
   HOURLY myHour = CTV.Hourly(CTime.ReturnHour(Date));
   MINUTELY myMinute = CTV.Minutely(CTime.ReturnMinute(Date));
   // Switch case to handle different hours
}

Clear()関数

  • この関数は、すべての時系列配列オブジェクト(myH1、myH2など)をクリアし、実質的に各時刻に保存されている時刻データをリセットします。

void CTimeByDay::Clear(void)
{
   // Clears all array objects
   myH1.Clear();
   myH2.Clear();
   // (Clears all 24 hours)
}



結論

この記事では、イベント時刻を1日の各時間ごとに別々の配列に分割し、メモリ内のカレンダーデータベースにアクセスする頻度を減らすことで、EAのパフォーマンスを向上させる方法を示しました。 時間の値を列挙型(MINUTELY、SECONDLY)として構造化することで、コードが管理しやすく、解釈もしやすくなり、論理エラーのリスクが減少します。

MINUTELY、SECONDLY、PRESECONDLYの列挙型は、それぞれ分、秒、イベント前の時間を表し、時間間隔の管理を直感的におこなえます。変換関数を使用することで、整数を簡単に意味のある列挙型の値に変換でき、操作が簡便になります。 CTimeByHourクラスは、1日の各時間に関する時刻とイベントデータを保存、検索、管理するメカニズムを提供します。これらの方法は、今後の記事でさらに詳しく説明する予定です。

重要なポイント

  • 効率的なDBアクセス:イベントは1日1回のみロードされ、メモリ内に保存されるため、不要なデータベースクエリが減少します。
  • 時間分割:イベントを1時間ごとに分割することで、EAは関連するイベントのみをチェックし、処理速度が向上し、CPU負荷も軽減されます。
  • スケーラビリティ:この方法は、イベントが多い日でもスケーラブルで、取引日全体を通じて安定したパフォーマンスを確保します。
  • 反応性の向上:関連する時間枠のイベントに焦点を当てることで、EAは市場イベントに迅速に反応でき、これはニュースベースの戦略において非常に重要です。

お時間を割いていただきありがとうございました。次回の記事では、さらに多くの価値を提供できることを楽しみにしています :) 

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15878

添付されたファイル |
NewsTrading_Part4.zip (591.45 KB)
知っておくべきMQL5ウィザードのテクニック(第43回):SARSAによる強化学習 知っておくべきMQL5ウィザードのテクニック(第43回):SARSAによる強化学習
SARSAは、State-Action-Reward-State-Actionの略で、強化学習を実装する際に使用できる別のアルゴリズムです。Q学習とDQNで見たように、ウィザードで組み立てられたエキスパートアドバイザー(EA)の中で、これを単なる訓練メカニズムとしてではなく、独立したモデルとしてどのように実装できるかを検討します。
MQL5で取引管理者パネルを作成する(第4回):ログインセキュリティ層 MQL5で取引管理者パネルを作成する(第4回):ログインセキュリティ層
悪意のある人物が取引管理者室に侵入し、世界中の何百万ものトレーダーに貴重な洞察を伝えるために使用されるコンピューターと管理パネルにアクセスしたと想像してください。このような侵入は、誤解を招くメッセージの不正送信や、意図しないアクションをトリガーするボタンのランダムクリックなど、悲惨な結果につながる可能性があります。このディスカッションでは、MQL5のセキュリティ対策と、これらの脅威から保護するために管理パネルに実装した新しいセキュリティ機能について説明します。セキュリティプロトコルを強化することで、通信チャネルを保護し、グローバルな取引コミュニティの信頼を維持することを目指しています。この記事のディスカッションでさらに詳しい情報を見つけてください。
日足レンジブレイクアウト戦略に基づくMQL5 EAの作成 日足レンジブレイクアウト戦略に基づくMQL5 EAの作成
この記事では、日足レンジブレイクアウト(Daily Range Breakout)戦略に基づいてMQL5エキスパートアドバイザー(EA)を作成します。戦略の重要な概念を説明し、EAの設計図を設計し、MQL5でブレイクアウトロジックを実装します。最後に、EAの効果を最大限に引き出すためのバックテストと最適化の手法について探ります。
MQL5取引ツールキット(第3回):未決注文管理EX5ライブラリの開発 MQL5取引ツールキット(第3回):未決注文管理EX5ライブラリの開発
MQL5のコードやプロジェクトで、包括的な未決注文管理EX5ライブラリを開発して実装する方法を学びましょう。本記事では、広範な未決注文管理EX5ライブラリを作成する手順を紹介し、それをインポートおよび実装する方法を、取引パネルまたはグラフィカルユーザーインターフェース(GUI)の構築を通じて解説します。このEA注文パネルを使用すれば、チャートウィンドウ上のGUIから、指定されたマジックナンバーに関連する未決注文を直接オープン、監視、削除することが可能です。