English Deutsch
preview
MQL5取引ツール(第6回):パルスアニメーションとコントロールを備えたダイナミックホログラフィックダッシュボード

MQL5取引ツール(第6回):パルスアニメーションとコントロールを備えたダイナミックホログラフィックダッシュボード

MetaTrader 5トレーディング |
83 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第5回)では、MetaQuotes Language 5 (MQL5)でローリングティッカーテープを作成し、価格やスプレッド、変動をリアルタイムで表示し、トレーダーが効率的に情報を把握できるようにしました。今回の第6回では、複数の銘柄と時間足のインジケーター(Relative Strength Index(相対力指数、RSI)やAverage True Range (ATR)に基づくボラティリティなどを表示する動的ホログラフィックダッシュボードを開発します。パルスアニメーション、ソート機能、インタラクティブコントロールを備えた、視覚的に魅力的な分析ツールを作成します。本記事では以下のトピックを扱います。

  1. ホログラフィックダッシュボードのアーキテクチャの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的には、カスタマイズ可能なホログラフィックダッシュボードを取引環境で使用できるようになります。それでは始めましょう。


ホログラフィックダッシュボードのアーキテクチャの理解

今回作成するホログラフィックダッシュボードは、複数の銘柄と時間足を監視する視覚的ツールです。RSIやボラティリティなどのインジケーターを表示し、ソートやアラート機能を組み合わせることで、取引機会を素早く把握できるようにします。このアーキテクチャは、リアルタイムデータとインタラクティブなコントロールやアニメーションを組み合わせるため、チャートが混雑している環境でも、効率的で魅力的な分析を可能にします。

データ管理には配列を使用し、ATRやRSIなどのインジケーターにはハンドルを使用します。ソートやパルス効果のための関数、表示切替やビュー切替のためのボタンも導入します。また、ユーザーインターフェース(UI)を動的に更新するループで更新を集中管理することで、ダッシュボードを戦略的な取引に対応できる柔軟で反応の良いものにします。以下の可視化を参照して、実装前に目指すイメージを確認してください。

完全なアーキテクチャ


MQL5での実装

MQL5でプログラムを作成するには、まずプログラムのメタデータを定義し、その後、プログラムの動作を直接コードに触れずに簡単に変更できるようにする入力パラメータを定義する必要があります。

//+------------------------------------------------------------------+
//|                                  Holographic Dashboard EA.mq5    |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Arrays\ArrayString.mqh> //--- Include ArrayString library for string array operations
#include <Files\FileTxt.mqh>      //--- Include FileTxt library for text file operations

// Input Parameters
input int BaseFontSize = 9;           // Base Font Size
input string FontType = "Calibri";    // Font Type (Professional)
input int X_Offset = 30;              // X Offset
input int Y_Offset = 30;              // Y Offset
input color PanelColor = clrDarkSlateGray; // Panel Background Color
input color TitleColor = clrWhite;    // Title/Symbol Color
input color DataColor = clrLightGray; // Bid/Neutral Color
input color ActiveColor = clrLime;    // Active Timeframe/Symbol Color
input color UpColor = clrDeepSkyBlue; // Uptrend Color
input color DownColor = clrCrimson;   // Downtrend Color
input color LineColor = clrSilver;    // Grid Line Color
input bool EnableAnimations = true;   // Enable Pulse Animations
input int PanelWidth = 730;           // Panel Width (px)
input int ATR_Period = 14;            // ATR Period for Volatility
input int RSI_Period = 14;            // RSI Period
input double Vol_Alert_Threshold = 2.0; // Volatility Alert Threshold (%)
input color GlowColor = clrDodgerBlue; // Glow Color for holographic effect
input int AnimationSpeed = 30; // Animation delay in ms for pulse
input int PulseCycles = 3; // Number of pulse cycles for animations

まず、文字列配列やテキストファイルへのログ記録のためのライブラリをインクルードし、UIやインジケーターをカスタマイズするための入力パラメータを設定します。銘柄リストを扱うために<Arrays\ArrayString.mqh>を、エラーをファイルに記録するために<Files\FileTxt.mqh>をインクルードします。入力パラメータにより、基本フォントサイズを9に調整したり、Calibriのようなプロフェッショナルフォントを選択したり、XおよびY位置のオフセットをそれぞれ30ピクセルに設定したり、パネル背景にダークスレートグレー、タイトルに白、データにライトグレー、アクティブ要素にライム、上昇トレンドにディープスカイブルー、下降トレンドにクリムゾン、グリッド線にシルバーなどの色を選択したりすることができます。

パルスアニメーションはデフォルトで有効にし、パネル幅を730ピクセルに設定します。また、ボラティリティやモメンタムの計算にはATRとRSIの期間をそれぞれ14に設定し、ボラティリティアラートの閾値を2.0%に設定します。ホログラフィックのグローにはドジャーブルーを選択し、アニメーション速度は30ms、パルスサイクルは3回に設定します。これらの設定により、ダッシュボードは視覚的および機能的な好みに応じて柔軟に適応できるようになります。その後、プログラム全体で使用するグローバル変数を定義する必要があります。

// Global Variables
double prices_PrevArray[];            //--- Array for previous prices
double volatility_Array[];            //--- Array for volatility values
double bid_array[];                   //--- Array for bid prices
long spread_array[];                  //--- Array for spreads
double change_array[];                //--- Array for percentage changes
double vol_array[];                   //--- Array for volumes
double rsi_array[];                   //--- Array for RSI values
int indices[];                        //--- Array for sorted indices
ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H2, PERIOD_H4, PERIOD_D1, PERIOD_W1}; //--- Array of timeframes
string logFileName = "Holographic_Dashboard_Log.txt"; //--- Log file name
int sortMode = 0;                     //--- Current sort mode
string sortNames[] = {"Name ASC", "Vol DESC", "Change ABS DESC", "RSI DESC"}; //--- Sort mode names
int atr_handles_sym[];                //--- ATR handles for symbols
int rsi_handles_sym[];                //--- RSI handles for symbols
int atr_handles_tf[];                 //--- ATR handles for timeframes
int rsi_handles_tf[];                 //--- RSI handles for timeframes
int totalSymbols;                     //--- Total number of symbols
bool dashboardVisible = true;         //--- Dashboard visibility flag

ここでは、プログラム内でデータやインジケーターを管理するためのグローバル変数を定義し、リアルタイム監視、ソート、アニメーションに対応できるようにします。たとえば、前回の価格から変化を計算するためのprices_PrevArray、ボラティリティ値のvolatility_Array、現在のBid値のbid_array、スプレッドを格納するspread_array(long型)、変化率のchange_array、出来高のvol_array、RSI値のrsi_array、ソート用のインデックスindicesなどの配列を作成します。また、時間足をPERIOD_M1からPERIOD_W1まで配列periodsとして設定し、エラーログ用にlogFileNameを「Holographic_Dashboard_Log.txt」に設定します。初期ソートはsortModeを0に設定し、ソートオプションは「Name ASC」や「Vol DESC」などの文字列でsortNames配列に格納します。さらに、ATRおよびRSIのハンドル用配列として、シンボル用にatr_handles_symとrsi_handles_sym、時間足用にatr_handles_tfとrsi_handles_tfを作成します。

整数型のtotalSymbolsは銘柄の総数を追跡し、dashboardVisibleはダッシュボードの表示状態を制御するためにtrueに設定します。オブジェクト管理をより効率的におこなうために、クラスを作成する予定です。

// Object Manager Class
class CObjectManager : public CArrayString {
public:
   void AddObject(string name) {      //--- Add object name to manager
      if (!Add(name)) {               //--- Check if add failed
         LogError(__FUNCTION__ + ": Failed to add object name: " + name); //--- Log error
      }
   }
   
