preview
Creating Custom Indicators in MQL5 (Part 3): Multi-Gauge Enhancements with Sector and Round Styles

Creating Custom Indicators in MQL5 (Part 3): Multi-Gauge Enhancements with Sector and Round Styles

MetaTrader 5Trading |
967 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

In our previous article (Part 2), we developed a Gauge-Style Relative Strength Index Display in MetaQuotes Language 5 (MQL5) utilizing canvas and needle mechanics that visualized Relative Strength Index values through a circular gauge with a dynamic needle, color-coded ranges indicating overbought and oversold levels, and customizable legends, integrating traditional line plotting for comprehensive momentum analysis. In Part 3, we develop Multi-Gauge Enhancements with Sector and Round Styles version. This model supports multiple oscillators, such as the Relative Strength Index (RSI), the Commodity Channel Index (CCI), and the Money Flow Index (MFI), through user-selectable combinations. It introduces derived classes for sector and round gauge designs, with improved case rendering using arcs, polygons, and relative positioning, resulting in a polished multi-indicator display. We will cover the following topics:

  1. Multi-Gauge Framework with Sector and Round Styles
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you will have a functional MQL5 indicator for enhanced visualization of the multi-gauge oscillator. It will be ready for further customization—let’s dive in!


Multi-Gauge Framework with Sector and Round Styles

The multi-gauge framework builds on a base class for customizable gauges, introducing derived classes for round and sector styles to visualize oscillators like the Relative Strength Index, the Commodity Channel Index, and the Money Flow Index, where we select single or combined displays via an enumeration for flexible momentum analysis across indicators. The round style maintains circular casing with filled circles, while the sector style enhances visuals with arc-based sectors, rounding arcs, connecting lines, and polygons for partial dial shapes, adapting to angle ranges and supporting relative positioning to align multiple gauges horizontally on the chart. This idea sprouted from the fact that at some instance, you might require just a small section of a gauge to display some information, and thus we thought it was a great idea to sub-divide the full round gauge into a half and a quarter, so you can choose whichever fits your style, but in our case, we are going to do the 3, conditionally.

To achieve that, we plan to extend the base gauge class with pure virtual methods for case calculation and drawing, allowing overrides in derived classes for style-specific logic. We will add an enumeration for gauge selection to conditionally initialize and position instances for the Relative Strength Index (round), the Commodity Channel Index (sector), and the Money Flow Index (sector), integrating their handles and buffers for data copying. We need these to get visual data, but in your case, you can use anything else, like displaying profits, indicator flow, and progress, or even account metrics, not limited to some other indicator data. The architecture separates layers for scale (with enhanced mark population for zero positions) and needle (with adjustable tail multipliers), ensuring efficient redraws and relative anchoring based on previous gauges. In brief, here is a visual representation of our objectives.

GAUGE ARCHITECTURE OVERVIEW


Implementation in MQL5

To begin the enhancements implementation, we will first need to adjust the indicator plots and buffers and add more indicator properties for the extra indicators that we want to add, specifically the CCI and MFI indicators. Here is how we redo that.

#property indicator_buffers 3
#property indicator_plots 3
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label1 "RSI"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGreen
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
#property indicator_label2 "CCI"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrBlue
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
#property indicator_label3 "MFI"
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_level1 30
#property indicator_level2 70
#property indicator_level3 -100
#property indicator_level4 100
#property indicator_level5 0
#property indicator_levelcolor clrGray
#property indicator_levelstyle STYLE_DOT

First, we redefine the indicator's metadata with #property directives, allocating 3 buffers using "indicator_buffers" for data storage and configuring 3 plots with indicator_plots, since we are now dealing with 3 indicators. For the first plot, we set its type to DRAW_LINE, color to dodger blue, solid style, width 2, and label "RSI", just as it was. The second plot is a green solid line with width 2 labeled "CCI", and the third is a blue solid line with width 2 labeled "MFI".

We establish the vertical scale from 0 to 100 via "indicator_minimum" and "indicator_maximum", and add five dotted gray levels at 30, 70, -100, 100, and 0 using "indicator_level1" through "indicator_level5", "indicator_levelcolor", and "indicator_levelstyle" to reference thresholds across the oscillators. These properties will enable simultaneous line visualizations for the Relative Strength Index, the Commodity Channel Index, and the Money Flow Index in a separate window.

To allows us to customize which gauges (RSI, CCI, MFI, or combinations) are displayed via the indicator inputs dialog to make the indicator more flexible and resource-efficient and support advanced scale rendering, especially for indicators like CCI (which can have negative values and zero in the middle), we will need to declare some enumerations to help determine where the "null" (zero) mark is placed, improving accuracy for non-positive scales.

//+------------------------------------------------------------------+
//| Gauge Selection Enum                                             |
//+------------------------------------------------------------------+
enum ENUM_GAUGE_SELECTION {                // Define gauge selection enum
   RSI_ONLY,                               // RSI Only
   CCI_ONLY,                               // CCI Only
   MFI_ONLY,                               // MFI Only
   RSI_CCI,                                // RSI CCI
   RSI_MFI,                                // RSI MFI
   CCI_MFI,                                // CCI MFI
   ALL                                     // All
};

// Inputs
input ENUM_GAUGE_SELECTION inpGaugeSelection = ALL; // Gauge Selection


//+------------------------------------------------------------------+
//| Null Mark Position Enum                                          |
//+------------------------------------------------------------------+
enum ENUM_NULLMARK_POS {                   // Define null mark position enum
   NULLMARK_NONE=0,                        // None
   NULLMARK_LEFT=1,                        // Left
   NULLMARK_MIDDLE=2,                      // Middle
   NULLMARK_RIGHT=3                        // Right
};

