English Deutsch
preview
MQL5経済指標カレンダーを使った取引(第5回):レスポンシブコントロールとフィルターボタンでダッシュボードを強化する

MQL5経済指標カレンダーを使った取引(第5回):レスポンシブコントロールとフィルターボタンでダッシュボードを強化する

MetaTrader 5トレーディング | 10 4月 2025, 07:53
33 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

この記事は、本MetaQuotes Language 5(MQL5)連載の第4回で、MQL5経済指標カレンダーダッシュボードにリアルタイム更新機能を追加した作業を基に進めます。ここでは、ダッシュボードをよりインタラクティブにすることに焦点を当て、通貨ペアフィルター、重要度レベル、時間範囲フィルターを直接操作できるボタンを追加します。これにより、コード内の設定を変更することなく、パネルから直接フィルターを制御できるようになります。また、[Cancel]ボタンも追加し、選択したフィルターをクリアし、ダッシュボードのコンポーネントを削除できるようにして、表示内容を完全に制御できるようにします。最後に、ボタンがクリックに反応するようにし、スムーズに機能し即座にフィードバックを提供することで、ユーザーエクスペリエンスを向上させます。この記事で取り上げるトピックは次のとおりです。

  1. フィルターボタンとコントロールの作成
  2. ボタンの自動化と応答性の追加
  3. 強化されたダッシュボードのテスト
  4. 結論

これらの追加により、ダッシュボードの使いやすさが大幅に向上し、ユーザーがリアルタイムでインタラクションできる柔軟で動的なものになります。これらのインタラクティブな要素を使用することで、コードを毎回変更することなく、表示されるニュースデータを簡単にフィルタリングして管理できるようになります。まずは、フィルターボタンを作成し、それを既存のダッシュボードレイアウトに統合することから始めましょう。


フィルターボタンとコントロールの作成

このセクションでは、通貨ペアフィルター、重要度レベルフィルター、時間範囲フィルターなど、ダッシュボードのさまざまな側面をパネルから直接制御できるようにするフィルターボタンの作成に焦点を当てます。これらのボタンを追加することで、フィルター設定を変更するたびにコードにアクセスしたり変更したりする必要がなくなり、ダッシュボードとのやり取りが容易になります。目標は、レイアウトをシンプルかつクリーンに保ちながら、柔軟性を提供する直感的なユーザーインターフェイスを設計することです。

まず、各フィルターボタンの位置とプロパティを定義します。これらのボタンをダッシュボードパネル内に配置し、通貨ペア、重要度レベル、時間フィルターのさまざまな設定を切り替えることができるようにします。たとえば、通貨フィルターボタンをダッシュボードの右上隅に配置し、通貨、重要度、および時間フィルターの選択ボタンをヘッダーセクションに配置し、キャンセルボタンをそれらの定義の直後に配置します。各ボタンは特定のフィルター設定に対応しており、これらのボタンをクリックして好みのフィルターを適用できます。以下は、ダッシュボード内のフィルターボタンの初期レイアウトを示す画像です。

レイアウト設計図

画像で示されているように、フィルターボタンはダッシュボード内に整理され、簡単にアクセスして管理できるようになっています。各ボタンは特定の機能を処理するように設計されており、利用可能な通貨ペアを有効にしたり、イベントの重要度レベルを設定したり、時間でフィルタリングすることができます。また、ボタンのデザインは視覚的に異なり、異なるコントロールグループを簡単に区別できるようになっています。

これを実装するには、ダッシュボードに含める追加のオブジェクトとコントロールの定数を定義する必要があります。

#define FILTER_LABEL "FILTER_LABEL"  //--- Define the label for the filter section on the dashboard

#define FILTER_CURR_BTN "FILTER_CURR_BTN"  //--- Define the button for filtering by currency pair
#define FILTER_IMP_BTN "FILTER_IMP_BTN"  //--- Define the button for filtering by event importance
#define FILTER_TIME_BTN "FILTER_TIME_BTN"  //--- Define the button for filtering by time range

#define CANCEL_BTN "CANCEL_BTN"  //--- Define the cancel button to reset or close the filters

#define CURRENCY_BTNS "CURRENCY_BTNS"  //--- Define the collection of currency buttons for user selection

ここでは、#defineキーワードを使用して、追加するさまざまなダッシュボード要素(主としてボタンとラベル)の名前を表す一連の文字列定数を定義します。これらの定数を使用して、フィルターボタンやキャンセルボタンなどのユーザーインターフェイスコンポーネントを作成および管理します。まず、ダッシュボードのフィルターセクションのラベルを表す「FILTER_LABEL」を定義します。

次に、3つのボタンを定義します。通貨ペアによるフィルタリングには「FILTER_CURR_BTN」、イベントの重要度によるフィルタリングには「FILTER_IMP_BTN」、イベントの時間範囲によるフィルタリングには「FILTER_TIME_BTN」を使用します。また、アクティブなフィルターをリセットまたは閉じてダッシュボードコンポーネントを削除できるように「CANCEL_BTN」を定義します。最後に、「CURRENCY_BTNS」は、特定の通貨ペアを選択するための通貨ボタンのコレクションを表します。これらの定義は、インターフェイスから直接表示されるデータを制御できる、動的でインタラクティブなダッシュボードを作成するのに役立ちます。ダイナミズムを強化するために、次のようにグローバルスコープの外部で定義された通貨の配列を削除します。

string curr_filter[] = {"AUD","CAD","CHF","EUR","GBP","JPY","NZD","USD"};
string curr_filter_selected[];

ここでは、関数内から定義済みの通貨リストを削除し、それをグローバルスコープに配置することで、通貨フィルターの柔軟性とダイナミズムを強化します。ここで、「AUD」、「CAD」、「CHF」、「EUR」、「GBP」、「JPY」、「NZD」、「USD」などの可能な通貨のリストを保持するための新しいグローバル配列「curr_filter」を定義します。さらに、実行時にユーザーが選択した通貨を動的に保存する空の配列curr_filter_selectedを作成します。最後に、更新の必要性を追跡するための変数を作成します。今回は、キャンセルボタンでダッシュボードを削除するため、更新は不要になります。簡単です。

bool isDashboardUpdate = true;

ブール変数「isDashboardUpdate」を定義し、それをtrueに初期化するだけです。この変数を使用して、ダッシュボードを更新する必要があるかどうかを追跡します。これをtrueに設定すると、ダッシュボードを新しいデータや設定で更新する必要がある変更やアクション(フィルターの選択やボタンのクリックなど)があったことをフラグ付けできます。同様に、これをfalseに設定すると、更新プロセスを実行する必要がなくなり、ダッシュボードの状態を効率的に管理し、必要な場合にのみ更新され、不要な再レンダリングが回避されます。

グローバルスコープから初期化セクションに移動し、追加のフィルターコンポーネントをダッシュボードにマップできます。まず、一番上の、フィルターを有効にするボタンから始めます。

createLabel(FILTER_LABEL,370,55,"Filters:",clrYellow,16,"Impact"); //--- Create a label for the "Filters" section in the dashboard

//--- Define the text, color, and state for the Currency filter button
string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set text based on filter state
color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_curr_state = enableCurrencyFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_CURR_BTN,430,55,110,26,filter_curr_text,filter_curr_txt_color,12,clrBlack); //--- Create Currency filter button
ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_STATE,filter_curr_state); //--- Set the state of the Currency button

ここでは、ダッシュボードに通貨フィルターのラベルとボタンを追加します。まず、createLabel関数を使用して、チャートの座標(370、55)に「Filters:」というタイトルのラベルを黄色、フォントサイズ16で配置します。このラベルはフィルターセクションの見出しとして機能し、フィルターオプションがどこにあるかをユーザーに明確に示します。

