English Deutsch
preview
MQL5取引ツール(第4回):動的配置とトグル機能による多時間軸スキャナダッシュボードの改善

MQL5取引ツール(第4回):動的配置とトグル機能による多時間軸スキャナダッシュボードの改善

MetaTrader 5トレーディング |
82 2
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第3回)では、MetaQuotes Language 5 (MQL5)で多時間軸スキャナーダッシュボードを開発し、複数の時間軸にわたって相対力指数(RSI)、ストキャスティクス、コモディティチャンネル指数(CCI)、平均方向性指数(ADX: Average Directional Index)、およびオーサムオシレーター(AO: Awesome Oscillator)インジケーターを表示して、現在の銘柄の取引シグナルを識別しました。第4回では、このダッシュボードに動的な配置機能を追加し、チャート上でドラッグできるようにするとともに、表示の最小化/最大化を切り替える機能を導入し、使い勝手と画面管理を向上させます。本記事では以下のトピックを扱います。

  1. 動的配置および切り替えアーキテクチャの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

本記事を読み終える頃には、柔軟な配置と切り替え機能を備えた高度なMQL5ダッシュボードが完成し、テストやさらなるカスタマイズにすぐに活用できる状態になります。それでは、始めましょう。


動的配置および切り替えアーキテクチャの理解

多時間軸スキャナーダッシュボードに動的配置機能を追加し、チャート上でドラッグできるようにするとともに、最小化/最大化を切り替えるトグル機能を実装して、使い勝手を向上させます。これらの機能は、チャートの混雑を避け、効率的な取引分析のために画面スペースを最適化する上で非常に重要です。具体的には、マウス操作によるドラッグでダッシュボードの位置を自由に変更できるようにし、コンパクト表示とフル表示を切り替えるトグルボタンを実装します。また、インジケーターの更新は常にシームレスにおこなわれるため、より正確なト取引判断をサポートします。以下のようなイメージを目指しています。

ポジショニングとドラッグ状態のアーキテクチャ


MQL5での実装

MQL5でこれらの機能強化を実装するために、まず、後で最大化/最小化の切り替えに使用する追加のトグルボタンオブジェクトを定義します。なお、ホバーやドラッグ操作は、既に実装済みのヘッダオブジェクト上でおこなうようにします。

// Define identifiers and properties for UI elements
#define MAIN_PANEL              "PANEL_MAIN"                     //--- Main panel rectangle identifier

//--- THE REST OF THE EXISTING OBJECTS

#define TOGGLE_BUTTON           "BUTTON_TOGGLE"                  //--- Toggle (minimize/maximize) button identifier

//--- THE REST OF THE EXISTING OBJECTS

#define COLOR_DARK_GRAY         C'105,105,105'                   //--- Dark gray color for indicator backgrounds

多時間軸スキャナーダッシュボードの強化は、まず ユーザーインターフェース(UI)要素の定義を更新し、トグルボタン用の新しい識別子を追加することから始めます。これにより、最小化/最大化機能の実装が可能になります。既存の定義はそのまま維持し、ダッシュボードの基本構造を保持します。主な変更点は、「TOGGLE_BUTTON」という識別子を「BUTTON_TOGGLE」として追加したことです。この識別子により、ダッシュボードを最小化/最大化状態に切り替えるボタンを作成できるようになります。

この追加は非常に重要です。トグル機能を実現することで、ダッシュボードを折りたたんで画面スペースを節約したり、必要に応じてフル表示に展開して視認性を向上させたりすることが可能となり、既存のインジケーター表示ロジックを変更せずに使いやすさを高めることができます。次に、ダッシュボードの状態を保存するために使用するグローバル変数を管理するための制御も追加する必要があります。

bool panel_minimized = false;                                    //--- Flag to control minimized state
int panel_x = 632, panel_y = 40;                                 //--- Panel position coordinates
bool panel_dragging = false;                                     //--- Flag to track if panel is being dragged
int panel_drag_x = 0, panel_drag_y = 0;                          //--- Mouse coordinates when drag starts
int panel_start_x = 0, panel_start_y = 0;                        //--- Panel coordinates when drag starts
int prev_mouse_state = 0;                                        //--- Variable to track previous mouse state
bool header_hovered = false;                                     //--- Header hover state
bool toggle_hovered = false;                                     //--- Toggle button hover state
bool close_hovered = false;                                      //--- Close button hover state
int last_mouse_x = 0, last_mouse_y = 0;                          //--- Track last mouse position for optimization
bool prev_header_hovered = false;                                //--- Track previous header hover state
bool prev_toggle_hovered = false;                                //--- Track previous toggle hover state
bool prev_close_hovered = false;                                 //--- Track previous close button hover state

ダッシュボードの状態を追跡するために、いくつかのグローバル変数を追加します。まずダッシュボードが最小化されているかどうかを追跡するpanel_minimizedを導入します。ダッシュボードの位置を管理するためにpanel_xとpanel_yを設定し、ドラッグ操作を管理するためにpanel_dragging、panel_drag_x、panel_drag_y、panel_start_x、panel_start_yを使用します。さらにマウスイベントやホバー状態を適切に処理するためにprev_mouse_state、header_hovered、toggle_hovered、close_hovered、last_mouse_x、last_mouse_y、prev_header_hovered、prev_toggle_hovered、prev_close_hoveredを追加します。これにより、ドラッグ可能でインタラクティブなダッシュボードをトグル機能付きで実現できるようになります。

次にダッシュボードにトグルボタンを追加します。切り替え状態を管理するためには最大化パネルと最小化パネルそれぞれの作成ロジックを別々の関数として実装する必要があります。まずは最大化状態のダッシュボードから作成を始めます。