   void DeleteAllObjects() {          //--- Delete all managed objects
      for (int i = Total() - 1; i >= 0; i--) { //--- Iterate through objects
         string name = At(i);         //--- Get object name
         if (ObjectFind(0, name) >= 0) { //--- Check if object exists
            if (!ObjectDelete(0, name)) { //--- Delete object
               LogError(__FUNCTION__ + ": Failed to delete object: " + name + ", Error: " + IntegerToString(GetLastError())); //--- Log deletion failure
            }
         }
         Delete(i);                   //--- Remove from array
      }
      ChartRedraw(0);                 //--- Redraw chart
   }
};

ダッシュボードのオブジェクトを効率的に管理するために、CArrayStringクラスを継承したCObjectManagerクラスを作成します。AddObjectメソッドでは、オブジェクトのnameを配列にAddメソッドで追加し、追加に失敗した場合はLogErrorでログを記録します。DeleteAllObjectsメソッドでは、配列の後ろからループを回し(Total)、各要素のnameをAtで取得します。オブジェクトの存在はObjectFindで確認し、削除はObjectDeleteでおこない、失敗した場合はログを記録します。その後、配列からDeleteで削除し、ChartRedraw関数でチャートを再描画します。このクラスを拡張することで、プログラム全体で呼び出して再利用できるヘルパー関数をいくつか作成することが可能になります。

CObjectManager objManager;            //--- Object manager instance

//+------------------------------------------------------------------+
//| Utility Functions                                                |
//+------------------------------------------------------------------+
void LogError(string message) {
   CFileTxt file;                     //--- Create file object
   if (file.Open(logFileName, FILE_WRITE | FILE_TXT | FILE_COMMON, true) >= 0) { //--- Open log file
      file.WriteString(message + "\n"); //--- Write message
      file.Close();                   //--- Close file
   }
   Print(message);                    //--- Print message
}

string Ask(string symbol) {
   double value;                      //--- Variable for ask price
   if (SymbolInfoDouble(symbol, SYMBOL_ASK, value)) { //--- Get ask price
      return DoubleToString(value, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); //--- Return formatted ask
   }
   LogError(__FUNCTION__ + ": Failed to get ask price for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error
   return "N/A";                      //--- Return N/A on failure
}

string Bid(string symbol) {
   double value;                      //--- Variable for bid price
   if (SymbolInfoDouble(symbol, SYMBOL_BID, value)) { //--- Get bid price
      return DoubleToString(value, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); //--- Return formatted bid
   }
   LogError(__FUNCTION__ + ": Failed to get bid price for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error
   return "N/A";                      //--- Return N/A on failure
}

string Spread(string symbol) {
   long value;                        //--- Variable for spread
   if (SymbolInfoInteger(symbol, SYMBOL_SPREAD, value)) { //--- Get spread
      return IntegerToString(value);  //--- Return spread as string
   }
   LogError(__FUNCTION__ + ": Failed to get spread for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error
   return "N/A";                      //--- Return N/A on failure
}

string PercentChange(double current, double previous) {
   if (previous == 0) return "0.00%"; //--- Handle zero previous value
   return StringFormat("%.2f%%", ((current - previous) / previous) * 100); //--- Calculate and format percentage
}

string TruncPeriod(ENUM_TIMEFRAMES period) {
   return StringSubstr(EnumToString(period), 7); //--- Truncate timeframe string
}

オブジェクトとデータを管理するために、UI要素を追跡するためのCObjectManagerのインスタンスobjManagerを作成します。LogError()関数では、エラーをログに記録します。CFileTxtを使用してlogFileNameをFILE_WRITE | FILE_TXT | FILE_COMMONモードで開き、WriteString()でmessageを書き込み、ファイルを閉じて画面にも出力します。Ask()関数は、指定したsymbolのAsk値をSymbolInfoDoubleで取得し、SymbolInfoInteger()で取得した桁数を用いてDoubleToStringで整形します。取得に失敗した場合はLogError()でエラーを記録し、「N/A」を返します。同様に、Bid()関数はBid値を取得し、整形とエラーハンドリングをおこないます。

Spread()関数では、SymbolInfoInteger()でスプレッドを取得し、IntegerToStringで文字列に変換して返します。失敗した場合は「N/A」を返します。PercentChange()関数は、currentとpreviousの価格差から変化率を計算し、StringFormatで整形します。previousがゼロの場合は「0.00%」を返します。TruncPeriod()関数では、ENUM_TIMEFRAMESの文字列をStringSubstrで切り詰め、時間足を簡潔に表示できるようにしています。これにより、整った出力を得ることができます。これで、ホログラフィックパルスのための関数を作成する準備が整いました。

//+------------------------------------------------------------------+
//| Holographic Animation Function                                   |
//+------------------------------------------------------------------+
void HolographicPulse(string objName, color mainClr, color glowClr) {
   if (!EnableAnimations) return;     //--- Exit if animations disabled
   int cycles = PulseCycles;          //--- Set pulse cycles
   int delay = AnimationSpeed;        //--- Set animation delay
   for (int i = 0; i < cycles; i++) { //--- Iterate through cycles
      ObjectSetInteger(0, objName, OBJPROP_COLOR, glowClr); //--- Set glow color
      ChartRedraw(0);                 //--- Redraw chart
      Sleep(delay);                   //--- Delay
      ObjectSetInteger(0, objName, OBJPROP_COLOR, mainClr); //--- Set main color
      ChartRedraw(0);                 //--- Redraw chart
      Sleep(delay / 2);               //--- Shorter delay
   }
}

ここでは、ダッシュボード要素にパルスアニメーション効果を付与するために、HolographicPulse()関数を実装します。EnableAnimationsがfalseの場合はアニメーションをスキップして早期に終了します。cyclesにPulseCyclesを、delayにAnimationSpeedを設定し、forループでcycles回繰り返します。各ループ内では、ObjectSetIntegerを使ってOBJPROP_COLORをglowClrに設定し、ChartRedrawでチャートを再描画します。その後、Sleep()でdelayだけ待機し、再び色をmainClrに戻してチャートを再描画し、「delay / 2」だけ待機して短いパルス効果を作ります。これにより、アクティブまたはアラート状態の要素を視覚的に強調するホログラフィックパルスを追加することができます。これらの関数が揃ったので、次にコアとなる初期化ダッシュボードを作成できます。その際、プログラムをモジュール化して管理しやすくするために、いくつかのヘルパー関数が必要になります。

//+------------------------------------------------------------------+
//| Create Text Label Function                                       |
//+------------------------------------------------------------------+
bool createText(string objName, string text, int x, int y, color clrTxt, int fontsize, string font, bool animate = false, double opacity = 1.0) {
   ResetLastError();                  //--- Reset error code
   if (ObjectFind(0, objName) < 0) {  //--- Check if object exists
      if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label
         LogError(__FUNCTION__ + ": Failed to create label: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         return false;                //--- Return failure
      }
      objManager.AddObject(objName);  //--- Add to manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
   ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontsize); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selection
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= 0 ? -1 : 0); //--- Set z-order

   if (animate && EnableAnimations) { //--- Check for animation
      ObjectSetInteger(0, objName, OBJPROP_COLOR, DataColor); //--- Set temporary color
      ChartRedraw(0);                 //--- Redraw chart
      Sleep(50);                      //--- Delay
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set final color
   }

   ChartRedraw(0);                    //--- Redraw chart
   return true;                       //--- Return success
}

//+------------------------------------------------------------------+
//| Create Button Function                                           |
//+------------------------------------------------------------------+
bool createButton(string objName, string text, int x, int y, int width, int height, color textColor, color bgColor, color borderColor, bool animate = false) {
   ResetLastError();                  //--- Reset error code
   if (ObjectFind(0, objName) < 0) {  //--- Check if object exists
      if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Create button
         LogError(__FUNCTION__ + ": Failed to create button: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         return false;                //--- Return failure
      }
      objManager.AddObject(objName);  //--- Add to manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set height
   ObjectSetString(0, objName, OBJPROP_TEXT, text); //--- Set text
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor); //--- Set text color
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor); //--- Set border color
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, BaseFontSize + (StringFind(objName, "SwitchTFBtn") >= 0 ? 3 : 0)); //--- Set font size
   ObjectSetString(0, objName, OBJPROP_FONT, FontType); //--- Set font
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, 1); //--- Set z-order
   ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Reset state

   if (animate && EnableAnimations) { //--- Check for animation
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrLightGray); //--- Set temporary background
      ChartRedraw(0);                 //--- Redraw chart
      Sleep(50);                      //--- Delay
      ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, bgColor); //--- Set final background
   }

   ChartRedraw(0);                    //--- Redraw chart
   return true;                       //--- Return success
}