We define the "ENUM_GAUGE_SELECTION" enumeration to provide options for selecting which gauges to display, including individual choices like "RSI_ONLY", "CCI_ONLY", or "MFI_ONLY", combinations such as "RSI_CCI", "RSI_MFI", or "CCI_MFI", and "ALL" for showing everything. We declare an input parameter "inpGaugeSelection" of type "ENUM_GAUGE_SELECTION" with a default value of "ALL", allowing users to choose the gauge configuration directly from the indicator settings. Next, we create the "ENUM_NULLMARK_POS" enumeration to specify positions for the zero or null mark on the scale, with values "NULLMARK_NONE" set to 0, "NULLMARK_LEFT" to 1, "NULLMARK_MIDDLE" to 2, and "NULLMARK_RIGHT" to 3, supporting flexible handling of scale layouts, especially for indicators with negative ranges. We thought this is important just to cover all the possible scenarios.

The next thing that we will do is expand the case structure so that it supports the new sector or partial, as you would like to call it, circles or gauges, and also expand the gauge input parameters to include the needle tail multiplier that we need. Let us start with the case structure.

Old case structure:

//+------------------------------------------------------------------+
//| Case Structure                                                   |
//+------------------------------------------------------------------+
struct Struct_Case {                       // Define case structure
   bool display;                           // Store display flag
   Struct_Circle circle;                   // Store circle structure
};

New case structure:

//+------------------------------------------------------------------+
//| Case Structure                                                   |
//+------------------------------------------------------------------+
struct Struct_Case {                       // Define case structure
   bool display;                           // Store display flag
   Struct_Circle circle;                   // Store circle structure
   int mode;                               // Store mode
   Struct_Arc mainArc;                     // Store main arc
   Struct_Arc secondaryArc;                // Store secondary arc
   Struct_Arc centerArc;                   // Store center arc
   Struct_Arc leftRoundingArc;             // Store left rounding arc
   Struct_Arc rightRoundingArc;            // Store right rounding arc
   Struct_Line leftConnectLine;            // Store left connect line
   Struct_Line rightConnectLine;           // Store right connect line
   Struct_Dot fillDot;                     // Store fill dot
};

Here, we enhance the "Struct_Case" structure to support more sophisticated gauge casing, particularly for sector styles, by including a display flag and an embedded "Struct_Circle" for basic circular elements. We add an integer "mode" to determine the casing configuration based on angle ranges, along with "Struct_Arc" instances for the main arc, secondary arc (for larger angles), center arc, left and right rounding arcs to smooth edges. Additionally, we incorporate "Struct_Line" for left and right connecting lines to bridge components, and a "Struct_Dot" for a fill point to ensure complete coverage in polygon fillings during rendering. As for the parameters stucture, we add just the multiplier tail input as below. We have highlighted it for clarity.

//+------------------------------------------------------------------+
//| Gauge Input Parameters Structure                                 |
//+------------------------------------------------------------------+
struct Struct_GaugeInputParams {           // Define gauge input parameters structure
   int xOffset;                            // Store x offset
   int yOffset;                            // Store y offset
   int anchorCorner;                       // Store anchor corner
   int relativeMode;                       // Store relative mode
   string relativeObjectName;              // Store relative object name
   int scaleAngleRange;                    // Store scale angle range
   int rotationAngle;                      // Store rotation angle
   color scaleColor;                       // Store scale color
   int scaleStyle;                         // Store scale style
   bool displayScaleArc;                   // Store display scale arc flag
   double minScaleValue;                   // Store minimum scale value
   double maxScaleValue;                   // Store maximum scale value
   int scaleMultiplier;                    // Store scale multiplier
   int tickStyle;                          // Store tick style
   int tickSize;                           // Store tick size
   double majorTickInterval;               // Store major tick interval
   int mediumTicksPerMajor;                // Store medium ticks per major
   int minorTicksPerInterval;              // Store minor ticks per interval
   int tickFontSize;                       // Store tick font size
   string tickFontName;                    // Store tick font name
   bool tickFontItalic;                    // Store tick font italic flag
   bool tickFontBold;                      // Store tick font bold flag
   color tickFontColor;                    // Store tick font color
   Struct_RangeParams ranges[4];           // Store ranges array
   color caseColor;                        // Store case color
   int borderStyle;                        // Store border style
   color borderColor;                      // Store border color
   int borderGapSize;                      // Store border gap size
   Struct_GaugeLegendParams description;   // Store description
   Struct_GaugeLegendParams units;         // Store units
   Struct_GaugeLegendParams multiplier;    // Store multiplier
   Struct_GaugeLegendParams value;         // Store value
   int needleCenterStyle;                  // Store needle center style
   color needleCenterColor;                // Store needle center color
   color needleColor;                      // Store needle color
   int needleFillStyle;                    // Store needle fill style
   double needleTailMultiplier;            // Store needle tail multiplier
};

The variable that we have added for the multiplier will allow customizing the needle's tail length (e.g., shorter for sector gauges), improving visual aesthetics, and fitting different gauge shapes. We will now redo the classes in a major overhaul so that we support polymorphism and inheritance for a better fit, classification, and future customization. We add two derived classes with private helpers as follows.

//+------------------------------------------------------------------+
//| Base Gauge Class                                                 |
//+------------------------------------------------------------------+
class CGaugeBase                           // Define base gauge class
{
private:
   int relativeX;                          //--- Store relative X
   int relativeY;                          //--- Store relative Y
   int centerX;                            //--- Store center X
   int centerY;                            //--- Store center Y
   double currentValue;                    //--- Store current value
   bool initializationComplete;            //--- Store initialization complete flag
   void Draw();                            //--- Declare draw method
   void CalculateNeedle();                 //--- Declare calculate needle method
   void RedrawNeedle(double value);        //--- Declare redraw needle method
   void CalculateAndDrawLegends();         //--- Declare calculate and draw legends method
   void CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString); //--- Declare calculate and draw legend string method
   void RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap); //--- Declare redraw scale marks method
   void CalculateRanges(int borderGap);    //--- Declare calculate ranges method
   bool IsValidRange(int index);           //--- Declare check valid range method
   void NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1); //--- Declare normalize range values method
   void CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr); //--- Declare calculate range pie method
   void DrawRanges();                      //--- Declare draw ranges method
   void DrawRange(Struct_Range &range);    //--- Declare draw range method
   void CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle); //--- Declare calculate inner outer radii method
   bool DrawTick(double angle, int length, Struct_Arc &scaleArc); //--- Declare draw tick method
   double CalculateAngleDelta(double angle1, double angle2, int direction); //--- Declare calculate angle delta method
   bool GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString); //--- Declare get label area size method
   bool EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr); //--- Declare erase legend string method
   bool RedrawValueDisplay(double value);  //--- Declare redraw value display method
   void SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams &param, int minRadius, int radiusDelta); //--- Declare set legend string params method