//+------------------------------------------------------------------+
//| Create full dashboard UI                                         |
//+------------------------------------------------------------------+
void create_full_dashboard() {
   create_rectangle(MAIN_PANEL, panel_x, panel_y, 617, 374, C'30,30,30', BORDER_FLAT); //--- Create main panel background
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('r'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create minimize button (-)

   // Create header rectangle and label
   create_rectangle(SYMBOL_RECTANGLE, panel_x - 2, panel_y + 35, WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, clrGray); //--- Create symbol rectangle
   create_label(SYMBOL_TEXT, _Symbol, panel_x - 47, panel_y + 45, 11, COLOR_WHITE); //--- Create symbol label
   
   // Create summary and indicator headers (rectangles and labels)
   string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"}; //--- Define header titles
   for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
      int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
      int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on header type
      create_rectangle(HEADER_RECTANGLE + IntegerToString(header_index), x_offset, panel_y + 35, width, HEIGHT_RECTANGLE, clrGray); //--- Create header rectangle
      create_label(HEADER_TEXT + IntegerToString(header_index), header_names[header_index], x_offset - width/2, panel_y + 45, 11, COLOR_WHITE); //--- Create header label
   }
   
   // Create timeframe rectangles and labels, and summary/indicator cells
   for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
      // Highlight current timeframe
      color timeframe_background = (timeframes_array[timeframe_index] == _Period) ? clrLimeGreen : clrGray; //--- Set background color for current timeframe
      color timeframe_text_color = (timeframes_array[timeframe_index] == _Period) ? COLOR_BLACK : COLOR_WHITE; //--- Set text color for current timeframe
      
      create_rectangle(TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), panel_x - 2, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), WIDTH_TIMEFRAME, HEIGHT_RECTANGLE, timeframe_background); //--- Create timeframe rectangle
      create_label(TIMEFRAME_TEXT + IntegerToString(timeframe_index), truncate_timeframe_name(timeframe_index), panel_x - 47, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 11, timeframe_text_color); //--- Create timeframe label
                  
      // Create summary and indicator cells
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers for cells
         string cell_rectangle_name, cell_text_name;              //--- Declare cell name and label variables
         color cell_background = (header_index < 2) ? COLOR_LIGHT_GRAY : COLOR_BLACK; //--- Set cell background color
         switch(header_index) {                                   //--- Select cell type
            case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
            case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
            case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
            case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
            case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
            case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
            case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
         }
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set width based on cell type
         create_rectangle(cell_rectangle_name, x_offset, (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), width, HEIGHT_RECTANGLE, cell_background); //--- Create cell rectangle
         create_label(cell_text_name, "-/-", x_offset - width/2, (panel_y + 45 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index), 10, COLOR_WHITE); //--- Create cell label
      }
   }
   ChartRedraw(0);
}

ダッシュボードの状態は、create_full_dashboard関数を作成することで実装します。これにより静的な作成ロジックを関数に移動し、動的配置とトグル機能をサポートできるように更新します。主な変更点はpanel_xとpanel_yの変数をすべてのダッシュボード要素の位置設定に組み込むことで、チャート上の任意の位置にダッシュボードを配置できるようにしたことです。メインパネルはcreate_rectangleを使用してMAIN_PANELとして作成し、位置はpanel_xとpanel_yを指定してサイズは従来通り617×374を維持します。同様にヘッダパネル、アイコン、タイトルもcreate_rectangleやcreate_labelを使用してHEADER_PANEL、HEADER_PANEL_ICON、HEADER_PANEL_TEXTとして配置し、それぞれのx座標とy座標をpanel_xとpanel_yに対して相対的に調整します。

新しいTOGGLE_BUTTONはcreate_labelで追加し、位置はpanel_x - 570、panel_y + 14に配置して最小化記号(Webdingsの「r」)を表示します。シンボルの矩形とラベル(SYMBOL_RECTANGLE、SYMBOL_TEXT)やヘッダの矩形とラベル(HEADER_RECTANGLE、HEADER_TEXT)もpanel_xとpanel_yをオフセットに使用することで、パネルと一緒に移動するようにします。timeframes_arrayに含まれる各時間軸について、時間軸の矩形とラベル(TIMEFRAME_RECTANGLE、TIMEFRAME_TEXT)やインジケーターセル(RSI_RECTANGLE、STOCH_RECTANGLEなど)を作成し、位置はpanel_xとpanel_yに対して相対的に計算することで、レイアウトを保持しつつ移動可能にします。表示の更新にはChartRedraw関数を呼び出します。従来の固定座標(たとえば632、40)とは異なり、この関数では動的座標を使用するため、ダッシュボードをドラッグできるようになり、最小化/最大化用のトグルボタンも追加されています。これにより、以下のようなサンプルに近いダッシュボードを作成できます。

最大化された状態

次に、最小化されたパネルを作成する関数を用意します。

//+------------------------------------------------------------------+
//| Create minimized dashboard UI                                    |
//+------------------------------------------------------------------+
void create_minimized_dashboard() {
   create_rectangle(HEADER_PANEL, panel_x, panel_y, 617, 27, C'60,60,60', BORDER_FLAT); //--- Create header panel background
   create_label(HEADER_PANEL_ICON, CharToString(91), panel_x - 12, panel_y + 14, 18, clrAqua, "Wingdings"); //--- Create header icon
   create_label(HEADER_PANEL_TEXT, "TimeframeScanner", panel_x - 105, panel_y + 12, 13, COLOR_WHITE); //--- Create header title
   create_label(CLOSE_BUTTON, CharToString('r'), panel_x - 600, panel_y + 14, 18, clrYellow, "Webdings"); //--- Create close button
   create_label(TOGGLE_BUTTON, CharToString('o'), panel_x - 570, panel_y + 14, 18, clrYellow, "Wingdings"); //--- Create maximize button (+)
   ChartRedraw(0);
}