//+------------------------------------------------------------------+
//| Create Panel Function                                            |
//+------------------------------------------------------------------+
bool createPanel(string objName, int x, int y, int width, int height, color clr, double opacity = 1.0) {
   ResetLastError();                  //--- Reset error code
   if (ObjectFind(0, objName) < 0) {  //--- Check if object exists
      if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create panel
         LogError(__FUNCTION__ + ": Failed to create panel: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         return false;                //--- Return failure
      }
      objManager.AddObject(objName);  //--- Add to manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x); //--- Set x-coordinate
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y); //--- Set y-coordinate
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width); //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height); //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clr); //--- Set background color
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, -1); //--- Set z-order
   ChartRedraw(0);                    //--- Redraw chart
   return true;                       //--- Return success
}

//+------------------------------------------------------------------+
//| Create Line Function                                             |
//+------------------------------------------------------------------+
bool createLine(string objName, int x1, int y1, int x2, int y2, color clrLine, double opacity = 1.0) {
   ResetLastError();                  //--- Reset error code
   if (ObjectFind(0, objName) < 0) {  //--- Check if object exists
      if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create line as rectangle
         LogError(__FUNCTION__ + ": Failed to create line: " + objName + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         return false;                //--- Return failure
      }
      objManager.AddObject(objName);  //--- Add to manager
   }
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x1); //--- Set x1
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y1); //--- Set y1
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, x2 - x1); //--- Set width
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, StringFind(objName, "Glow") >= 0 ? 3 : 1); //--- Set height
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrLine); //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= 0 ? -1 : 0); //--- Set z-order
   ChartRedraw(0);                    //--- Redraw chart
   return true;                       //--- Return success
}

ここでは、テキストラベルを生成するために、createText()関数を定義します。まず、ResetLastError()を呼び出して、以前のエラーをクリアします。オブジェクトが存在しない場合(ObjectFind(0, objName) < 0で確認)、ObjectCreateを使ってOBJ_LABELタイプで作成します。作成に失敗した場合は、エラーをログに記録し、falseを返します。その後、AddObject()でobjManagerに追加します。次に、オブジェクトのプロパティを設定します。OBJPROP_XDISTANCEをxに、OBJPROP_YDISTANCEをyに、その他のプロパティも同様に設定します。animateおよびEnableAnimationsがtrueの場合、一時的にOBJPROP_COLORをDataColorに設定し、ChartRedraw()で再描画、Sleep(50)で待機した後、テキストの色に戻します。最後に再描画してtrueを返します。

次に、createButton()関数も同様の手順で定義します。エラーをリセットし、存在を確認し、必要に応じてOBJ_BUTTONで作成し、失敗した場合はログに記録、objManagerに追加します。オブジェクトのプロパティを設定した後、アニメーションが有効な場合は一時的にOBJPROP_BGCOLORをclrLightGrayに設定し、再描画、50ms待機、bgColorに戻します。再描画してtrueを返します。createPanel()も同様のアプローチで作成します。

最後に、createLine()関数も同様のパターンで定義します。リセット、存在確認、OBJ_RECTANGLE_LABEL (線をシミュレート)で作成、失敗時にログを記録、objManagerに追加します。プロパティとして、OBJPROP_XDISTANCEをx1、OBJPROP_YDISTANCEをy1、OBJPROP_XSIZEをx2-x1、OBJPROP_YSIZEを名前にGlowが含まれる場合は3、それ以外は1、OBJPROP_BGCOLORをclrLine、OBJPROP_ZORDERをGlowの場合は-1、それ以外は0に設定します。再描画してtrueを返します。これらの関数を使用して、次にメインダッシュボードを作成するコア関数を作成します。