protected:
   Struct_GaugeInputParams inputParams;    //--- Store input parameters
   Struct_ScaleLayer scaleLayer;           //--- Store scale layer
   Struct_NeedleLayer needleLayer;         //--- Store needle layer
   int m_radius;                           //--- Store radius
   virtual void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) = 0; //--- Declare calculate case elements method
   virtual void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) = 0; //--- Declare draw case elements method
public:
   bool Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency); //--- Declare create method
   bool CalculateLocation();               //--- Declare calculate location method
   void Redraw();                          //--- Declare redraw method
   void NewValue(double value);            //--- Declare new value method
   void Delete();                          //--- Declare delete method
   void SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc = false); //--- Declare set scale parameters method
   void SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval); //--- Declare set tick parameters method
   void SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr = clrBlack); //--- Declare set tick label font method
   void SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize); //--- Declare set case parameters method
   void SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend parameters method
   void SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend param method
   void SetRangeParameters(int index, bool enable, double start, double end, color rangeClr); //--- Declare set range parameters method
   void SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle, double tailMultiplier = 2.0); //--- Declare set needle parameters method
};

//+------------------------------------------------------------------+
//| Round Gauge Class                                                |
//+------------------------------------------------------------------+
class CRoundGauge : public CGaugeBase      // Define round gauge class
{
protected:
   void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements
   {
      if(borderSize > 0) {                 //--- Check border size
         externalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set external center X
         externalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set external center Y
         externalCase.circle.radius = m_radius; //--- Set external radius
         externalCase.circle.clr = inputParams.borderColor; //--- Set external color
         externalCase.display = true;      //--- Set display flag
      } else                               //--- Handle no border
         externalCase.display = false;     //--- Set display flag false
      internalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set internal center X
      internalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set internal center Y
      internalCase.circle.radius = m_radius - borderSize; //--- Set internal radius
      internalCase.circle.clr = inputParams.caseColor; //--- Set internal color
      internalCase.display = true;         //--- Set display flag
   }
   void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements
   {
      if(externalCase.display)             //--- Check external display
         scaleLayer.obj_Canvas.FillCircle(externalCase.circle.centerX, externalCase.circle.centerY, externalCase.circle.radius, ColorToARGB(externalCase.circle.clr, scaleLayer.transparency)); //--- Fill external circle
      if(internalCase.display)             //--- Check internal display
         scaleLayer.obj_Canvas.FillCircle(internalCase.circle.centerX, internalCase.circle.centerY, internalCase.circle.radius, ColorToARGB(internalCase.circle.clr, scaleLayer.transparency)); //--- Fill internal circle
   }
};