次に、「通貨」フィルターのボタンを定義して設定します。enableCurrencyFilter変数の状態を確認し、その値に基づいて、filter_curr_text変数を使用してボタンのテキストを動的に設定します。通貨フィルターが有効になっている場合(enableCurrencyFilterがtrueの場合)、ボタンには「Currency」というテキストのチェックマーク(0x2714)が表示され、フィルターがアクティブであることを示します。無効になっている場合は、代わりにクロス(0x274c)が表示され、フィルターが非アクティブであることを示します。これを実現するには、if演算子と同様に動作する三項演算子を使用しますが、小さく、シンプルで、わかりやすいという点が異なります。

フィルターの状態を視覚的にさらに反映するために、filter_curr_txt_color変数を使用してボタンのテキストの色を設定します。フィルターがアクティブな場合、テキストは緑色で表示され、非アクティブな場合は赤色で表示されます。また、ボタンの実際の状態を制御するためにfilter_curr_stateブール変数も使用します。これにより、ボタンが有効か無効かが決まります。

次に、createButton関数を使用してボタン自体を作成し、適切なラベル(filter_curr_text)、テキストの色(filter_curr_txt_color)、および黒の背景でチャートの(430,55)に配置します。最後に、ObjectSetInteger関数を使用して、filter_curr_state変数を参照してボタンの状態(有効または無効)を設定します。これにより、ボタンの外観と機能が現在のフィルター設定と一致するようになります。コンパイルすると、次の出力が得られます。

通貨フィルターとラベル

うまくいきました。同じロジックを使用して、フィルターボタンの追加に進みます。

//--- Define the text, color, and state for the Importance filter button
string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set text based on filter state
color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_imp_state = enableImportanceFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_IMP_BTN,430+110,55,120,26,filter_imp_text,filter_imp_txt_color,12,clrBlack); //--- Create Importance filter button
ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_STATE,filter_imp_state); //--- Set the state of the Importance button

//--- Define the text, color, and state for the Time filter button
string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set text based on filter state
color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set text color based on filter state
bool filter_time_state = enableTimeFilter ? true : false; //--- Set button state (enabled/disabled)
createButton(FILTER_TIME_BTN,430+110+120,55,70,26,filter_time_text,filter_time_txt_color,12,clrBlack); //--- Create Time filter button
ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_STATE,filter_time_state); //--- Set the state of the Time button

//--- Create a Cancel button to reset all filters
createButton(CANCEL_BTN,430+110+120+79,51,50,30,"X",clrWhite,17,clrRed,clrNONE); //--- Create the Cancel button with an "X"

//--- Redraw the chart to update the visual elements
ChartRedraw(0); //--- Redraw the chart to reflect all changes made above

ここでは、ダッシュボードに「重要度」、「時間」、「キャンセル」フィルターのボタンを設定します。「重要度」フィルターの場合、まずfilter_imp_text変数を使用してボタンのテキストを定義します。enableImportanceFilterの値に基づいて、フィルターがアクティブな場合は、「Importance」というテキストの横にチェックマーク(0x2714)が表示され、フィルターが有効であることを示します。そうでない場合は、同じテキストの横にクロス(0x274c)が表示され、フィルターが無効であることを示します。また、filter_imp_txt_colorを使用してボタンのテキストの色を設定します。有効な場合は緑色、無効な場合は赤色になります。ブール値「filter_imp_state」は、ボタンが有効か無効かを制御します。

次に、createButton関数を使用して「重要度」フィルターボタンを作成し、適切なテキスト、色、状態で位置(430+110,55)に配置します。次に、ObjectSetIntegerを使用して、filter_imp_stateに基づいてボタンの状態(OBJPROP_STATE)を設定し、ボタンが正しいステータスを反映するようにします。

「時間」フィルターボタンでも同様のプロセスに従います。filter_time_textでテキストを定義し、enableTimeFilterの値に基づいてfilter_time_txt_colorで色を調整します。ボタンは位置(430+110+120,55)に作成され、状態はfilter_time_stateを使用して適切に設定されます。

最後に、createButton関数を使用して[Cancel]ボタンを作成します。このボタンをクリックすると、すべてのフィルターがリセットされ、ダッシュボードが削除されます。このボタンは位置(430+110+120+79,51)に配置され、その目的を示すために赤い背景に白い[X]が表示されます。最後に、ChartRedraw関数を呼び出してチャートを更新し、新しく作成されたボタンと変更を視覚的に更新します。実行すると、次の結果が得られます。

フルフィルターボタン

うまくいきました。これで、ダッシュボードにすべてのフィルターボタンが追加されました。ただし、テキストの作成中は、Unicode文字と呼ばれる非英語文字の連結を使用しました。詳しく見てみましょう。

ShortToString(0x2714);
ShortToString(0x274C);

ここでは、MQL5でUnicode文字を表すために「ShortToString(0x2714)」および「ShortToString(0x274C)」関数を使用しています。ここで、0x2714および0x274Cの値はUnicode文字セット内の特定の記号を参照します。

  • 0x2714はチェックマーク記号のUnicodeコードポイントです。これは、何かが有効、完了、または正しいことを示すために使用されます。フィルターボタンのコンテキストでは、これを使用してフィルタ(通貨フィルターや重要度フィルターなど)がアクティブまたは有効であることを示します。
  • 0x274Cは、バツ記号のUnicodeコードポイントです。これは、何かが無効、未完了、または間違っていることを表すために使用されます。ここでは、フィルターが非アクティブまたは無効であることを示すために使用されます。

MQL5コーディング環境と互換性のある文字を提供する場合は、独自の文字を使用することもできます。文字のセットは以下のとおりです。

チェックマークユニコードの例

コードでは、ShortToString関数がこれらのUnicodeコードポイントを対応する文字表現に変換します。これらの文字はフィルターボタンのテキストに追加され、フィルターがアクティブかどうかを視覚的に示します。次に、通貨フィルターボタンを動的に作成します。

int curr_size = 51;               //--- Button width
int button_height = 22;           //--- Button height
int spacing_x = 0;               //--- Horizontal spacing
int spacing_y = 3;               //--- Vertical spacing
int max_columns = 4;              //--- Number of buttons per row

for (int i = 0; i < ArraySize(curr_filter); i++){
   int row = i / max_columns;                              //--- Determine the row
   int col = i % max_columns;                             //--- Determine the column

   int x_pos = 575 + col * (curr_size + spacing_x);        //--- Calculate X position
   int y_pos = 83 + row * (button_height + spacing_y);    //--- Calculate Y position
   
   //--- Create button with dynamic positioning
   createButton(CURRENCY_BTNS+IntegerToString(i),x_pos,y_pos,curr_size,button_height,curr_filter[i],clrBlack);
}

if (enableCurrencyFilter == true){
   ArrayFree(curr_filter_selected);
   ArrayCopy(curr_filter_selected,curr_filter);
   Print("CURRENCY FILTER ENABLED");
   ArrayPrint(curr_filter_selected);
   
   for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
      // Set the button to "clicked" (selected) state by default
      ObjectSetInteger(0, CURRENCY_BTNS + IntegerToString(i), OBJPROP_STATE, true);  // true means clicked
   }
}

