MQL5取引ツール(第4回):動的配置とトグル機能による多時間軸スキャナダッシュボードの改善
はじめに
前回の記事(第3回)では、MetaQuotes Language 5 (MQL5)で多時間軸スキャナーダッシュボードを開発し、複数の時間軸にわたって相対力指数(RSI)、ストキャスティクス、コモディティチャンネル指数(CCI)、平均方向性指数(ADX: Average Directional Index)、およびオーサムオシレーター(AO: Awesome Oscillator)インジケーターを表示して、現在の銘柄の取引シグナルを識別しました。第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」です。以下にそれらの中央配置イメージを示します。

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

これらの関数を用意することで、ユーザーの操作に応じてどちらの状態を使用するかを選択できるようになります。また、複数のオブジェクトを作成しているため、必要に応じて全オブジェクトを削除して再作成できるようにし、オブジェクトをまとめて削除する関数を用意することも可能です。
//+------------------------------------------------------------------+ //| 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 }
ここでは、プログラムの初期化および終了時にそれぞれの関数を呼び出すことで、次のセクションに進む前に基本的な動作をテストできるようにしています。プログラムを段階的にコンパイルして動作を確認することは、常に良いプログラミングの習慣です。コンパイルすると次の結果が得られます。
この可視化から、パネルを正常に初期化および削除できることが確認できます。次に、ダッシュボードをインタラクティブに反応させる作業に移ります。まず、カーソルの位置を取得し、ヘッダ上にあるのかボタン上にあるのかを判定する必要があります。これにより、ユーザーが具体的にどの操作をおこなおうとしているのかを把握できます。また、ボタンの移動経路を取得することで、ホバー状態やクリック状態を色の変更によって視覚化できるようにします。まずは、ダッシュボード要素に対するカーソルの位置を判定する関数を定義しましょう。
//+------------------------------------------------------------------+ //| 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関数を呼び出し、カーソル位置を確認します。この変更により、ダッシュボードが最小化されている場合や、ユーザーがヘッダ、クローズボタン、またはトグルボタン上で操作をおこなっている場合には、インジケーターの更新が一時停止されるようになります。これにより、不要な処理を削減し、ドラッグやトグル操作中のパフォーマンスを大幅に向上させることができます。コンパイルすると、次の結果が得られます。

視覚化から、ダッシュボードのパフォーマンスが向上し、すべての目標が達成されたことがわかります。残されているのはプロジェクトの実用性をテストすることであり、それは次のセクションで扱います。
バックテスト
テストを実施しました。以下に示すのはコンパイル後の可視化を1つのGraphics Interchange Format (GIF)ビットマップ画像形式としてまとめたものです。

結論
まとめとして、今回の取り組みでは、MQL5における多時間軸スキャナーダッシュボードを拡張し、動的な位置調整機能とトグル機能を追加しました。第3回で構築した基盤をもとに、インターフェースを移動可能にし、最小化・最大化の切り替え機能や、ユーザー操作に応じたホバー効果を実装することで、操作性を大幅に向上させました。これらの改善は、create_minimized_dashboard関数やupdate_header_hover_state関数などを活用して実現し、既存のインジケーターグリッドとシームレスに統合することで、リアルタイムの取引分析をよりスムーズにおこなえるようにしています。今後は、このダッシュボードをさらにカスタマイズしてご自身の取引スタイルに最適化することで、複数の時間軸にわたる市場シグナルをより効率的に監視できるようになります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18786
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5で他の言語の実用的なモジュールを実装する(第2回):Pythonに着想を得たREQUESTSライブラリの構築
プライスアクション分析ツールキットの開発(第31回):Python Candlestick Recognitionエンジン(I) - 手動検出
知っておくべきMQL5ウィザードのテクニック(第75回):Awesome Oscillatorとエンベロープの使用
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(V) - イベントリマインダーシステム
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
アラン、ありがとう。とてもクールで、よく文書化されているし、私が知らなかった機能もカバーしている。