English
preview
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen

Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen

MetaTrader 5Handel |
18 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorangegangenen Artikel (Teil 2) haben wir eine Relative Strength Index-Anzeige im Messuhren-Stil in MetaQuotes Language 5 (MQL5) entwickelt, die Canvas- und Nadelmechanik verwendet und Relative Strength Index-Werte durch eine kreisförmige Messuhr (Gauge) mit einer dynamischen Nadel, farbcodierten Bereichen, die überkaufte und überverkaufte Niveaus anzeigen, und anpassbaren Legenden visualisiert, wobei die traditionelle Liniendarstellung für eine umfassende Momentumanalyse integriert wird. In Teil 3 entwickeln wir Multi-Messuhren Erweiterungen mit den Versionen Sector und Round Styles. Dieses Modell unterstützt mehrere Oszillatoren, wie z. B. den Relative Strength Index (RSI), den Commodity Channel Index (CCI) und den Money Flow Index (MFI), durch vom Nutzer wählbare Kombinationen. Es werden abgeleitete Klassen für sektorielle und runde Messuhrendesigns eingeführt, mit verbesserter Darstellung von Gehäusen unter Verwendung von Bögen, Polygonen und relativer Positionierung, was zu einer ausgefeilten Anzeige mit mehreren Indikatoren führt. Wir werden die folgenden Themen behandeln:

  1. Multi-Messuhren mit Sektor- und Rundstilen
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie einen funktionsfähigen MQL5-Indikator für eine verbesserte Visualisierung des Multi-Messuhren-Oszillators. Sie ist bereit für weitere Anpassungen – fangen wir an!


Multi-Messuhren mit Sektor- und Rundstilen

Das System mehrerer Messuhren baut auf einer Basisklasse für anpassbare Messuhren auf und führt abgeleitete Klassen für runde und sektorale Stile ein, um Oszillatoren wie den Relative-Stärke-Index, den Commodity-Channel-Index und den Money-Flow-Index zu visualisieren, indem wir einzelne oder kombinierte Anzeigen über eine Enumeration für eine flexible Momentum-Analyse über Indikatoren hinweg auswählen. Es wird der runde Stil eines kreisförmigen Gehäuses mit gefüllten Kreisen beibehalten, während der Sektorstil die visuelle Darstellung mit bogenbasierten Sektoren, Rundbögen, Verbindungslinien und Polygonen für partielle Zifferblattformen verbessert, sich an Winkelbereiche anpasst und die relative Positionierung unterstützt, um mehrere Messuhren horizontal auf dem Chart auszurichten. Diese Idee entstand aus der Tatsache, dass man in manchen Fällen nur einen kleinen Teil einer Messuhr benötigt, um einige Informationen anzuzeigen, und so hielten wir es für eine großartige Idee, die volle runde Messuhr in eine halbgroße und eine viertelgroße zu unterteilen, sodass Sie wählen können, was zu Ihrem Stil passt. In unserem Fall werden wir die 3 in bedingter Weise erstellen.

Um dies zu erreichen, planen wir, die Basisklasse für Messuhren um rein virtuelle Methoden für die Fallberechnung und das Zeichnen zu erweitern und Überschreibungen in abgeleiteten Klassen für stil-spezifische Logik zu ermöglichen. Wir werden eine Enumeration für die Auswahl von Messuhren hinzufügen, um Instanzen für den Relative Strength Index (Runde), den Commodity Channel Index (Sektor) und den Money Flow Index (Sektor) bedingt zu initialisieren und zu positionieren und ihre Handles und Puffer für das Kopieren von Daten zu integrieren. Wir benötigen diese, um visuelle Daten zu erhalten, aber in Ihrem Fall können Sie alles andere verwenden, wie z. B. die Anzeige von Gewinnen, Indikatorfluss und Fortschritt oder sogar Kontometrien, die nicht auf andere Indikatordaten beschränkt sind. Die Architektur trennt Ebenen für Skala (mit verbesserter Markierungspopulation für Nullpositionen) und Nadel (mit einstellbaren Nadelmultiplikatoren), was effiziente Neuzeichnungen und eine relative Verankerung auf der Grundlage früherer Messwerte gewährleistet. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