ここでは、通貨フィルターボタンを動的に作成し、フィルターが有効かどうかに基づいてレイアウトと状態を管理します。まず、ボタンレイアウトパラメータを定義します。各ボタンの幅を決定する整数型変数「curr_size」を51に設定し、ボタンの高さを決定するbutton_heightを22に設定します。水平方向と垂直方向のボタン間の間隔を制御するために、spacing_xとspacing_yをそれぞれ0と3に設定します。また、max_columnsを4に定義し、行あたりのボタンの数を4に制限します。

次に、forループを使用してAUD、CADなどの通貨ペアコードを含む配列「curr_filter」をループ処理します。各反復ごとに、ボタンを配置する行と列を計算します。行番号を決定するために行を「i/max_columns」として計算し、行内の列を決定するために列を「i%max_columns」として計算します。これらの値を使用して、画面上のボタンのX(x_pos)位置とY(y_pos)位置を計算します。次に、createButton関数を呼び出して、ラベルをcurr_filter配列の対応する通貨に設定し、色を黒に設定した各ボタンを動的に作成します。

ボタンを作成した後、enableCurrencyFilterの値を評価して、フィルターが有効になっているかどうかを確認します。フィルターが有効になっている場合は、ArrayFree関数を使用して選択した通貨配列をクリアし、ArrayCopy関数を使用してcurr_filterの内容をcurr_filter_selectedにコピーします。これにより、すべての通貨が選択した配列にコピーされます。次に、「CURRENCYFILTERENABLED」を出力し、ArrayPrint関数を使用して選択したフィルター配列を表示します。最後に、curr_filter_selected配列内の選択された通貨をループし、それぞれのパラメータを指定してObjectSetInteger関数を使用して、対応する各ボタンの状態を選択済みに設定します。IntegerToString関数を使用して選択範囲のインデックスを連結し、それをマクロ「CURRENCY_BTNS」に追加し、状態をtrueに設定して、ボタンがクリックされたことを視覚的にマークします。

コンパイルすると、次の結果が得られます。

最終ボタンの結果

画像から、ダッシュボードにフィルターボタンが正常に追加されたことがわかります。ここから次におこなう必要があるのは、ダッシュボードを破棄する関数を更新して、新しく追加されたコンポーネントも考慮するようにすることです。

//+------------------------------------------------------------------+
//|      Function to destroy the Dashboard panel                     |
//+------------------------------------------------------------------+

void destroy_Dashboard(){
   
   //--- Delete the main rectangle that defines the dashboard background
   ObjectDelete(0,"MAIN_REC");
   
   //--- Delete the sub-rectangles that separate sections in the dashboard
   ObjectDelete(0,"SUB_REC1");
   ObjectDelete(0,"SUB_REC2");
   
   //--- Delete the header label that displays the title of the dashboard
   ObjectDelete(0,"HEADER_LABEL");
   
   //--- Delete the time and impact labels from the dashboard
   ObjectDelete(0,"TIME_LABEL");
   ObjectDelete(0,"IMPACT_LABEL");

   //--- Delete all calendar-related objects
   ObjectsDeleteAll(0,"ARRAY_CALENDAR");
   
   //--- Delete all news-related objects
   ObjectsDeleteAll(0,"ARRAY_NEWS");

   //--- Delete all data holder objects (for storing data within the dashboard)
   ObjectsDeleteAll(0,"DATA_HOLDERS");
   
   //--- Delete the impact label objects (impact-related elements in the dashboard)
   ObjectsDeleteAll(0,"IMPACT_LABEL");

   //--- Delete the filter label that identifies the filter section
   ObjectDelete(0,"FILTER_LABEL");
   
   //--- Delete the filter buttons for Currency, Importance, and Time
   ObjectDelete(0,"FILTER_CURR_BTN");
   ObjectDelete(0,"FILTER_IMP_BTN");
   ObjectDelete(0,"FILTER_TIME_BTN");
   
   //--- Delete the cancel button that resets or closes the filters
   ObjectDelete(0,"CANCEL_BTN");
   
   //--- Delete all currency filter buttons dynamically created
   ObjectsDeleteAll(0,"CURRENCY_BTNS");
   
   //--- Redraw the chart to reflect the removal of all dashboard components
   ChartRedraw(0);
   
}

また、更新フラグがtrueである限り更新のみを実行するように、OnTickイベントハンドラロジックを更新する必要があります。次のロジックを通じてそれを実現します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected);
   }   
}

ここでは、条件「isDashboardUpdate」がtrueかどうかをチェックしています。これは、ダッシュボードを最新のデータで更新する必要があることを示しています。この条件が満たされている場合、関数update_dashboard_valuesを呼び出して、curr_filter_selected配列に保存されている選択された通貨フィルターを使用してダッシュボードに表示される値を更新します。

配列「curr_filter_selected」には、フィルタリング用に選択された通貨が含まれており、これを「update_dashboard_values」関数に渡すことで、ダッシュボードに最新のフィルター選択が反映されるようになります。フラグ変数がfalseの場合、更新はおこなわれません。フィルターボタンを作成するために考慮するべきことはこれだけです。次に、作成したボタンに応答性を追加する必要があり、これは次のセクションで扱います。


ボタンの自動化と応答性の追加

ダッシュボードに応答性を追加するには、クリックを追跡し、それに基づいてアクションを実行するイベントリスナーを含める必要があります。このため、MQL5に組み込まれているOnChartEventイベントハンドラを使用します。

//+------------------------------------------------------------------+
//|    OnChartEvent handler function                                 |
//+------------------------------------------------------------------+
void  OnChartEvent(
   const int       id,       // event ID  
   const long&     lparam,   // long type event parameter 
   const double&   dparam,   // double type event parameter 
   const string&   sparam    // string type event parameter 
){

//---

}

これは、変更、クリック、オブジェクトの作成など、チャートのアクティビティを認識する機能です。ただし、ボタンのクリックのみをリッスンしたいので、チャートのクリックのみに関心があります。最も単純なものから最も複雑なものまで順に説明します。キャンセルボタンは最もシンプルです。

if (id == CHARTEVENT_OBJECT_CLICK){ //--- Check if the event is a click on an object
   
   if (sparam == CANCEL_BTN){ //--- If the Cancel button is clicked
      isDashboardUpdate = false; //--- Set dashboard update flag to false
      destroy_Dashboard(); //--- Call the function to destroy the dashboard
   }
}

ここでは、イベントIDがCHARTEVENT_OBJECT_CLICKのときに検出されたイベントがオブジェクトのクリックである場合のシナリオを処理します。イベントがクリックの場合、文字列パラメータ「sparam」が「CANCEL_BTN」であるかどうかを評価して、クリックされたオブジェクトが「CANCEL_BTN」であるかどうかを確認します。この条件が満たされた場合、ダッシュボードの[Cancel]ボタンがクリックされたことを意味します。これに応じて、グローバルフラグ「isDashboardUpdate」をfalseに設定し、ダッシュボードへのそれ以上の更新を事実上無効にします。次に、destroy_Dashboard関数を呼び出して、ダッシュボードに関連付けられているすべてのグラフィカル要素をチャートから削除し、クリアします。これにより、[Cancel]ボタンがクリックされたときにインターフェイスがリセットされ、クリアされます。これがそのイラストです。

GIFをキャンセル

うまくいきました。同じロジックを適用して、他のボタンを自動化できるようになりました。通貨フィルターボタンを自動化します。これを実現するために、コードスニペットで次のロジックを使用します。

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

ここでは、「FILTER_CURR_BTN」(通貨フィルターボタン)がクリックされたときの動作を処理します。この条件が満たされると、プロパティOBJPROP_STATEを持つObjectGetInteger関数を使用してボタンの現在の状態(クリックされているか、クリックされていないか)を取得し、それをbtn_state変数に保存します。次に、通貨フィルターがアクティブかどうかを反映するために、enableCurrencyFilterフラグをbtn_stateの値で更新します。