コンパクト表示への切り替えをサポートするために、create_minimized_dashboard関数を定義します。ヘッダパネルはcreate_rectangleを使用してHEADER_PANELとしてpanel_xとpanel_yに作成し、アイコンとタイトルはcreate_labelでHEADER_PANEL_ICONとHEADER_PANEL_TEXTとして追加します。また、CLOSE_BUTTONを含め、TOGGLE_BUTTONを(panel_x - 570、panel_y + 14)に配置し、最大化記号(Webdingsの「o」)を表示します。表示の更新にはChartRedrawを呼び出し、移動可能な最小化ダッシュボード状態を実現します。ご覧のとおり、最小化ボタンと最大化ボタンには一貫性を持たせるためにWingdingsフォントを使用しています。もちろん、好みに応じて別のフォントを選ぶことも可能です。今回の例では、アイコン文字はそれぞれ「r」と「o」です。以下にそれらの中央配置イメージを示します。

WINGDINGSフォント文字

最小化パネル状態を実行すると、以下のような結果になります。

最小化された状態

これらの関数を用意することで、ユーザーの操作に応じてどちらの状態を使用するかを選択できるようになります。また、複数のオブジェクトを作成しているため、必要に応じて全オブジェクトを削除して再作成できるようにし、オブジェクトをまとめて削除する関数を用意することも可能です。

//+------------------------------------------------------------------+
//| Delete all dashboard objects                                     |
//+------------------------------------------------------------------+
void delete_all_objects() {
   ObjectDelete(0, MAIN_PANEL);                                  //--- Delete main panel
   ObjectDelete(0, HEADER_PANEL);                                //--- Delete header panel
   ObjectDelete(0, HEADER_PANEL_ICON);                           //--- Delete header icon
   ObjectDelete(0, HEADER_PANEL_TEXT);                           //--- Delete header title
   ObjectDelete(0, CLOSE_BUTTON);                                //--- Delete close button
   ObjectDelete(0, TOGGLE_BUTTON);                               //--- Delete toggle button

   ObjectsDeleteAll(0, SYMBOL_RECTANGLE);                        //--- Delete all symbol rectangles
   ObjectsDeleteAll(0, SYMBOL_TEXT);                             //--- Delete all symbol labels
   ObjectsDeleteAll(0, TIMEFRAME_RECTANGLE);                     //--- Delete all timeframe rectangles
   ObjectsDeleteAll(0, TIMEFRAME_TEXT);                          //--- Delete all timeframe labels
   ObjectsDeleteAll(0, HEADER_RECTANGLE);                        //--- Delete all header rectangles
   ObjectsDeleteAll(0, HEADER_TEXT);                             //--- Delete all header labels
   ObjectsDeleteAll(0, RSI_RECTANGLE);                           //--- Delete all RSI rectangles
   ObjectsDeleteAll(0, RSI_TEXT);                                //--- Delete all RSI labels
   ObjectsDeleteAll(0, STOCH_RECTANGLE);                         //--- Delete all Stochastic rectangles
   ObjectsDeleteAll(0, STOCH_TEXT);                              //--- Delete all Stochastic labels
   ObjectsDeleteAll(0, CCI_RECTANGLE);                           //--- Delete all CCI rectangles
   ObjectsDeleteAll(0, CCI_TEXT);                                //--- Delete all CCI labels
   ObjectsDeleteAll(0, ADX_RECTANGLE);                           //--- Delete all ADX rectangles
   ObjectsDeleteAll(0, ADX_TEXT);                                //--- Delete all ADX labels
   ObjectsDeleteAll(0, AO_RECTANGLE);                            //--- Delete all AO rectangles
   ObjectsDeleteAll(0, AO_TEXT);                                 //--- Delete all AO labels
   ObjectsDeleteAll(0, BUY_RECTANGLE);                           //--- Delete all buy rectangles
   ObjectsDeleteAll(0, BUY_TEXT);                                //--- Delete all buy labels
   ObjectsDeleteAll(0, SELL_RECTANGLE);                          //--- Delete all sell rectangles
   ObjectsDeleteAll(0, SELL_TEXT);                               //--- Delete all sell labels
}

オブジェクトを削除するタイミングをより柔軟に管理するために、delete_all_objects関数を作成および更新し、新しいTOGGLE_BUTTONの削除も含めるようにしました。具体的には、TOGGLE_BUTTONを削除するためにObjectDelete関数を追加し、ダッシュボードを閉じたりトグル操作をおこなったりした際に、トグルボタンも正しく削除されるようにしています。その他のオブジェクト、たとえばMAIN_PANEL、HEADER_PANEL、HEADER_PANEL_ICON、HEADER_PANEL_TEXT、CLOSE_BUTTON、およびすべてのシンボル、時間軸、ヘッダ、インジケーター、シグナルの矩形やラベルについては、従来通りObjectsDeleteAll関数を使用して削除します。この変更により、移動可能で最小化可能なダッシュボードが、トグルボタンを含むすべてのコンポーネントを正しくクリーンアップし、ダッシュボードが非表示または再初期化された際もチャートを整った状態に保つことができます。

次に、カーソル位置に応じてダッシュボード要素を作成および更新する関数を作成する必要があります。そのためのロジックを以下に実装します。