ÜBERSICHT ÜBER DIE MESSUHR-ARCHITEKTUR


Implementation in MQL5

Um mit der Implementierung der Erweiterungen zu beginnen, müssen wir zunächst die Plots und Puffer des Indikators anpassen und weitere Indikatoreigenschaften für die zusätzlichen Indikatoren hinzufügen, die wir hinzufügen wollen, insbesondere die CCI- und MFI-Indikatoren. Hier ist, wie wir das neu machen.

#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

Zunächst definieren wir die Metadaten des Indikators mit #property-Direktiven neu, weisen 3 Puffer mit „indicator_buffers“ für die Datenspeicherung zu und konfigurieren 3 Plots mit indicator_plots, da wir es nun mit 3 Indikatoren zu tun haben. Für das erste Plot setzen wir den Typ auf DRAW_LINE, die Farbe auf dodger blue, den Stil auf solid, die Breite auf 2 und die Beschriftung auf „RSI“, so wie sie war. Das zweite Plot ist eine grüne durchgezogene Linie mit der Breite 2, die mit „CCI“ beschriftet ist, und das dritte ist eine blaue durchgezogene Linie mit der Breite 2, die mit „MFI“ beschriftet ist.

Wir legen die vertikale Skala von 0 bis 100 über „indicator_minimum“ und „indicator_maximum“ fest und fügen fünf gepunktete Graustufen bei 30, 70, -100, 100 und 0 hinzu, indem wir „indicator_level1“ bis „indicator_level5“, „indicator_levelcolor“ und „indicator_levelstyle“ verwenden, um Schwellenwerte für die Oszillatoren zu referenzieren. Diese Eigenschaften ermöglichen die gleichzeitige Darstellung von Linien für den Relative Strength Index, den Commodity Channel Index und den Money Flow Index in einem separaten Fenster.

Damit wir anpassen können, welche Messuhr (RSI, CCI, MFI oder Kombinationen) über den Indikator-Eingabe-Dialog angezeigt werden, um den Indikator flexibler und ressourceneffizienter zu machen und eine erweiterte Skalierungsdarstellung zu unterstützen, insbesondere für Indikatoren wie den CCI (der negative Werte und eine Null in der Mitte haben kann), müssen wir einige Enumerationen deklarieren, um zu bestimmen, wo die Marke „Null“ platziert wird, was die Genauigkeit für nicht-positive Skalierungen verbessert.

//+------------------------------------------------------------------+
//| 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
};

Wir definieren die Enumeration „ENUM_GAUGE_SELECTION“, um Optionen für die Auswahl der anzuzeigenden Messuhren bereitzustellen, einschließlich individueller Auswahlmöglichkeiten wie „RSI_ONLY“, „CCI_ONLY“ oder „MFI_ONLY“, Kombinationen wie „RSI_CCI“, „RSI_MFI“ oder „CCI_MFI“ und „ALL“ für die Anzeige aller Werte. Wir deklarieren einen Eingabeparameter „inpGaugeSelection“ vom Typ „ENUM_GAUGE_SELECTION“ mit dem Standardwert „ALL“, sodass der Nutzer die Messuhrenkonfiguration direkt aus den Indikatoreinstellungen auswählen kann. Als Nächstes erstellen wir die Enumeration „ENUM_NULLMARK_POS“, um die Positionen für die Nullmarke auf der Skala festzulegen, wobei die Werte „NULLMARK_NONE“ auf 0, „NULLMARK_LEFT“ auf 1, „NULLMARK_MIDDLE“ auf 2 und „NULLMARK_RIGHT“ auf 3 gesetzt werden, um eine flexible Handhabung von Skalierungslayouts zu ermöglichen, insbesondere für Indikatoren mit negativen Bereichen. Wir hielten dies für wichtig, um alle möglichen Szenarien abzudecken.