次に、Print関数を使用してボタンの状態と更新されたフラグ値をログに記録し、フィードバックを提供します。通貨フィルターの状態に基づいて、ShortToString関数を使用してチェックマークまたはクロスを使用してボタンのテキストを動的に設定し、アクティブな場合はその色を緑色に、非アクティブな場合は赤色に更新します。これらの更新は、テキストにObjectSetString、色プロパティにObjectSetIntegerを使用してボタンに適用されます。

最後に、変更が適用されたことを確認する成功メッセージをログに記録し、ChartRedraw関数を呼び出してチャートを更新し、ボタンの視覚的な変更がすぐに表示されるようにします。この操作により、通貨フィルターを動的に切り替えて、ダッシュボードに変更を反映することができます。他のフィルターボタンにも同じプロセスが適用されます。

if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   Print("Success. Changes updated! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

ここでは、「FILTER_IMP_BTN」(重要度フィルターボタン)と「FILTER_TIME_BTN」(時間フィルターボタン)のクリックを処理する動作を定義し、これらのフィルターの動的な切り替えを有効にします。「FILTER_IMP_BTN」の場合、クリックされると、まずプロパティOBJPROP_STATEを持つObjectGetIntegerを使用して現在の状態を取得し、btn_state変数に保存します。次に、enableImportanceFilterフラグを更新して、重要度フィルターがアクティブかどうかを反映させます。Print関数を使用して、ボタンの状態とフラグの更新された値をログに記録します。状態に応じて、ShortToString関数を使用してボタンのテキストをチェックマークまたはクロスに設定し、アクティブな場合はその色を緑色に、非アクティブな場合は赤色に更新します。これらの変更は、テキストの場合はObjectSetString、色プロパティ関数の場合はObjectSetIntegerを使用して適用されます。最後に、成功メッセージをログに記録し、ChartRedraw関数を呼び出して、更新が視覚的に適用されていることを確認します。

同様に、「FILTER_TIME_BTN」についても、同じプロセスに従います。ObjectGetInteger関数を使用してボタンの状態を取得し、それに応じてenableTimeFilterフラグを更新します。フィードバックのために状態とフラグの更新を記録します。ボタンのテキストと色は、それぞれShortToString、ObjectSetString、ObjectSetInteger関数を使用して、状態(アクティブ/非アクティブ)を反映するように動的に更新されます。ログで更新を確認した後、ChartRedraw関数でチャートを再描画します。このプロセスにより、フィルターボタンのリアルタイム応答性が保証され、フィルター機能の切り替えをシームレスにおこなうことができます。視覚的な結果は次のとおりです。

フィルターボタンGIF

うまくいきました。これで、通貨ボタンの自動化も進めることができます。

if (StringFind(sparam,CURRENCY_BTNS) >= 0){ //--- If a Currency button is clicked
   string selected_curr = ObjectGetString(0,sparam,OBJPROP_TEXT); //--- Get the text of the clicked button
   Print("BTN NAME = ",sparam,", CURRENCY = ",selected_curr); //--- Log the button name and currency
   
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   
   if (btn_state == false){ //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the currency from the array
      for (int i = 0; i < ArraySize(curr_filter_selected); i++) {
         if (curr_filter_selected[i] == selected_curr) {
            //--- Shift elements to remove the selected currency
            for (int j = i; j < ArraySize(curr_filter_selected) - 1; j++) {
               curr_filter_selected[j] = curr_filter_selected[j + 1];
            }
            ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected filters: ", selected_curr); //--- Log removal
            break;
         }
      }
   }
   else if (btn_state == true){ //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(curr_filter_selected); j++) {
         if (curr_filter_selected[j] == selected_curr) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(curr_filter_selected, ArraySize(curr_filter_selected) + 1); //--- Resize array
         curr_filter_selected[ArraySize(curr_filter_selected) - 1] = selected_curr; //--- Add the new currency
         Print("Added to selected filters: ", selected_curr); //--- Log addition
      }
      else {
         Print("Currency already selected: ", selected_curr); //--- Log already selected
      }
      
   }
   Print("SELECTED ARRAY SIZE = ",ArraySize(curr_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(curr_filter_selected); //--- Print the selected array
   
   update_dashboard_values(curr_filter_selected); //--- Update the dashboard with the selected filters
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success
   
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

ここでは、通貨フィルターボタンのクリックを処理して、選択した通貨を動的に管理し、それに応じてダッシュボードを更新します。ボタンがクリックされたら、まずStringFind関数を使用して、ボタンがCURRENCY_BTNSグループに属しているかどうかを判断します。trueの場合、ObjectGetString関数をOBJPROP_TEXTプロパティとともに使用してボタンのテキストラベルを取得し、それが表す通貨を識別します。次に、ObjectGetIntegerとOBJPROP_STATEプロパティを使用してボタンの状態を確認し、ボタンが選択されている(true)か選択されていない(false)かを判断します。

ボタンが選択されていない状態の場合、curr_filter_selected配列から対応する通貨を削除します。これを実現するには、配列をループして一致する通貨を見つけ、後続のすべての要素を左にシフトして上書きし、ArrayResize関数を使用して配列のサイズを変更して最後の位置を削除します。各削除はアクションを確認するためにログに記録されます。逆に、ボタンが選択状態にある場合は、同じ通貨が複数回追加されるのを防ぐために重複をチェックします。通貨がまだ配列内にない場合は、ArrayResize関数を使用して配列のサイズを変更し、新しい通貨を最後の位置に追加して、追加を記録します。通貨がすでに選択されている場合は、それ以上のアクションが必要ないことを示すメッセージが記録されます。

curr_filter_selected配列を更新した後、ArrayPrint関数を使用してそのサイズと内容をログに記録し、可視性を確保します。次に、update_dashboard_values関数を呼び出して、新しく選択したフィルターでダッシュボードを更新します。すべての変更が視覚的に反映されるようにするには、最後にChartRedraw関数を呼び出してチャートインターフェイスをリアルタイムで更新します。

ユーザーの操作後にさまざまな通貨フィルターが使用されるようになったため、ユーザーが選択した最新の通貨で更新を担当する関数を更新する必要があります。

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[]){

//---

      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter_array); j++) {
            if (country.currency == curr_filter_array[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }

//---

}

ここでは、「&」記号を使用して参照によって渡されるcurr_filter_arrayパラメータを利用して重要な改善を導入し、update_dashboard_values関数を更新します。この設計により、選択した通貨フィルター配列を直接操作して作業することができ、ダッシュボードがユーザーの設定と同期された状態を維持できるようになります。

参照渡し(&)の意味は次のとおりです。curr_filter_arrayパラメータは関数に参照によって渡されます。これは、関数がコピーではなくメモリ内の実際の配列にアクセスすることを意味します。関数内で配列が変更されると、その変更は関数外の元の配列に直接影響を与えます。このアプローチは、特に大きな配列に対して効率性を向上させ、ユーザーの現在のフィルター選択との整合性を維持します。その後、初期の配列を使用する代わりに、ユーザーの設定を反映した最新の配列を参照渡しで代入します。変更点は、わかりやすくするために黄色で強調しています。コンパイルすると、次の結果が得られます。

通貨ボタンGIF

可視化から、いずれかの通貨ボタンをクリックするたびにダッシュボードが更新されていることがわかります。これは成功です。しかし現状では、通貨フィルターボタンが通貨データに依存しており、単一通貨フィルターをクリックした後にダッシュボードが更新されないという問題があります。これを解決するには、すべてのフィルターボタンに対して更新関数を呼び出すだけで済みます。以下のように実装します。

if (sparam == FILTER_CURR_BTN){ //--- If the Currency filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state (clicked/unclicked)
   enableCurrencyFilter = btn_state; //--- Update the Currency filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableCurrencyFilter); //--- Log the state
   string filter_curr_text = enableCurrencyFilter ? ShortToString(0x2714)+"Currency" : ShortToString(0x274C)+"Currency"; //--- Set button text based on state
   color filter_curr_txt_color = enableCurrencyFilter ? clrLime : clrRed; //--- Set button text color based on state
   ObjectSetString(0,FILTER_CURR_BTN,OBJPROP_TEXT,filter_curr_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_CURR_BTN,OBJPROP_COLOR,filter_curr_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableCurrencyFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart to reflect changes
}
if (sparam == FILTER_IMP_BTN){ //--- If the Importance filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableImportanceFilter = btn_state; //--- Update the Importance filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableImportanceFilter); //--- Log the state
   string filter_imp_text = enableImportanceFilter ? ShortToString(0x2714)+"Importance" : ShortToString(0x274C)+"Importance"; //--- Set button text
   color filter_imp_txt_color = enableImportanceFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_IMP_BTN,OBJPROP_TEXT,filter_imp_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_IMP_BTN,OBJPROP_COLOR,filter_imp_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableImportanceFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}
if (sparam == FILTER_TIME_BTN){ //--- If the Time filter button is clicked
   bool btn_state = ObjectGetInteger(0,sparam,OBJPROP_STATE); //--- Get the button state
   enableTimeFilter = btn_state; //--- Update the Time filter flag
   Print(sparam+" STATE = "+(string)btn_state+", FLAG = "+(string)enableTimeFilter); //--- Log the state
   string filter_time_text = enableTimeFilter ? ShortToString(0x2714)+"Time" : ShortToString(0x274C)+"Time"; //--- Set button text
   color filter_time_txt_color = enableTimeFilter ? clrLime : clrRed; //--- Set button text color
   ObjectSetString(0,FILTER_TIME_BTN,OBJPROP_TEXT,filter_time_text); //--- Update the button text
   ObjectSetInteger(0,FILTER_TIME_BTN,OBJPROP_COLOR,filter_time_txt_color); //--- Update the button text color
   update_dashboard_values(curr_filter_selected);
   Print("Success. Changes updated! State: "+(string)enableTimeFilter); //--- Log success
   ChartRedraw(0); //--- Redraw the chart
}

ここでは、ダッシュボードイベントの独立性を高めるために、更新関数を呼び出すようにします。変更点は、わかりやすくするために黄色で強調しています。したがって、これらの変更により、ボタンは独立性を採用します。以下に簡単な可視化を示します。

ボタンの独立性GIF

ここまでで、通貨フィルターを統合することができました。同じ手順を使用して、重要度フィルターも統合できます。これは少し複雑です。既存のボタンを処理してフィルター効果を追加し、重要度レベル用に2つの配列(メインの配列と比較に使用するサイド文字列の配列)が必要になるためです。まず、すべての重要度配列をグローバルスコープに移動して、コード内のどこからでもアクセスできるようにします。

//--- Define labels for impact levels and size of impact display areas
string impact_labels[] = {"None","Low","Medium","High"};
string impact_filter_selected[];

// Define the levels of importance to filter (low, moderate, high)
ENUM_CALENDAR_EVENT_IMPORTANCE allowed_importance_levels[] = {CALENDAR_IMPORTANCE_NONE,CALENDAR_IMPORTANCE_LOW, CALENDAR_IMPORTANCE_MODERATE, CALENDAR_IMPORTANCE_HIGH};
ENUM_CALENDAR_EVENT_IMPORTANCE imp_filter_selected[];

ここでは、ユーザーが選択できるさまざまな影響レベルを保持する文字列配列impact_labelsを定義します。ラベルは「None」、「Low」、「Medium」、「High」です。この配列を使用して、認識された影響に基づいてカレンダーイベントをフィルター処理するためのオプションをユーザーに提示しました。

次に、配列「impact_filter_selected」を導入します。この配列には、ユーザーがimpact_labels配列から選択した実際のラベルが格納されます。ユーザーがインターフェイスを操作して影響レベルを選択するたびに、対応するラベルがこの配列に追加されます。文字列形式なので、列挙リストからではなく、選択されたそれぞれのレベルを簡単に解釈できます。これにより、ユーザーのフィルター設定を動的に追跡できるようになります。

次に、ENUM_CALENDAR_EVENT_IMPORTANCE型の列挙値を含むallowed_importance_levels配列を定義します。これらの列挙値は影響のレベル(CALENDAR_IMPORTANCE_NONE、CALENDAR_IMPORTANCE_LOW、CALENDAR_IMPORTANCE_MODERATE、CALENDAR_IMPORTANCE_HIGH)に関連付けられています。これらはすでに定義されており、グローバルスコープに移行しただけです。これらの値は、重要度に基づいてカレンダーイベントをフィルターするために使用されます。

また、配列「imp_filter_selected」も定義し、ユーザーが選択した影響ラベルに対応する重要度レベルを保存します。ユーザーがimpact_labelsからラベルを選択すると、各ラベルがallowed_importance_levelsの対応する重要度レベルと照合され、その結果がimp_filter_selectedに保存されます。この配列はカレンダーイベントをフィルター処理するために使用され、選択した重要度のレベルのイベントのみが表示されます。

次は初期化関数です。ボタンはイベント結果のガイダンスだけでなく、フィルタリングプロセスにも使用されるようになったため、わかりやすくするためにボタンを更新します。したがって、クリックされたときにアクティブまたは非アクティブの状態を表示する必要があります。

if (enableImportanceFilter == true) { 
   ArrayFree(imp_filter_selected); //--- Clear the existing selections in the importance filter array
   ArrayCopy(imp_filter_selected, allowed_importance_levels); //--- Copy all allowed importance levels as default selections
   ArrayFree(impact_filter_selected);
   ArrayCopy(impact_filter_selected, impact_labels);
   Print("IMPORTANCE FILTER ENABLED"); //--- Log that importance filter is enabled
   ArrayPrint(imp_filter_selected); //--- Print the current selection of importance levels
   ArrayPrint(impact_filter_selected);
   
   // Loop through the importance levels and set their buttons to "selected" state
   for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
      string btn_name = IMPACT_LABEL+IntegerToString(i); //--- Dynamically name the button for each importance level
      ObjectSetInteger(0, btn_name, OBJPROP_STATE, true); //--- Set the button state to "clicked" (selected)
      ObjectSetInteger(0, btn_name, OBJPROP_BORDER_COLOR, clrNONE); //--- Set the button state to "clicked" (selected)
   }
}

ここでは、まずenableImportanceFilter変数がtrueに設定されているかどうかを確認します。そうであれば、重要度フィルターシステムの構成に進みます。まず、ArrayFree関数を使用して、imp_filter_selected配列内の既存の選択をクリアします。次に、allowed_importance_levels配列のすべての値をimp_filter_selectedにコピーし、基本的にすべての重要度レベルをデフォルトの選択として設定します。つまり、デフォルトでは、フィルタリングにはすべての重要度レベルが最初に選択されます。

次に、ArrayFree関数を使用してimpact_filter_selected配列をクリアします。これにより、影響ラベル配列内の以前の選択がすべて削除されます。続いて、impact_labels配列のすべての値をimpact_filter_selectedにコピーします。これにより、影響レベルを表すラベル(「None」、「Low」、「Medium」、「High」など)がフィルターで使用できるようになります。配列を設定した後、重要度フィルターがアクティブになったことを確認するために、「IMPORTANCE FILTER ENABLED」というログメッセージを出力します。また、現在の選択内容を表示するために、imp_filter_selected配列とimpact_filter_selected配列の内容も出力します。

最後に、現在選択されている重要度レベルを保持するimp_filter_selected配列をループし、対応する重要度レベルごとにボタンの状態を動的に設定します。重要度レベルごとに、IMPACT_LABELを使用してボタン名を動的に作成し、IntegerToString関数を使用して現在の重要度レベルのインデックスを作成します。次に、ObjectSetInteger関数を使用して、ボタンの状態をtrue(選択済み)に設定します。さらに、OBJPROP_BORDER_COLORプロパティをnoneに設定して境界線の色を削除し、ボタンが選択されていることを視覚的に強調表示します。初期化に必要なのはこれだけです。ここで、イベントリスナー関数に進み、重要度ボタンのクリックを追跡し、それに応じて動作します。ここでは、通貨フィルターボタンの場合と同様のロジックを使用します。

if (StringFind(sparam, IMPACT_LABEL) >= 0) { //--- If an Importance button is clicked
   string selected_imp = ObjectGetString(0, sparam, OBJPROP_TEXT); //--- Get the importance level of the clicked button
   ENUM_CALENDAR_EVENT_IMPORTANCE selected_importance_lvl = get_importance_level(impact_labels,allowed_importance_levels,selected_imp);
   Print("BTN NAME = ", sparam, ", IMPORTANCE LEVEL = ", selected_imp,"(",selected_importance_lvl,")"); //--- Log the button name and importance level

   bool btn_state = ObjectGetInteger(0, sparam, OBJPROP_STATE); //--- Get the button state
   
   color color_border = btn_state ? clrNONE : clrBlack;
   
   if (btn_state == false) { //--- If the button is unselected
      Print("BUTTON IS IN UN-SELECTED MODE.");
      //--- Loop to find and remove the importance level from the array
      for (int i = 0; i < ArraySize(imp_filter_selected); i++) {
         if (impact_filter_selected[i] == selected_imp) {
            //--- Shift elements to remove the unselected importance level
            for (int j = i; j < ArraySize(imp_filter_selected) - 1; j++) {
               imp_filter_selected[j] = imp_filter_selected[j + 1];
               impact_filter_selected[j] = impact_filter_selected[j + 1];
            }
            ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) - 1); //--- Resize the array
            ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) - 1); //--- Resize the array
            Print("Removed from selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log removal
            break;
         }
      }
   } 
   else if (btn_state == true) { //--- If the button is selected
      Print("BUTTON IS IN SELECTED MODE. TAKE ACTION");
      //--- Check for duplicates
      bool already_selected = false;
      for (int j = 0; j < ArraySize(imp_filter_selected); j++) {
         if (impact_filter_selected[j] == selected_imp) {
            already_selected = true;
            break;
         }
      }

      //--- If not already selected, add to the array
      if (!already_selected) {
         ArrayResize(imp_filter_selected, ArraySize(imp_filter_selected) + 1); //--- Resize the array
         imp_filter_selected[ArraySize(imp_filter_selected) - 1] = selected_importance_lvl; //--- Add the new importance level
         
         ArrayResize(impact_filter_selected, ArraySize(impact_filter_selected) + 1); //--- Resize the array
         impact_filter_selected[ArraySize(impact_filter_selected) - 1] = selected_imp; //--- Add the new importance level
         Print("Added to selected importance filters: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log addition
      } 
      else {
         Print("Importance level already selected: ", selected_imp,"(",selected_importance_lvl,")"); //--- Log already selected
      }
   }
   Print("SELECTED ARRAY SIZE = ", ArraySize(imp_filter_selected)," >< ",ArraySize(impact_filter_selected)); //--- Log the size of the selected array
   ArrayPrint(imp_filter_selected); //--- Print the selected array
   ArrayPrint(impact_filter_selected);
   
   update_dashboard_values(curr_filter_selected,imp_filter_selected); //--- Update the dashboard with the selected filters
   
   ObjectSetInteger(0,sparam,OBJPROP_BORDER_COLOR,color_border);
   Print("SUCCESS. DASHBOARD UPDATED"); //--- Log success

   ChartRedraw(0); //--- Redraw the chart to reflect changes
}