//+------------------------------------------------------------------+
//| Update panel object positions                                    |
//+------------------------------------------------------------------+
void update_panel_positions() {
   // Update header and buttons
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set header panel x position
   ObjectSetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set header panel y position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_XDISTANCE, panel_x - 12); //--- Set header icon x position
   ObjectSetInteger(0, HEADER_PANEL_ICON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set header icon y position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_XDISTANCE, panel_x - 105); //--- Set header title x position
   ObjectSetInteger(0, HEADER_PANEL_TEXT, OBJPROP_YDISTANCE, panel_y + 12); //--- Set header title y position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE, panel_x - 600); //--- Set close button x position
   ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set close button y position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE, panel_x - 570); //--- Set toggle button x position
   ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE, panel_y + 14); //--- Set toggle button y position

   if (!panel_minimized) {
      // Update main panel
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_XDISTANCE, panel_x); //--- Set main panel x position
      ObjectSetInteger(0, MAIN_PANEL, OBJPROP_YDISTANCE, panel_y); //--- Set main panel y position

      // Update symbol rectangle and label
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_XDISTANCE, panel_x - 2); //--- Set symbol rectangle x position
      ObjectSetInteger(0, SYMBOL_RECTANGLE, OBJPROP_YDISTANCE, panel_y + 35); //--- Set symbol rectangle y position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_XDISTANCE, panel_x - 47); //--- Set symbol text x position
      ObjectSetInteger(0, SYMBOL_TEXT, OBJPROP_YDISTANCE, panel_y + 45); //--- Set symbol text y position

      // Update header rectangles and labels
      string header_names[] = {"BUY", "SELL", "RSI", "STOCH", "CCI", "ADX", "AO"};
      for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through headers
         int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset); //--- Set header rectangle x position
         ObjectSetInteger(0, HEADER_RECTANGLE + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 35); //--- Set header rectangle y position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_XDISTANCE, x_offset - (header_index < 2 ? WIDTH_SIGNAL/2 : WIDTH_INDICATOR/2)); //--- Set header text x position
         ObjectSetInteger(0, HEADER_TEXT + IntegerToString(header_index), OBJPROP_YDISTANCE, panel_y + 45); //--- Set header text y position
      }

      // Update timeframe rectangles, labels, and cells
      for(int timeframe_index = 0; timeframe_index < ArraySize(timeframes_array); timeframe_index++) { //--- Loop through timeframes
         int y_offset = (panel_y + 35 + HEIGHT_RECTANGLE) + timeframe_index * HEIGHT_RECTANGLE - (1 + timeframe_index); //--- Calculate y position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 2); //--- Set timeframe rectangle x position
         ObjectSetInteger(0, TIMEFRAME_RECTANGLE + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset); //--- Set timeframe rectangle y position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_XDISTANCE, panel_x - 47); //--- Set timeframe text x position
         ObjectSetInteger(0, TIMEFRAME_TEXT + IntegerToString(timeframe_index), OBJPROP_YDISTANCE, y_offset + 10); //--- Set timeframe text y position

         for(int header_index = 0; header_index < ArraySize(header_names); header_index++) { //--- Loop through cells
            string cell_rectangle_name, cell_text_name;
            switch(header_index) { //--- Select cell type
               case 0: cell_rectangle_name = BUY_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = BUY_TEXT + IntegerToString(timeframe_index); break; //--- Buy cell
               case 1: cell_rectangle_name = SELL_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = SELL_TEXT + IntegerToString(timeframe_index); break; //--- Sell cell
               case 2: cell_rectangle_name = RSI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = RSI_TEXT + IntegerToString(timeframe_index); break; //--- RSI cell
               case 3: cell_rectangle_name = STOCH_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = STOCH_TEXT + IntegerToString(timeframe_index); break; //--- Stochastic cell
               case 4: cell_rectangle_name = CCI_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = CCI_TEXT + IntegerToString(timeframe_index); break; //--- CCI cell
               case 5: cell_rectangle_name = ADX_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = ADX_TEXT + IntegerToString(timeframe_index); break; //--- ADX cell
               case 6: cell_rectangle_name = AO_RECTANGLE + IntegerToString(timeframe_index); cell_text_name = AO_TEXT + IntegerToString(timeframe_index); break; //--- AO cell
            }
            int x_offset = panel_x - WIDTH_TIMEFRAME - (header_index < 2 ? header_index * WIDTH_SIGNAL : 2 * WIDTH_SIGNAL + (header_index - 2) * WIDTH_INDICATOR) + (1 + header_index); //--- Calculate x position
            int width = (header_index < 2 ? WIDTH_SIGNAL : WIDTH_INDICATOR); //--- Set cell width
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_XDISTANCE, x_offset); //--- Set cell rectangle x position
            ObjectSetInteger(0, cell_rectangle_name, OBJPROP_YDISTANCE, y_offset); //--- Set cell rectangle y position
            ObjectSetInteger(0, cell_text_name, OBJPROP_XDISTANCE, x_offset - width/2); //--- Set cell text x position
            ObjectSetInteger(0, cell_text_name, OBJPROP_YDISTANCE, y_offset + 10); //--- Set cell text y position
         }
      }
   }
   ChartRedraw(0);                                               //--- Redraw chart
}

ダッシュボードの動的配置をサポートするために、新しくupdate_panel_positions関数を導入します。この関数は、現在のpanel_xとpanel_yの座標に基づいてすべてのダッシュボード要素の位置を調整し、チャート上でダッシュボードをドラッグできるようにします。ヘッダパネル、アイコン、タイトル、クローズボタン、トグルボタン(HEADER_PANEL、HEADER_PANEL_ICON、HEADER_PANEL_TEXT、CLOSE_BUTTON、TOGGLE_BUTTON)は、ObjectSetInteger関数を使用し、OBJPROP_XDISTANCEとOBJPROP_YDISTANCEでpanel_xとpanel_yに対して相対的な位置を設定して更新します。