Als Nächstes werden wir die Gehäusestruktur so erweitern, dass sie den neuen Sektor oder Teilsektor, wie Sie es nennen möchten, Kreise oder Messuhren unterstützt, und auch die Eingabeparameter für die Messuhren erweitern, um die benötigten Multiplikatoren für die Nadelspitzen (needle tail) aufzunehmen. Beginnen wir mit der Struktur des Gehäuses.

Alte Struktur des Gehäuses

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

Neue Struktur des Gehäuses

//+------------------------------------------------------------------+
//| 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
};

Hier wird die Struktur „Struct_Case“ erweitert, um anspruchsvollere Messuhrengehäuse, insbesondere für Sektoren, zu unterstützen, indem ein Anzeigekennzeichen und ein eingebettetes „Struct_Circle“ für einfache kreisförmige Elemente aufgenommen werden. Wir fügen einen ganzzahligen „Modus“ hinzu, um die Gehäusekonfiguration auf der Grundlage von Winkelbereichen zu bestimmen, zusammen mit Instanzen von „Struct_Arc“ für den Hauptbogen, den sekundären Bogen (für größere Winkel), den mittleren Bogen sowie die linken und rechten Rundungsbögen zur Glättung der Kanten. Zusätzlich haben wir „Struct_Line“ für linke und rechte Verbindungslinien zu Brückenkomponenten und einen „Struct_Dot“ für einen Füllpunkt eingebaut, um eine vollständige Abdeckung von Polygonfüllungen während des Renderings zu gewährleisten. Was die Struktur der Parameter betrifft, so fügen wir nur den Multiplikator am Ende wie folgt ein. Wir haben sie zur Verdeutlichung hervorgehoben.

//+------------------------------------------------------------------+
//| 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
};

Die Variable, die wir für den Multiplikator hinzugefügt haben, ermöglicht es, die Länge der Nadelspitze anzupassen (z. B. kürzer für Sektorenlehren), die visuelle Ästhetik zu verbessern und verschiedene Formen der Messuhren anzupassen. Wir werden die Klassen nun grundlegend überarbeiten, sodass wir Polymorphismus und Vererbung unterstützen, um eine bessere Anpassung, Klassifizierung und zukünftige Anpassung zu ermöglichen. Wir fügen zwei abgeleitete Klassen mit privaten Helfern wie folgt hinzu.

//+------------------------------------------------------------------+
//| 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
   }
};

Hier definieren wir die Klasse „CGaugeBase“ mit privaten Mitgliedern für Positionen, den aktuellen Wert und das Initialisierungs-Flag, zusammen mit deklarierten Methoden für das Zeichnen, Nadelberechnungen, Legende, Skalierungsmarkierungen, Bereiche, Skalierungsstriche und Beschriftungen wie zuvor, belassen aber „CalculateCaseElements“ und „DrawCaseElements“ als rein virtuell, um eine Implementierung in Unterklassen zu erfordern, während wir das Standardverhalten für andere Setter und Aktualisierungen in öffentlichen und geschützten Abschnitten bereitstellen. Wir erstellen die Klasse „CRoundGauge“, die von „CGaugeBase“ erbt, überschreiben „CalculateCaseElements“, um externe und interne Kreise auf der Grundlage der Randgröße, der Zentren aus dem Skalierungsbogen, der Radiusanpassungen, der Farben aus den Eingaben und der Anzeige-Flags zu konfigurieren, und überschreiben „DrawCaseElements“, um diese Kreise auf der Skalierungsleinwand mit „FillCircle“ unter Verwendung von ARGB-konvertierten Farben zu füllen, falls angezeigt.