//+------------------------------------------------------------------+
//| Dashboard Creation Function with Holographic Effects             |
//+------------------------------------------------------------------+
void InitDashboard() {
   // Get chart dimensions
   long chartWidth, chartHeight;      //--- Variables for chart dimensions
   if (!ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidth) || !ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0, chartHeight)) { //--- Get chart size
      LogError(__FUNCTION__ + ": Failed to get chart dimensions, Error: " + IntegerToString(GetLastError())); //--- Log error
      return;                         //--- Exit on failure
   }
   int fontSize = (int)(BaseFontSize * (chartWidth / 800.0)); //--- Calculate font size
   int cellWidth = PanelWidth / 8;    //--- Calculate cell width
   int cellHeight = 18;               //--- Set cell height
   int panelHeight = 70 + (ArraySize(periods) + 1) * cellHeight + 40 + (totalSymbols + 1) * cellHeight + 50; //--- Calculate panel height

   // Create Dark Panel
   createPanel("DashboardPanel", X_Offset, Y_Offset, PanelWidth, panelHeight, PanelColor); //--- Create dashboard panel

   // Create Header with Glow
   createText("Header", "HOLOGRAPHIC DASHBOARD", X_Offset + 10, Y_Offset + 10, TitleColor, fontSize + 4, FontType); //--- Create header text
   createText("HeaderGlow", "HOLOGRAPHIC DASHBOARD", X_Offset + 11, Y_Offset + 11, GlowColor, fontSize + 4, FontType, true); //--- Create header glow
   createText("SubHeader", StringFormat("%s | TF: %s", _Symbol, TruncPeriod(_Period)), X_Offset + 10, Y_Offset + 30, DataColor, fontSize, FontType); //--- Create subheader

   // Timeframe Grid
   int y = Y_Offset + 50;             //--- Set y-coordinate for timeframe grid
   createText("TF_Label", "Timeframe", X_Offset + 10, y, TitleColor, fontSize, FontType); //--- Create timeframe label
   createText("Trend_Label", "Trend", X_Offset + 10 + cellWidth, y, TitleColor, fontSize, FontType); //--- Create trend label
   createText("Vol_Label", "Vol", X_Offset + 10 + cellWidth * 2, y, TitleColor, fontSize, FontType); //--- Create vol label
   createText("RSI_Label", "RSI", X_Offset + 10 + cellWidth * 3, y, TitleColor, fontSize, FontType); //--- Create RSI label
   createLine("TF_Separator", X_Offset + 5, y + cellHeight + 2, X_Offset + PanelWidth - 5, y + cellHeight + 2, LineColor, 0.6); //--- Create separator line
   createLine("TF_Separator_Glow", X_Offset + 4, y + cellHeight + 1, X_Offset + PanelWidth - 4, y + cellHeight + 3, GlowColor, 0.3); //--- Create glow separator
   if (EnableAnimations) HolographicPulse("TF_Separator", LineColor, GlowColor); //--- Animate separator if enabled

   y += cellHeight + 5;               //--- Update y-coordinate
   for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes
      color periodColor = (periods[i] == _Period) ? ActiveColor : DataColor; //--- Set period color
      createText("Period_" + IntegerToString(i), TruncPeriod(periods[i]), X_Offset + 10, y, periodColor, fontSize, FontType); //--- Create period text
      createText("Trend_" + IntegerToString(i), "-", X_Offset + 10 + cellWidth, y, DataColor, fontSize, FontType); //--- Create trend text
      createText("Vol_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 2, y, DataColor, fontSize, FontType); //--- Create vol text
      createText("RSI_" + IntegerToString(i), "0.0", X_Offset + 10 + cellWidth * 3, y, DataColor, fontSize, FontType); //--- Create RSI text
      y += cellHeight;                //--- Update y-coordinate
   }

   // Symbol Grid
   y += 30;                           //--- Update y-coordinate for symbol grid
   createText("Symbol_Label", "Symbol", X_Offset + 10, y, TitleColor, fontSize, FontType); //--- Create symbol label
   createText("Bid_Label", "Bid", X_Offset + 10 + cellWidth, y, TitleColor, fontSize, FontType); //--- Create bid label
   createText("Spread_Label", "Spread", X_Offset + 10 + cellWidth * 2, y, TitleColor, fontSize, FontType); //--- Create spread label
   createText("Change_Label", "% Change", X_Offset + 10 + cellWidth * 3, y, TitleColor, fontSize, FontType); //--- Create change label
   createText("Vol_Label_Symbol", "Vol", X_Offset + 10 + cellWidth * 4, y, TitleColor, fontSize, FontType); //--- Create vol label
   createText("RSI_Label_Symbol", "RSI", X_Offset + 10 + cellWidth * 5, y, TitleColor, fontSize, FontType); //--- Create RSI label
   createText("UpArrow_Label", CharToString(236), X_Offset + 10 + cellWidth * 6, y, TitleColor, fontSize, "Wingdings"); //--- Create up arrow label
   createText("DownArrow_Label", CharToString(238), X_Offset + 10 + cellWidth * 7, y, TitleColor, fontSize, "Wingdings"); //--- Create down arrow label
   createLine("Symbol_Separator", X_Offset + 5, y + cellHeight + 2, X_Offset + PanelWidth - 5, y + cellHeight + 2, LineColor, 0.6); //--- Create separator line
   createLine("Symbol_Separator_Glow", X_Offset + 4, y + cellHeight + 1, X_Offset + PanelWidth - 4, y + cellHeight + 3, GlowColor, 0.3); //--- Create glow separator
   if (EnableAnimations) HolographicPulse("Symbol_Separator", LineColor, GlowColor); //--- Animate separator if enabled

   y += cellHeight + 5;               //--- Update y-coordinate
   for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols
      string symbol = SymbolName(i, true); //--- Get symbol name
      string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; //--- Format display symbol
      color symbolColor = (symbol == _Symbol) ? ActiveColor : DataColor; //--- Set symbol color
      createText("Symbol_" + IntegerToString(i), displaySymbol, X_Offset + 10, y, symbolColor, fontSize, FontType); //--- Create symbol text
      createText("Bid_" + IntegerToString(i), Bid(symbol), X_Offset + 10 + cellWidth, y, DataColor, fontSize, FontType); //--- Create bid text
      createText("Spread_" + IntegerToString(i), Spread(symbol), X_Offset + 10 + cellWidth * 2, y, DataColor, fontSize, FontType); //--- Create spread text
      createText("Change_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 3, y, DataColor, fontSize, FontType); //--- Create change text
      createText("Vol_" + IntegerToString(i), "0.00%", X_Offset + 10 + cellWidth * 4, y, DataColor, fontSize, FontType); //--- Create vol text
      createText("RSI_" + IntegerToString(i), "0.0", X_Offset + 10 + cellWidth * 5, y, DataColor, fontSize, FontType); //--- Create RSI text
      createText("ArrowUp_" + IntegerToString(i), CharToString(236), X_Offset + 10 + cellWidth * 6, y, UpColor, fontSize, "Wingdings"); //--- Create up arrow
      createText("ArrowDown_" + IntegerToString(i), CharToString(238), X_Offset + 10 + cellWidth * 7, y, DownColor, fontSize, "Wingdings"); //--- Create down arrow
      y += cellHeight;                //--- Update y-coordinate
   }

   // Interactive Buttons with Pulse Animation
   createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + 10, y + 20, 150, 25, TitleColor, PanelColor, UpColor); //--- Create toggle button
   createButton("SwitchTFBtn", "NEXT TF", X_Offset + 170, y + 20, 120, 25, UpColor, PanelColor, UpColor); //--- Create switch TF button
   createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + 300, y + 20, 150, 25, TitleColor, PanelColor, UpColor); //--- Create sort button

   ChartRedraw(0);                    //--- Redraw chart
}

ここでは、まずチャートの幅と高さを取得してダッシュボードを初期化します。chartWidthおよびchartHeight変数を使用して、ChartGetIntegerを2回呼び出し、CHART_WIDTH_IN_PIXELSで幅を、CHART_HEIGHT_IN_PIXELSで高さを取得します。次に、fontSizeを計算します。これは、BaseFontSizeをチャート幅に応じて800ピクセル基準でスケーリングし、結果を整数にキャストして求めます。cellWidthはPanelWidthを8で割って求め、cellHeightは固定値18に設定します。panelHeightは、70に(ArraySize(periods) + 1) * cellHeight、さらに40に(totalSymbols + 1) * cellHeight、最後に50を加えて計算します。これにより、時間足や銘柄を含む全体レイアウトを考慮した高さが決まります。

次に、ダークパネル背景を作成します。createPanel()関数を呼び出し、名前をDashboardPanel、位置をX_OffsetとY_Offset、サイズをPanelWidthとpanelHeight、色をPanelColorに設定します。ヘッダーには、メインテキストラベル「Header」をcreateText()で作成し、文字列は「HOLOGRAPHIC DASHBOARD」、位置は「X_Offset + 10、Y_Offset + 10」、色はTitleColor、フォントサイズは「fontSize + 4」、フォントはFontTypeに設定します。グロー効果を追加するために、別のテキストラベル「HeaderGlow」を作成し、文字列は同じ、xとyをそれぞれ1ピクセルずらして、色はGlowColor、フォントサイズとフォントは同じ、透明度フラグをtrueに設定します。

サブヘッダーにはSubHeaderラベルを作成し、現在の銘柄(_Symbol)とTruncPeriod(_Period)で整形した文字列をStringFormat()で設定します。位置は「X_Offset + 10、Y_Offset + 30」、色はDataColor、フォントサイズはfontSize、フォントはFontTypeです。

時間足グリッドセクションでは、yを「Y_Offset + 50」に設定します。Timeframe、Trend、Vol、RSIの各ラベルをcreateText()で作成し、水平位置はcellWidthに基づいてオフセットを設定し、すべてTitleColor、fontSize、FontTypeで表示します。ラベルの下には、createLine()で区切り線「TF_Separator」を描画します。X位置は「X_Offset + 5」から「X_Offset + PanelWidth - 5」、高さは「y + cellHeight + 2」、色はLineColor、透明度は0.6に設定します。グロー用に、少しオフセットして幅を広げた"TF_Separator_Glow"を作成し、色はGlowColor、透明度は0.3に設定します。EnableAnimationsがtrueの場合は、HolographicPulse()でアニメーションを適用します。他のラベルオブジェクトも同様のロジックで作成します。

最後に、インタラクティブボタンを作成します。ToggleBtnはTOGGLE DASHBOARD"として、位置は「X_Offset + 10、y + 20」、サイズは150×25、色はTitleColor、PanelColor、UpColorです。SwitchTFBtnはNEXT TFとして、X位置は「X_Offset + 170」、同じy、サイズ120×25、色はUpColor、PanelColor、UpColorです。SortBtnは「SORT: " + sortNames[sortMode]」として、X位置は「X_Offset + 300」、同じy、サイズ150×25、色はTitleColor、PanelColor、UpColorです。最後に、ChartRedraw(0)でチャートを再描画します。この関数を初期化イベントハンドラーで呼び出すことで、ダッシュボードの主要な初期設定をまとめて実行できます。