panel_minimizedがfalseの場合には、メインパネル(MAIN_PANEL)、シンボルの矩形とラベル(SYMBOL_RECTANGLE、SYMBOL_TEXT)、ヘッダの矩形とラベル(HEADER_RECTANGLE、HEADER_TEXT)、および時間軸の矩形、ラベル、インジケーターセル(TIMEFRAME_RECTANGLE、TIMEFRAME_TEXT、BUY_RECTANGLE、RSI_RECTANGLEなど)も、panel_xとpanel_yに対して計算したオフセットを使用して再配置します。表示の更新にはChartRedraw関数を呼び出します。この関数により、ダッシュボードをドラッグした際にすべての要素が一緒に移動することが保証され、移動可能なダッシュボード強化において非常に重要な機能となります。これでダッシュボードのテストが可能になります。テストのためには、OnInitイベントハンドラでフルダッシュボードを作成する関数を呼び出し、OnDeinitイベントハンドラでオブジェクト削除の関数を呼び出します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                                                     //--- Initialize EA
{
   create_full_dashboard();                                      //--- Create full dashboard
   ArraySetAsSeries(rsi_values, true);                           //--- Set RSI array as timeseries
   ArraySetAsSeries(stochastic_values, true);                    //--- Set Stochastic array as timeseries
   ArraySetAsSeries(cci_values, true);                           //--- Set CCI array as timeseries
   ArraySetAsSeries(adx_values, true);                           //--- Set ADX array as timeseries
   ArraySetAsSeries(ao_values, true);                            //--- Set AO array as timeseries
    
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);             //--- Enable mouse move events
   return(INIT_SUCCEEDED);                                       //--- Return initialization success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)                                  //--- Deinitialize EA
{
   delete_all_objects();                                         //--- Delete all objects
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);            //--- Disable mouse move events
   ChartRedraw(0);                                               //--- Redraw chart
}

ここでは、プログラムの初期化および終了時にそれぞれの関数を呼び出すことで、次のセクションに進む前に基本的な動作をテストできるようにしています。プログラムを段階的にコンパイルして動作を確認することは、常に良いプログラミングの習慣です。コンパイルすると次の結果が得られます。

INITとDEINIT GIF

この可視化から、パネルを正常に初期化および削除できることが確認できます。次に、ダッシュボードをインタラクティブに反応させる作業に移ります。まず、カーソルの位置を取得し、ヘッダ上にあるのかボタン上にあるのかを判定する必要があります。これにより、ユーザーが具体的にどの操作をおこなおうとしているのかを把握できます。また、ボタンの移動経路を取得することで、ホバー状態やクリック状態を色の変更によって視覚化できるようにします。まずは、ダッシュボード要素に対するカーソルの位置を判定する関数を定義しましょう。

//+------------------------------------------------------------------+
//| Check if cursor is inside header or buttons                      |
//+------------------------------------------------------------------+
bool is_cursor_in_header_or_buttons(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Header panel bounds
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;
   bool in_header = (mouse_x >= header_left && mouse_x <= header_right && 
                     mouse_y >= header_y && mouse_y <= header_y + header_height);

   // Close button bounds
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool in_close = (mouse_x >= close_left && mouse_x <= close_right && 
                    mouse_y >= close_y && mouse_y <= close_y + close_height);

   // Toggle button bounds
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool in_toggle = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                     mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   return in_header || in_close || in_toggle;
}

まず、ダッシュボードの動的配置とトグル機能をサポートするために、新しくis_cursor_in_header_or_buttons関数を導入します。この関数は、マウスカーソルがヘッダパネル、クローズボタン、またはトグルボタンの上にあるかを判定し、インタラクティブなドラッグ操作やボタン操作を可能にします。まず、ChartGetIntegerを使用してCHART_WIDTH_IN_PIXELSからチャートの幅を取得します。ヘッダパネルについては、ObjectGetIntegerでHEADER_PANELのOBJPROP_XDISTANCE、OBJPROP_YDISTANCE、OBJPROP_XSIZE、OBJPROP_YSIZEを取得し、header_x、header_y、header_width、header_heightを算出します。さらに、チャート幅に対してheader_leftとheader_rightを計算し、mouse_xとmouse_yがこの範囲内にあればin_headerをtrueに設定します。

[Close]ボタンについては、CLOSE_BUTTONのclose_xとclose_yを取得し、20×20ピクセルの範囲を定義してclose_leftとclose_rightを計算します。カーソルがこの範囲内にあればin_closeをtrueに設定します。同様にトグルボタンについては、TOGGLE_BUTTONのtoggle_xとtoggle_yを取得し、20×20ピクセルの範囲を定義して、カーソルがこれらの領域のいずれか(in_header、in_close、in_toggle)にある場合はtrueを返します。この関数は、カーソルがヘッダパネルやボタン上にあるかを検出するために非常に重要であり、ドラッグ可能でインタラクティブなダッシュボード機能を実現する基盤となります。その後、ホバー状態を更新し、色の変化で視覚的に認識しやすくすることができます。

//+------------------------------------------------------------------+
//| Update button hover states                                       |
//+------------------------------------------------------------------+
void update_button_hover_states(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   
   // Close button hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;
   bool is_close_hovered = (mouse_x >= close_left && mouse_x <= close_right && 
                            mouse_y >= close_y && mouse_y <= close_y + close_height);

   if (is_close_hovered != prev_close_hovered) {
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, is_close_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, is_close_hovered ? clrDodgerBlue : clrNONE);
      prev_close_hovered = is_close_hovered;
      ChartRedraw(0);
   }

   // Toggle button hover
   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;
   bool is_toggle_hovered = (mouse_x >= toggle_left && mouse_x <= toggle_right && 
                             mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height);

   if (is_toggle_hovered != prev_toggle_hovered) {
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, is_toggle_hovered ? clrWhite : clrYellow);
      ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, is_toggle_hovered ? clrDodgerBlue : clrNONE);
      prev_toggle_hovered = is_toggle_hovered;
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| Update header hover state                                        |
//+------------------------------------------------------------------+
void update_header_hover_state(int mouse_x, int mouse_y) {
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
   int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
   int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
   int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
   int header_left = chart_width - header_x;
   int header_right = header_left + header_width;

   // Exclude button areas from header hover
   int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
   int close_y = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_YDISTANCE);
   int close_width = 20;
   int close_height = 20;
   int close_left = chart_width - close_x;
   int close_right = close_left + close_width;

   int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
   int toggle_y = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_YDISTANCE);
   int toggle_width = 20;
   int toggle_height = 20;
   int toggle_left = chart_width - toggle_x;
   int toggle_right = toggle_left + toggle_width;

   bool is_header_hovered = (mouse_x >= header_left && mouse_x <= header_right && 
                             mouse_y >= header_y && mouse_y <= header_y + header_height &&
                             !(mouse_x >= close_left && mouse_x <= close_right && 
                               mouse_y >= close_y && mouse_y <= close_y + close_height) &&
                             !(mouse_x >= toggle_left && mouse_x <= toggle_right && 
                               mouse_y >= toggle_y && mouse_y <= toggle_y + toggle_height));

   if (is_header_hovered != prev_header_hovered && !panel_dragging) {
      ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, is_header_hovered ? clrRed : C'60,60,60');
      prev_header_hovered = is_header_hovered;
      ChartRedraw(0);
   }

   update_button_hover_states(mouse_x, mouse_y);
}