Für die Klasse „CSectorGauge“, die ebenfalls von „CGaugeBase“ erbt, fügen wir die private Methode „CaseCalculateSector“ zur Berechnung von Sektorelementen hinzu: Bestimmung des Modus auf der Grundlage des Winkelbereichs, der Pi übersteigt, Festlegung von Haupt- und Nebenbögen für große Winkel, Berechnung von linken und rechten Rundungsbögen mit Trig-Funktionen wie MathArcsin und „NormalizeRadians“, Mittelpunktsbogen mit Versatz, Verbindungslinien mit Sinus- und Kosinusanpassungen, die je nach Modus variieren, und ein Füllpunkt in der Mitte des Winkels. Wir überschreiben „CalculateCaseElements“ in „CSectorGauge“, um den Gesamtabstand von externen Elementen und der Umrandung zu berechnen, rufen „CaseCalculateSector“ für externe (wenn die Umrandung positiv ist) und interne Elemente mit den entsprechenden Abständen auf, weisen allen Bogen-, Linien- und Punktkomponenten Umrandungs- oder Gehäusefarben zu und aktivieren die Anzeigen.

Schließlich überschreiben wir „DrawCaseElements“, um bedingt „RedrawSectorCase“ für externe und interne, falls angezeigt, aufzurufen, wobei „RedrawSectorCase“ Haupt- und Nebentortendiagramme mit „Pie“ zeichnet, Rundungen und Mittelkreise mit „FillCircle“, und Polygone für Füllverbindungen (unter Verwendung von Arrays für Koordinaten) mit „FillPolygon“ in ARGB-Farben, wobei Arrays für Modus 0 (getrennte linke/rechte Polygone) oder Modus 1 (einzelnes Polygon) angepasst werden.

Im Allgemeinen ist es so, dass die rein virtuellen Methoden Polymorphismus ermöglichen: Der RSI verwendet eine runde Skala, während der CCI/MFI aus Gründen der Abwechslung und besseren Anpassung einen Sektor verwendet (z. B. die symmetrische Skala des CCI um den Nullpunkt). Die relative Positionierung in „Erstellen“ ermöglicht die automatische Ausrichtung von Messuhren nebeneinander, und die Überarbeitung von „RedrawScaleMarks“ unterstützt Indikatoren mit negativen Bereichen (CCI) oder invertierter Skalierung (MFI: 100 bis 0), um eine genaue Platzierung und Beschriftung der Skalierungsstriche zu gewährleisten. Die Funktion „calc_digits“ setzt dynamisch Dezimalstellen für eine saubere Anzeige. Hier ist die Logik der Funktionsimplementierung, die wir verwendet haben.

//+------------------------------------------------------------------+
//| 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
}

Die Funktion „calc_digits“ bestimmt die Anzahl der Dezimalstellen, die für die Anzeige eines Wertes erforderlich sind, und behandelt Fragen der Fließkommagenauigkeit. Wenn der Wert null ist, wird sofort 0 zurückgegeben. Wir berechnen einen vorzeichenlosen ganzzahligen Wert vom Typ long „v“, indem wir den absoluten Wert mit 100.000.000 multiplizieren, um den Dezimalteil in die Ganzzahl zu verschieben. Dann wird eine Schleife über die kleinen Offsets „j“ von -5 bis 5 gezogen, die jeweils zu „v“ addiert werden, um „vtmp“ zu erstellen, und es werden die nachgestellten Nullen gezählt, indem wiederholt durch 10 geteilt wird, während der Rest Null ist, und zwar bis zu 8 Mal, wobei die maximale Anzahl solcher Nullen „max_nulls“ über die Offsets hinweg verfolgt wird. Schließlich geben wir 8 minus „max_nulls“ als effektive Stellenzahl zurück. Schließlich müssen wir die Messuhren während der Initialisierung unter bestimmten Bedingungen erstellen. Zuerst müssen wir die globalen Variablen ändern.

//+------------------------------------------------------------------+
//| 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