//+------------------------------------------------------------------+
//| Expert Initialization Function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Clear existing objects
   if (ObjectsDeleteAll(0, -1, -1) < 0) { //--- Delete all objects
      LogError(__FUNCTION__ + ": Failed to delete objects, Error: " + IntegerToString(GetLastError())); //--- Log error
   }
   objManager.DeleteAllObjects();     //--- Delete managed objects

   // Initialize arrays
   totalSymbols = SymbolsTotal(true); //--- Get total symbols
   if (totalSymbols == 0) {           //--- Check for symbols
      LogError(__FUNCTION__ + ": No symbols available"); //--- Log error
      return INIT_FAILED;             //--- Return failure
   }
   ArrayResize(prices_PrevArray, totalSymbols); //--- Resize previous prices array
   ArrayResize(volatility_Array, totalSymbols); //--- Resize volatility array
   ArrayResize(bid_array, totalSymbols); //--- Resize bid array
   ArrayResize(spread_array, totalSymbols); //--- Resize spread array
   ArrayResize(change_array, totalSymbols); //--- Resize change array
   ArrayResize(vol_array, totalSymbols); //--- Resize vol array
   ArrayResize(rsi_array, totalSymbols); //--- Resize RSI array
   ArrayResize(indices, totalSymbols);   //--- Resize indices array
   ArrayResize(atr_handles_sym, totalSymbols); //--- Resize ATR symbol handles
   ArrayResize(rsi_handles_sym, totalSymbols); //--- Resize RSI symbol handles
   ArrayResize(atr_handles_tf, ArraySize(periods)); //--- Resize ATR timeframe handles
   ArrayResize(rsi_handles_tf, ArraySize(periods)); //--- Resize RSI timeframe handles
   ArrayInitialize(prices_PrevArray, 0); //--- Initialize previous prices
   ArrayInitialize(volatility_Array, 0); //--- Initialize volatility

   // Create indicator handles for timeframes
   for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes
      atr_handles_tf[i] = iATR(_Symbol, periods[i], ATR_Period); //--- Create ATR handle
      if (atr_handles_tf[i] == INVALID_HANDLE) { //--- Check for invalid handle
         LogError(__FUNCTION__ + ": Failed to create ATR handle for TF " + EnumToString(periods[i])); //--- Log error
         return INIT_FAILED;          //--- Return failure
      }
      rsi_handles_tf[i] = iRSI(_Symbol, periods[i], RSI_Period, PRICE_CLOSE); //--- Create RSI handle
      if (rsi_handles_tf[i] == INVALID_HANDLE) { //--- Check for invalid handle
         LogError(__FUNCTION__ + ": Failed to create RSI handle for TF " + EnumToString(periods[i])); //--- Log error
         return INIT_FAILED;          //--- Return failure
      }
   }

   // Create indicator handles for symbols on H1
   for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols
      string symbol = SymbolName(i, true); //--- Get symbol name
      atr_handles_sym[i] = iATR(symbol, PERIOD_H1, ATR_Period); //--- Create ATR handle
      if (atr_handles_sym[i] == INVALID_HANDLE) { //--- Check for invalid handle
         LogError(__FUNCTION__ + ": Failed to create ATR handle for symbol " + symbol); //--- Log error
         return INIT_FAILED;          //--- Return failure
      }
      rsi_handles_sym[i] = iRSI(symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE); //--- Create RSI handle
      if (rsi_handles_sym[i] == INVALID_HANDLE) { //--- Check for invalid handle
         LogError(__FUNCTION__ + ": Failed to create RSI handle for symbol " + symbol); //--- Log error
         return INIT_FAILED;          //--- Return failure
      }
   }

   InitDashboard();                   //--- Initialize dashboard
   dashboardVisible = true;           //--- Set dashboard visible

   return INIT_SUCCEEDED;             //--- Return success
}

OnInit関数では、まず既存のオブジェクトをすべて削除します。すべてのチャートとタイプに対してObjectsDeleteAllを呼び出し、失敗した場合はLogError()でログを記録します。その後、objManager.DeleteAllObjects()を呼び出して管理下のオブジェクトを削除します。次に、SymbolsTotalで気配値表示に表示されている銘柄の総数を取得し、totalSymbolsに代入します。もし0の場合はLogError()でエラーを記録し、INIT_FAILEDを返します。続いて、配列prices_PrevArray、volatility_Array、bid_array、spread_array、change_array、vol_array、rsi_array、indices、atr_handles_sym、rsi_handles_sym、atr_handles_tf、rsi_handles_tfをArrayResizeでtotalSymbolsまたはArraySize(periods)に合わせてリサイズします。また、ArrayInitializeを使用してprices_PrevArrayおよびvolatility_Arrayをゼロで初期化します。

時間足については、periodsをループし、それぞれに対してatr_handles_tf[i]をiATR(_Symbol, periods[i], ATR_Period)で、rsi_handles_tf[i]をiRSI(_Symbol, periods[i], RSI_Period, PRICE_CLOSE)で作成します。いずれかがINVALID_HANDLEの場合はログを記録し、INIT_FAILEDを返します。同様に、銘柄ごとにループしてsymbolをSymbolName(i, true)で取得し、「atr_handles_sym[i]をiATR(symbol, PERIOD_H1, ATR_Period)」で、rsi_handles_sym[i]を「iRSI(symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE)」で作成します。これらが無効な場合も同様にLogError()で記録し、INIT_FAILEDを返します。すべての初期化が完了したら、InitDashboard()を呼び出してUIを構築し、dashboardVisibleをtrueに設定して成功を返します。プログラムを実行すると、以下の結果が得られます。

初期ダッシュボード

出力から、プログラムが正常に初期化されたことを確認できます。プログラムの初期化解除処理では、作成されたオブジェクトを削除し、インジケーターハンドルを解放する必要があります。

//+------------------------------------------------------------------+
//| Expert Deinitialization Function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (ObjectsDeleteAll(0, -1, -1) < 0) { //--- Delete all objects
      LogError(__FUNCTION__ + ": Failed to delete objects, Error: " + IntegerToString(GetLastError())); //--- Log error
   }
   objManager.DeleteAllObjects();     //--- Delete managed objects

   // Release indicator handles
   for (int i = 0; i < ArraySize(atr_handles_tf); i++) { //--- Iterate through timeframe ATR handles
      if (atr_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_tf[i]); //--- Release handle
      if (rsi_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_tf[i]); //--- Release handle
   }
   for (int i = 0; i < ArraySize(atr_handles_sym); i++) { //--- Iterate through symbol ATR handles
      if (atr_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_sym[i]); //--- Release handle
      if (rsi_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_sym[i]); //--- Release handle
   }
}

OnDeinitイベントハンドラでは、EAが削除された際に使用したリソースをクリーンアップします。まず、すべてのチャートおよびオブジェクトタイプに対して「ObjectsDeleteAll(-1, 0, 0)」を呼び出してチャートオブジェクトを削除します。結果が負の場合は、LogError()で削除失敗を記録します。その後、objManager.DeleteAllObjects()を呼び出し、管理下のオブジェクトをすべて削除します。時間足ハンドルについては、atr_handles_tfおよびrsi_handles_tfの配列をArraySizeでループし、各要素がINVALID_HANDLEでない場合はIndicatorReleaseを使用してハンドルを解放します。同様に、銘柄ごとのatr_handles_symおよびrsi_handles_symについても同じ手順で処理します。これにより、すべてのオブジェクトとインジケータが完全にクリーンアップされ、リソースのリークを防ぐことができます。以下はそのイメージ図です。

削除GIF

作成したオブジェクトの処理がすべて完了したので、次は更新処理に進みます。更新はシンプルに保つためにOnTick イベントハンドラ内でおこなう予定ですが、OnTimerイベントハンドラ内で実行することも可能です。まずは、時間足セクションから始めます。

