
MQL5 Trading Tools (Part 6): Dynamic Holographic Dashboard with Pulse Animations and Controls
Introduction
In our previous article (Part 5), we created a rolling ticker tape in MetaQuotes Language 5 (MQL5) for real-time symbol monitoring with scrolling prices, spreads, and changes to keep traders informed efficiently. In Part 6, we develop a Dynamic Holographic Dashboard that displays multi-symbol and timeframe indicators, such as the Relative Strength Index (RSI) and volatility (based on the Average True Range (ATR)), with pulse animations, sorting, and interactive controls, creating an engaging analysis tool. We will cover the following topics:
By the end, you’ll have a customizable holographic dashboard ready for your trading setup—let’s get started!
Understanding the Holographic Dashboard Architecture
The holographic dashboard we’re building is a visual tool that monitors multiple symbols and timeframes, displaying indicators such as RSI and volatility, along with sorting and alerts, to help us quickly spot opportunities. This architecture is important because it combines real-time data with interactive controls and animations, making analysis more engaging and efficient in a cluttered chart environment.
We will achieve this by using arrays for data management, handles for indicators like ATR and RSI, and functions for sorting and pulsing effects, with buttons for toggling visibility and switching views. We plan to centralize updates in a loop that refreshes the User Interface (UI) dynamically, ensuring the dashboard remains adaptable and responsive for strategic trading. View the visualization below to get what we want to achieve before we proceed to the implementation.
Implementation in MQL5
To create the program in MQL5, we will need to define the program metadata and then define some inputs that will enable us to easily modify the functioning of the program without interfering with the code directly.
//+------------------------------------------------------------------+ //| 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
We start by including libraries for string arrays and text file logging, and setting up inputs to customize the UI and indicators. We include "<Arrays\ArrayString.mqh>" for handling symbol lists and "<Files\FileTxt.mqh>" for logging errors to a file. The inputs will allow us to adjust the base font size to 9, choose a professional font like Calibri, set offsets for x and y positioning at 30 pixels each, and select colors such as dark slate gray for the panel background, white for titles, light gray for data, lime for active elements, deep sky blue for uptrends, crimson for downtrends, and silver for grid lines.
We enable pulse animations by default, define the panel width at 730 pixels, set ATR and RSI periods to 14 for volatility and momentum calculations, establish a 2.0% threshold for volatility alerts, choose Dodger Blue for the holographic glow, and configure animation speed at 30 ms with 3 pulse cycles. These settings will make the dashboard highly adaptable for visual and functional preferences. We then need to define some global variables that we will use throughout the program.
// 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
Here, we define global variables to manage data and indicators in our program, supporting real-time monitoring, sorting, and animations. We create arrays like "prices_PrevArray" for previous prices to calculate changes, "volatility_Array" for volatility values, "bid_array" for current bids, "spread_array" for spreads as longs, "change_array" for percentage changes, "vol_array" for volumes, "rsi_array" for RSI values, and "indices" for sorting indices. We set "periods" as an array of timeframes from PERIOD_M1 to PERIOD_W1, "logFileName" to "Holographic_Dashboard_Log.txt" for error logging, "sortMode" to 0 for initial sorting, "sortNames" as strings for sort options like "Name ASC" or "Vol DESC", and arrays for ATR and RSI handles ("atr_handles_sym", "rsi_handles_sym" for symbols, "atr_handles_tf", "rsi_handles_tf" for timeframes).
The "totalSymbols" integer tracks the number of symbols, and "dashboardVisible" is true, which controls the dashboard's state. To better manage objects, we will create a class.
// 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 } };
To manage the dashboard objects efficiently, we create the "CObjectManager" class, extending the CArrayString class. In the "AddObject" method, we add the object "name" to the array with "Add", logging failures via "LogError" if unsuccessful. We use the "DeleteAllObjects" method looping backward through the array with "Total", get each "name" with "At", check existence with the ObjectFind function, delete with ObjectDelete and log errors if failed, remove it from the array with "Delete", and redraw the chart with the ChartRedraw function. With the class extension, we can create some helper functions that we will call throughout the program to reuse.
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 }
To manage objects and data, we instantiate "objManager" as a "CObjectManager" to track UI elements. We create the "LogError" function to log errors, opening "logFileName" with "CFileTxt" using "FILE_WRITE | FILE_TXT | FILE_COMMON", writing the "message" with "WriteString", closing the file, and printing it. The "Ask" function retrieves the ask price for a "symbol" with SymbolInfoDouble, formats it with DoubleToString using "SymbolInfoInteger" for digits, logs errors with "LogError" if failed, and returns "N/A" on failure. Similarly, the "Bid" function fetches the bid price, formats it, and handles errors.
The "Spread" function gets the spread with "SymbolInfoInteger", returns it as a string with IntegerToString, or "N/A" on failure. The "PercentChange" function calculates the percentage change between "current" and "previous" prices using StringFormat, returning "0.00%" if "previous" is zero. The "TruncPeriod" function truncates ENUM_TIMEFRAMES strings with StringSubstr for concise timeframe display, ensuring we have clean outputs. We can now create the function for the holographic pulses.
//+------------------------------------------------------------------+ //| 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 } }
Here, we implement the "HolographicPulse" function to create a pulse animation effect for dashboard elements. We exit early if "EnableAnimations" is false to skip animations. We set "cycles" to "PulseCycles" and "delay" to "AnimationSpeed", then loop through "cycles" with a for loop. In each iteration, we set the object's color to "glowClr" with ObjectSetInteger for OBJPROP_COLOR, redraw the chart with ChartRedraw, pause with "Sleep" for "delay", switch back to "mainClr", redraw again, and pause for "delay / 2" for a shorter effect. This will add the holographic pulse to highlight active or alert elements visually. Armed with these functions, we can graduate to creating the core initialization dashboard. For that, we will need some helper functions to keep the program modular.
//+------------------------------------------------------------------+ //| 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 }
Here, we define the "createText" function to generate a text label. We start by calling ResetLastError to clear any prior error. If the object doesn't exist (checked via "ObjectFind(0, objName) < 0"), we create it using ObjectCreate with type OBJ_LABEL. On failure, we log the error and return false. We add it to "objManager" via "AddObject". We set properties: OBJPROP_XDISTANCE to "x", "OBJPROP_YDISTANCE" to "y", and the same to the others. If "animate" and "EnableAnimations" are true, we temporarily set "OBJPROP_COLOR" to "DataColor", redraw, delay via "Sleep(50)", then set to the color of the text. Finally, redraw and return true.
Next, we define "createButton" similarly: reset error, check existence, create with OBJ_BUTTON if needed, log on failure, and add to manager. We then set the object properties and, if animations are enabled, temporarily set OBJPROP_BGCOLOR to "clrLightGray", redraw, sleep 50ms, then set to "bgColor". Redraw and return true. For "createPanel", we use a similar approach.
Lastly, "createLine" uses a similar pattern: reset, check, create as OBJ_RECTANGLE_LABEL (simulating a line), log on failure, and add to the manager. Set "OBJPROP_XDISTANCE" to "x1", "OBJPROP_YDISTANCE" to "y1", "OBJPROP_XSIZE" to "x2-x1", "OBJPROP_YSIZE" to 3 if "Glow" in name else 1, "OBJPROP_BGCOLOR" to "clrLine", OBJPROP_ZORDER to -1 if "Glow" else 0. Redraw and return true. We now use these functions to create the core function that will enable us to craft the main dashboard as follows.
//+------------------------------------------------------------------+ //| 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 }
Here, we initialize the dashboard by first retrieving the chart's dimensions using the "chartWidth" and "chartHeight" variables. We achieve this by calling the ChartGetInteger function twice: once with CHART_WIDTH_IN_PIXELS to get the width and once with "CHART_HEIGHT_IN_PIXELS" to get the height. Next, we calculate the "fontSize" by scaling the "BaseFontSize" based on the chart's width relative to 800 pixels, casting the result to an integer. We then determine the "cellWidth" by dividing "PanelWidth" by 8 and set "cellHeight" to a fixed value of 18. The "panelHeight" is computed by adding 70, the product of ("ArraySize(periods)" + 1) and "cellHeight", 40, the product of ("totalSymbols" + 1) and "cellHeight", and 50—this accounts for the overall layout, including timeframes and symbols.
We proceed to create the dark panel background by invoking the "createPanel" function with the name "DashboardPanel", positioned at "X_Offset" and "Y_Offset", with dimensions "PanelWidth" and "panelHeight", and colored with "PanelColor". For the header, we create the main text label "Header" with the string "HOLOGRAPHIC DASHBOARD" using the "createText" function at coordinates "X_Offset" + 10" and "Y_Offset" + 10", styled with "TitleColor", a font size of "fontSize" + 4", and "FontType". To add a glow effect, we create another text label "HeaderGlow"" with the same string, but offset by 1 pixel in both x and y directions, using "GlowColor", the same font size, "FontType", and setting the opacity flag to true.
We then add a subheader label "SubHeader", formatted with the current symbol _Symbol and truncated period from "TruncPeriod(_Period)" using "StringFormat", positioned at "X_Offset" + 10" and "Y_Offset" + 30", colored with "DataColor", "fontSize", and "FontType".
Moving to the timeframe grid section, we set "y" to "Y_Offset" + 50". We create labels for "Timeframe", "Trend", "Vol", and "RSI" using "createText"" for each, positioned horizontally with offsets based on "cellWidth", all using "TitleColor", "fontSize", and "FontType"". Below these, we draw a separator line "TF_Separator"" using "createLine" function from "X_Offset + 5" to "X_Offset + PanelWidth - 5"", at height "y + cellHeight + 2"", colored "LineColor" with opacity 0.6". For glow, we add "TF_Separator_Glow"" as another line slightly offset and wider, with "GlowColor" and opacity 0.3". If "EnableAnimations" is true, we apply animation via "HolographicPulse" with "LineColor"" and "GlowColor". We use a similar logic for all the other label objects.
Finally, we create interactive buttons: "ToggleBtn"" as "TOGGLE DASHBOARD" at "X_Offset" + 10", "y + 20", size 150x25, with "TitleColor", "PanelColor", "UpColor"; "SwitchTFBtn"" as "NEXT TF" at "X_Offset" + 170", same y, size 120x25, with "UpColor", "PanelColor", "UpColor"; and "SortBtn"" as "SORT: " + sortNames[sortMode]" at "X_Offset" + 300", same y, size 150x25, with "TitleColor", "PanelColor", "UpColor". We conclude by redrawing the chart with the "ChartRedraw(0)" function. With the function, we can call it in the initialization event handler, and we can have the heavy lifting done.
//+------------------------------------------------------------------+ //| 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 }
In the OnInit function, we clear existing objects with the ObjectsDeleteAll function for all charts and types, logging failures with "LogError" if unsuccessful, and call "objManager.DeleteAllObjects" to remove managed items. We get "totalSymbols" from SymbolsTotal with true for market watch symbols, returning INIT_FAILED if zero, and logging with "LogError". We resize arrays like "prices_PrevArray", "volatility_Array", "bid_array", "spread_array", "change_array", "vol_array", "rsi_array", "indices", "atr_handles_sym", "rsi_handles_sym", "atr_handles_tf", and "rsi_handles_tf" to match "totalSymbols" or "ArraySize(periods)" using ArrayResize, and initialize "prices_PrevArray" and "volatility_Array" to zero with the ArrayInitialize function.
For timeframes, we loop through "periods" and create "atr_handles_tf[i]" with iATR on "_Symbol", "periods[i]", and "ATR_Period", and "rsi_handles_tf[i]" with "iRSI" on _Symbol, "periods[i]", "RSI_Period", and "PRICE_CLOSE", logging and returning INIT_FAILED if "INVALID_HANDLE". Similarly for symbols, we loop through "totalSymbols", get "symbol" with "SymbolName" and true, create "atr_handles_sym[i]" with "iATR" on "symbol", "PERIOD_H1", and "ATR_Period", and "rsi_handles_sym[i]" with iRSI on "symbol", "PERIOD_H1", "RSI_Period", and "PRICE_CLOSE", logging and returning "INIT_FAILED" if invalid. We call "InitDashboard" to build the UI, set "dashboardVisible" to true, and return success. When we run the program, we get the following outcome.
From the image, we can see that we initialized the program successfully. We can take care of the program deinitialization, where we will need to delete the created objects and release the indicator handles.
//+------------------------------------------------------------------+ //| 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 } }
In the OnDeinit event handler, we clean up resources when the EA is removed. We delete all chart objects with ObjectsDeleteAll using -1 for all charts and types, logging failures with "LogError" if the result is negative. We call "objManager.DeleteAllObjects" to remove managed items. For timeframe handles, we loop through "atr_handles_tf" and "rsi_handles_tf" with ArraySize, releasing valid handles with IndicatorRelease if not "INVALID_HANDLE". Similarly, for symbol handles in "atr_handles_sym" and "rsi_handles_sym". This ensures complete cleanup of objects and indicators. Here is an illustration.
With the created objects taken care of completely, we can now go to the updates. We intend to do the updates in the OnTick event handler to keep everything simple, but you could do them in the OnTimer event handler. Let's first start with the time frame section.
//+------------------------------------------------------------------+ //| 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 } }
In the OnTick function, we handle updates on every market tick, ensuring real-time data refresh for timeframes and symbols. We exit early if "dashboardVisible" is false to skip unnecessary processing. We retrieve "chartWidth" with ChartGetInteger using CHART_WIDTH_IN_PIXELS, calculate "fontSize" scaled by "chartWidth / 800.0", "cellWidth" as "PanelWidth / 8", and "cellHeight" as 18. We set "y" to "Y_Offset + 75" for the timeframe grid and loop through "periods" with the ArraySize function. For each timeframe, we get "open" with iOpen and "close" with "iClose" at shift 0, copy "atr_buf" with CopyBuffer from "atr_handles_tf[i]", and calculate "vol" as percentage ATR over "close" if positive.
We copy "rsi_buf" from "rsi_handles_tf[i]" and get "rsi", setting "clr" and "trend" based on RSI > 50 for up ("↑" in "UpColor") or < 50 for down ("↓" in "DownColor"). We could have used the arrows from fonts, but these hand-coded ones blend in smoothly and increase the holographic appeal. We update texts with "createText" for trend, vol (colored "UpColor" if > "Vol_Alert_Threshold" with animation), and RSI (colored based on overbought/oversold with animation), and call "HolographicPulse" on the period text with "ActiveColor" if matching _Period. We increment "y" by "cellHeight".
We update "y" by 50 for the symbol grid and loop through "totalSymbols". For each symbol from SymbolName with true, we fetch "bidPrice" with "SymbolInfoDouble" using "SYMBOL_BID" and "spread" with SymbolInfoInteger using SYMBOL_SPREAD, logging and skipping on failure. We calculate "change" as percentage over "prices_PrevArray[i]", get "close" with iClose on "PERIOD_H1" at shift 0, copy "atr_buf" from "atr_handles_sym[i]" to compute "vol", and "rsi_buf" from "rsi_handles_sym[i]" to get "rsi". We store values in "bid_array", "spread_array", "change_array", "vol_array", "rsi_array", and "volatility_array", and update "prices_PrevArray[i]" to "bidPrice". We can now move on to the symbols section, where we will need to sort and display them with effects.
// 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 }
Here, we sort the indices by first initializing the "indices" array from 0 to "totalSymbols" - 1 in a loop. We use a bubble sort approach with the "swapped" flag set to true initially, entering a while loop until no more swaps occur. Inside, we reset "swapped" to false, then loop from 0 to "totalSymbols" - 2, setting "do_swap" to false and getting "a" and "b" as "indices[j]" and "indices[j+1]". Depending on "sortMode": for 0 (name ASC), we get names via "SymbolName(a, true)" and "SymbolName(b, true)", swap if "na > nb"; for 1 (vol DESC), swap if "vol_array[a] < vol_array[b]"; for 2 (change ABS DESC), swap if "MathAbs(change_array[a]) < MathAbs(change_array[b])"; for 3 (RSI DESC), swap if "rsi_array[a] < rsi_array[b]". If "do_swap", we swap "indices[j]" and "indices[j+1]" using a "temp" variable and set "swapped" to true.
Next, we display sorted symbols by looping over "totalSymbols", getting "i" as "indices[j]", then fetching "symbol" via "SymbolName(i, true)", "bidPrice" from "bid_array[i]", "spread" from "spread_array[i]", "change" from "change_array[i]", "vol" from "vol_array[i]", and "rsi" from "rsi_array[i]". We set "clr_s" to "ActiveColor" if it matches "_Symbol", else "DataColor"; default other colors to "DataColor". For price change: if "change > 0", set "clr_p", "clr_ch", "clr_a1" to "UpColor" and "clr_a2" to "DataColor"; if < 0, set to "DownColor" with "clr_a1" as "DataColor". For volatility alert: if "vol > Vol_Alert_Threshold", set "clr_vol" to "UpColor" and update "clr_s" if not the current symbol. For RSI: set "clr_rsi" to "DownColor" if >70, "UpColor" if <30, else "DataColor".
We format "displaySymbol" with "*" if it matches "_Symbol". Update texts via "createText": symbol ("Symbol_j") with "displaySymbol", "clr_s", animate if high vol and enabled; bid ("Bid_j") with "Bid(symbol)", "clr_p", animate if enabled; spread ("Spread_j") with "Spread(symbol)", "clr_sp"; change ("Change_j") formatted "%.2f%%" via "StringFormat", "clr_ch"; vol ("Vol_j") formatted "%.2f%%", "clr_vol", animate if high vol and enabled; RSI ("RSI_j") formatted "%.1f", "clr_rsi", animate if overbought/oversold and enabled; up arrow ("ArrowUp_j") with "CharToString(236)", "clr_a1", "Wingdings"; down arrow ("ArrowDown_j") with "CharToString(238)", "clr_a2", "Wingdings". If high vol, apply "HolographicPulse" on symbol text with "clr_s" and "GlowColor". Increment "y" by "cellHeight" each iteration and finally redraw. When we compile, we get the following outcome.
From the visualization, we can see that the updates are taking effect on every market tick. We can now graduate to adding life to the buttons we created. We will achieve that via the OnChartEvent event handler.
//+------------------------------------------------------------------+ //| 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 } }
We implement the OnChartEvent event handler to handle interactive events, responding to button clicks for toggling visibility, switching timeframes, and cycling sort modes. For CHARTEVENT_OBJECT_CLICK, we check "sparam" against "ToggleBtn", toggling "dashboardVisible", deleting objects with "objManager.DeleteAllObjects", and reinitializing with "InitDashboard" if visible or creating a new "ToggleBtn" with "createButton" if hidden. If "sparam" is "SwitchTFBtn", we find the current timeframe index in "periods" with a loop, calculate "nextIdx" as "(currentIdx + 1) % ArraySize(periods)", switch the chart with "ChartSetSymbolPeriod" using "periods[nextIdx]", log failures with "LogError", and update the button with "createButton" including animation if "EnableAnimations".
For "SortBtn", we cycle "sortMode" with "(sortMode + 1) % 4" and update the button text to "SORT: " + "sortNames[sortMode]" using "createButton" with animation. We reset the button state with ObjectSetInteger for OBJPROP_STATE to false and redraw the chart with the ChartRedraw function. This enables control over the dashboard's display and data organization. Upon compilation, we have the following output.
We can see that we can update the dashboard on every market tick and respond to the button clicks for the dashboard toggle, timeframe change, and the sorting of the indices for the symbol metrics, hence achieving our objectives. What now remains is testing the workability of the project, and that is handled in the preceding section.
Backtesting
We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.
Conclusion
In conclusion, we’ve created a Dynamic Holographic Dashboard in MQL5 that monitors symbols and timeframes with RSI, volatility alerts, and sorting, featuring pulse animations and interactive buttons for an immersive trading experience. We’ve detailed the architecture and implementation, using class components like "CObjectManager" and functions such as "HolographicPulse" to deliver real-time, visually engaging insights. You can customize this dashboard to fit your trading needs, elevating your analysis with holographic effects and controls.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use