Hier deklarieren wir globale Instanzen der Messuhren-Klassen: „rsiGauge“ als „CRoundGauge“ für die Visualisierung des Relative-Stärke-Index mit einem kreisförmigen Stil, „cciGauge“ und „mfiGauge“ als „CSectorGauge“ für den Commodity Channel Index und den Money Flow Index mit Sektordesigns. Wir initialisieren die ganzzahligen Handles „rsiHandle“, „cciHandle“ und „mfiHandle“ auf INVALID_HANDLE, um später auf die jeweiligen technischen Indikatoren zu verweisen. Wir definieren das Double-Array „scaleMultipliers“ mit 9 Skalierungsfaktoren von 10000 bis hinunter zu 0,0001 zur Anpassung der Wertanzeigen auf den Messuhren. Das String-Array „scaleMultiplierStrings“ enthält entsprechende Bezeichnungen für diese Multiplikatoren, die in Legenden zur visuellen Darstellung verwendet werden. Schließlich deklarieren wir Double-Arrays „rsiBuffer“, „cciBuffer“ und „mfiBuffer“, um die berechneten Daten der einzelnen Indikatoren für die Darstellung und die Aktualisierung der Messuhren zu speichern. Wir können nun die Initialisierung der Messuhren vornehmen.

//+------------------------------------------------------------------+
//| 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 OnInit legen wir zunächst die Sichtbarkeits-Flags für jeden Indikator auf der Grundlage der Eingabe „inpGaugeSelection“ fest: „showRSI“ ist wahr für Auswahlen, die den Relative Strength Index enthalten, ebenso für „showCCI“ und „showMFI“, um die entsprechenden Indikatoren bedingt anzuzeigen. Wir initialisieren die leere Zeichenkette „prevName“ für die relative Positionierung, setzen die Basiskoordinaten „baseX“ und „baseY“ auf 30, konfigurieren fünf Indikatorstufen mit „IndicatorSetInteger“ und binden Puffer mit „SetIndexBuffer“ an den Index 0 für „rsiBuffer“, 1 für „cciBuffer“ und 2 für „mfiBuffer“ als Indikatordaten. Wir setzen leere Plot-Werte mit PlotIndexSetDouble für alle drei Plots auf EMPTY_VALUE und legen den Zeichenbeginn mit PlotIndexSetInteger auf den Balken-Index 13 fest, um die Berechnungen auf 14 Perioden abzustimmen. Ihre Logik könnte sich ändern, wenn Sie einen anderen Ansatz wählen.

Wenn „showRSI“ auf „true“ gesetzt ist, erstellen wir die Instanz „rsiGauge“ mit „Create“ an der Basisposition und einer Größe von 230 (keine relative Größe) und konfigurieren sie anschließend über „SetCaseParameters“ mit einem mintfarbenen Hintergrund, Rahmenstil 1, hellblauem Rand und einem Abstand von 3; „SetScaleParameters“ für einen Bereich von 250 Grad, Werte von 0 bis 100, Multiplikator 4, Farbe Schwarz, kein Bogen; „SetTickParameters“ Stil 0, Größe 2, groß 10, mittel 1, klein 4; „SetTickLabelFont“ Größe 1, Arial, keine Stile, schwarz; Bereiche mit „SetRangeParameters“ 0–30 limettengrün, 70–100 korallenrot, 30–70 gelb, deaktiviert grau; Legende mit „SetLegendParameters“ Beschreibung „RSI“ bei Radius 8, Winkel -180, Größe 20, Arial, keine Stile, blau-violett; Wert „2“ bei Radius 4, Winkel 180, Größe 13, Arial, kursiv, keine Fettschrift, rot; „SetNeedleParameters“ Mitte 1 schwarz, Füllung 1 mattgrau, Nadel 2,0; „Redraw“ und „NewValue“ 0 aufrufen; „prevName“ auf „rsi_gauge“ aktualisieren; „rsiHandle“ mit iRSI auf Symbolperiodenlänge 14 Schlusskursen initialisieren und, falls ungültig, INIT_FAILED zurückgeben.