//+------------------------------------------------------------------+
//| Expert Tick Function with Holographic Updates                    |
//+------------------------------------------------------------------+
void OnTick() {
   if (!dashboardVisible) return;     //--- Exit if dashboard hidden

   long chartWidth;                   //--- Variable for chart width
   ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidth); //--- Get chart width
   int fontSize = (int)(BaseFontSize * (chartWidth / 800.0)); //--- Calculate font size
   int cellWidth = PanelWidth / 8;    //--- Calculate cell width
   int cellHeight = 18;               //--- Set cell height
   int y = Y_Offset + 75;             //--- Set y-coordinate for timeframe data

   // Update Timeframe Data with Pulse
   for (int i = 0; i < ArraySize(periods); i++) { //--- Iterate through timeframes
      double open = iOpen(_Symbol, periods[i], 0); //--- Get open price
      double close = iClose(_Symbol, periods[i], 0); //--- Get close price
      double atr_buf[1];              //--- Buffer for ATR
      if (CopyBuffer(atr_handles_tf[i], 0, 0, 1, atr_buf) != 1) { //--- Copy ATR data
         LogError(__FUNCTION__ + ": Failed to copy ATR buffer for TF " + EnumToString(periods[i])); //--- Log error
         continue;                    //--- Skip on failure
      }
      double vol = (close > 0) ? (atr_buf[0] / close) * 100 : 0.0; //--- Calculate volatility
      double rsi_buf[1];              //--- Buffer for RSI
      if (CopyBuffer(rsi_handles_tf[i], 0, 0, 1, rsi_buf) != 1) { //--- Copy RSI data
         LogError(__FUNCTION__ + ": Failed to copy RSI buffer for TF " + EnumToString(periods[i])); //--- Log error
         continue;                    //--- Skip on failure
      }
      double rsi = rsi_buf[0];        //--- Get RSI value
      color clr = DataColor;          //--- Set default color
      string trend = "-";             //--- Set default trend
      if (rsi > 50) { clr = UpColor; trend = "↑"; } //--- Set up trend
      else if (rsi < 50) { clr = DownColor; trend = "↓"; } //--- Set down trend
      createText("Trend_" + IntegerToString(i), trend, X_Offset + 10 + cellWidth, y, clr, fontSize, FontType, EnableAnimations); //--- Update trend text
      createText("Vol_" + IntegerToString(i), StringFormat("%.2f%%", vol), X_Offset + 10 + cellWidth * 2, y, vol > Vol_Alert_Threshold ? UpColor : DataColor, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update vol text
      color rsi_clr = (rsi > 70 ? DownColor : (rsi < 30 ? UpColor : DataColor)); //--- Set RSI color
      createText("RSI_" + IntegerToString(i), StringFormat("%.1f", rsi), X_Offset + 10 + cellWidth * 3, y, rsi_clr, fontSize, FontType, (rsi > 70 || rsi < 30) && EnableAnimations); //--- Update RSI text
      HolographicPulse("Period_" + IntegerToString(i), (periods[i] == _Period) ? ActiveColor : DataColor, GlowColor); //--- Pulse period text
      y += cellHeight;                //--- Update y-coordinate
   }

   // Update Symbol Data with Advanced Glow
   y += 50;                           //--- Update y-coordinate for symbol data
   for (int i = 0; i < totalSymbols; i++) { //--- Iterate through symbols
      string symbol = SymbolName(i, true); //--- Get symbol name
      double bidPrice;                //--- Variable for bid price
      if (!SymbolInfoDouble(symbol, SYMBOL_BID, bidPrice)) { //--- Get bid price
         LogError(__FUNCTION__ + ": Failed to get bid for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         continue;                    //--- Skip on failure
      }
      long spread;                    //--- Variable for spread
      if (!SymbolInfoInteger(symbol, SYMBOL_SPREAD, spread)) { //--- Get spread
         LogError(__FUNCTION__ + ": Failed to get spread for " + symbol + ", Error: " + IntegerToString(GetLastError())); //--- Log error
         continue;                    //--- Skip on failure
      }
      double change = (prices_PrevArray[i] == 0 ? 0 : (bidPrice - prices_PrevArray[i]) / prices_PrevArray[i] * 100); //--- Calculate change
      double close = iClose(symbol, PERIOD_H1, 0); //--- Get close price
      double atr_buf[1];              //--- Buffer for ATR
      if (CopyBuffer(atr_handles_sym[i], 0, 0, 1, atr_buf) != 1) { //--- Copy ATR data
         LogError(__FUNCTION__ + ": Failed to copy ATR buffer for symbol " + symbol); //--- Log error
         continue;                    //--- Skip on failure
      }
      double vol = (close > 0) ? (atr_buf[0] / close) * 100 : 0.0; //--- Calculate volatility
      double rsi_buf[1];              //--- Buffer for RSI
      if (CopyBuffer(rsi_handles_sym[i], 0, 0, 1, rsi_buf) != 1) { //--- Copy RSI data
         LogError(__FUNCTION__ + ": Failed to copy RSI buffer for symbol " + symbol); //--- Log error
         continue;                    //--- Skip on failure
      }
      double rsi = rsi_buf[0];        //--- Get RSI value
      bid_array[i] = bidPrice;        //--- Store bid
      spread_array[i] = spread;       //--- Store spread
      change_array[i] = change;       //--- Store change
      vol_array[i] = vol;             //--- Store vol
      rsi_array[i] = rsi;             //--- Store RSI
      volatility_Array[i] = vol;      //--- Store volatility
      prices_PrevArray[i] = bidPrice; //--- Update previous price
   }
}

OnTick関数では、市場のティックごとに更新処理をおこない、時間足および銘柄のリアルタイムデータをリフレッシュします。dashboardVisibleがfalseの場合は、不要な処理をスキップするために早期終了します。まず、ChartGetIntegerを使用してCHART_WIDTH_IN_PIXELSからchartWidthを取得します。次に、fontSizeを「chartWidth / 800.0」でスケーリングして計算し、cellWidthを「PanelWidth / 8」、cellHeightを18に設定します。時間足グリッドの開始位置として、yを「Y_Offset + 75」に設定し、ArraySize関数でperiodsをループ処理します。各時間足に対して、iOpen で始値を、iClose()で終値をそれぞれシフト0で取得します。さらに、CopyBufferを使用してatr_handles_tf[i]からatr_bufをコピーし、closeが正の場合はATRを終値で割ってパーセンテージボラティリティ(vol)を計算します。

同様に、CopyBuffer()でrsi_handles_tf[i]からrsi_bufを取得し、RSI値を計算します。RSIが50を上回る場合は上昇(↑)、下回る場合は下降(↓)とし、色をそれぞれUpColorまたはDownColorに設定します。フォントの矢印を使うことも可能ですが、ここではあえてコード内で矢印を定義することで、ホログラフィックな一体感を高めています。createText()を使って、トレンド、ボラティリティ(Vol_Alert_Thresholdを超えた場合はUpColorで色付けし、アニメーションを適用)、およびRSI(買われすぎ・売られすぎに応じて色分けとアニメーションを適用)の各テキストを更新します。現在の時間足が_Periodと一致する場合は、期間テキストに対してHolographicPulse()を呼び出し、ActiveColorでハイライトします。ループごとにyをcellHeight分インクリメントします。

次に、銘柄グリッドの更新に進みます。yをさらに50増加させ、totalSymbols分ループを回します。各銘柄について、SymbolName(i, true)でsymbolを取得し、「SymbolInfoDouble(symbol, SYMBOL_BID)」でbidPriceを、「SymbolInfoInteger(symbol, SYMBOL_SPREAD)」でspreadを取得します。取得に失敗した場合は記録して、その銘柄の処理をスキップします。prices_PrevArray[i]から変化率(change)を計算し、「iClose(symbol, PERIOD_H1, 0)」で終値を取得します。さらに、atr_handles_sym[i]からatr_bufをコピーしてボラティリティ(vol)を、rsi_handles_sym[i]からrsi_bufをコピーしてRSI(rsi)を取得します。取得したデータをbid_array、spread_array、change_array、vol_array、rsi_array、およびvolatility_arrayに格納し、prices_PrevArray[i]をbidPriceで更新します。これで、次は銘柄セクションに進み、ソート処理と視覚効果付きの表示をおこなう準備が整いました。

// Sort indices
for (int i = 0; i < totalSymbols; i++) indices[i] = i; //--- Initialize indices
bool swapped = true;               //--- Swap flag
while (swapped) {                  //--- Loop until no swaps
   swapped = false;                //--- Reset flag
   for (int j = 0; j < totalSymbols - 1; j++) { //--- Iterate through indices
      bool do_swap = false;        //--- Swap decision
      int a = indices[j], b = indices[j + 1]; //--- Get indices
      if (sortMode == 0) {         //--- Sort by name ASC
         string na = SymbolName(a, true), nb = SymbolName(b, true); //--- Get names
         if (na > nb) do_swap = true; //--- Swap if needed
      } else if (sortMode == 1) {  //--- Sort by vol DESC
         if (vol_array[a] < vol_array[b]) do_swap = true; //--- Swap if needed
      } else if (sortMode == 2) {  //--- Sort by change ABS DESC
         if (MathAbs(change_array[a]) < MathAbs(change_array[b])) do_swap = true; //--- Swap if needed
      } else if (sortMode == 3) {  //--- Sort by RSI DESC
         if (rsi_array[a] < rsi_array[b]) do_swap = true; //--- Swap if needed
      }
      if (do_swap) {               //--- Perform swap
         int temp = indices[j];    //--- Temporary store
         indices[j] = indices[j + 1]; //--- Swap
         indices[j + 1] = temp;    //--- Complete swap
         swapped = true;           //--- Set flag
      }
   }
}

// Display sorted symbols with pulse on high vol
for (int j = 0; j < totalSymbols; j++) { //--- Iterate through sorted indices
   int i = indices[j];                   //--- Get index
   string symbol = SymbolName(i, true);  //--- Get symbol
   double bidPrice = bid_array[i];       //--- Get bid
   long spread = spread_array[i];        //--- Get spread
   double change = change_array[i];      //--- Get change
   double vol = vol_array[i];            //--- Get vol
   double rsi = rsi_array[i];            //--- Get RSI
   color clr_s = (symbol == _Symbol) ? ActiveColor : DataColor; //--- Set symbol color
   color clr_p = DataColor, clr_sp = DataColor, clr_ch = DataColor, clr_vol = DataColor, clr_rsi = DataColor; //--- Set default colors
   color clr_a1 = DataColor, clr_a2 = DataColor; //--- Set arrow colors

   // Price Change
   if (change > 0) {               //--- Check positive change
      clr_p = UpColor; clr_ch = UpColor; clr_a1 = UpColor; clr_a2 = DataColor; //--- Set up colors
   } else if (change < 0) {        //--- Check negative change
      clr_p = DownColor; clr_ch = DownColor; clr_a1 = DataColor; clr_a2 = DownColor; //--- Set down colors
   }

   // Volatility Alert
   if (vol > Vol_Alert_Threshold) { //--- Check high volatility
      clr_vol = UpColor;            //--- Set vol color
      clr_s = (symbol == _Symbol) ? ActiveColor : UpColor; //--- Set symbol color
   }

   // RSI Color
   clr_rsi = (rsi > 70 ? DownColor : (rsi < 30 ? UpColor : DataColor)); //--- Set RSI color

   // Update Texts
   string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; //--- Format display symbol
   createText("Symbol_" + IntegerToString(j), displaySymbol, X_Offset + 10, y, clr_s, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update symbol text
   createText("Bid_" + IntegerToString(j), Bid(symbol), X_Offset + 10 + cellWidth, y, clr_p, fontSize, FontType, EnableAnimations); //--- Update bid text
   createText("Spread_" + IntegerToString(j), Spread(symbol), X_Offset + 10 + cellWidth * 2, y, clr_sp, fontSize, FontType); //--- Update spread text
   createText("Change_" + IntegerToString(j), StringFormat("%.2f%%", change), X_Offset + 10 + cellWidth * 3, y, clr_ch, fontSize, FontType); //--- Update change text
   createText("Vol_" + IntegerToString(j), StringFormat("%.2f%%", vol), X_Offset + 10 + cellWidth * 4, y, clr_vol, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); //--- Update vol text
   createText("RSI_" + IntegerToString(j), StringFormat("%.1f", rsi), X_Offset + 10 + cellWidth * 5, y, clr_rsi, fontSize, FontType, (rsi > 70 || rsi < 30) && EnableAnimations); //--- Update RSI text
   createText("ArrowUp_" + IntegerToString(j), CharToString(236), X_Offset + 10 + cellWidth * 6, y, clr_a1, fontSize, "Wingdings"); //--- Update up arrow
   createText("ArrowDown_" + IntegerToString(j), CharToString(238), X_Offset + 10 + cellWidth * 7, y, clr_a2, fontSize, "Wingdings"); //--- Update down arrow

   // Pulse on high volatility
   if (vol > Vol_Alert_Threshold) { //--- Check high volatility
      HolographicPulse("Symbol_" + IntegerToString(j), clr_s, GlowColor); //--- Pulse symbol text
   }

   y += cellHeight;                //--- Update y-coordinate
}

ChartRedraw(0);                    //--- Redraw chart
}

ここでは、まずindices配列を0から「totalSymbols - 1」までループで初期化し、インデックスの並び替えをおこないます。ソートにはバブルソート方式を採用し、最初にswappedフラグをtrueに設定してwhileループに入ります。ループ内でswappedをfalseにリセットし、0から「totalSymbols - 2」までをforループで走査します。各反復で、do_swapをfalseに初期化し、「a = indices[j]」、「b = indices[j+1]」を取得します。sortModeの値に応じて処理を分けます。0 (name ASC)の場合は、「SymbolName(a, true)」と「SymbolName(b, true)」を取得し、「na > nb」ならばスワップします。1 (vol DESC)の場合は、「vol_array[a] < vol_array[b]」ならスワップします。2 (change ABS DESC)の場合は、「MathAbs(change_array[a]) < MathAbs(change_array[b])」ならスワップします。3 (RSI DESC)の場合は、「rsi_array[a] < rsi_array[b]」ならスワップします。do_swapがtrueの場合は、temp変数を使用してindices[j]とindices[j+1]を入れ替え、swappedをtrueに設定します。

次に、ソートされた銘柄を表示します。totalSymbols回ループし、各反復で「i = indices[j]」を取得します。その後、「SymbolName(i, true)」でsymbolを、bid_array[i]でbidPriceを、spread_array[i]でspreadを、change_array[i]でchangeを、vol_array[i]でvolを、rsi_array[i]でrsiをそれぞれ取得します。現在の銘柄が_Symbolと一致する場合はclr_sをActiveColorに、それ以外はDataColorに設定します。その他の色もデフォルトでDataColorに初期化します。価格変化については、「change > 0」の場合はclr_p、clr_ch、clr_a1をUpColorに、clr_a2をDataColorに設定します。「change < 0」の場合はそれらをDownColorに、clr_a1をDataColorに設定します。ボラティリティアラートは、「vol > Vol_Alert_Threshold」の場合にclr_volをUpColorにし、現在銘柄でない場合はclr_sも更新します。RSIについては、70を超える場合にclr_rsiをDownColor、30未満の場合にUpColor、それ以外はDataColorに設定します。

_Symbol(現在の銘柄)と一致する場合は、displaySymbolの先頭に「*」を付加して強調します。createText()を使用して各テキストを更新します。銘柄("Symbol_j")にはdisplaySymbolを表示し、clr_sで着色し、高ボラティリティかつアニメーション有効時はアニメーションを適用します。Bid値(Bid_j)にはBid(symbol)を表示し、clr_pで着色し、アニメーションが有効であればアニメーションを適用します。スプレッド(Spread_j)にはSpread(symbol)を表示し、clr_spで着色します。変化率(Change_j)には「StringFormat("%.2f%%", change)」で整形した文字列を表示し、clr_chで着色します。ボラティリティ(Vol_j)には「StringFormat("%.2f%%", vol)」を使用し、clr_volで着色し、高ボラティリティの場合はアニメーションを適用します。RSI (RSI_j)には「StringFormat("%.1f", rsi)」を表示し、clr_rsiで着色し、買われすぎ・売られすぎの場合はアニメーションを適用します。上向き矢印(ArrowUp_j)はCharToString(236)をWingdingsフォントで表示し、clr_a1で着色します。下向き矢印(ArrowDown_j)はCharToString(238)を同フォントで表示し、clr_a2で着色します。高ボラティリティの場合は、銘柄テキストに対してHolographicPulse()を呼び出し、clr_sとGlowColorでホログラフィックパルスを適用します。各ループの最後にyをcellHeight分増加させ、最後にChartRedraw(0)で再描画します。コンパイルすると、次の結果が得られます。

ONTICKのパルスアップデート

この可視化から、市場の各ティックで更新が正しく反映されていることが分かります。これで、次はボタンに動きを加える段階に進みます。この処理はOnChartEventイベントハンドラを使用して実現します。

//+------------------------------------------------------------------+
//| Chart Event Handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_OBJECT_CLICK) { //--- Handle click event
      if (sparam == "ToggleBtn") {    //--- Check toggle button
         dashboardVisible = !dashboardVisible; //--- Toggle visibility
         objManager.DeleteAllObjects(); //--- Delete objects
         if (dashboardVisible) {      //--- Check if visible
            InitDashboard();          //--- Reinitialize dashboard
         } else {
            createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + 10, Y_Offset + 10, 150, 25, TitleColor, PanelColor, UpColor); //--- Create toggle button
         }
      }
      else if (sparam == "SwitchTFBtn") { //--- Check switch TF button
         int currentIdx = -1;         //--- Initialize current index
         for (int i = 0; i < ArraySize(periods); i++) { //--- Find current timeframe
            if (periods[i] == _Period) { //--- Match found
               currentIdx = i;        //--- Set index
               break;                 //--- Exit loop
            }
         }
         int nextIdx = (currentIdx + 1) % ArraySize(periods); //--- Calculate next index
         if (!ChartSetSymbolPeriod(0, _Symbol, periods[nextIdx])) { //--- Switch timeframe
            LogError(__FUNCTION__ + ": Failed to switch timeframe, Error: " + IntegerToString(GetLastError())); //--- Log error
         }
         createButton("SwitchTFBtn", "NEXT TF", X_Offset + 170, (int)ObjectGetInteger(0, "SwitchTFBtn", OBJPROP_YDISTANCE), 120, 25, UpColor, PanelColor, UpColor, EnableAnimations); //--- Update button
      }
      else if (sparam == "SortBtn") { //--- Check sort button
         sortMode = (sortMode + 1) % 4; //--- Cycle sort mode
         createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + 300, (int)ObjectGetInteger(0, "SortBtn", OBJPROP_YDISTANCE), 150, 25, TitleColor, PanelColor, UpColor, EnableAnimations); //--- Update button
      }
      ObjectSetInteger(0, sparam, OBJPROP_STATE, false); //--- Reset button state
      ChartRedraw(0);                 //--- Redraw chart
   }
}