ここでは、ユーザー操作に対する視覚的フィードバックを追加し、ダッシュボードの使いやすさを向上させるために、update_button_hover_states関数とupdate_header_hover_state関数の2つの新しい関数を導入します。まずupdate_button_hover_states関数についてです。この関数はmouse_xとmouse_yを受け取り、クローズボタンとトグルボタンのホバー状態を検出します。CLOSE_BUTTONについては、ObjectGetIntegerを使用してOBJPROP_XDISTANCEとOBJPROP_YDISTANCEからclose_xとclose_yを取得し、ChartGetIntegerでCHART_WIDTH_IN_PIXELSを取得してチャート幅に対して20×20ピクセルの範囲を計算します。カーソルがこの範囲内にある場合、is_close_hoveredをtrueに設定します。

is_close_hoveredの状態がprev_close_hoveredと異なる場合、ObjectSetIntegerでCLOSE_BUTTONのOBJPROP_COLORをclrWhite、OBJPROP_BGCOLORをclrDodgerBlueに設定してホバー時の色を変更し、ホバーしていない場合はclrYellowとclrNONEに設定します。その後、prev_close_hoveredを更新し、ChartRedrawを呼び出して表示を更新します。同様に、TOGGLE_BUTTONについてもtoggle_xとtoggle_yを取得し、20×20ピクセルの範囲を確認して、is_toggle_hoveredの状態が変化した場合には色とprev_toggle_hoveredを更新し、ボタンに対するレスポンシブなフィードバックを提供します。

次にupdate_header_hover_state関数を作成します。この関数もmouse_xとmouse_yを受け取り、HEADER_PANELのheader_x、header_y、header_width、header_heightをObjectGetIntegerで取得し、ヘッダの範囲を計算します。その際、CLOSE_BUTTONとTOGGLE_BUTTONの20×20ピクセルの範囲は除外し、重なりを避けます。is_header_hoveredの状態がprev_header_hoveredと異なり、かつpanel_draggingがfalseの場合には、HEADER_PANELのOBJPROP_BGCOLORをホバー時はclrRed、非ホバー時はC'60,60,60'に設定し、prev_header_hoveredを更新してChartRedrawを呼び出します。その後、update_button_hover_statesを呼び出してボタンの状態も更新します。これらの関数により、ドラッグ操作やボタン操作に対して視覚的な手がかりを提供でき、ダッシュボードのインタラクティブ性が向上します。これらの関数はOnChartEventイベントハンドラ内で使用することで完全に実装できます。私たちが適用するロジックは次のとおりです。