ここでは、まず関数StringFindを使用して、重要度フィルターボタンがクリックされたかどうかを識別します。この関数は、クリックされたボタンの名前(sparamで表される)に文字列「IMPACT_LABEL」が含まれているかどうかを確認します。該当する場合は、ObjectGetString関数を呼び出してテキストプロパティを渡し、クリックされたボタンのテキスト(「Low」、「Medium」など)を取得することで、ボタンに関連付けられた重要度のレベルを取得します。次に、選択したラベルを関数「get_importance_level(impact_labels,allowed_importance_levels,selected_imp)」に渡して、このテキストを対応する列挙値(CALENDAR_IMPORTANCE_LOWなど)に変換します。この関数は、ラベルの配列、許可された列挙値、および選択されたテキストラベルを受け取り、適切な重要度レベルを列挙として返します。カスタム関数については後ほど説明します。

次に、ObjectGetInteger関数を使用してボタンの状態を確認し、ボタンが選択された状態(true)か選択されていない状態(false)かを決定する状態プロパティを渡します。この状態に基づいて、選択した重要度レベルをフィルター配列に追加または削除します。ボタンが選択されていない場合は、imp_filter_selected配列を反復処理して重要度レベルを削除し、ArrayResize関数を使用して配列のサイズを変更します。両方の配列が同期された状態を保つために、impact_filter_selected配列に対しても同じことをおこないます。ボタンが選択されている場合は、まずループを使用して、重要度レベルがフィルター配列にすでに存在するかどうかを確認します。そうでない場合は、両方の配列のサイズを変更し、新しい値を追加して、両方の配列に追加します。