OnChartEventイベントハンドラでは、インタラクティブなイベントを処理し、可視性の切り替え、時間足の変更、ソートモードの切り替えといったボタンクリックに対応します。CHARTEVENT_OBJECT_CLICKイベントの場合、sparamの値を確認します。sparamがToggleBtnの場合は、dashboardVisibleをトグルし、objManager.DeleteAllObjects()でオブジェクトを削除します。可視状態であればInitDashboard()を呼び出して再初期化し、非表示の場合はcreateButton()を使用して新しいToggleBtnを作成します。sparamがSwitchTFBtnの場合は、periods配列をループして現在の時間足インデックスを検索し、「(currentIdx + 1) % ArraySize(periods)」でnextIdxを計算します。次に、ChartSetSymbolPeriod()を使用してperiods[nextIdx]の時間足に切り替えます。失敗した場合はLogError()でエラーログを記録します。その後、createButton()を使ってボタンを更新し、EnableAnimationsがtrueの場合はアニメーションを適用します。

sparamがSortBtnの場合は、sortModeを「(sortMode + 1) % 4」で循環させ、「"SORT: " + sortNames[sortMode]」というテキストでボタンを更新します。この際もcreateButton()を呼び出し、アニメーションが有効であれば適用します。最後に、ObjectSetIntegerOBJPROP_STATE tをfalseにリセットし、ChartRedrawでチャートを再描画します。これにより、ダッシュボードの表示やデータ構成を動的に制御できるようになります。コンパイルすると以下の出力が得られます。