//+------------------------------------------------------------------+
//| Expert chart event handler                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int event_id, const long& long_param, const double& double_param, const string& string_param)
{
   if (event_id == CHARTEVENT_OBJECT_CLICK) {                    //--- Handle object click event
      if (string_param == CLOSE_BUTTON) {                        //--- Check if close button clicked
         Print("Closing the panel now");                         //--- Log panel closure
         PlaySound("alert.wav");                                 //--- Play alert sound
         panel_is_visible = false;                               //--- Hide panel
         delete_all_objects();                                   //--- Delete all objects
         ChartRedraw(0);                                         //--- Redraw chart
      } else if (string_param == TOGGLE_BUTTON) {                //--- Toggle button clicked
         delete_all_objects();                                   //--- Delete current UI
         panel_minimized = !panel_minimized;                     //--- Toggle minimized state
         if (panel_minimized) {
            Print("Minimizing the panel");                       //--- Log minimization
            create_minimized_dashboard();                        //--- Create minimized UI
         } else {
            Print("Maximizing the panel");                       //--- Log maximization
            create_full_dashboard();                             //--- Create full UI
         }
         // Reset hover states after toggle
         prev_header_hovered = false;
         prev_close_hovered = false;
         prev_toggle_hovered = false;
         ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, C'60,60,60');
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, CLOSE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, TOGGLE_BUTTON, OBJPROP_BGCOLOR, clrNONE);
         ChartRedraw(0);
      }
   }
   else if (event_id == CHARTEVENT_MOUSE_MOVE && panel_is_visible) { //--- Handle mouse move events
      int mouse_x = (int)long_param;                             //--- Get mouse x-coordinate
      int mouse_y = (int)double_param;                           //--- Get mouse y-coordinate
      int mouse_state = (int)string_param;                       //--- Get mouse state

      if (mouse_x == last_mouse_x && mouse_y == last_mouse_y && !panel_dragging) { //--- Skip redundant updates
         return;
      }
      last_mouse_x = mouse_x;                                    //--- Update last mouse x position
      last_mouse_y = mouse_y;                                    //--- Update last mouse y position

      update_header_hover_state(mouse_x, mouse_y);               //--- Update header and button hover states

      int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      int header_x = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XDISTANCE);
      int header_y = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YDISTANCE);
      int header_width = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_XSIZE);
      int header_height = (int)ObjectGetInteger(0, HEADER_PANEL, OBJPROP_YSIZE);
      int header_left = chart_width - header_x;
      int header_right = header_left + header_width;

      int close_x = (int)ObjectGetInteger(0, CLOSE_BUTTON, OBJPROP_XDISTANCE);
      int close_width = 20;
      int close_left = chart_width - close_x;
      int close_right = close_left + close_width;

      int toggle_x = (int)ObjectGetInteger(0, TOGGLE_BUTTON, OBJPROP_XDISTANCE);
      int toggle_width = 20;
      int toggle_left = chart_width - toggle_x;
      int toggle_right = toggle_left + toggle_width;

      if (prev_mouse_state == 0 && mouse_state == 1) {           //--- Detect mouse button down
         if (mouse_x >= header_left && mouse_x <= header_right && 
             mouse_y >= header_y && mouse_y <= header_y + header_height &&
             !(mouse_x >= close_left && mouse_x <= close_right) &&
             !(mouse_x >= toggle_left && mouse_x <= toggle_right)) { //--- Exclude button areas
            panel_dragging = true;                              //--- Start dragging
            panel_drag_x = mouse_x;                             //--- Store mouse x-coordinate
            panel_drag_y = mouse_y;                             //--- Store mouse y-coordinate
            panel_start_x = header_x;                           //--- Store panel x-coordinate
            panel_start_y = header_y;                           //--- Store panel y-coordinate
            ObjectSetInteger(0, HEADER_PANEL, OBJPROP_BGCOLOR, clrMediumBlue); //--- Set header to blue on drag start
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable chart scrolling
         }
      }

      if (panel_dragging && mouse_state == 1) {                  //--- Handle dragging
         int dx = mouse_x - panel_drag_x;                        //--- Calculate x displacement
         int dy = mouse_y - panel_drag_y;                        //--- Calculate y displacement
         panel_x = panel_start_x - dx;                           //--- Update panel x-position (inverted for CORNER_RIGHT_UPPER)
         panel_y = panel_start_y + dy;                           //--- Update panel y-position

         int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width
         int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height
         panel_x = MathMax(617, MathMin(chart_width, panel_x));  //--- Keep panel within right edge
         panel_y = MathMax(0, MathMin(chart_height - (panel_minimized ? 27 : 374), panel_y)); //--- Adjust height based on state

         update_panel_positions();                               //--- Update all panel object positions
         ChartRedraw(0);                                         //--- Redraw chart during dragging
      }

      if (mouse_state == 0 && prev_mouse_state == 1) {           //--- Detect mouse button release
         if (panel_dragging) {
            panel_dragging = false;                              //--- Stop dragging
            update_header_hover_state(mouse_x, mouse_y);          //--- Update hover state immediately after drag
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);        //--- Re-enable chart scrolling
            ChartRedraw(0);                                      //--- Redraw chart
         }
      }

      prev_mouse_state = mouse_state;                            //--- Update previous mouse state
   }
}

ここでは、OnChartEventイベントハンドラを更新して、動的配置とトグル機能をサポートすることで、前バージョンよりも大幅に機能を強化します。まず、CHARTEVENT_OBJECT_CLICKでのCLOSE_BUTTONの処理はそのまま保持します。これは、閉じる操作をPrintでログに記録し、PlaySoundで音を再生し、panel_is_visibleをfalseに設定した後、delete_all_objects関数を呼び出してすべてのオブジェクトを削除し、ChartRedrawでチャートを再描画します。

新たな追加はTOGGLE_BUTTONのクリック処理です。ここでは、delete_all_objects関数を呼び出し、panel_minimizedを切り替えます。panel_minimizedがtrueの場合はcreate_minimized_dashboardを呼び出して「Minimizing the panel」とログを出力し、falseの場合はcreate_full_dashboardを呼び出して「Maximizing the panel」とログを出力します。さらに、hover状態(prev_header_hovered、prev_close_hovered、prev_toggle_hovered)をすべてfalseにリセットし、ObjectSetIntegerでHEADER_PANEL、CLOSE_BUTTON、TOGGLE_BUTTONの色をデフォルトに戻し、ChartRedrawでチャートを更新します。

動的配置のために、panel_is_visibleがtrueのときにCHARTEVENT_MOUSE_MOVEの処理を追加します。イベントパラメータからmouse_x、mouse_y、mouse_stateを取得し、座標がlast_mouse_x、last_mouse_yと同じでpanel_draggingがfalseの場合は更新をスキップし、座標を更新します。次にupdate_header_hover_stateを呼び出してホバー効果を管理します。prev_mouse_stateが0でmouse_stateが1の場合、カーソルがHEADER_PANEL上(CLOSE_BUTTONとTOGGLE_BUTTONの範囲を除く)にあるかをObjectGetIntegerとChartGetIntegerで判定し、panel_draggingをtrueに設定します。さらにpanel_drag_x、panel_drag_y、panel_start_x、panel_start_yに座標を保存し、HEADER_PANELの色をclrMediumBlueに設定、ChartSetIntegerでチャートスクロールを無効化します。

panel_draggingがtrueでmouse_stateが1の場合は、移動量を計算してpanel_xとpanel_yをチャート範囲内で更新し、update_panel_positionsを呼び出してパネル位置を反映し、ChartRedrawで再描画します。マウスを放した時点でドラッグを停止し、ホバー状態を更新、スクロールを再有効化して再描画します。最後にprev_mouse_stateを更新します。これらの変更により、従来の静的なクリック操作のみではなく、ドラッグとトグルが可能なダッシュボードが実現します。コンパイルすると、次の結果が得られます。

ドラッグ、ホバー、可視性の状態