配列が更新されたら、ArrayPrint関数を使用して、デバッグ用にフィルター配列の現在の内容をログに記録します。次に、呼び出し「update_dashboard_values(curr_filter_selected,imp_filter_selected)」によって、ダッシュボードを新しいフィルター選択で更新します。これにより、ダッシュボードのフィルター配列の変更が反映されます。ユーザーの設定に応じて、2つの配列パラメータを取るように関数を更新しました。後ほどそれも見てみましょう。最後に、クリックされたボタンの外観は、計算された色に境界線の色を設定することで更新されます。色は、ボタンが選択されているかどうかに基づいて決定されます。次に、ChartRedraw関数を使用してグラフを再描画し、変更を視覚的に反映します。

ここで、対応する重要度レベルの列挙を取得するカスタム関数を見てみましょう。

//+------------------------------------------------------------------+
//| Function to get the importance level based on the selected label |
//+------------------------------------------------------------------+
ENUM_CALENDAR_EVENT_IMPORTANCE get_importance_level(string &impact_label[], ENUM_CALENDAR_EVENT_IMPORTANCE &importance_levels[], string selected_label) {
    // Loop through the impact_labels array to find the matching label
    for (int i = 0; i < ArraySize(impact_label); i++) {
        if (impact_label[i] == selected_label) {
            // Return the corresponding importance level
            return importance_levels[i];
        }
    }
    
    // If no match found, return CALENDAR_IMPORTANCE_NONE as the default
    return CALENDAR_IMPORTANCE_NONE;
}