レスポンシブボタンクリック

この結果から、各マーケットティックでダッシュボードを更新できること、さらにダッシュボードの表示切り替え、時間足の変更、銘柄メトリクスのインデックスソートといったボタンクリックにも正しく反応できることが確認できました。これにより、本プロジェクトで目標としていた機能をすべて達成したことになります。残されているのはプロジェクトの実用性をテストすることであり、それは次のセクションで扱います。


バックテスト

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

バックテスト


結論

結論として、今回はMQL5で動的ホログラフィックダッシュボードを作成しました。このダッシュボードは、銘柄および時間足をRSIやボラティリティアラートとともに監視し、ソート機能を備えています。また、パルスアニメーションやインタラクティブボタンを組み合わせることで、没入感のある取引体験を実現しています。本記事では、アーキテクチャおよび実装の詳細を説明し、CObjectManagerクラスやHolographicPulse関数などのコンポーネントを活用して、リアルタイムかつ視覚的に魅力的な分析を実現しました。このダッシュボードは、トレーダーのニーズに合わせて自由にカスタマイズすることができ、ホログラフィックな演出と操作性により、分析体験をさらに高めることが可能です

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

添付されたファイル |
プライスアクション分析ツールキットの開発(第33回):Candle Range Theory Tool プライスアクション分析ツールキットの開発(第33回):Candle Range Theory Tool
MetaTrader 5向けのCandle-Range Theoryスイートで、市場の読みをアップグレードできます。これは完全にMQL5ネイティブなソリューションで、ローソク足をリアルタイムのボラティリティ情報に変換します。軽量なCRangePatternライブラリは、各ローソク足の真の値幅を適応型ATRと比較し、確定直後に分類します。CRTインジケーターは、その分類結果をチャート上に鮮明な色分けされた矩形や矢印として表示し、収束の進行、急騰・急落、全レンジ包み込みを瞬時に可視化します。
MQL5入門(第19回):ウォルフ波動の自動検出 MQL5入門(第19回):ウォルフ波動の自動検出
本記事では、強気(上昇)および弱気(下降)のウォルフ波動パターンをプログラムで識別し、MQL5を使用して取引する方法を紹介します。ウォルフ波動構造をプログラムで検出し、それに基づいて取引の実行方法を詳しく解説します。これには、主要なスイングポイントの検出、パターンルールの検証、シグナルに基づくエキスパートアドバイザー(EA)の準備が含まれます。
MQL5サービスからPythonアプリケーションへのMetaTraderティック情報アクセス(ソケット使用) MQL5サービスからPythonアプリケーションへのMetaTraderティック情報アクセス(ソケット使用)
場合によっては、MQL5言語だけではすべてをプログラムできないことがあります。また、既存の高度なライブラリをMQL5に移植することは可能であっても、非常に時間がかかります。本記事では、MetaTraderのティック情報(Bid、Ask、時刻など)をMetaTraderサービスを経由してPythonアプリケーションに送信し、Windows OSへの依存を回避する方法を紹介します。
MQL5での取引戦略の自動化(第24回):リスク管理とトレーリングストップを備えたロンドンセッションブレイクアウトシステム MQL5での取引戦略の自動化(第24回):リスク管理とトレーリングストップを備えたロンドンセッションブレイクアウトシステム
本記事では、ロンドン市場開場前のレンジブレイクアウトを検出し、任意の取引タイプおよびリスク設定に基づいてペンディング注文(指値・逆指値注文)を自動で発注する「ロンドンセッションブレイクアウトシステム」を開発します。トレーリングストップ、リスクリワード比率、最大ドローダウン制限、そしてリアルタイム監視と管理をおこなうためのコントロールパネルなどの機能も組み込みます。