//+------------------------------------------------------------------+
//| Sector Gauge Class                                               |
//+------------------------------------------------------------------+
class CSectorGauge : public CGaugeBase     // Define sector gauge class
{
private:
   void CaseCalculateSector(Struct_Case &caseStruct, int gap) //--- Declare calculate sector method
   {
      double fi0,fi1,fi2;                  //--- Declare angles
      double sa;                           //--- Declare scale range
      Struct_Arc referenceArc = scaleLayer.scaleArc; //--- Set reference arc
      if(referenceArc.endAngle > referenceArc.startAngle) //--- Check end > start
         sa = NormalizeRadians(referenceArc.endAngle - referenceArc.startAngle); //--- Set sa
      else                                 //--- Handle wrap
         sa = NormalizeRadians(referenceArc.endAngle + (2 * M_PI - referenceArc.startAngle)); //--- Set sa
      if(sa > M_PI)                        //--- Check > PI
         caseStruct.mode = 1;              //--- Set mode 1
      else                                 //--- Handle <= PI
         caseStruct.mode = 0;              //--- Set mode 0
      if(sa > M_PI) {                      //--- Check > PI
         caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X
         caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y
         caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius
         caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle
         caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.startAngle + sa * 0.55); //--- Set main end angle
         caseStruct.mainArc.clr = clrNONE; //--- Set main color
         caseStruct.secondaryArc.display = true; //--- Set secondary display
         caseStruct.secondaryArc.centerX = referenceArc.centerX; //--- Set secondary center X
         caseStruct.secondaryArc.centerY = referenceArc.centerY; //--- Set secondary center Y
         caseStruct.secondaryArc.radius = referenceArc.radius + gap; //--- Set secondary radius
         caseStruct.secondaryArc.startAngle = NormalizeRadians(referenceArc.endAngle - sa * 0.55); //--- Set secondary start angle
         caseStruct.secondaryArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set secondary end angle
         caseStruct.secondaryArc.clr = clrNONE; //--- Set secondary color
      } else {                             //--- Handle <= PI
         caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X
         caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y
         caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius
         caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle
         caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set main end angle
         caseStruct.mainArc.clr = clrNONE; //--- Set main color
      }
      caseStruct.leftRoundingArc.radius = gap; //--- Set left rounding radius
      caseStruct.leftRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.endAngle)); //--- Set left rounding center X
      caseStruct.leftRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.endAngle)); //--- Set left rounding center Y
      if(caseStruct.mode == 1)             //--- Check mode 1
         fi1 = referenceArc.endAngle + (2 * M_PI - sa) / 2; //--- Set fi1
      else                                 //--- Handle mode 0
         fi1 = referenceArc.endAngle + M_PI * 0.5; //--- Set fi1
      caseStruct.leftRoundingArc.startAngle = referenceArc.endAngle; //--- Set left start angle
      caseStruct.leftRoundingArc.endAngle = NormalizeRadians(fi1); //--- Set left end angle
      caseStruct.leftRoundingArc.clr = clrNONE; //--- Set left color
      caseStruct.rightRoundingArc.radius = gap; //--- Set right rounding radius
      caseStruct.rightRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.startAngle)); //--- Set right rounding center X
      caseStruct.rightRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.startAngle)); //--- Set right rounding center Y
      if(caseStruct.mode == 1)             //--- Check mode 1
         fi0 = referenceArc.startAngle - (2 * M_PI - sa) / 2; //--- Set fi0
      else                                 //--- Handle mode 0
         fi0 = referenceArc.startAngle - M_PI * 0.5; //--- Set fi0
      caseStruct.rightRoundingArc.startAngle = NormalizeRadians(fi0); //--- Set right start angle
      caseStruct.rightRoundingArc.endAngle = referenceArc.startAngle; //--- Set right end angle
      caseStruct.rightRoundingArc.clr = clrNONE; //--- Set right color
      caseStruct.centerArc.centerX = referenceArc.centerX; //--- Set center arc center X
      caseStruct.centerArc.centerY = referenceArc.centerY; //--- Set center arc center Y
      caseStruct.centerArc.radius = needleLayer.needleCenter.radius + gap; //--- Set center arc radius
      fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2
      fi1 = NormalizeRadians(referenceArc.endAngle + fi2); //--- Set fi1
      caseStruct.centerArc.startAngle = fi1; //--- Set center start angle
      fi1 = NormalizeRadians(referenceArc.startAngle - fi2); //--- Set fi1
      caseStruct.centerArc.endAngle = fi1; //--- Set center end angle
      caseStruct.centerArc.clr = clrNONE;  //--- Set center color
      if(caseStruct.mode == 1) {           //--- Check mode 1
         double angleOffset = M_PI - (sa / 2); //--- Calculate offset
         caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start X
         caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start Y
         caseStruct.leftConnectLine.endX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end X
         caseStruct.leftConnectLine.endY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end Y
         caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color
      } else {                             //--- Handle mode 0
         caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start X
         caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start Y
         fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2
         fi1 = NormalizeRadians(caseStruct.mainArc.endAngle + fi2); //--- Set fi1
         caseStruct.leftConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set left end X
         caseStruct.leftConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set left end Y
         caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color
         caseStruct.rightConnectLine.startX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin(caseStruct.rightRoundingArc.endAngle)); //--- Set right start X
         caseStruct.rightConnectLine.startY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos(caseStruct.rightRoundingArc.endAngle)); //--- Set right start Y
         fi1 = NormalizeRadians(caseStruct.mainArc.startAngle - fi2); //--- Set fi1
         caseStruct.rightConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set right end X
         caseStruct.rightConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set right end Y
         caseStruct.rightConnectLine.clr = clrNONE; //--- Set right color
      }
      fi1 = M_PI - NormalizeRadians(referenceArc.endAngle - (sa / 2)); //--- Set fi1
      caseStruct.fillDot.x = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(fi1)); //--- Set fill dot X
      caseStruct.fillDot.y = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(fi1)); //--- Set fill dot Y
      caseStruct.fillDot.clr = clrNONE;    //--- Set fill dot color
   }
   void RedrawSectorCase(Struct_Case &caseStruct) //--- Declare redraw sector case method
   {
      int polygonX[5];                     //--- Declare polygon X
      int polygonY[5];                     //--- Declare polygon Y
      scaleLayer.obj_Canvas.Pie(caseStruct.mainArc.centerX, caseStruct.mainArc.centerY, caseStruct.mainArc.radius, caseStruct.mainArc.radius, caseStruct.mainArc.startAngle, caseStruct.mainArc.endAngle, ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency)); //--- Draw main pie
      if(caseStruct.secondaryArc.display)  //--- Check secondary display
         scaleLayer.obj_Canvas.Pie(caseStruct.secondaryArc.centerX, caseStruct.secondaryArc.centerY, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.startAngle, caseStruct.secondaryArc.endAngle, ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency)); //--- Draw secondary pie
      scaleLayer.obj_Canvas.FillCircle(caseStruct.leftRoundingArc.centerX, caseStruct.leftRoundingArc.centerY, caseStruct.leftRoundingArc.radius, ColorToARGB(caseStruct.leftRoundingArc.clr, scaleLayer.transparency)); //--- Fill left rounding
      scaleLayer.obj_Canvas.FillCircle(caseStruct.rightRoundingArc.centerX, caseStruct.rightRoundingArc.centerY, caseStruct.rightRoundingArc.radius, ColorToARGB(caseStruct.rightRoundingArc.clr, scaleLayer.transparency)); //--- Fill right rounding
      if(caseStruct.mode == 0)             //--- Check mode 0
         scaleLayer.obj_Canvas.FillCircle(caseStruct.centerArc.centerX, caseStruct.centerArc.centerY, caseStruct.centerArc.radius, ColorToARGB(caseStruct.centerArc.clr, scaleLayer.transparency)); //--- Fill center arc
      if(caseStruct.mode == 0) {           //--- Check mode 0
         caseStruct.secondaryArc.display = false; //--- Set secondary display false
         polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1
         polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0
         polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2
         polygonX[4] = caseStruct.mainArc.centerX; //--- Set polygonX4
         polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3
         polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1
         polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0
         polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2
         polygonY[4] = caseStruct.mainArc.centerY; //--- Set polygonY4
         polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3
         scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill left polygon
         polygonX[3] = caseStruct.rightConnectLine.startX; //--- Set polygonX3
         polygonX[4] = caseStruct.rightConnectLine.endX; //--- Set polygonX4
         polygonX[2] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX2
         polygonX[0] = caseStruct.mainArc.centerX; //--- Set polygonX0
         polygonX[1] = caseStruct.fillDot.x; //--- Set polygonX1
         polygonY[3] = caseStruct.rightConnectLine.startY; //--- Set polygonY3
         polygonY[4] = caseStruct.rightConnectLine.endY; //--- Set polygonY4
         polygonY[2] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY2
         polygonY[0] = caseStruct.mainArc.centerY; //--- Set polygonY0
         polygonY[1] = caseStruct.fillDot.y; //--- Set polygonY1
         scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.rightConnectLine.clr, scaleLayer.transparency)); //--- Fill right polygon
      } else {                             //--- Handle mode 1
         polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0
         polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1
         polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2
         polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3
         polygonX[4] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX4
         polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0
         polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1
         polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2
         polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3
         polygonY[4] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY4
         scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill polygon
      }
   }