可視化の結果から、ダッシュボードが順調に機能し始めていることが確認できますが、イベントハンドラ間の競合を解消する必要があります。生産性を高めるためには、どのイベントハンドラを優先させるかを明確にし、処理の優先順位を適切に制御することが重要です。たとえば、ホバー中やドラッグ中、または最小化モードのときは、チャートイベントを優先させる必要があります。一方で、待機状態や最大化モードのときは、ダッシュボードの更新を優先させる必要があります。これを実現するために、ダッシュボードの更新がおこなわれるティック処理イベント(OnTick)を調整します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                                                     //--- Handle tick events
{
   if (panel_is_visible && !panel_minimized && !is_cursor_in_header_or_buttons(last_mouse_x, last_mouse_y)) { //--- Update indicators only if panel is visible, not minimized, and cursor is not in header/buttons
      updateIndicators();                                        //--- Update indicators
   }
}

OnTickOnTickイベントハンドラを更新し、インジケーター更新処理を最適化します。以前のバージョンでは、panel_is_visibleがtrueであるかどうかのみを条件にupdateIndicators関数を呼び出していましたが、今回の改良では、panel_is_visibleがtrueであり、かつpanel_minimizedがfalseであり、さらにマウスカーソルがヘッダやボタン上にない場合にのみインジケーターを更新するように条件を追加します。この判定には、last_mouse_xおよびlast_mouse_yを使用してis_cursor_in_header_or_buttons関数を呼び出し、カーソル位置を確認します。この変更により、ダッシュボードが最小化されている場合や、ユーザーがヘッダ、クローズボタン、またはトグルボタン上で操作をおこなっている場合には、インジケーターの更新が一時停止されるようになります。これにより、不要な処理を削減し、ドラッグやトグル操作中のパフォーマンスを大幅に向上させることができます。コンパイルすると、次の結果が得られます。

パフォーマンス向上GIF

視覚化から、ダッシュボードのパフォーマンスが向上し、すべての目標が達成されたことがわかります。残されているのはプロジェクトの実用性をテストすることであり、それは次のセクションで扱います。


バックテスト

テストを実施しました。以下に示すのはコンパイル後の可視化を1つのGraphics Interchange Format (GIF)ビットマップ画像形式としてまとめたものです。

ダッシュボードのバックテスト


結論

まとめとして、今回の取り組みでは、MQL5における多時間軸スキャナーダッシュボードを拡張し、動的な位置調整機能とトグル機能を追加しました。第3回で構築した基盤をもとに、インターフェースを移動可能にし、最小化・最大化の切り替え機能や、ユーザー操作に応じたホバー効果を実装することで、操作性を大幅に向上させました。これらの改善は、create_minimized_dashboard関数やupdate_header_hover_state関数などを活用して実現し、既存のインジケーターグリッドとシームレスに統合することで、リアルタイムの取引分析をよりスムーズにおこなえるようにしています。今後は、このダッシュボードをさらにカスタマイズしてご自身の取引スタイルに最適化することで、複数の時間軸にわたる市場シグナルをより効率的に監視できるようになります。 

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

最後のコメント | ディスカッションに移動 (2)
linfo2
linfo2 | 17 7月 2025 において 17:38
アラン、ありがとう。とてもクールで、よく文書化されているし、僕が知らなかった特徴もカバーしてくれている。
Allan Munene Mutiiria
Allan Munene Mutiiria | 17 7月 2025 において 18:58
linfo2 #:
アラン、ありがとう。とてもクールで、よく文書化されているし、私が知らなかった機能もカバーしている。
linfo2さん、大歓迎です。ありがとう。
MQL5で他の言語の実用的なモジュールを実装する(第2回):Pythonに着想を得たREQUESTSライブラリの構築 MQL5で他の言語の実用的なモジュールを実装する(第2回):Pythonに着想を得たREQUESTSライブラリの構築
この記事では、MetaTrader 5 (MQL5)でWebリクエストの送受信をより簡単におこなうために、Pythonのrequestsモジュールに似たモジュールを実装します。
プライスアクション分析ツールキットの開発(第31回):Python Candlestick Recognitionエンジン(I) - 手動検出 プライスアクション分析ツールキットの開発(第31回):Python Candlestick Recognitionエンジン(I) - 手動検出
ローソク足パターンはプライスアクション取引において基本的な要素であり、市場の反転や継続の可能性を示す貴重な手がかりを提供します。信頼できるツールを想像してみてください。このツールは、新しい価格バーが生成されるたびにそれを監視し、包み足、ハンマー、十字線、スターなどの主要な形成を特定し、重要な取引セットアップが検出された際に即座に通知します。これがまさに私たちが開発した機能です。このシステムは、取引初心者の方から経験豊富なプロフェッショナルまで幅広く活用できます。ローソク足パターンをリアルタイムで通知することで、取引の実行に集中し、より自信を持って効率的に取引をおこなうことが可能になります。以下では、本ツールの動作方法と、どのように取引戦略を強化できるかについて詳しく説明します。
知っておくべきMQL5ウィザードのテクニック(第75回):Awesome Oscillatorとエンベロープの使用 知っておくべきMQL5ウィザードのテクニック(第75回):Awesome Oscillatorとエンベロープの使用
ビル・ウィリアムズによるオーサムオシレータ(AO: Awesome Oscillator)とエンベロープチャネル(Envelopes Channel)は、MQL5のエキスパートアドバイザー(EA)内で補完的に使用できる組み合わせです。AOはトレンドを検出する能力を持つためこれを利用し、一方でエンベロープチャネルはサポートおよびレジスタンスレベルを定義する目的で組み込みます。本記事は、このインジケーターの組み合わせを探求するにあたり、MQL5ウィザードを用いて両者が持つ可能性を構築および検証します。
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(V) - イベントリマインダーシステム 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(V) - イベントリマインダーシステム
本ディスカッションでは、News Headline EAに表示される経済指標カレンダーイベントに対して、精緻化されたイベント通知ロジックを統合することで得られる追加的な改善について検討します。この強化により、主要な今後のイベント直前にユーザーがタイムリーに通知を受け取れるようになります。詳細については、本ディスカッションでご確認ください。