Wenn „showCCI“ wahr ist, setzen wir den relativen Namen auf „prevName“ und den Modus auf 1, sofern nicht leer, Position x 0 oder base, y base+90; erstellen „cciGauge“ relativ; konfigurieren das Gehäuse wie beim RSI, jedoch mit einem Abstand von 1; Skalierung 200 Grad von -200 bis 200; Skalierungsstriche gleich; Schriftart gleich; Bereiche -200--100 korallenrot, -100-100 dodgerblau, 100-200 limettengrün, deaktiviert; Legenden „CCI“ Radius 4 Winkel 0 Größe 16 Arial keine Stile blau-violett, Wert „1“ Radius 1 Winkel 0 Größe 12 Arial kursiv keine Fettschrift rot; Nadelspitze 1,5; neu zeichnen und auf 0 initialisieren; prevName aktualisieren; „cciHandle“ mit iCCI und der Periodenlänge 14 und dem typischen Preis abrufen, einschließlich der Prüfung auf Ungültigkeit.

Für „showMFI“: Falls true, verwenden wir eine ähnliche relative Konfiguration mit Position y: Basis + 90; „mfiGauge“ erstellen, Größe 250; Abstand 3; Skalierung: 120 Grad, 100 bis 0 (umgekehrt), Multiplikator 3; Markierungen wie oben; Schriftgröße 0; Bereiche 80–100 rot, 20–80 magenta, 0–20 grün, deaktiviert; Beschriftungen „MFI“ Radius 4 Winkel -15 Größe 14 Arial keine Stile blauviolett, Multiplikator leer Radius 4 Winkel -50 Größe 10 dunkelgrau, Wert „0“ Radius 3 Winkel -80 Größe 16 Arial kursiv keine Fettschrift rot; Nadelspitze 1,2; Neuzeichnen auf 0 initialisieren; „mfiHandle“ mit Abrufen des iMFI, Periodenlänge 14, Tick-Volumen und der Prüfung auf Ungültigkeit.

Wir geben INIT_SUCCEEDED zurück, um die Initialisierung abzuschließen. Nach der Initialisierung erhalten wir das folgende Ergebnis.

MESSUHR INITIALISIERUNG

Wir können sehen, dass die Puffer leer sind und die Indikatoren nicht gezeichnet werden, aber die Messuhren sind in Ordnung. Wir müssen die Berechnungen im Berechnungs-Ereignishandler für die von uns ausgewählten Indikatoren unter bestimmten Bedingungen durchführen.

//+------------------------------------------------------------------+
//| 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 der Ereignisbehandlung von OnCalculate werden die Sichtbarkeits-Flags „showRSI“, „showCCI“ und „showMFI“ auf der Grundlage der Eingabe „inpGaugeSelection“ neu definiert, um zu bestimmen, welche Indikatoren und Messuhren in diesem Berechnungszyklus verarbeitet werden sollen. Wir verwenden eine statische „datetime“ „lastBarTime“, um neue Balken zu erkennen, setzen „isNewBar“ auf true, wenn der letzte Zeitstempel abweicht, und aktualisieren „lastBarTime“ entsprechend. Wenn „showRSI“ wahr und der Handle gültig ist, kopieren wir den gesamten Verlauf aus dem Puffer des Relative Strength Index mit CopyBuffer in „rsiBuffer“ und geben bei einem Fehlschlag 0 zurück; andernfalls füllen wir „rsiBuffer“ mit EMPTY_VALUE unter Verwendung von ArrayFill, um die Darstellung auszublenden.

Ebenso kopieren wir für den Commodity Channel Index den Wert in „cciBuffer“ oder füllen das Feld mit Nullen; und für den Money Flow Index kopieren wir den Wert in „mfiBuffer“ oder füllen das Feld mit Nullen. Bei einem neuen Balken, wenn die Anzeige und die Handles gültig sind, kopieren wir den letzten Einzelwert in ein temporäres Array „val“ mit „CopyBuffer“ shift 0 count 1, und aktualisieren bei Erfolg die entsprechende Anzeige mit „NewValue“ über „val[0]“. Wir geben „ratesTotal“ zurück, um die Bearbeitung fortzusetzen. Vergessen wir nicht, die Messuhren zu löschen, um ein unübersichtliches Chart zu vermeiden.