protected:
   void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements
   {
      int totalGap = scaleLayer.externalScaleGap + scaleLayer.externalLabelArea + scaleLayer.borderGap; //--- Calculate total gap
      if(borderSize > 0) {                 //--- Check border size
         CaseCalculateSector(externalCase, totalGap + borderSize); //--- Calculate external sector
         externalCase.mainArc.clr = inputParams.borderColor; //--- Set main color
         externalCase.secondaryArc.clr = inputParams.borderColor; //--- Set secondary color
         externalCase.centerArc.clr = inputParams.borderColor; //--- Set center color
         externalCase.leftRoundingArc.clr = inputParams.borderColor; //--- Set left rounding color
         externalCase.rightRoundingArc.clr = inputParams.borderColor; //--- Set right rounding color
         externalCase.leftConnectLine.clr = inputParams.borderColor; //--- Set left connect color
         externalCase.rightConnectLine.clr = inputParams.borderColor; //--- Set right connect color
         externalCase.fillDot.clr = inputParams.borderColor; //--- Set fill dot color
         externalCase.display = true;      //--- Set display flag
      } else                               //--- Handle no border
         externalCase.display = false;     //--- Set display flag false
      CaseCalculateSector(internalCase, totalGap); //--- Calculate internal sector
      internalCase.mainArc.clr = inputParams.caseColor; //--- Set main color
      internalCase.secondaryArc.clr = inputParams.caseColor; //--- Set secondary color
      internalCase.centerArc.clr = inputParams.caseColor; //--- Set center color
      internalCase.leftRoundingArc.clr = inputParams.caseColor; //--- Set left rounding color
      internalCase.rightRoundingArc.clr = inputParams.caseColor; //--- Set right rounding color
      internalCase.leftConnectLine.clr = inputParams.caseColor; //--- Set left connect color
      internalCase.rightConnectLine.clr = inputParams.caseColor; //--- Set right connect color
      internalCase.fillDot.clr = inputParams.caseColor; //--- Set fill dot color
      internalCase.display = true;         //--- Set display flag
   }
   void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements
   {
      if(externalCase.display)             //--- Check external display
         RedrawSectorCase(externalCase);   //--- Redraw external case
      if(internalCase.display)             //--- Check internal display
         RedrawSectorCase(internalCase);   //--- Redraw internal case
   }
};

Here, we define the "CGaugeBase" class with private members for positions, current value, and initialization flag, along with declared methods for drawing, needle calculations, legends, scale marks, ranges, ticks, and labels as before, but leave "CalculateCaseElements" and "DrawCaseElements" as pure virtual to require implementation in subclasses, while providing default behavior for other setters and updates in public and protected sections. We create the "CRoundGauge" class inheriting from "CGaugeBase", overriding "CalculateCaseElements" to configure external and internal circles based on border size, centers from scale arc, radii adjustments, colors from inputs, and display flags; and overriding "DrawCaseElements" to fill these circles on the scale canvas with "FillCircle" using ARGB-converted colors if displayed.

For the "CSectorGauge" class, which also inherits from "CGaugeBase", we add a private "CaseCalculateSector" method to compute sector elements: determining mode based on angle range exceeding pi, setting main and secondary arcs for large angles, calculating left and right rounding arcs with trig functions like MathArcsin and "NormalizeRadians", center arc with offset, connecting lines with sine and cosine adjustments varying by mode, and a fill dot at mid-angle. We override "CalculateCaseElements" in "CSectorGauge" to compute total gap from external elements and border, call "CaseCalculateSector" for external (if border positive) and internal with appropriate gaps, assign border or case colors to all arc, line, and dot components, and enable displays.

Finally, we override "DrawCaseElements" to conditionally call "RedrawSectorCase" for external and internal if displayed, where "RedrawSectorCase" draws main and secondary pies with "Pie", fills rounding and center circles with "FillCircle", and constructs polygons for filling connections (using arrays for coordinates) with "FillPolygon" in ARGB colors, adapting arrays for mode 0 (separate left/right polygons) or mode 1 (single polygon).