ここでは、選択したラベルに基づいて重要度レベルを決定するために使用される列挙関数「get_importance_level」を定義します。この関数は3つのパラメータを取ります。

  • impact_label:影響レベルのさまざまなラベル(「None」、「Low」、「Medium」、「High」など)を保持する文字列の配列
  • importance_levels:対応する重要度レベル(CALENDAR_IMPORTANCE_NONE、CALENDAR_IMPORTANCE_LOWなど)を列挙値として保持する配列
  • selected_label:ユーザーが選択した重要度レベルを表す関数に渡されるラベル(文字列)(例:「Medium」)

関数内では、forループを使用してimpact_label配列をループします。各反復ごとに、配列内の現在の項目がselected_labelと一致するかどうかを確認します。一致が見つかった場合、同じインデックスのimportance_levels配列から対応する重要度レベルを返します。すべてのラベルをチェックした後も一致するものが見つからない場合、関数はデフォルトでCALENDAR_IMPORTANCE_NONEを返します。この関数は、選択した影響レベル(「Medium」など)を表す文字列を、対応する重要度レベル(CALENDAR_IMPORTANCE_MODERATEなど)に変換するために必要です。

ここでおこなったその他の変更は、新しい重要度フィルターされた配列データを参照として更新関数に渡し、更新されたユーザー設定に応じてフィルターが動的に有効になるようにしたことです。関数の宣言は、以下のコードスニペットに示すようになります。

//+------------------------------------------------------------------+
//| Function to update dashboard values                              |
//+------------------------------------------------------------------+
void update_dashboard_values(string &curr_filter_array[], ENUM_CALENDAR_EVENT_IMPORTANCE &imp_filter_array[]){

//---      

}

関数を更新した後、重要度フィルター配列を含めるように既存の同様の関数も更新する必要があります。たとえば、OnInitイベントハンドラ関数の呼び出しは、次のようになります。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

   if (isDashboardUpdate){
      update_dashboard_values(curr_filter_selected,imp_filter_selected);
   }
   
}

通貨フィルターと重要度フィルターの両方を組み込むために必要なのはこれだけです。現在のマイルストーンを見て、重要度フィルターも予想どおりに機能していることを確認しましょう。

重要度フィルターGIF

視覚化から、重要度フィルターの応答性を追加するという目的が達成されたことがわかります。最後に、更新できるものの一つは、表示するイベントの合計です。フィルタリングされたイベントの数、チャートに表示できる可能性のあるイベントの合計数、および考慮されるイベントの合計数を取得できます。これを実現するために使用できるロジックは次のとおりです。グローバルスコープでは、いくつかのトラック変数を定義できます。

int totalEvents_Considered = 0;
int totalEvents_Filtered = 0;
int totalEvents_Displayable = 0;

ここでは、totalEvents_Considered、totalEvents_Filtered、totalEvents_Displayableという3つの整数変数を定義します。これらの変数は、処理中のイベントのステータスを追跡するためのカウンタとして機能します。

  • totalEvents_Considered:処理ステップ中に最初に考慮されるイベントの合計数を追跡します。これは、フィルタリングが適用される前にすべてのイベントが考慮される開始点を表します。
  • totalEvents_Filtered:通貨、重要度、時間フィルターなどの適用された条件に基づいて除外またはフィルター処理されたイベントの合計数をカウントします。データセットから削除されたイベントの数を示します。
  • totalEvents_Displayable:フィルタリング後に残り、ダッシュボードに表示できるイベントの合計数を追跡します。これは、すべてのフィルタリング基準を満たし、表示可能なイベントの最終セットを表します。

これらのカウンタを使用することで、イベント処理パイプラインを監視および分析し、フィルタリングロジックが期待どおりに機能していることを確認し、データの全体的なフローに関する洞察を得ることができます。次に、データフィルタリングアクションが実行される前に、それらをデフォルトで0に設定します。

totalEvents_Displayable = 0;
totalEvents_Filtered = 0;
totalEvents_Considered = 0;

最初のループの前に、すべてのイベントを考慮するために、以前の値を最新の値に置き換えます。ここに例があります。

//--- Loop through each calendar value up to the maximum defined total
for (int i = 0; i < allValues; i++){

//---

}

制限条件を早期に適用する代わりに、すべてのイベントを考慮していることがわかります。これにより、オーバーフローデータを表示できるようになります。したがって、ループ内には次の更新ロジックが存在します。

   //--- Loop through each calendar value up to the maximum defined total
   for (int i = 0; i < allValues; i++){
   
      MqlCalendarEvent event; //--- Declare event structure
      CalendarEventById(values[i].event_id,event); //--- Retrieve event details by ID
   
      //--- Other declarations
      
      totalEvents_Considered++;
      
      //--- Check if the event’s currency matches any in the filter array (if the filter is enabled)
      bool currencyMatch = false;
      if (enableCurrencyFilter) {
         for (int j = 0; j < ArraySize(curr_filter); j++) {
            if (country.currency == curr_filter[j]) {
               currencyMatch = true;
               break;
            }
         }
         
         //--- If no match found, skip to the next event
         if (!currencyMatch) {
            continue;
         }
      }
      
      //--- Other filters

      //--- If we reach here, the filters passed
      totalEvents_Filtered++;
      
      //--- Restrict the number of displayable events to a maximum of 11
      if (totalEvents_Displayable >= 11) {
        continue; // Skip further processing if display limit is reached
      }
      
      //--- Increment total displayable events
      totalEvents_Displayable++;
      
      //--- Set alternating colors for data holders
      color holder_color = (totalEvents_Displayable % 2 == 0) ? C'213,227,207' : clrWhite;
      
      //--- Create rectangle label for the data holder
      createRecLabel(DATA_HOLDERS + string(totalEvents_Displayable), 62, startY - 1, 716, 26 + 1, holder_color, 1, clrNONE);

      //--- Initialize starting x-coordinate for each data entry
      int startX = 65;
      
      //--- Loop through calendar data columns
      for (int k=0; k<ArraySize(array_calendar); k++){
         
         //--- Prepare news data array with time, country, and other event details
         string news_data[ArraySize(array_calendar)];
         news_data[0] = TimeToString(values[i].time,TIME_DATE); //--- Event date
         news_data[1] = TimeToString(values[i].time,TIME_MINUTES); //--- Event time
         news_data[2] = country.currency; //--- Event country currency
      
         //--- Other fills and creations
      }
      
      ArrayResize(current_eventNames_data,ArraySize(current_eventNames_data)+1);
      current_eventNames_data[ArraySize(current_eventNames_data)-1] = event.name;
      
      //--- Increment y-coordinate for the next row of data
      startY += 25;
      
   }
   Print("CURRENT EVENT NAMES DATA SIZE = ",ArraySize(current_eventNames_data));
   //--- Other logs
      
   updateLabel(TIME_LABEL,"Server Time: "+TimeToString(TimeCurrent(),
              TIME_DATE|TIME_SECONDS)+"   |||   Total News: "+
              IntegerToString(totalEvents_Displayable)+"/"+IntegerToString(totalEvents_Filtered)+"/"+IntegerToString(totalEvents_Considered));
//---

ここでは、イベント番号ホルダーを適宜更新します。注目すべき重要な点の1つは、水色で強調表示されているコードスニペットです。そのロジックはフィルターと同じように機能し、表示可能な合計制限に達した場合は処理をスキップします。最後に、ラベルを更新して3つのイベント数をすべて含めます。これにより、長期的にはデータオーバーフロー数を把握しやすくなります。次に、同じロジックを更新機能に適用して、リアルタイムで同期されるようにします。システムを実行すると、次の結果が得られます。