//+------------------------------------------------------------------+
//| 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 der Ereignisbehandlung von OnDeinit rufen wir die Methode „Delete“ für „rsiGauge“, „cciGauge“ und „mfiGauge“ auf, um ihre Skalierungs- und Nadelebenenobjekte aus dem Chart zu entfernen. Anschließend rufen wir ChartRedraw auf, um das Chart zu aktualisieren und sicherzustellen, dass nach der Deinitialisierung des Indikators keine visuellen Überbleibsel zurückbleiben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ENDBEARBEITUNG DER KOMBINIERTEN MESSUHREN

Anhand der Visualisierung können wir sehen, dass wir die Indikatoren berechnen, die Messuhren zeichnen, Parameter festlegen und sie löschen, wenn sie nicht benötigt werden, und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und der wird im nächsten Abschnitt behandelt.


Backtests

Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

ENDGÜLTIGER BACKTEST ALLER MESSUHREN GIF


Schlussfolgerung

Abschließend haben wir den Indikator auf Basis von Messuhren in MQL5 verbessert, um mehrere Oszillatoren wie den Relative Strength Index, den Commodity Channel Index und den Money Flow Index durch vom Nutzer auswählbare Kombinationen zu unterstützen und runde und sektorale Stile über abgeleitete Klassen mit fortgeschrittenen Gehäusedarstellungen unter Verwendung von Bögen, Polygonen und relativer Positionierung für ausgerichtete Anzeigen einzuführen. Dieser Indikator ist ein vielseitiges Werkzeug für die Analyse von Multi-Oszillatoren, mit konfigurierbaren Skalierungen, Bereichen, Legenden und Nadeln, die sich an verschiedene Wertebereiche anpassen lassen. Sie können sie erweitern, um nutzerdefinierte Datenindikatoren einzubeziehen, wie Sie möchten.

Viel Spaß beim Handeln!

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20719

Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur
Lernen Sie, wie Sie die Marktstrukturkonzepte von Larry Williams in MQL5 automatisieren können, indem Sie einen vollständigen Expert Advisor erstellen, der Umkehrpunkte liest, Handelssignale erzeugt, das Risiko verwaltet und eine dynamische Trailing-Stop-Strategie anwendet.
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5 Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5
Dieser Artikel zeigt, wie man komplexe MQL5-Datei-Operationen vereinfachen kann, indem man eine Schnittstelle im Python-Stil für müheloses Lesen und Schreiben erstellt. Es wird erklärt, wie man die intuitiven Dateiverarbeitungsmuster von Python durch nutzerdefinierte Funktionen und Klassen nachbilden kann. Das Ergebnis ist ein sauberer, zuverlässiger Ansatz für MQL5-Datei-E/A.
Sigma Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor Sigma Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor
Erstellen Sie einen praktischen MetaTrader 5 „Sigma Score“ Indikator von Grund auf und lernen Sie, was er wirklich misst: den z-Score der logarithmischen Renditen (wie viele Standardabweichungen die letzte Bewegung vom letzten Durchschnitt abweicht). Der Artikel geht jeden Codeblock in OnInit(), OnCalculate() und OnDeinit() durch und zeigt dann, wie man Schwellenwerte (z. B. ±2) interpretiert und den Sigma Score als einfaches „Marktstress-Messgerät“ für Mean-Reversion und Momentum-Trading einsetzt.
Larry Williams Marktgeheimnisse (Teil 1): Aufbau eines Swing-Struktur-Indikators in MQL5 Larry Williams Marktgeheimnisse (Teil 1): Aufbau eines Swing-Struktur-Indikators in MQL5
Ein praktischer Leitfaden zum Aufbau eines Marktstrukturindikators im Stil von Larry Williams in MQL5, der die Einrichtung von Puffern, die Erkennung von Umkehrpunkten (swing-points), die Konfiguration von Darstellungen und die Anwendung des Indikators in der technischen Marktanalyse durch Händler umfasst.