Generally, what we get is that the pure virtual methods enable polymorphism: RSI uses a round gauge, CCI/MFI use sector for variety and better fit (e.g., CCI's symmetric scale around zero). Relative positioning in "Create" will allow gauges to align side-by-side automatically, and "RedrawScaleMarks" overhaul willsupports indicators with negative ranges (CCI) or inverted scales (MFI: 100 to 0), ensuring accurate tick placement and labeling. The "calc_digits" function will dynamically set decimals for a clean display. Here is the function implementation logic that we used.

//+------------------------------------------------------------------+
//| Calculate Digits                                                 |
//+------------------------------------------------------------------+
int calc_digits(double value) {
   int i, j, max_nulls = 0, nulls = 0;     //--- Declare variables
   if(value == 0) return(0);               //--- Return 0 if zero
   ulong v = ulong(MathAbs(value) * 100000000); //--- Calculate v
   ulong vtmp;                             //--- Declare vtmp
   for(j = -5; j <= 5; j++) {              //--- Loop j
      nulls = 0;                           //--- Reset nulls
      vtmp = v + (ulong)j;                 //--- Set vtmp
      for(i = 0; i < 8; i++) {             //--- Loop i
         if(vtmp % 10 == 0) {              //--- Check mod 10 == 0
            vtmp = vtmp / 10;              //--- Divide vtmp
            nulls++;                       //--- Increment nulls
         } else break;                     //--- Break else
      }
      if(max_nulls < nulls) max_nulls = nulls; //--- Update max nulls
   }
   return(8 - max_nulls);                  //--- Return digits
}

The "calc_digits" function determines the number of decimal digits required for displaying a value, handling floating-point precision issues. If the value is zero, we return 0 immediately. We compute an unsigned long "v" by taking the absolute value multiplied by 100,000,000 to shift the decimal part into the integer. We then loop over small offsets "j" from -5 to 5, adding each to "v" to create "vtmp", and count trailing zeros by repeatedly dividing by 10 while the remainder is zero, up to 8 times, tracking the maximum such count "max_nulls" across offsets. Finally, we return 8 minus "max_nulls" as the effective digit count. Finally, what we need to do is conditionally create the gauges during initialization. We will need to change the global variables first.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CRoundGauge rsiGauge;                      //--- Declare RSI gauge
CSectorGauge cciGauge;                     //--- Declare CCI gauge
CSectorGauge mfiGauge;                     //--- Declare MFI gauge
int rsiHandle = INVALID_HANDLE;            //--- Initialize RSI handle
int cciHandle = INVALID_HANDLE;            //--- Initialize CCI handle
int mfiHandle = INVALID_HANDLE;            //--- Initialize MFI handle
double scaleMultipliers[9] = {10000, 1000, 100, 10, 1, 0.1, 0.01, 0.001, 0.0001}; //--- Define scale multipliers array
string scaleMultiplierStrings[9] = {"x10k", "x1k", "x100", "x10", " ", "/10", "/100", "/1k", "/10k"}; //--- Define multiplier strings array
double rsiBuffer[], cciBuffer[], mfiBuffer[]; //--- Declare buffers

Here, we declare global instances of the gauge classes: "rsiGauge" as "CRoundGauge" for the Relative Strength Index visualization using a circular style, "cciGauge" and "mfiGauge" as "CSectorGauge" for the Commodity Channel Index and Money Flow Index with sector designs. We initialize integer handles "rsiHandle", "cciHandle", and "mfiHandle" to INVALID_HANDLE to reference the respective technical indicators later. We define the "scaleMultipliers" double array with 9 scaling factors from 10000 down to 0.0001 for adjusting value displays on the gauges. The "scaleMultiplierStrings" string array provides corresponding labels for these multipliers, used in legends for visual indication. Finally, we declare double arrays "rsiBuffer", "cciBuffer", and "mfiBuffer" to store the calculated data from each indicator for plotting and gauge updates. We can now do the initialization of the gauges.

//+------------------------------------------------------------------+
//| Initialize Indicator                                             |
//+------------------------------------------------------------------+
int OnInit() {
   bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI
   bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI
   bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI
   string prevName = "";                   //--- Initialize prev name
   int baseX = 30;                         //--- Set base X
   int baseY = 30;                         //--- Set base Y
   IndicatorSetInteger(INDICATOR_LEVELS, 5); //--- Set levels
   SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA); //--- Set RSI buffer
   SetIndexBuffer(1, cciBuffer, INDICATOR_DATA); //--- Set CCI buffer
   SetIndexBuffer(2, mfiBuffer, INDICATOR_DATA); //--- Set MFI buffer
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set RSI empty
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set CCI empty
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set MFI empty
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14 - 1); //--- Set RSI draw begin
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 14 - 1); //--- Set CCI draw begin
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 14 - 1); //--- Set MFI draw begin
   if(showRSI) {                           //--- Check show RSI
      if(!rsiGauge.Create("rsi_gauge", baseX, baseY, 230, "", 0, 0, false, 0, 0)) return(INIT_FAILED); //--- Create RSI gauge
      rsiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set RSI case
      rsiGauge.SetScaleParameters(250, 0, 0, 100, 4, 0, clrBlack, false); //--- Set RSI scale
      rsiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set RSI ticks
      rsiGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set RSI font
      rsiGauge.SetRangeParameters(0, true, 0, 30, clrLimeGreen); //--- Set RSI range 0
      rsiGauge.SetRangeParameters(1, true, 70, 100, clrCoral); //--- Set RSI range 1
      rsiGauge.SetRangeParameters(2, true, 30, 70, clrYellow); //--- Set RSI range 2
      rsiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set RSI range 3
      rsiGauge.SetLegendParameters(0, true, "RSI", 8, -180, 20, "Arial", false, false, clrBlueViolet); //--- Set RSI legend 0
      rsiGauge.SetLegendParameters(3, true, "2", 4, 180, 13, "Arial", true, false, clrRed); //--- Set RSI legend 3
      rsiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 2.0); //--- Set RSI needle
      rsiGauge.Redraw();                   //--- Redraw RSI
      rsiGauge.NewValue(0);                //--- Set RSI value 0
      prevName = "rsi_gauge";              //--- Set prev name
      rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE); //--- Get RSI handle
      if(rsiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check RSI handle
   }
   if(showCCI) {                           //--- Check show CCI
      string relName = prevName;           //--- Set rel name
      int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode
      int posX = (prevName != "") ? 0 : baseX; //--- Set pos X
      int posY = baseY + 90;               //--- Set pos Y
      if(!cciGauge.Create("cci_gauge", posX, posY, 230, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create CCI gauge
      cciGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 1); //--- Set CCI case
      cciGauge.SetScaleParameters(200, 0, -200, 200, 4, 0, clrBlack, false); //--- Set CCI scale
      cciGauge.SetTickParameters(0, 2, 100, 1, 4); //--- Set CCI ticks
      cciGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set CCI font
      cciGauge.SetRangeParameters(0, true, -200, -100, clrCoral); //--- Set CCI range 0
      cciGauge.SetRangeParameters(1, true, -100, 100, clrDodgerBlue); //--- Set CCI range 1
      cciGauge.SetRangeParameters(2, true, 100, 200, clrLimeGreen); //--- Set CCI range 2
      cciGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set CCI range 3
      cciGauge.SetLegendParameters(0, true, "CCI", 4, 0, 16, "Arial", false, false, clrBlueViolet); //--- Set CCI legend 0
      cciGauge.SetLegendParameters(3, true, "1", 1, 0, 12, "Arial", true, false, clrRed); //--- Set CCI legend 3
      cciGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.5); //--- Set CCI needle
      cciGauge.Redraw();                   //--- Redraw CCI
      cciGauge.NewValue(0);                //--- Set CCI value 0
      prevName = "cci_gauge";              //--- Set prev name
      cciHandle = iCCI(_Symbol, _Period, 14, PRICE_TYPICAL); //--- Get CCI handle
      if(cciHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check CCI handle
   }
   if(showMFI) {                           //--- Check show MFI
      string relName = prevName;           //--- Set rel name
      int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode
      int posX = (prevName != "") ? 0 : baseX; //--- Set pos X
      int posY = baseY + 90;               //--- Set pos Y
      if(!mfiGauge.Create("mfi_gauge", posX, posY, 250, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create MFI gauge
      mfiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set MFI case
      mfiGauge.SetScaleParameters(120, -35, 100, 0, 3, 0, clrBlack, false); //--- Set MFI scale
      mfiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set MFI ticks
      mfiGauge.SetTickLabelFont(0, "Arial", false, false, clrBlack); //--- Set MFI font
      mfiGauge.SetRangeParameters(0, true, 80, 100, clrRed); //--- Set MFI range 0
      mfiGauge.SetRangeParameters(1, true, 20, 80, clrMagenta); //--- Set MFI range 1
      mfiGauge.SetRangeParameters(2, true, 0, 20, clrGreen); //--- Set MFI range 2
      mfiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set MFI range 3
      mfiGauge.SetLegendParameters(0, true, "MFI", 4, -15, 14, "Arial", false, false, clrBlueViolet); //--- Set MFI legend 0
      mfiGauge.SetLegendParameters(2, true, "", 4, -50, 10, "Arial", false, false, clrDimGray); //--- Set MFI legend 2
      mfiGauge.SetLegendParameters(3, true, "0", 3, -80, 16, "Arial", true, false, clrRed); //--- Set MFI legend 3
      mfiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.2); //--- Set MFI needle
      mfiGauge.Redraw();                   //--- Redraw MFI
      mfiGauge.NewValue(0);                //--- Set MFI value 0
      mfiHandle = iMFI(_Symbol, _Period, 14, VOLUME_TICK); //--- Get MFI handle
      if(mfiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check MFI handle
   }
   return(INIT_SUCCEEDED);                 //--- Return succeeded
}

In the OnInit event handler, we begin by determining visibility flags for each gauge based on the "inpGaugeSelection" input: "showRSI" is true for selections including Relative Strength Index, similarly for "showCCI" and "showMFI" to conditionally display the corresponding indicators. We initialize an empty string "prevName" for relative positioning, set base coordinates "baseX" and "baseY" to 30, configure five indicator levels with "IndicatorSetInteger", and bind buffers with "SetIndexBuffer" to index 0 for "rsiBuffer", 1 for "cciBuffer", and 2 for "mfiBuffer" as indicator data. We set empty plot values to EMPTY_VALUE using PlotIndexSetDouble for all three plots and specify the drawing start at bar 13 with PlotIndexSetInteger to align with the 14-period calculations. Your logic might change if you chose a different approach.

If "showRSI" is true, we create the "rsiGauge" instance with "Create" at base position and size 230, no relative, then configure it via "SetCaseParameters" with mint cream case, border style 1, light sky blue border, gap 3; "SetScaleParameters" for 250-degree range, 0 to 100 values, multiplier 4, black color, no arc; "SetTickParameters" style 0 size 2 major 10 medium 1 minor 4; "SetTickLabelFont" size 1 Arial no styles black; ranges with "SetRangeParameters" 0-30 lime green, 70-100 coral, 30-70 yellow, disabled gray; legends with "SetLegendParameters" description "RSI" at radius 8 angle -180 size 20 Arial no styles blue violet, value "2" radius 4 angle 180 size 13 Arial italic no bold red; "SetNeedleParameters" center 1 black dim gray fill 1 tail 2.0; call "Redraw" and "NewValue" 0; update "prevName" to "rsi_gauge"; initialize "rsiHandle" with iRSI on symbol period 14 close prices, returning INIT_FAILED if invalid.

If "showCCI" is true, we set relative name from "prevName" and mode 1 if not empty, position x 0 or base, y base+90; create "cciGauge" relatively; configure case same as RSI but gap 1; scale 200 degrees -200 to 200; ticks same; font same; ranges -200--100 coral, -100-100 dodger blue, 100-200 lime green, disabled; legends "CCI" radius 4 angle 0 size 16 Arial no styles blue violet, value "1" radius 1 angle 0 size 12 Arial italic no bold red; needle tail 1.5; redraw and initialize to 0; update prevName; get "cciHandle" with iCCI period 14 typical price, check invalid.

For "showMFI" if true, similar relative setup, position y base+90; create "mfiGauge" size 250; case gap 3; scale 120 degrees 100 to 0 (reversed) multiplier 3; ticks same; font size 0; ranges 80-100 red, 20-80 magenta, 0-20 green, disabled; legends "MFI" radius 4 angle -15 size 14 Arial no styles blue violet, multiplier empty radius 4 angle -50 size 10 dim gray, value "0" radius 3 angle -80 size 16 Arial italic no bold red; needle tail 1.2; redraw initialize to 0; get "mfiHandle" with iMFI period 14 tick volume, check invalid.

We return INIT_SUCCEEDED to complete initialization. Upon initialization, we get the following outcome.

GAUGES INITIALIZATION

We can see the buffers are empty and the indicators are not plotted but gauges are okay. What we need to do is do the calculations in the calculation event handler for the indicators that we have chosen, conditionally.

//+------------------------------------------------------------------+
//| Calculate Indicator                                              |
//+------------------------------------------------------------------+
int OnCalculate(const int ratesTotal,
                const int prevCalculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tickVolume[],
                const long &volume[],
                const int &spread[]) {
   bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI
   bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI
   bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI
   static datetime lastBarTime = 0;        //--- Declare last bar time
   bool isNewBar = (ratesTotal > 0 && time[ratesTotal - 1] != lastBarTime); //--- Check new bar
   if(isNewBar) lastBarTime = time[ratesTotal - 1]; //--- Update last bar time
   if(showRSI) {                           //--- Check show RSI
      if(rsiHandle != INVALID_HANDLE && CopyBuffer(rsiHandle, 0, 0, ratesTotal, rsiBuffer) < 0) return(0); //--- Copy RSI buffer
   } else {                                //--- Handle no show
      ArrayFill(rsiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill RSI empty
   }
   if(showCCI) {                           //--- Check show CCI
      if(cciHandle != INVALID_HANDLE && CopyBuffer(cciHandle, 0, 0, ratesTotal, cciBuffer) < 0) return(0); //--- Copy CCI buffer
   } else {                                //--- Handle no show
      ArrayFill(cciBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill CCI empty
   }
   if(showMFI) {                           //--- Check show MFI
      if(mfiHandle != INVALID_HANDLE && CopyBuffer(mfiHandle, 0, 0, ratesTotal, mfiBuffer) < 0) return(0); //--- Copy MFI buffer
   } else {                                //--- Handle no show
      ArrayFill(mfiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill MFI empty
   }
   if(isNewBar) {                          //--- Check new bar
      if(showRSI && rsiHandle != INVALID_HANDLE) { //--- Check RSI
         double val[1];                    //--- Declare val
         if(CopyBuffer(rsiHandle, 0, 0, 1, val) > 0) rsiGauge.NewValue(val[0]); //--- Set RSI value
      }
      if(showCCI && cciHandle != INVALID_HANDLE) { //--- Check CCI
         double val[1];                    //--- Declare val
         if(CopyBuffer(cciHandle, 0, 0, 1, val) > 0) cciGauge.NewValue(val[0]); //--- Set CCI value
      }
      if(showMFI && mfiHandle != INVALID_HANDLE) { //--- Check MFI
         double val[1];                    //--- Declare val
         if(CopyBuffer(mfiHandle, 0, 0, 1, val) > 0) mfiGauge.NewValue(val[0]); //--- Set MFI value
      }
   }
   return(ratesTotal);                     //--- Return rates total
}

In the OnCalculate event handler, we redefine the visibility flags "showRSI", "showCCI", and "showMFI" based on the "inpGaugeSelection" input to determine which indicators and gauges to process in this calculation cycle. We use a static "datetime" "lastBarTime" to detect new bars, setting "isNewBar" true if the latest timestamp differs, and update "lastBarTime" accordingly. If "showRSI" is true and the handle is valid, we copy the entire history from the Relative Strength Index buffer to "rsiBuffer" with CopyBuffer, returning 0 on failure; otherwise, fill "rsiBuffer" with EMPTY_VALUE using ArrayFill to hide the plot.

Similarly, for Commodity Channel Index, copy to "cciBuffer" or fill empty; and for Money Flow Index, copy to "mfiBuffer" or fill empty. On a new bar, if showing and handles are valid, we copy the latest single value to a temp array "val" with "CopyBuffer" shift 0 count 1, and if successful, update the corresponding gauge with "NewValue" passing "val[0]". We return "ratesTotal" to continue processing. Finally, do not forget to delete the gauges to avoid chart clutter.

//+------------------------------------------------------------------+
//| Deinitialize Indicator                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   rsiGauge.Delete();                      //--- Delete RSI gauge
   cciGauge.Delete();                      //--- Delete CCI gauge
   mfiGauge.Delete();                      //--- Delete MFI gauge
   ChartRedraw();                          //--- Redraw chart
}

In the OnDeinit event handler, we invoke the "Delete" method on "rsiGauge", "cciGauge", and "mfiGauge" to clean up their scale and needle layer objects from the chart. We then call ChartRedraw to refresh the chart, ensuring no visual remnants remain after the indicator is deinitialized. Upon compilation, we get the following outcome.

FINAL COMBINED GAUGES FINISH

From the visualization, we can see that we calculate the indicators, draw the gauges, set parameters, and delete them when not needed, hence achieving our objectives. The thing that remains is backtesting the program, and that is handled in the next section.


Backtesting

We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) bitmap image format.

FINAL FULL GAUGES BACKTEST GIF


Conclusion

In conclusion, we’ve enhanced the gauge-based indicator in MQL5 to support multiple oscillators like the Relative Strength Index, the Commodity Channel Index, and the Money Flow Index through user-selectable combinations, introducing round and sector styles via derived classes with advanced case rendering using arcs, polygons, and relative positioning for aligned displays. This indicator offers a versatile tool for multi-oscillator analysis, with configurable scales, ranges, legends, and needles adapting to different value domains. You can advance it to include custom data indicators as you like.

Happy trading!

Implementing Practical Modules from Other Languages in MQL5 (Part 06): Python-Like File IO operations in MQL5 Implementing Practical Modules from Other Languages in MQL5 (Part 06): Python-Like File IO operations in MQL5
This article shows how to simplify complex MQL5 file operations by building a Python-style interface for effortless reading and writing. It explains how to recreate Python’s intuitive file-handling patterns through custom functions and classes. The result is a cleaner, more reliable approach to MQL5 file I/O.
Data Science and ML (Part 47): Forecasting the Market Using the DeepAR model in Python Data Science and ML (Part 47): Forecasting the Market Using the DeepAR model in Python
In this article, we will attempt to predict the market with a decent model for time series forecasting named DeepAR. A model that is a combination of deep neural networks and autoregressive properties found in models like ARIMA and Vector Autoregressive (VAR).
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Creating a mean-reversion strategy based on machine learning Creating a mean-reversion strategy based on machine learning
This article proposes another original approach to creating trading systems based on machine learning, using clustering and trade labeling for mean reversion strategies.