結果を表示

画像から、ニュース数が3であることがわかります。最初の数字(この場合は11)は、表示可能なニュースの合計数を示します。2番目の数字(24)は、フィルタリングされたイベントの合計数を示します(ダッシュボードにすべてを表示できるわけではありません)。3番目の数字(539)は、処理対象として検討されたニュースの合計数を示します。これらすべてにより、時間フィルターを有効にするために、入力形式で必要な時間範囲を設定して、プログラムを初期化するときに設定できるようになります。これを実現するためのロジックは次のとおりです。

sinput group "General Calendar Settings"
input ENUM_TIMEFRAMES start_time = PERIOD_H12;
input ENUM_TIMEFRAMES end_time = PERIOD_H12;
input ENUM_TIMEFRAMES range_time = PERIOD_H8;

ここでは、カレンダー関連の機能を管理するための構成可能なオプションを提供するために、「General Calendar Settings」というグループを定義します。カレンダーイベントをフィルター処理または分析する時間パラメータを制御するために、ENUM_TIMEFRAMESタイプの3つの入力を使用します。まず、カレンダーイベントの時間範囲の開始を指定する「start_time」を定義します。デフォルトでは12時間(PERIOD_H12)になります。

次に、この時間範囲の終了を示す「end_time」を導入します。これもデフォルトではPERIOD_H12に設定されています。最後に、range_timeを使用して、カレンダーのフィルタリングまたは計算の対象となる期間または期間を定義します。デフォルト値は8時間(PERIOD_H8)です。これにより、プログラムがユーザー定義の時間枠に基づいて柔軟に動作するようになり、カレンダーデータを特定の関心期間に適応させることができます。これらの設定により、動的なフィルタリングが可能になり、表示または分析されるイベントの時間的範囲をユーザーが制御できるようになります。

変更を有効にするには、次のように、それぞれのホルダー関数とロジックの制御入力に変更を追加します。

//--- Define start and end time for calendar event retrieval
datetime startTime = TimeTradeServer() - PeriodSeconds(start_time);
datetime endTime = TimeTradeServer() + PeriodSeconds(end_time);

//--- Define time range for filtering news events based on daily period
datetime timeRange = PeriodSeconds(range_time);
datetime timeBefore = TimeTradeServer() - timeRange;
datetime timeAfter = TimeTradeServer() + timeRange;

それだけのことです。コンパイルすると、次の出力が得られます。

入力結果

画像から、入力パラメータにアクセスし、有効なドロップダウンリストから時間設定を選択できることがわかります。この時点で、ダッシュボードは完全に機能し、応答性も良好です。さまざまな条件や環境でテストして、問題なく正常に動作することを確認し、問題が発生した場合にはそれを軽減する必要があります。それは次のセクションでおこなわれます。


強化されたダッシュボードのテスト

このセクションでは、開発した拡張ダッシュボードのテストを実行します。目標は、すべてのフィルター、イベントトラッキング、およびデータ表示メカニズムが意図したとおりに機能することを確認することです。通貨、重要度、時間などの一連のフィルターを実装し、カレンダーイベントをより効果的に表示および分析できるようになりました。

テストプロセスには、ダッシュボードでのユーザー操作のシミュレーション、フィルターの有効化と無効化、選択した基準に基づいてカレンダーイベントが動的に更新されることを確認することが含まれます。また、定義された時間範囲内で、国、通貨、重要度、イベント時間などのイベントデータが正しく表示されるかどうかも確認します。

さまざまなテストシナリオを実行することで、重要度や通貨に基づいてイベントをフィルター処理したり、表示されるイベントの数を制限したり、データラベルが期待どおりに更新されることを確認したりといった、各機能の機能性を確認します。以下のビデオでは、これらのテストの実際の動作を説明しています。



結論

結論として、私たちはフィルター、データ表示、イベント追跡システムがシームレスに機能することを確認しながら、強化された MQL5経済指標カレンダーダッシュボードの開発およびテストに成功しました。このダッシュボードは、通貨、重要度、時間に基づいてフィルターを適用することで、経済イベントを直感的に追跡できるインターフェースを提供し、常に最新の情報を把握し、市場データに基づいた意思決定を行うのに役立ちます。

次回は、この基盤の上にシグナル生成および取引エントリの機能を統合していきます。強化されたダッシュボードから得られるデータを活用することで、経済イベントや市場状況に応じた取引シグナルを自動で生成し、より効率的かつ的確な取引戦略の実現を目指します。ご期待ください。

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

添付されたファイル |
ケリー基準とモンテカルロシミュレーションを使用したポートフォリオリスクモデル ケリー基準とモンテカルロシミュレーションを使用したポートフォリオリスクモデル
数十年にわたり、トレーダーは破産リスクを最小限に抑えつつ長期的な資産成長を最大化する手法として、ケリー基準の公式を活用してきました。しかし、単一のバックテスト結果に基づいてケリー基準を盲目的に適用することは、個人トレーダーにとって非常に危険です。というのも、実際の取引では時間の経過とともに取引優位性が薄れ、過去の実績は将来の結果を保証するものではないからです。本記事では、Pythonによるモンテカルロシミュレーションの結果を取り入れ、MetaTrader 5上で1つ以上のエキスパートアドバイザー(EA)にケリー基準を現実的に適用するためのリスク配分アプローチを紹介します。
CatBoost機械学習モデルをトレンド追従戦略のフィルターとして活用する CatBoost機械学習モデルをトレンド追従戦略のフィルターとして活用する
CatBoostは、定常的な特徴量に基づいて意思決定をおこなうことに特化した、強力なツリーベースの機械学習モデルです。XGBoostやRandom Forestといった他のツリーベースモデルも、堅牢性、複雑なパターンへの対応力、そして高い解釈性といった点で共通した特長を備えています。これらのモデルは、特徴量分析からリスク管理に至るまで、幅広い分野で活用されています。本記事では、学習済みのCatBoostモデルを、従来型の移動平均クロスを用いたトレンドフォロー戦略のフィルターとして活用する手順を解説します。戦略構築の過程で直面しうる課題を取り上げながら、具体的な開発プロセスへの理解を深めることを目的としています。MetaTrader 5からのデータ取得、Pythonによる機械学習モデルの学習、そしてそれをMetaTrader 5のエキスパートアドバイザー(EA)へ統合するまでのワークフローをご紹介します。記事の終盤では、統計的検証を通じて戦略の有効性を確認し、現在のアプローチをもとにした今後の展望についても考察していきます。
MQL5入門(第10回):MQL5の組み込みインジケーターの操作に関する初心者向けガイド MQL5入門(第10回):MQL5の組み込みインジケーターの操作に関する初心者向けガイド
この記事では、プロジェクトベースのアプローチを使用してRSIベースのエキスパートアドバイザー(EA)を作成する方法に焦点を当て、MQL5の組み込みインジケーターの活用方法を紹介します。RSI値を取得して活用し、流動性スイープに対応し、チャートオブジェクトを使用して取引の視覚化を強化する方法を学びます。さらに、パーセンテージベースのリスク設定、リスク報酬比率の実装、利益確保のためのリスク修正など、効果的なリスク管理についても解説します。
MQL5とデータ処理パッケージの統合(第4回):ビッグデータの取り扱い MQL5とデータ処理パッケージの統合(第4回):ビッグデータの取り扱い
今回は、MQL5と強力なデータ処理ツールを統合する高度なテクニックに焦点を当て、取引分析および意思決定を強化するためのビッグデータの効率的な活用方法を探ります。
OSZAR »