English
preview
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 2): Bau eines RSI-Displays im Stil einer Messuhr mit Leinwand und Nadelmechanik

Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 2): Bau eines RSI-Displays im Stil einer Messuhr mit Leinwand und Nadelmechanik

MetaTrader 5Handelssysteme |
13 3
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 1) haben wir einen Pivot-basierten Trendindikator in MetaQuotes Language 5 (MQL5) erstellt. Es berechnete schnelle und langsame Pivot-Linien über nutzerdefinierte Zeiträume, erkannte die Trendrichtung relativ zu diesen Linien und signalisierte Trendstarts mit Pfeilen, wobei die Linien optional über den aktuellen Balken hinaus verlängert werden konnten. In Teil 2 erstellen wir eine Darstellung des Relative-Strength-Index (RSI) im Stile einer Messuhr, die Leinwand und Nadelmechanik verwendet. Dieses Modell zeigt die Werte des Relative-Strength-Index auf einer runden Anzeige mit einer dynamischen Nadel, farbcodierten Bereichen für überkaufte und überverkaufte Niveaus und einer anpassbaren Legende. Es integriert auch traditionelle Liniendiagramme für eine umfassende Momentum-Analyse. Wir werden die folgenden Themen behandeln:

  1. Das Verständnis des RSI-Indikators im Stil einer Messuhr
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende haben Sie einen funktionsfähigen MQL5-Indikator für die Visualisierung des Relative-Strength-Index auf Basis einer Messuhr, der zur Anpassung bereit ist – fangen wir an!


Das Verständnis des RSI-Indikators im Stil einer Messuhr

Der Anzeigestil des Strength Index-Indikators ist eine Neuinterpretation des standardmäßigen Relative-Strength-Index als kreisförmiges Zifferblatt, bei dem eine Nadel dynamisch auf den aktuellen Momentum-Wert auf einer Skala von 0 bis 100 zeigt und überkaufte Zustände über 70 und überverkaufte Zustände unter 30 durch unterschiedliche Farbzonen für eine schnelle visuelle Beurteilung hervorhebt. Er enthält Skalenstrich-Markierungen für eine präzise Ablesung, Legenden für den Kontext, wie den Namen des Indikators und die Anzeige des Wertes, und ein traditionelles Liniendiagramm in einem separaten Fenster, um die Messuhr mit historischen Datentrends zu ergänzen. Wir haben uns für diesen Ansatz einer Messuhr entschieden, weil er intuitiv und ansprechend ist, um Analysen durchzuführen und die Ergebnisse anzuzeigen, und weil wir einen standardmäßigen, bekannten Berechnungsansatz verwendet haben, aber in Zukunft auch komplexe Daten für die Darstellung und Kommentierung einbeziehen können.

Unser Ziel ist es, einen modularen Rahmen zu schaffen, der die grafischen Ebenen für die Skala und die Nadel voneinander trennt und unabhängige Transparenz und Aktualisierungen für mehr Effizienz ermöglicht. Wir beginnen mit der Festlegung von Eingabeparametern für die Anpassung, z. B. Winkelbereichen, Farben und Skalenstrichintervalle, und definieren dann Strukturen für Elemente wie Bögen, Torten und Beschriftungen, um die Zeichenlogik zu organisieren. Von dort aus erstellen wir eine Basisklasse, die die Erstellung, das Setzen der Parameter und das erneute Zeichnen übernimmt und sicherstellt, dass die Messuhr ordnungsgemäß initialisiert wird und auf neue Werte des Relative-Strength-Index vom Markt reagiert. Unser Plan ist es, die Canvas-Zeichnung für alle visuellen Komponenten zu nutzen, sie in die integrierte Relative-Strength-Index-Berechnung zu integrieren und Event-Handler zu verwalten, um einen nahtlosen Betrieb bei Chart-Aktualisierungen zu gewährleisten. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele. Zum besseren Verständnis haben wir die meisten Elemente detailliert aufgeführt.

MESSUHRGERÜST


Implementation in MQL5

Um den Indikator in MQL5 zu erstellen, öffnen Sie einfach den MetaEditor, gehen zum Navigator, suchen den Ordner Indikatoren, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald der Indikator erstellt ist, werden wir in der Programmierumgebung die Eigenschaften und Einstellungen des Indikators festlegen, z. B. die Anzahl der Puffer, die Darstellungen und die Eigenschaften der einzelnen Linien, wie Farbe, Breite und Beschriftung.

//+------------------------------------------------------------------+
//|                           1. Gauge-Based RSI Indicator Part1.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
#property indicator_label1 "RSI"
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_level1 30
#property indicator_level2 70
#property indicator_levelcolor clrGray
#property indicator_levelstyle STYLE_DOT

Wir beginnen die Implementierung, indem wir die Metadaten des Indikators mit den Direktiven #property definieren, mit indicator_separate_window festlegen, dass er in einem separaten Teilfenster gezeichnet wird, mit „indicator_buffers“ zwei Puffer zuweisen und mit „indicator_plots“ einen Plot konfigurieren. Für die Darstellung setzen wir den Typ auf DRAW_LINE, die Farbe auf dodger blue, den Stil auf solid, die Breite auf 2 und beschriften es mit „RSI“. Wir legen auch die vertikale Skala von 0 bis 100 mit „indicator_minimum“ und „indicator_maximum“ fest und fügen gepunktete Graustufen bei 30 und 70 mit „indicator_level1“, „indicator_level2“, „indicator_levelcolor“ und „indicator_levelstyle“ hinzu, um überverkaufte und überkaufte Zonen hervorzuheben. Diese Eigenschaften schaffen eine saubere RSI-Liniendarstellung in einem eigenen Fenster für die Momentum-Analyse. Dann können wir die Canvas-Bibliothek für nutzerdefinierte Zeichenelemente einbinden.

#include <Canvas\Canvas.mqh>

Wir binden die Canvas-Bibliothek mit „#include <Canvas\Canvas.mqh>“ ein, um integrierte Instrumente für die nutzerdefinierte grafische Darstellung einzubinden, wie z. B. die Klasse CCanvas, die die Erstellung von Bitmaps und Zeichenoperationen auf dem Chart ermöglicht. Dadurch wird das Programm auf die Erstellung visueller Elemente wie Bögen, Kreise und Text in der Messuhranzeige vorbereitet. Dann können wir Strukturen definieren, um die grafischen Komponenten zu organisieren.

//+------------------------------------------------------------------+
//| Circle Structure                                                 |
//+------------------------------------------------------------------+
struct Struct_Circle {                     // Define circle structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   color clr;                              // Store color
   bool display;                           // Store display flag
};

//+------------------------------------------------------------------+
//| Arc Structure                                                    |
//+------------------------------------------------------------------+
struct Struct_Arc {                        // Define arc structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   double startAngle;                      // Store start angle in radians
   double endAngle;                        // Store end angle in radians
   color clr;                              // Store color
   bool display;                           // Store display flag
};

//+------------------------------------------------------------------+
//| Line Structure                                                   |
//+------------------------------------------------------------------+
struct Struct_Line {                       // Define line structure
   int startX;                             // Store start X coordinate
   int startY;                             // Store start Y coordinate
   int endX;                               // Store end X coordinate
   int endY;                               // Store end Y coordinate
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Dot Structure                                                    |
//+------------------------------------------------------------------+
struct Struct_Dot {                        // Define dot structure
   int x;                                  // Store X coordinate
   int y;                                  // Store Y coordinate
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Pie/Sector Structure                                             |
//+------------------------------------------------------------------+
struct Struct_Pie {                        // Define pie structure
   int centerX;                            // Store center X coordinate
   int centerY;                            // Store center Y coordinate
   int radius;                             // Store radius
   int eraseRadius;                        // Store erase radius
   double startAngle;                      // Store start angle in radians
   double endAngle;                        // Store end angle in radians
   double eraseStartAngle;                 // Store erase start angle in radians
   double eraseEndAngle;                   // Store erase end angle in radians
   color clr;                              // Store color
   color eraseClr;                         // Store erase color
};

//+------------------------------------------------------------------+
//| Range Structure                                                  |
//+------------------------------------------------------------------+
struct Struct_Range {                      // Define range structure
   bool active;                            // Store active status
   double startValue;                      // Store start value
   double endValue;                        // Store end value
   color clr;                              // Store color
   Struct_Pie pie;                         // Store pie structure
};

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

//+------------------------------------------------------------------+
//| Scale Marks Structure                                            |
//+------------------------------------------------------------------+
struct Struct_ScaleMarks {                 // Define scale marks structure
   double minValue;                        // Store minimum value
   double maxValue;                        // Store maximum value
   double valueRange;                      // Store value range
   bool forwardDirection;                  // Store forward direction flag
   int nullMarkPosition;                   // Store null mark position
   double nullMarkAngle;                   // Store null mark angle
   int decimalPlaces;                      // Store decimal places
   int majorTickLength;                    // Store major tick length
   int mediumTickLength;                   // Store medium tick length
   int minorTickLength;                    // Store minor tick length
   double minAngle;                        // Store minimum angle
   double maxAngle;                        // Store maximum angle
   double angleRange;                      // Store angle range
   double multiplier;                      // Store multiplier
   string gaugeName;                       // Store gauge name
   string currentValue;                    // Store current value
   string units;                           // Store units
   int tickFontSize;                       // Store tick font size
   string tickFontName;                    // Store tick font name
   uint tickFontFlags;                     // Store tick font flags
   int tickFontGap;                        // Store tick font gap
};

//+------------------------------------------------------------------+
//| Label Area Size Structure                                        |
//+------------------------------------------------------------------+
struct Struct_LabelAreaSize {              // Define label area size structure
   int height;                             // Store height
   int width;                              // Store width
   int diagonal;                           // Store diagonal
};

//+------------------------------------------------------------------+
//| Gauge Legend Parameters Structure                                |
//+------------------------------------------------------------------+
struct Struct_GaugeLegendParams {          // Define gauge legend parameters structure
   bool enable;                            // Store enable flag
   string text;                            // Store text
   uint radius;                            // Store radius
   double angle;                           // Store angle
   uint fontSize;                          // Store font size
   string fontName;                        // Store font name
   bool italic;                            // Store italic flag
   bool bold;                              // Store bold flag
   color textColor;                        // Store text color
};

//+------------------------------------------------------------------+
//| Gauge Legend String Structure                                    |
//+------------------------------------------------------------------+
struct Struct_GaugeLegendString {          // Define gauge legend string structure
   string text;                            // Store text
   int radius;                             // Store radius
   double angle;                           // Store angle
   int fontSize;                           // Store font size
   string fontName;                        // Store font name
   uint fontFlags;                         // Store font flags
   color textColor;                        // Store text color
   color backgroundColor;                  // Store background color
   uint decimalPlaces;                     // Store decimal places
   uint x;                                 // Store x coordinate
   uint y;                                 // Store y coordinate
   bool draw;                              // Store draw flag
};

//+------------------------------------------------------------------+
//| Gauge Label Structure                                            |
//+------------------------------------------------------------------+
struct Struct_GaugeLabel {                 // Define gauge label structure
   Struct_GaugeLegendString description;   // Store description
   Struct_GaugeLegendString units;         // Store units
   Struct_GaugeLegendString multiplier;    // Store multiplier
   Struct_GaugeLegendString value;         // Store value
};

//+------------------------------------------------------------------+
//| Scale Layer Structure                                            |
//+------------------------------------------------------------------+
struct Struct_ScaleLayer {                 // Define scale layer structure
   string objectName;                      // Store object name
   CCanvas obj_Canvas;                     // Store canvas object
   uchar transparency;                     // Store transparency
   color caseColor;                        // Store case color
   Struct_Case externalCase;               // Store external case
   int borderSize;                         // Store border size
   Struct_Case internalCase;               // Store internal case
   int borderGap;                          // Store border gap
   int externalLabelArea;                  // Store external label area
   int externalScaleGap;                   // Store external scale gap
   Struct_Arc scaleArc;                    // Store scale arc
   int internalScaleGap;                   // Store internal scale gap
   int internalLabelArea;                  // Store internal label area
   Struct_ScaleMarks scaleMarks;           // Store scale marks
   Struct_GaugeLabel gaugeLabel;           // Store gauge label
   Struct_Range ranges[4];                 // Store ranges array
};

//+------------------------------------------------------------------+
//| Needle Structure                                                 |
//+------------------------------------------------------------------+
struct Struct_Needle {                     // Define needle structure
   int tipRadius;                          // Store tip radius
   int tailRadius;                         // Store tail radius
   int x[4];                               // Store x coordinates array
   int y[4];                               // Store y coordinates array
   int fillStyle;                          // Store fill style
   color clr;                              // Store color
};

//+------------------------------------------------------------------+
//| Needle Layer Structure                                           |
//+------------------------------------------------------------------+
struct Struct_NeedleLayer {                // Define needle layer structure
   string objectName;                      // Store object name
   CCanvas obj_Canvas;                     // Store canvas object
   uchar transparency;                     // Store transparency
   Struct_Arc needleCenter;                // Store needle center
   Struct_Needle needle;                   // Store needle
};

//+------------------------------------------------------------------+
//| Range Parameters Structure                                       |
//+------------------------------------------------------------------+
struct Struct_RangeParams {                // Define range parameters structure
   bool enable;                            // Store enable flag
   double start;                           // Store start value
   double end;                             // Store end value
   color clr;                              // Store color
};

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

Für die Strukturen definieren wir zunächst die Struktur „Struct_Circle“ mit dem Schlüsselwort struct, um Eigenschaften für kreisförmige Elemente zu speichern, darunter Mittelpunktskoordinaten, Radius, Farbe und ein Flag für die Anzeige. Wir haben der Klarheit halber Kommentare hinzugefügt. Als Nächstes wird die Struktur „Struct_Arc“ für Bogenformen erstellt, in der Mittelpunkt, Radius, Start- und Endwinkel im Bogenmaß, Farbe und Anzeigestatus gespeichert werden. Anschließend wird die Struktur „Struct_Line“ für Linien mit Anfangs- und Endkoordinaten und Farbe eingerichtet. Die Struktur „Struct_Dot“ ist für Punkte definiert und enthält die x- und y-Position und die Farbe. Für Sektoren oder Teile eines Tortendiagramms definieren wir die Struktur „Struct_Pie“ mit Zentrum, Radien zum Zeichnen und Löschen, Winkelbereichen für beide und Farben zum Füllen und Löschen. Wir richten die Struktur „Struct_Range“ ein, um Wertebereiche zu verwalten, einschließlich des aktiven Status, der Start- und Endwerte, der Farbe und eines eingebetteten „Struct_Pie“ zur Visualisierung.

Für das Messuhrgehäuse wird die Struktur „Struct_Case“ mit einem Flag für die Anzeige und einem enthaltenen „Struct_Circle“ angelegt. Wir definieren die Struktur „Struct_ScaleMarks“, um Skalendetails wie Wertgrenzen, Bereiche, Richtungen, Skalenstrichlängen, Winkel, Multiplikatoren, Schrifteigenschaften und Lücken zu organisieren. Für die Größenbestimmung der Etiketten erstellen wir die Struktur „Struct_LabelAreaSize“, die die Maße für Höhe, Breite und Diagonale enthält. Die Struktur „Struct_GaugeLegendParams“ wird für Legendenkonfigurationen, einschließlich Flag für die Aktivierung, Text, Radius, Winkel, Schriftart und Farbe, verwendet. Dann definieren wir „Struct_GaugeLegendString“ für spezifische Legendenstrings mit Text, Position, Schriftart, Farben, Dezimalstellen, Koordinaten und Flag für die Anzeige. Die Struktur „Struct_GaugeLabel“ fasst mehrere Legendenstrings für Beschreibung, Einheiten, Multiplikator und Wert zusammen. Für die Schichtung erstellen wir „Struct_ScaleLayer“, um die Skalierungskomponente zu verwalten, einschließlich Objektname, Leinwandinstanz, Transparenz, Gehäusefarbe, Rahmendetails, Beschriftungsbereiche, Skalenbogen, Markierungen, Beschriftungen und eine Reihe von Bereichen.

Wir definieren „Struct_Needle“ für den Zeiger, mit Spitzen- und Endradien, Koordinatenfeldern, Füllstil und Farbe. Die Struktur „Struct_NeedleLayer“ behandelt die Nadelebene mit Objektname, Leinwand, Transparenz, Mittelpunktsbogen und Nadeldaten. Für die Bereichseinstellungen setzen wir „Struct_RangeParams“ mit Aktivierungsflag, Start- und Endwerten und Farbe. Schließlich definieren wir „Struct_GaugeInputParams“, um alle Eingabeoptionen zu konsolidieren, die Offsets, Anker, Skalenwinkel, Farben, Anzeigeflags, Wertgrenzen, Multiplikatoren, Skalenstrich-Konfigurationen, Schrifttyp-Flags, Bereichsarrays, Gehäuseeigenschaften, Legenden und Nadelstile umfassen. Nun können wir mit der Erstellung des Standard-RSI-Indikators fortfahren, was am einfachsten ist. Den komplexen Indikator werden wir später erstellen, da wir die Standard-RSI-Daten so oder so benötigen. Dies ist der Ansatz, mit dem wir das erreicht haben.

int rsiHandle;                             //--- Declare RSI handle
double rsiBuffer[];                        //--- Declare RSI buffer

//+------------------------------------------------------------------+
//| Initialize Indicator                                             |
//+------------------------------------------------------------------+
int OnInit() {
   rsiHandle = iRSI(_Symbol, _Period, 14, 4);       //--- Get RSI handle
   if(rsiHandle == INVALID_HANDLE)                  //--- Check handle
      return(INIT_FAILED);                          //--- Return failed
   SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA);    //--- Set index buffer
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set plot empty
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14 - 1); //--- Set draw begin
   IndicatorSetInteger(INDICATOR_LEVELS, 2);        //--- Set levels
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 30); //--- Set level 0
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 70); //--- Set level 1
   return(INIT_SUCCEEDED);                          //--- Return succeeded
}

//+------------------------------------------------------------------+
//| 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[]) {
   if(CopyBuffer(rsiHandle, 0, 0, ratesTotal, rsiBuffer) < 0) { //--- Copy buffer
      Print("RSI CopyBuffer error for plot"); //--- Print error
      return(0);                           //--- Return 0
   }
   return(ratesTotal);                     //--- Return rates total
}

Um den Standard-RSI-Indikator zu erstellen, deklarieren wir auf globaler Ebene „rsiHandle“ als Ganzzahl, um den Verweis darauf zu speichern, und „rsiBuffer“ als Double-Array, um die berechneten Werte des Relative-Strength-Index zu speichern.

In der Ereignisbehandlung von OnInit initialisieren wir „rsiHandle“ mithilfe der Funktion iRSI mit dem aktuellen Symbol, dem Zeitrahmen, einer Periode von 14 und den Schlusskursen. Wenn der Handle ungültig ist, wird INIT_FAILED zurückgegeben, um die Initialisierung abzubrechen. Dann binden wir mit SetIndexBuffer den Pufferindex 0 an „rsiBuffer“ für die Indikatordaten, setzen die Werte der leeren Zeichnung mit PlotIndexSetDouble auf „EMPTY_VALUE“ und legen mit PlotIndexSetInteger den Zeichenbeginn bei dem Balken 13 fest, um den Berechnungszeitraum des Relative-Strength-Index zu berücksichtigen. Zusätzlich konfigurieren wir zwei Indikatorstufen mit „IndicatorSetInteger“ und setzen sie mit „IndicatorSetDouble“ auf 30 und 70 für überverkaufte und überkaufte Schwellenwerte, bevor wir INIT_SUCCEEDED zurückgeben.

In der Ereignisbehandlung durch OnCalculate kopieren wir die Daten aus dem Puffer 0 des Relative-Strength-Index-Handles mit der Funktion CopyBuffer in den „rsiBuffer“ für die insgesamt verfügbaren Kurse. Wenn das Kopieren fehlschlägt, wird eine Fehlermeldung gedruckt und 0 zurückgegeben, um die Verarbeitung abzubrechen; andernfalls wird die Summe der Kurse zurückgegeben, um die erfolgreiche Berechnung anzuzeigen. Es ist immer ein guter Programmierstil, Ihr Programm an jedem Meilenstein laufen zu lassen, um sicherzustellen, dass alles reibungslos läuft. Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis.

TRADITIONELLER RSI-INDIKATOR

Aus dem Bild ist ersichtlich, dass wir den Standardindikator eingerichtet haben. Das war ganz einfach und unkompliziert. Jetzt müssen wir zum nächsten Punkt übergehen, wo wir eine Klasse für das Zeichnen der Messuhr definieren, damit wir sie später problemlos wiederverwenden können, wenn wir weitere Messuhren erstellen müssen, aber zuerst werden wir einige Hilfsfunktionen definieren.

//+------------------------------------------------------------------+
//| Degrees to Radians                                               |
//+------------------------------------------------------------------+
double DegreesToRadians(double degrees) {
   return((M_PI * degrees) / 180.0);       //--- Convert degrees to radians
}

//+------------------------------------------------------------------+
//| Normalize Radians                                                |
//+------------------------------------------------------------------+
double NormalizeRadians(double angle) {
   while(angle < 0.0) angle += 2.0 * M_PI; //--- Adjust negative
   while(angle >= 2.0 * M_PI) angle -= 2.0 * M_PI; //--- Adjust positive
   return(angle);                          //--- Return normalized
}

//+------------------------------------------------------------------+
//| Get Tick Font Gap                                                |
//+------------------------------------------------------------------+
int GetTickFontGap(Struct_ScaleMarks &scaleMarks, int stringLength) {
   int gap = 0;                            //--- Initialize gap
   Struct_LabelAreaSize areaSize;          //--- Declare area size
   CCanvas obj_Canvas_temp;                //--- Declare temp canvas
   if(!obj_Canvas_temp.FontSet(scaleMarks.tickFontName, scaleMarks.tickFontSize, scaleMarks.tickFontFlags, 0)) //--- Set font
      return gap;                          //--- Return gap
   string str = "000";                     //--- Set str
   obj_Canvas_temp.TextSize(str, areaSize.width, areaSize.height); //--- Get text size
   if(areaSize.width == 0 || areaSize.height == 0) //--- Check size
      return gap;                          //--- Return gap
   areaSize.diagonal = (int)MathCeil(MathSqrt((double)(areaSize.width * areaSize.width + areaSize.height * areaSize.height))); //--- Calculate diagonal
   gap = (int)(areaSize.diagonal * 0.5);   //--- Set gap
   return gap;                             //--- Return gap
}

//+------------------------------------------------------------------+
//| Get Tick Label Area Size                                         |
//+------------------------------------------------------------------+
bool GetTickLabelAreaSize(int &areaSize, Struct_ScaleMarks &scaleMarks, int stringLength) {
   CCanvas obj_Canvas_temp;                //--- Declare temp canvas
   int width = 0, height = 0;              //--- Initialize width height
   if(!obj_Canvas_temp.FontSet(scaleMarks.tickFontName, scaleMarks.tickFontSize, scaleMarks.tickFontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   string str = "000";                     //--- Set str
   obj_Canvas_temp.TextSize(str, width, height); //--- Get text size
   if(width == 0 || height == 0)           //--- Check size
      return false;                        //--- Return false
   areaSize = (int)MathCeil(MathSqrt((double)(width * width + height * height))); //--- Calculate area size
   return true;                            //--- Return true
}

Zunächst definieren wir die Funktion „DegreesToRadians“, um einen eingegebenen Gradwert in Bogenmaß umzuwandeln, indem wir ihn mit M_PI multiplizieren und durch 180 dividieren. Dann erstellen wir die Funktion „NormalizeRadians“, um sicherzustellen, dass ein Winkel innerhalb des Bereichs von 0 bis 2pi liegt, indem wir 2pi für negative Werte addieren oder 2pi für solche subtrahieren, die 2pi überschreiten. Die Funktion „GetTickFontGap“ berechnet einen Abstand für Skalenstrich-Labels unter Verwendung eines temporären CCanvas-Objekts; sie stellt die Schriftart aus dem Parameter „Struct_ScaleMarks“ ein, misst die Textgröße von „000“, berechnet die Diagonale der Breite und Höhe mit MathCeil und MathSqrt und gibt die Hälfte dieser Diagonale als Abstand zurück, wobei der Standardwert 0 ist, wenn die Schrifteinstellung fehlschlägt oder die Größe Null ist.

Wir definieren auch die Funktion „GetTickLabelAreaSize“, um die benötigte Fläche für Skalenstrich-Labels mit einem temporären „CCanvas“ zu bestimmen, die Schriftart zu setzen, die Textabmessungen „000“ zu messen und die Obergrenze der Diagonale sqrt für die Größe des Ausgabebereichs zu berechnen, wobei bei Fehlern oder Nullgrößen false zurückgegeben wird. Mit diesen Funktionen ausgestattet, können wir eine vollständige Klasse mit vollständigen Methodendefinitionen erstellen. Wir deklarieren zunächst eine Basisklasse mit allen Methoden, die wir benötigen, und definieren sie dann später.

//+------------------------------------------------------------------+
//| 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
   void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap); //--- Declare calculate case elements method
   void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase); //--- Declare draw case elements 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
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); //--- Declare set needle parameters method
};

Wir definieren die Klasse „CGaugeBase“, die als Kern für die Erstellung und Verwaltung der Messuhrvisualisierung dient und die gesamte Logik für die Erstellung, das Zeichnen und die Aktualisierung kapselt. Im privaten Bereich werden Variablen zur Erfassung der relativen und der mittleren Position, des aktuellen Anzeigewerts und ein Flag für die Initialisierung deklariert. Außerdem definieren wir Methoden wie „Draw“ zum Zeichnen der Messuhr, „CalculateNeedle“ zur Positionierung des Zeigers, „RedrawNeedle“ zur Aktualisierung des Zeigers basierend auf einem Wert, „CalculateAndDrawLegends“ und „CalculateAndDrawLegendString“ zur Bearbeitung von Textelementen, „RedrawScaleMarks“ für Skalenstriche und Beschriftungen, „CalculateRanges“ und zugehörige Hilfsfunktionen wie „IsValidRange“, „NormalizeRangeValues“, „CalculateRangePie“, „DrawRanges“ und „DrawRange“ für Farbzonen, „CalculateInnerOuterRadii“ für Radiusberechnungen, „DrawTick“ für einzelne Markierungen, „CalculateAngleDelta“ für Winkelunterschiede, „GetLabelAreaSize“ und „EraseLegendString“ für die Beschriftungsverwaltung, „RedrawValueDisplay“ zur Aktualisierung angezeigter Werte, „SetLegendStringParams“ zur Legendenkonfiguration sowie „CalculateCaseElements“ mit „DrawCaseElements“ für das Gehäuse der Messuhr.

Der geschützte Bereich enthält Strukturen für Eingabeparameter, Skalenebene, Nadelebene und den Radius, um abgeleiteten Klassen den Zugriff zu ermöglichen, während sie gekapselt bleiben.

Im öffentlichen Bereich bieten wir Methoden wie „Create“ zum Initialisieren der Messuhr mit Name, Position, Größe, Relativität, Ecke, Hintergrund und Transparenzen; „CalculateLocation“ für die Positionierung; „Redraw“ für die vollständige Aktualisierung; „NewValue“ zum Aktualisieren mit einem neuen Wert; „Delete“ zum Aufräumen; und Setter-Methoden wie „SetScaleParameters“ für Winkel- und Wertebereiche, „SetScaleParameters“ für Skalenstrich-Intervalle, „SetTickLabelFont“ für die Schriftgestaltung, „SetCaseParameters“ für Rahmen, „SetLegendParameters“ und „SetLegendParam“ für Legenden, „SetRangeParameters“ für Zonen und „SetNeedleParameters“ für die Zeigerstile. Jetzt können wir die Logik für die Erstellung und den Standort von Messuhren definieren.

//+------------------------------------------------------------------+
//| Create Gauge                                                     |
//+------------------------------------------------------------------+
bool CGaugeBase::Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency) {
   initializationComplete = false;         //--- Set initialization complete flag to false
   m_radius = size / 2;                    //--- Calculate radius
   inputParams.xOffset = x;                //--- Set x offset
   inputParams.yOffset = y;                //--- Set y offset
   inputParams.anchorCorner = corner;      //--- Set anchor corner
   inputParams.relativeMode = relativeMode;//--- Set relative mode
   inputParams.relativeObjectName = relativeObjectName; //--- Set relative object name
   if(!CalculateLocation())                //--- Check location calculation
      return false;                        //--- Return false if failed
   int canvasWidthHeight = (m_radius + 5) * 2; //--- Calculate canvas size
   scaleLayer.objectName = name + "_s";    //--- Set scale layer object name
   ObjectDelete(0, scaleLayer.objectName); //--- Delete scale layer object
   if(!scaleLayer.obj_Canvas.CreateBitmapLabel(scaleLayer.objectName, centerX, centerY, canvasWidthHeight, canvasWidthHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create scale canvas
      return false;                        //--- Return false if failed
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_CORNER, inputParams.anchorCorner); //--- Set corner property
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor property
   ObjectSetInteger(0, scaleLayer.objectName, OBJPROP_BACK, background); //--- Set back property
   needleLayer.objectName = name + "_n";   //--- Set needle layer object name
   ObjectDelete(0, needleLayer.objectName);//--- Delete needle layer object
   if(!needleLayer.obj_Canvas.CreateBitmapLabel(needleLayer.objectName, centerX, centerY, canvasWidthHeight, canvasWidthHeight, COLOR_FORMAT_ARGB_NORMALIZE)) //--- Create needle canvas
      return false;                        //--- Return false if failed
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_CORNER, inputParams.anchorCorner); //--- Set corner property
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor property
   ObjectSetInteger(0, needleLayer.objectName, OBJPROP_BACK, background); //--- Set back property
   scaleLayer.transparency = 255 - scaleTransparency; //--- Set scale transparency
   needleLayer.transparency = 255 - needleTransparency; //--- Set needle transparency
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Calculate Gauge Center Location                                  |
//+------------------------------------------------------------------+
bool CGaugeBase::CalculateLocation() {
   bool locationChanged = false;           //--- Initialize location changed flag
   int cX = m_radius;                      //--- Set initial X
   int cY = m_radius;                      //--- Set initial Y
   cX += inputParams.xOffset;              //--- Add X offset
   cY += inputParams.yOffset;              //--- Add Y offset
   if(centerX != cX || centerY != cY) {    //--- Check if position changed
      centerX = cX;                        //--- Update center X
      centerY = cY;                        //--- Update center Y
      locationChanged = true;              //--- Set changed flag
   }
   return locationChanged;                 //--- Return changed flag
}

Hier implementieren wir die Methode „Create“ in der Klasse „CGaugeBase“, um die Messuhr zu initialisieren, indem wir zunächst das Flag „initializationComplete“ auf „false“ zurücksetzen und „m_radius“ als Hälfte der angegebenen Größe berechnen. Wir speichern die Eingabeparameter, einschließlich x- und y-Versatz, Ankerecke, relativer Modus und relativer Objektname. Wenn die Methode „CalculateLocation“ fehlschlägt, geben wir false zurück. Dann legen wir die Abmessungen der Leinwand als zweifache Summe des Radius plus 5 fest, weisen dem Objektnamen der Skalierungsebene einen Namen zu, indem wir „_s“ an den Basisnamen anhängen, löschen alle vorhandenen Objekte mit ObjectDelete und erstellen mit „CreateBitmapLabel“ ein neues Bitmap-Label für die Skalierungsleinwand an der mittleren Position mit ARGB-Normalisierung. Wir konfigurieren seine Eigenschaften mit ObjectSetInteger für Ecke, Verankerung in der Mitte und Hintergrundstatus. In ähnlicher Weise fügen wir für die Nadelebene „_n“ an den Namen an, löschen sie, falls sie existiert, erstellen die Bitmap-Beschriftung und legen die gleichen Eigenschaften fest. Schließlich passen wir die Transparenzen an, indem wir die Eingabewerte von 255 subtrahieren und bei Erfolg true zurückgeben.

Wir definieren auch die Methode „CalculateLocation“, um die Mittelposition der Messuhr zu aktualisieren, indem wir ein Änderungsflag auf „false“ initialisieren, temporäre cX- und cY-Werte auf den Radius setzen, die gespeicherten Offsets hinzufügen und prüfen, ob sie von den aktuellen Mittelwerten abweichen. Wenn dies nicht der Fall ist, werden die Mittelpunktskoordinaten aktualisiert und das Flag auf true gesetzt, bevor es zurückgegeben wird. Um die Messuhr vor der Einstellung seiner Parameter zu erstellen, müssen wir ein globales Objekt unserer Klasse deklarieren und es verwenden, um Zugriff auf die Klassenmitglieder und -methoden zu erhalten (siehe unten).

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CGaugeBase gauge;                          //--- Declare gauge object


// In the initialization, call the class method
if(!gauge.Create("rsi_gauge", 30, 30, 250, "", 0, 0, false, 0, 0)) //--- Create gauge
   return(INIT_FAILED);                 //--- Return failed

Wir deklarieren eine globale Instanz der Klasse „CGaugeBase“ mit dem Namen „gauge“, um die gesamte Visualisierung der Messuhr während des Programms zu verwalten. In OnInit rufen wir die „Create“-Methode für diese Instanz mit den Parametern Name „rsi_gauge“, x- und y-Position bei 30, Größe 250, leeres relatives Objekt, relativer Modus 0, Ecke 0, Hintergrund false und beide Transparenzen bei 0 auf. Wenn die Erstellung fehlschlägt, geben wir INIT_FAILED zurück, um die Einrichtung des Indikators zu beenden. Bei der Kompilierung ergibt sich folgendes Ergebnis.

ERSTE ERSTELLUNG EINER MESSUHR

Wie wir sehen, haben wir die anfängliche Silhouette der Messuhr erstellt. Was jetzt noch zu tun ist, ist die Erstellung der Ränder und der anderen Parameter auf der Basis, die wir derzeit haben, indem wir den Rest der Methoden definieren. Wir werden zunächst die Parameter der Lehre definieren.

//+------------------------------------------------------------------+
//| Set Scale Parameters                                             |
//+------------------------------------------------------------------+
void CGaugeBase::SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc) {
   inputParams.scaleAngleRange = angleRange; //--- Set scale angle range
   inputParams.rotationAngle = rotation;   //--- Set rotation angle
   inputParams.minScaleValue = minValue;   //--- Set minimum scale value
   inputParams.maxScaleValue = maxValue;   //--- Set maximum scale value
   inputParams.scaleMultiplier = multiplier; //--- Set scale multiplier
   inputParams.scaleStyle = style;         //--- Set scale style
   inputParams.scaleColor = scaleClr;      //--- Set scale color
   inputParams.displayScaleArc = displayArc; //--- Set display scale arc flag
}

//+------------------------------------------------------------------+
//| Set Tick Parameters                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval) {
   inputParams.tickStyle = style;          //--- Set tick style
   inputParams.tickSize = size;            //--- Set tick size
   inputParams.majorTickInterval = majorInterval; //--- Set major tick interval
   inputParams.mediumTicksPerMajor = mediumPerMajor; //--- Set medium ticks per major
   inputParams.minorTicksPerInterval = minorPerInterval; //--- Set minor ticks per interval
}

//+------------------------------------------------------------------+
//| Set Tick Label Font                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr) {
   inputParams.tickFontSize = fontSize;    //--- Set tick font size
   inputParams.tickFontName = fontName;    //--- Set tick font name
   inputParams.tickFontItalic = italic;    //--- Set tick font italic flag
   inputParams.tickFontBold = bold;        //--- Set tick font bold flag
   inputParams.tickFontColor = fontClr;    //--- Set tick font color
}

//+------------------------------------------------------------------+
//| Set Case Parameters                                              |
//+------------------------------------------------------------------+
void CGaugeBase::SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize) {
   inputParams.caseColor = caseClr;        //--- Set case color
   inputParams.borderStyle = borderStyle;  //--- Set border style
   inputParams.borderColor = borderClr;    //--- Set border color
   inputParams.borderGapSize = borderGapSize; //--- Set border gap size
}

//+------------------------------------------------------------------+
//| Set Legend Parameters                                            |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr) {
   switch(legendType) {                    //--- Switch on legend type
   case 0:                                 //--- Handle description
      SetLegendParam(inputParams.description, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set description param
      break;                               //--- Break
   case 1:                                 //--- Handle units
      SetLegendParam(inputParams.units, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set units param
      break;                               //--- Break
   case 2:                                 //--- Handle multiplier
      SetLegendParam(inputParams.multiplier, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set multiplier param
      break;                               //--- Break
   case 3:                                 //--- Handle value
      SetLegendParam(inputParams.value, enable, text, radius, angle, fontSize, fontName, italic, bold, textClr); //--- Set value param
      break;                               //--- Break
   }
}

//+------------------------------------------------------------------+
//| Set Individual Legend Parameter                                  |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr) {
   legendParam.enable = enable;            //--- Set enable flag
   legendParam.text = text;                //--- Set text
   legendParam.radius = radius;            //--- Set radius
   legendParam.angle = angle;              //--- Set angle
   legendParam.fontSize = fontSize;        //--- Set font size
   legendParam.fontName = fontName;        //--- Set font name
   legendParam.italic = italic;            //--- Set italic flag
   legendParam.bold = bold;                //--- Set bold flag
   legendParam.textColor = textClr;        //--- Set text color
}

//+------------------------------------------------------------------+
//| Set Range Parameters                                             |
//+------------------------------------------------------------------+
void CGaugeBase::SetRangeParameters(int index, bool enable, double start, double end, color rangeClr) {
   if(index >= 0 && index < 4) {           //--- Check index range
      inputParams.ranges[index].enable = enable; //--- Set enable flag
      inputParams.ranges[index].start = start; //--- Set start
      inputParams.ranges[index].end = end; //--- Set end
      inputParams.ranges[index].clr = rangeClr; //--- Set color
   }
}

//+------------------------------------------------------------------+
//| Set Needle Parameters                                            |
//+------------------------------------------------------------------+
void CGaugeBase::SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle) {
   inputParams.needleCenterStyle = centerStyle; //--- Set needle center style
   inputParams.needleCenterColor = centerClr; //--- Set needle center color
   inputParams.needleColor = needleClr;    //--- Set needle color
   inputParams.needleFillStyle = fillStyle;//--- Set needle fill style
}

Hier definieren wir die Methode „SetScaleParameters“ in der Klasse „CGaugeBase“, um die Skala der Messuhr zu konfigurieren, indem wir den entsprechenden Feldern in „inputParams“ den angegebenen Winkelbereich, den Drehwinkel, die Minimal- und Maximalwerte, den Multiplikatorindex, den Stil, die Farbe und das Bogenanzeigeflag zuweisen. Wir definieren die Methode „SetTickParameters“, um skalenstrichbezogene Optionen einzustellen, indem wir den Stil, die Größe, das große Intervall, die Anzahl der mittleren Skalenstriche pro großem Intervall und der kleinen Skalenstriche pro Intervall in „inputParams“ speichern. Die Methode „SetTickLabelFont“ übernimmt die Schrifteinstellungen für die Beschriftung der Skalenstriche und aktualisiert „inputParams“ mit Schriftgröße, Name, Kursiv- und Fettschrift und Farbe, wobei standardmäßig Schwarz verwendet wird, wenn keine Angaben gemacht werden. Wir erstellen die Methode „SetCaseParameters“, um das Gehäuse der Messuhr zu definieren, und weisen „inputParams“ die Gehäusefarbe, den Rahmenstil, die Rahmenfarbe und die Lückengröße zu.

Für Legenden implementieren wir die Methode „SetLegendParameters“, die eine Switch-Anweisung auf der Grundlage des Legendentyps (0 für Beschreibung, 1 für Einheiten, 2 für Multiplikator, 3 für Wert) verwendet, um die Parameter über die Hilfsmethode „SetLegendParam“ an die entsprechenden „Struct_GaugeLegendParams“ weiterzuleiten, wobei die Standardtextfarbe dunkelgrau ist. Die Methode „SetLegendParam“ setzt direkt das Flag für die Aktivierung, den Text, den Radius, den Winkel, die Schriftgröße, den Namen, kursiv, fett und die Textfarbe in der übergebenen Legendenparameterstruktur. Wir fügen die Methode „SetRangeParameters“ hinzu, um bis zu vier Bereiche zu konfigurieren, indem wir den Index zwischen 0 und 3 validieren, bevor wir enable, start, end und color im Array „inputParams.ranges“ festlegen. Die Methode „SetNeedleParameters“ schließlich weist „inputParams“ für die Zeigerkonfiguration den Zentrumsstil, die Zentrumsfarbe, die Nadelfarbe und den Füllstil zu. Bei der Zeichnung der anderen Elemente gehen wir nach folgender Logik vor.

//+------------------------------------------------------------------+
//| Calculate Case Elements                                          |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) {
   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
}

//+------------------------------------------------------------------+
//| Draw Case Elements                                               |
//+------------------------------------------------------------------+
void CGaugeBase::DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) {
   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
}

//+------------------------------------------------------------------+
//| Calculate Needle                                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateNeedle() {
   int innerRadius = 0, outerRadius = 0;   //--- Initialize radii
   if(inputParams.minorTicksPerInterval > 0) //--- Check minor ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.minorTickLength, inputParams.tickStyle); //--- Calculate for minor
   else if(inputParams.mediumTicksPerMajor > 0) //--- Check medium ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.mediumTickLength, inputParams.tickStyle); //--- Calculate for medium
   else if(inputParams.majorTickInterval > 0) //--- Check major ticks
      CalculateInnerOuterRadii(innerRadius, outerRadius, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate for major
   needleLayer.needle.tipRadius = outerRadius; //--- Set tip radius
   needleLayer.needle.clr = inputParams.needleColor; //--- Set needle color
   needleLayer.needle.fillStyle = inputParams.needleFillStyle; //--- Set fill style
   needleLayer.needle.tailRadius = needleLayer.needleCenter.radius * 2; //--- Set tail radius
}

//+------------------------------------------------------------------+
//| Redraw Needle                                                    |
//+------------------------------------------------------------------+
void CGaugeBase::RedrawNeedle(double value) {
   needleLayer.obj_Canvas.Erase();         //--- Erase canvas
   double normalizedValue = 0;             //--- Initialize normalized value
   if(scaleLayer.scaleMarks.minValue < scaleLayer.scaleMarks.maxValue) { //--- Check direct order
      if(value < scaleLayer.scaleMarks.minValue) //--- Check min value
         value = scaleLayer.scaleMarks.minValue; //--- Clamp to min
      if(value > scaleLayer.scaleMarks.maxValue) //--- Check max value
         value = scaleLayer.scaleMarks.maxValue; //--- Clamp to max
      normalizedValue = value - scaleLayer.scaleMarks.minValue; //--- Normalize
   } else {                                //--- Handle inverse order
      if(value > scaleLayer.scaleMarks.minValue) //--- Check min value
         value = scaleLayer.scaleMarks.minValue; //--- Clamp to min
      if(value < scaleLayer.scaleMarks.maxValue) //--- Check max value
         value = scaleLayer.scaleMarks.maxValue; //--- Clamp to max
      normalizedValue = scaleLayer.scaleMarks.minValue - value; //--- Normalize
   }
   if(scaleLayer.scaleMarks.valueRange == 0) //--- Check value range
      return;                              //--- Return if zero
   double currentAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - ((normalizedValue * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Calculate current angle
   needleLayer.needle.x[0] = (int)(scaleLayer.scaleArc.centerX - needleLayer.needle.tipRadius * MathCos(M_PI - currentAngle)); //--- Set x0
   needleLayer.needle.y[0] = (int)(scaleLayer.scaleArc.centerY - needleLayer.needle.tipRadius * MathSin(M_PI - currentAngle)); //--- Set y0
   double bufferX[3], bufferY[3];          //--- Declare buffers
   bufferX[0] = scaleLayer.scaleArc.centerX - needleLayer.needle.tipRadius * MathCos(M_PI - currentAngle); //--- Set bufferX0
   bufferY[0] = scaleLayer.scaleArc.centerY - needleLayer.needle.tipRadius * MathSin(M_PI - currentAngle); //--- Set bufferY0
   double tailX = scaleLayer.scaleArc.centerX - needleLayer.needle.tailRadius * MathCos(2 * M_PI - currentAngle); //--- Calculate tail X
   double tailY = scaleLayer.scaleArc.centerY - needleLayer.needle.tailRadius * MathSin(2 * M_PI - currentAngle); //--- Calculate tail Y
   int r = (int)(needleLayer.needle.tailRadius / 3.0); //--- Calculate r
   bufferX[1] = tailX - r * MathCos(0.5 * M_PI - currentAngle); //--- Set bufferX1
   bufferY[1] = tailY - r * MathSin(0.5 * M_PI - currentAngle); //--- Set bufferY1
   bufferX[2] = tailX - r * MathCos(1.5 * M_PI - currentAngle); //--- Set bufferX2
   bufferY[2] = tailY - r * MathSin(1.5 * M_PI - currentAngle); //--- Set bufferY2
   uint clr = ColorToARGB(needleLayer.needle.clr, needleLayer.transparency); //--- Get color
   needleLayer.obj_Canvas.LineAA((int)bufferX[0], (int)bufferY[0], (int)bufferX[1], (int)bufferY[1], clr); //--- Draw line AA 0-1
   needleLayer.obj_Canvas.LineAA((int)bufferX[1], (int)bufferY[1], (int)bufferX[2], (int)bufferY[2], clr); //--- Draw line AA 1-2
   needleLayer.obj_Canvas.LineAA((int)bufferX[2], (int)bufferY[2], (int)bufferX[0], (int)bufferY[0], clr); //--- Draw line AA 2-0
   double centroidX = (bufferX[0] + bufferX[1] + bufferX[2]) / 3.0; //--- Calculate centroid X
   double centroidY = (bufferY[0] + bufferY[1] + bufferY[2]) / 3.0; //--- Calculate centroid Y
   needleLayer.obj_Canvas.Fill((int)centroidX, (int)centroidY, clr); //--- Fill
   needleLayer.obj_Canvas.LineAA(scaleLayer.scaleArc.centerX, scaleLayer.scaleArc.centerY, (int)bufferX[0], (int)bufferY[0], clr); //--- Draw line AA center to 0
   if(needleLayer.needleCenter.display)    //--- Check display
      needleLayer.obj_Canvas.FillCircle(needleLayer.needleCenter.centerX, needleLayer.needleCenter.centerY, needleLayer.needleCenter.radius, ColorToARGB(needleLayer.needleCenter.clr, needleLayer.transparency)); //--- Fill needle center
}

//+------------------------------------------------------------------+
//| Calculate and Draw Legends                                       |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateAndDrawLegends() {
   if(inputParams.description.enable)      //--- Check description enable
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.description); //--- Calculate and draw description
   if(inputParams.units.enable)            //--- Check units enable
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.units); //--- Calculate and draw units
   if(inputParams.multiplier.enable) {     //--- Check multiplier enable
      scaleLayer.gaugeLabel.multiplier.text = scaleMultiplierStrings[inputParams.scaleMultiplier]; //--- Set multiplier text
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.multiplier); //--- Calculate and draw multiplier
   }
   if(inputParams.value.enable) {          //--- Check value enable
      scaleLayer.gaugeLabel.value.decimalPlaces = 0; //--- Set decimal places
      if(inputParams.value.text != "" ) {  //--- Check text
         int digits = (int)StringToInteger(inputParams.value.text); //--- Get digits
         if(digits >= 1 && digits <= 8)    //--- Check digits range
            scaleLayer.gaugeLabel.value.decimalPlaces = (uint)digits; //--- Set decimal places
      }
      scaleLayer.gaugeLabel.value.text = " "; //--- Set text
      CalculateAndDrawLegendString(scaleLayer.gaugeLabel.value); //--- Calculate and draw value
   }
}

//+------------------------------------------------------------------+
//| Calculate and Draw Legend String                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString) {
   if(legendString.text != "") {           //--- Check text
      legendString.draw = true;            //--- Set draw flag
      scaleLayer.obj_Canvas.FontSet(legendString.fontName, legendString.fontSize, legendString.fontFlags, 0); //--- Set font
      double normalizedAngle = NormalizeRadians(DegreesToRadians(legendString.angle + 90)); //--- Normalize angle
      legendString.x = (uint)(scaleLayer.scaleArc.centerX - legendString.radius * MathCos(M_PI - normalizedAngle)); //--- Set x
      legendString.y = (uint)(scaleLayer.scaleArc.centerY - legendString.radius * MathSin(M_PI - normalizedAngle)); //--- Set y
      legendString.backgroundColor = scaleLayer.caseColor; //--- Set background color
      scaleLayer.obj_Canvas.TextOut(legendString.x, legendString.y, legendString.text, ColorToARGB(legendString.textColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
   }
}

Wir definieren die Methode „CalculateCaseElements“ in der Klasse „CGaugeBase“, um das äußere und innere Gehäuse für die Messuhr vorzubereiten. Wenn die Größe des Rahmens positiv ist, konfigurieren wir den Kreis des externen Falles mit den Mittelpunktskoordinaten des Skalenbogens, dem vollen Radius und der Rahmenfarbe und setzen sein Anzeigeflag auf true; andernfalls deaktivieren wir seine Anzeige. Für das interne Gehäuse wird der Mittelpunkt immer so eingestellt, dass er mit dem Skalenbogen übereinstimmt, der Radius um die Größe des Rahmens reduziert, die Gehäusefarbe angewendet und die Anzeige aktiviert. Die Methode „DrawCaseElements“ rendert diese Fälle auf der Leinwand der Skalenebene. Wenn das externe Gehäuse angezeigt wird, füllen wir seinen Kreis mit „FillCircle“ mit der ARGB-konvertierten Farbe, die über die Funktion ColorToARGB an die Transparenz angepasst wird. In ähnlicher Weise füllen wir für das interne Gehäuse den Kreis mit der entsprechenden ARGB-Farbe, sofern diese aktiviert ist.

Wir definieren die Methode „CalculateNeedle“, um die Abmessungen der Nadel auf der Grundlage von Skalenstrich-Konfigurationen zu bestimmen. Wir initialisieren innere und äußere Radien und rufen dann bedingt „CalculateInnerOuterRadii“ auf, wobei wir die kleine, mittlere oder große Skalenstrichlänge verwenden, je nachdem, welche Intervalle eingestellt sind, und übergeben den Skalenbogenradius und den Skalenstrichstil. Der äußere Radius wird der Nadelspitze zugewiesen, die Farbe und der Füllstil werden aus den Eingabeparametern festgelegt und der Radius des Endes wird als doppelter Radius der Nadelmitte berechnet.

Bei der Methode „RedrawNeedle“ löschen wir zunächst mit „Erase“ den Canvas der Nadelebene. Wir normalisieren den Eingabewert, indem wir ihn auf den Minimal- und Maximalwert der Skala klemmen, und berechnen dann einen normalisierten Wert, der darauf basiert, ob die Skala aufsteigend oder absteigend ist. Ist der Bereich der Werte null, wird vorzeitig abgebrochen. Wir berechnen den aktuellen Winkel mit „NormalizeRadians“, wobei wir den Anteil des normalisierten Wertes am Winkelbereich berücksichtigen. Wir setzen die Koordinaten der Nadelspitze mithilfe von Cosinus- und Sinusfunktionen mit Pi-Anpassungen, bereiten Pufferfelder für das Dreieck vor, indem wir Positionen und Offset-Punkte mit einem Radius von einem Drittel des Bereiches berechnen, die Farbe in ARGB konvertieren, mit „LineAA“ Anti-Aliasing-Linien für die Kanten des Dreiecks zeichnen, den Schwerpunkt für das Füllen des Dreiecks mit „Fill“ finden, eine Linie vom Mittelpunkt zur Spitze zeichnen und, wenn der Mittelpunkt angezeigt wird, ihn als Kreis mit ARGB-Farbe füllen.

Wir erstellen die Methode „CalculateAndDrawLegends“, um verschiedene Legendenelemente zu behandeln, wenn sie aktiviert sind. Für die Beschreibung und die Einheiten rufen wir direkt „CalculateAndDrawLegendString“ auf. Für den Multiplikator wird vor dem Zeichnen der Text aus einem vordefinierten Array basierend auf dem Index des Skalenmultiplikators festgelegt. Für den Wert setzen wir die Dezimalstellen auf 0, analysieren jeden Eingabetext auf Ziffern zwischen 1 und 8, um die Dezimalstellen zu überschreiben, setzen den Anfangstext auf ein Leerzeichen und zeichnen ihn. Die Methode „CalculateAndDrawLegendString“ verarbeitet einzelne Legenden. Wenn Text vorhanden ist, setzt sie das Zeichenflag, konfiguriert die Schriftart mit „FontSet“, normalisiert den Winkel durch Addition von 90 Grad und Konvertierung über „DegreesToRadians“ und „NormalizeRadians“, die x- und y-Positionen mit Cosinus und Sinus, weist Gehäusefarbe als Hintergrund zu und zentrierte den Text mit „TextOut“ unter Verwendung von ARGB-Farbe und Ausrichtungsflags. Für die Skalenmarkierungen und -bereiche gilt die folgende Logik.

//+------------------------------------------------------------------+
//| Redraw Scale Marks                                               |
//+------------------------------------------------------------------+
void CGaugeBase::RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap) {
   int majorIndex, mediumIndex, minorIndex;           //--- Declare indices
   double angle = 0, mediumAngle = 0, minorAngle = 0; //--- Declare angles
   scaleLayer.scaleMarks.multiplier = scaleMultipliers[inputParams.scaleMultiplier]; //--- Set multiplier
   if(scaleLayer.scaleMarks.multiplier <= 0)          //--- Check multiplier
      scaleLayer.scaleMarks.multiplier = 1.0;         //--- Set default multiplier
   scaleLayer.scaleMarks.minValue = inputParams.minScaleValue; //--- Set min value
   scaleLayer.scaleMarks.maxValue = inputParams.maxScaleValue; //--- Set max value
   scaleLayer.scaleMarks.decimalPlaces = 0;           //--- Set decimal places
   if(scaleLayer.scaleMarks.maxValue > scaleLayer.scaleMarks.minValue) { //--- Check direct order
      scaleLayer.scaleMarks.forwardDirection = true;  //--- Set forward direction
      scaleLayer.scaleMarks.valueRange = scaleLayer.scaleMarks.maxValue - scaleLayer.scaleMarks.minValue; //--- Set value range
   } else {                                           //--- Handle inverse order
      scaleLayer.scaleMarks.forwardDirection = false; //--- Set forward direction false
      scaleLayer.scaleMarks.valueRange = scaleLayer.scaleMarks.minValue - scaleLayer.scaleMarks.maxValue; //--- Set value range
   }
   scaleLayer.scaleMarks.nullMarkPosition = 1;         //--- Set null mark position
   scaleLayer.scaleMarks.minAngle = scaleArc.endAngle; //--- Set min angle
   scaleLayer.scaleMarks.maxAngle = scaleArc.startAngle; //--- Set max angle
   if(scaleArc.endAngle > scaleArc.startAngle)         //--- Check angles
      scaleLayer.scaleMarks.angleRange = NormalizeRadians(scaleArc.endAngle - scaleArc.startAngle); //--- Set angle range
   else                                                //--- Handle wrap around
      scaleLayer.scaleMarks.angleRange = NormalizeRadians(scaleArc.endAngle + (2 * M_PI - scaleArc.startAngle)); //--- Set angle range
   int leftMarkCount = 0;                              //--- Initialize left mark count
   int rightMarkCount = 0;                             //--- Initialize right mark count
   double markBuffer[361][2];                          //--- Declare mark buffer
   int bufferCenterIndex = (int)(361 / 2);             //--- Set buffer center index
   double tempValue = 0;                               //--- Initialize temp value
   int sign = 0;                                       //--- Initialize sign
   double multiplier = scaleMultipliers[inputParams.scaleMultiplier]; //--- Set multiplier
   markBuffer[bufferCenterIndex][0] = 0;               //--- Set zero value
   markBuffer[bufferCenterIndex][1] = scaleLayer.scaleMarks.minAngle; //--- Set zero angle
   tempValue = 0;                                      //--- Reset temp value
   sign = scaleLayer.scaleMarks.forwardDirection ? 1 : -1; //--- Set sign
   for(majorIndex = 1; majorIndex < (int)(361 / 2); majorIndex++) { //--- Loop major indices
      tempValue = majorIndex * inputParams.majorTickInterval; //--- Calculate temp value
      if(tempValue <= scaleLayer.scaleMarks.valueRange) { //--- Check range
         markBuffer[bufferCenterIndex + majorIndex][0] = (majorIndex * inputParams.majorTickInterval * sign) / multiplier; //--- Set mark value
         markBuffer[bufferCenterIndex + majorIndex][1] = NormalizeRadians(scaleLayer.scaleMarks.minAngle - ((majorIndex * inputParams.majorTickInterval * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set mark angle
         rightMarkCount++;                             //--- Increment right count
      } else                                           //--- Handle out of range
         break;                                        //--- Break loop
   }
   double majorAngleStep, mediumAngleStep, minorAngleStep; //--- Declare angle steps
   majorAngleStep = (inputParams.majorTickInterval * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set major step
   mediumAngleStep = 0;                                //--- Initialize medium step
   if(inputParams.mediumTicksPerMajor != 0)            //--- Check medium ticks
      mediumAngleStep = ((inputParams.majorTickInterval / (inputParams.mediumTicksPerMajor + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set medium step
   minorAngleStep = 0;                                 //--- Initialize minor step
   if(inputParams.minorTicksPerInterval != 0) {        //--- Check minor ticks
      if(mediumAngleStep != 0)                         //--- Check medium step
         minorAngleStep = (((inputParams.majorTickInterval / (inputParams.mediumTicksPerMajor + 1)) / (inputParams.minorTicksPerInterval + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set minor step with medium
      else                                             //--- Handle no medium
         minorAngleStep = ((inputParams.majorTickInterval / (inputParams.minorTicksPerInterval + 1)) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange; //--- Set minor step without medium
   }
   CalculateRanges(borderGap);             //--- Calculate ranges
   DrawRanges();                           //--- Draw ranges
   int innerR, outerR;                     //--- Declare radii
   double startX, startY, endX, endY;      //--- Declare coordinates
   int textX, textY;                       //--- Declare text coordinates
   string markText;                        //--- Declare mark text
   int digits;                             //--- Declare digits
   scaleLayer.obj_Canvas.FontSet(scaleLayer.scaleMarks.tickFontName, scaleLayer.scaleMarks.tickFontSize, scaleLayer.scaleMarks.tickFontFlags, 0); //--- Set font
   rightMarkCount++;                       //--- Increment right count
   for(majorIndex = 0; majorIndex < rightMarkCount; majorIndex++) { //--- Loop right marks
      angle = markBuffer[bufferCenterIndex + majorIndex][1];        //--- Get angle
      CalculateInnerOuterRadii(innerR, outerR, (int)scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
      startX = scaleArc.centerX - innerR * MathCos(M_PI - angle);   //--- Set start X
      startY = scaleArc.centerY - innerR * MathSin(M_PI - angle);   //--- Set start Y
      endX = scaleArc.centerX - outerR * MathCos(M_PI - angle);     //--- Set end X
      endY = scaleArc.centerY - outerR * MathSin(M_PI - angle);     //--- Set end Y
      textX = (int)(scaleArc.centerX - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathCos(M_PI - angle)); //--- Set text X
      textY = (int)(scaleArc.centerY - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathSin(M_PI - angle)); //--- Set text Y
      scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
      digits = (markBuffer[bufferCenterIndex + majorIndex][0] == 0) ? 0 : scaleLayer.scaleMarks.decimalPlaces; //--- Set digits
      markText = DoubleToString(markBuffer[bufferCenterIndex + majorIndex][0], digits); //--- Get mark text
      scaleLayer.obj_Canvas.TextOut(textX, textY, markText, ColorToARGB(inputParams.tickFontColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
      if(mediumAngleStep != 0) {           //--- Check medium step
         mediumAngle = angle;              //--- Set medium angle
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(mediumAngle - minorAngleStep * minorIndex);         //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))        //--- Draw minor tick
               break;                      //--- Break if failed
         }
         for(mediumIndex = 1; mediumIndex <= inputParams.mediumTicksPerMajor; mediumIndex++) { //--- Loop medium
            mediumAngle = NormalizeRadians(angle - mediumAngleStep * mediumIndex);            //--- Calculate medium angle
            if(!DrawTick(mediumAngle, scaleLayer.scaleMarks.mediumTickLength, scaleArc))      //--- Draw medium tick
               break;                      //--- Break if failed
            for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
               minorAngle = NormalizeRadians(mediumAngle - minorAngleStep * minorIndex);      //--- Calculate minor angle
               if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))     //--- Draw minor tick
                  break;                   //--- Break if failed
            }
         }
      } else {                             //--- Handle no medium
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(angle - minorAngleStep * minorIndex); //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc)) //--- Draw minor tick
               break;                      //--- Break if failed
         }
      }
   }
   for(majorIndex = 0; majorIndex < (leftMarkCount + 1); majorIndex++) { //--- Loop left marks
      angle = markBuffer[bufferCenterIndex - majorIndex][1];      //--- Get angle
      CalculateInnerOuterRadii(innerR, outerR, (int)scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
      startX = scaleArc.centerX - innerR * MathCos(M_PI - angle); //--- Set start X
      startY = scaleArc.centerY - innerR * MathSin(M_PI - angle); //--- Set start Y
      endX = scaleArc.centerX - outerR * MathCos(M_PI - angle);   //--- Set end X
      endY = scaleArc.centerY - outerR * MathSin(M_PI - angle);   //--- Set end Y
      textX = (int)(scaleArc.centerX - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathCos(M_PI - angle));  //--- Set text X
      textY = (int)(scaleArc.centerY - (outerR - scaleLayer.scaleMarks.tickFontGap) * MathSin(M_PI - angle));  //--- Set text Y
      digits = (markBuffer[bufferCenterIndex - majorIndex][0] == 0) ? 0 : scaleLayer.scaleMarks.decimalPlaces; //--- Set digits
      markText = DoubleToString(markBuffer[bufferCenterIndex - majorIndex][0], digits);        //--- Get mark text
      if(majorIndex > 0 || (majorIndex == 0 && scaleLayer.scaleMarks.nullMarkPosition == 3)) { //--- Check condition
         scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
         scaleLayer.obj_Canvas.TextOut(textX, textY, markText, ColorToARGB(inputParams.tickFontColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
      }
      if(mediumAngleStep != 0) {           //--- Check medium step
         mediumAngle = angle;              //--- Set medium angle
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
            minorAngle = NormalizeRadians(mediumAngle + minorAngleStep * minorIndex);         //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))        //--- Draw minor tick
               break;                      //--- Break if failed
         }
         for(mediumIndex = 1; mediumIndex <= inputParams.mediumTicksPerMajor; mediumIndex++) { //--- Loop medium
            mediumAngle = NormalizeRadians(angle + mediumAngleStep * mediumIndex);             //--- Calculate medium angle
            if(!DrawTick(mediumAngle, scaleLayer.scaleMarks.mediumTickLength, scaleArc))       //--- Draw medium tick
               break;                      //--- Break if failed
            for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) { //--- Loop minor
               minorAngle = NormalizeRadians(mediumAngle + minorAngleStep * minorIndex);       //--- Calculate minor angle
               if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))      //--- Draw minor tick
                  break;                   //--- Break if failed
            }
         }
      } else {                             //--- Handle no medium
         for(minorIndex = 1; minorIndex <= inputParams.minorTicksPerInterval; minorIndex++) {  //--- Loop minor
            minorAngle = NormalizeRadians(angle + minorAngleStep * minorIndex);                //--- Calculate minor angle
            if(!DrawTick(minorAngle, scaleLayer.scaleMarks.minorTickLength, scaleArc))         //--- Draw minor tick
               break;                      //--- Break if failed
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate Ranges                                                 |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateRanges(int borderGap) {
   int innerR, outerR;                     //--- Declare radii
   CalculateInnerOuterRadii(innerR, outerR, scaleLayer.scaleArc.radius, scaleLayer.scaleMarks.majorTickLength, inputParams.tickStyle); //--- Calculate radii
   for(int rangeIndex = 0; rangeIndex < 4; rangeIndex++) { //--- Loop ranges
      if(IsValidRange(rangeIndex))         //--- Check valid range
         CalculateRangePie(scaleLayer.ranges[rangeIndex], innerR, borderGap, outerR, inputParams.ranges[rangeIndex].start, inputParams.ranges[rangeIndex].end, inputParams.ranges[rangeIndex].clr, scaleLayer.caseColor); //--- Calculate pie
   }
}

//+------------------------------------------------------------------+
//| Check Valid Range                                                |
//+------------------------------------------------------------------+
bool CGaugeBase::IsValidRange(int index) {
   if(!inputParams.ranges[index].enable)   //--- Check enable
      return false;                        //--- Return false
   if(inputParams.ranges[index].start == inputParams.ranges[index].end) //--- Check start end
      return false;                        //--- Return false
   double paramMin, paramMax, rangeMin, rangeMax; //--- Declare mins maxs
   NormalizeRangeValues(paramMin, paramMax, inputParams.minScaleValue, inputParams.maxScaleValue); //--- Normalize param
   NormalizeRangeValues(rangeMin, rangeMax, inputParams.ranges[index].start, inputParams.ranges[index].end); //--- Normalize range
   if(rangeMin < paramMin && rangeMax < paramMin) //--- Check below param
      return false;                        //--- Return false
   if(rangeMin > paramMax && rangeMax > paramMax) //--- Check above param
      return false;                        //--- Return false
   if(rangeMin < paramMin)                 //--- Check min
      rangeMin = paramMin;                 //--- Clamp min
   if(rangeMax > paramMax)                 //--- Check max
      rangeMax = paramMax;                 //--- Clamp max
   inputParams.ranges[index].start = rangeMin; //--- Set start
   inputParams.ranges[index].end = rangeMax;   //--- Set end
   return true;                                //--- Return true
}

//+------------------------------------------------------------------+
//| Normalize Range Values                                           |
//+------------------------------------------------------------------+
void CGaugeBase::NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1) {
   if(val0 < val1) {                       //--- Check val0 < val1
      minValue = val0;                     //--- Set min
      maxValue = val1;                     //--- Set max
   } else {                                //--- Handle val0 >= val1
      minValue = val1;                     //--- Set min
      maxValue = val0;                     //--- Set max
   }
}

//+------------------------------------------------------------------+
//| Calculate Range Pie                                              |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr) {
   range.startValue = rangeStart;          //--- Set start value
   range.endValue = rangeEnd;              //--- Set end value
   double rangeStartNorm, rangeEndNorm;    //--- Declare norms
   if(range.startValue > range.endValue) { //--- Check start > end
      rangeStartNorm = range.startValue;   //--- Set start norm
      rangeEndNorm = range.endValue;       //--- Set end norm
   } else if(range.startValue < range.endValue) { //--- Check start < end
      rangeEndNorm = range.startValue;     //--- Set end norm
      rangeStartNorm = range.endValue;     //--- Set start norm
   } else                                  //--- Handle equal
      return;                              //--- Return
   if(scaleLayer.scaleMarks.minValue > scaleLayer.scaleMarks.maxValue) { //--- Check inverse
      double temp = rangeStartNorm;        //--- Temp start
      rangeStartNorm = -rangeEndNorm;      //--- Set start norm
      rangeEndNorm = -temp;                //--- Set end norm
   }
   range.active = true;                    //--- Set active
   range.clr = rangeClr;                   //--- Set color
   range.pie.centerX = scaleLayer.scaleArc.centerX; //--- Set center X
   range.pie.centerY = scaleLayer.scaleArc.centerY; //--- Set center Y
   range.pie.radius = innerRadius;         //--- Set radius
   range.pie.eraseRadius = outerRadius;    //--- Set erase radius
   double angularOffset = MathArcsin(((double)radialGap / (double)range.pie.radius) / 2.0); //--- Calculate offset
   if(scaleLayer.scaleMarks.minValue < scaleLayer.scaleMarks.maxValue) { //--- Check direct
      range.pie.startAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set start angle
      range.pie.endAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set end angle
      range.pie.eraseStartAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) - angularOffset); //--- Set erase start
      range.pie.eraseEndAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm - scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) + angularOffset); //--- Set erase end
   } else {                                //--- Handle inverse
      range.pie.startAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((scaleLayer.scaleMarks.minValue + rangeStartNorm) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set start angle
      range.pie.endAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((scaleLayer.scaleMarks.minValue + rangeEndNorm) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange)); //--- Set end angle
      range.pie.eraseStartAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeStartNorm + scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) - angularOffset); //--- Set erase start
      range.pie.eraseEndAngle = NormalizeRadians(scaleLayer.scaleMarks.minAngle - (((rangeEndNorm + scaleLayer.scaleMarks.minValue) * scaleLayer.scaleMarks.angleRange) / scaleLayer.scaleMarks.valueRange) + angularOffset); //--- Set erase end
   }
   range.pie.clr = rangeClr;               //--- Set pie color
   range.pie.eraseClr = caseClr;           //--- Set erase color
}

//+------------------------------------------------------------------+
//| Draw Ranges                                                      |
//+------------------------------------------------------------------+
void CGaugeBase::DrawRanges() {
   for(int i = 0; i < 4; i++)              //--- Loop indices
      DrawRange(scaleLayer.ranges[i]);     //--- Draw range
}

//+------------------------------------------------------------------+
//| Draw Range                                                       |
//+------------------------------------------------------------------+
void CGaugeBase::DrawRange(Struct_Range &range) {
   if(!range.active)                       //--- Check active
      return;                              //--- Return
   int r_min = MathMin(range.pie.radius, range.pie.eraseRadius); //--- Get min r
   int r_max = MathMax(range.pie.radius, range.pie.eraseRadius); //--- Get max r
   for(int r = r_min + 1; r <= r_max; r++) { //--- Loop radii
      double frac = (double)(r - r_min) / (r_max - r_min); //--- Calculate frac
      uchar alpha = (uchar)(scaleLayer.transparency * frac); //--- Calculate alpha
      uint col = ColorToARGB(range.pie.clr, alpha); //--- Get color
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.startAngle, range.pie.endAngle, col); //--- Draw arc
      uint erase_col = ColorToARGB(range.pie.eraseClr, scaleLayer.transparency); //--- Get erase color
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.eraseStartAngle, range.pie.startAngle, erase_col); //--- Draw left erase
      scaleLayer.obj_Canvas.Arc(range.pie.centerX, range.pie.centerY, r, r, range.pie.endAngle, range.pie.eraseEndAngle, erase_col); //--- Draw right erase
   }
}

//+------------------------------------------------------------------+
//| Calculate Inner Outer Radii                                      |
//+------------------------------------------------------------------+
void CGaugeBase::CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle) {
   innerRadius = baseRadius;               //--- Set inner radius
   outerRadius = baseRadius - tickLength;  //--- Set outer radius
}

//+------------------------------------------------------------------+
//| Draw Tick                                                        |
//+------------------------------------------------------------------+
bool CGaugeBase::DrawTick(double angle, int length, Struct_Arc &scaleArc) {
   int innerR, outerR;                     //--- Declare radii
   double startX, startY, endX, endY;      //--- Declare coordinates
   double arcStartAngle = scaleArc.startAngle; //--- Get start angle
   double arcEndAngle = scaleArc.endAngle; //--- Get end angle
   double deltaToStart = CalculateAngleDelta(arcStartAngle, angle, -1); //--- Calculate delta to start
   double deltaToEnd = CalculateAngleDelta(arcEndAngle, angle, 1); //--- Calculate delta to end
   double totalArcDelta = CalculateAngleDelta(arcStartAngle, arcEndAngle, -1); //--- Calculate total delta
   if(MathAbs(totalArcDelta - (deltaToEnd + deltaToStart)) < (M_PI / 180.0)) //--- Check within arc
      return false;                        //--- Return false
   CalculateInnerOuterRadii(innerR, outerR, scaleArc.radius, length, inputParams.tickStyle); //--- Calculate radii
   startX = scaleArc.centerX - innerR * MathCos(M_PI - angle); //--- Set start X
   startY = scaleArc.centerY - innerR * MathSin(M_PI - angle); //--- Set start Y
   endX = scaleArc.centerX - outerR * MathCos(M_PI - angle); //--- Set end X
   endY = scaleArc.centerY - outerR * MathSin(M_PI - angle); //--- Set end Y
   scaleLayer.obj_Canvas.LineAA((int)startX, (int)startY, (int)endX, (int)endY, ColorToARGB(scaleArc.clr, scaleLayer.transparency)); //--- Draw line AA
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Calculate Angle Delta                                            |
//+------------------------------------------------------------------+
double CGaugeBase::CalculateAngleDelta(double angle1, double angle2, int direction) {
   double normAngle1 = NormalizeRadians(angle1); //--- Normalize angle1
   double normAngle2 = NormalizeRadians(angle2); //--- Normalize angle2
   double delta1, delta2;                  //--- Declare deltas
   if(normAngle1 == normAngle2)            //--- Check equal
      return 0;                            //--- Return 0
   if(normAngle1 > normAngle2) {           //--- Check angle1 > angle2
      delta1 = normAngle1 - normAngle2;    //--- Set delta1
      delta2 = normAngle2 + (2 * M_PI - normAngle1); //--- Set delta2
   } else {                                //--- Handle angle1 <= angle2
      delta1 = normAngle1 + (2 * M_PI - normAngle2); //--- Set delta1
      delta2 = normAngle2 - normAngle1;    //--- Set delta2
   }
   if(direction < 0)                       //--- Check direction
      return delta1;                       //--- Return delta1
   return delta2;                          //--- Return delta2
}

//+------------------------------------------------------------------+
//| Set Legend String Params                                         |
//+------------------------------------------------------------------+
void CGaugeBase::SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams &param, int minRadius, int radiusDelta) {
   if(param.enable && param.fontName != "") { //--- Check enable and font
      legendString.text = param.text;      //--- Set text
      legendString.angle = param.angle;    //--- Set angle
      legendString.radius = minRadius + (int)((radiusDelta * param.radius * 10) / 100.0); //--- Set radius
      legendString.fontName = param.fontName; //--- Set font name
      legendString.fontFlags = 0;          //--- Initialize flags
      if(param.italic)                     //--- Check italic
         legendString.fontFlags |= FONT_ITALIC; //--- Add italic
      if(param.bold)                       //--- Check bold
         legendString.fontFlags |= FW_BOLD; //--- Add bold
      legendString.fontSize = (int)(((param.fontSize + 2) * radiusDelta) / 64); //--- Set font size
      legendString.textColor = param.textColor; //--- Set text color
   }
}

//+------------------------------------------------------------------+
//| Delete Gauge                                                     |
//+------------------------------------------------------------------+
void CGaugeBase::Delete() {
   ObjectDelete(0, scaleLayer.objectName); //--- Delete scale object
   ObjectDelete(0, needleLayer.objectName);//--- Delete needle object
}

//+------------------------------------------------------------------+
//| Set New Value                                                    |
//+------------------------------------------------------------------+
void CGaugeBase::NewValue(double value) {
   if(!initializationComplete)             //--- Check initialization
      return;                              //--- Return
   currentValue = value;                   //--- Set current value
   if(scaleLayer.gaugeLabel.value.draw)    //--- Check draw
      RedrawValueDisplay(currentValue);    //--- Redraw value
   RedrawNeedle(currentValue);             //--- Redraw needle
   needleLayer.obj_Canvas.Update(true);    //--- Update canvas
}

//+------------------------------------------------------------------+
//| Get Label Area Size                                              |
//+------------------------------------------------------------------+
bool CGaugeBase::GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString) {
   if(!scaleLayer.obj_Canvas.FontSet(legendString.fontName, legendString.fontSize, legendString.fontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.TextSize(legendString.text, areaSize.width, areaSize.height); //--- Get text size
   if(areaSize.width == 0 || areaSize.height == 0) //--- Check size
      return false;                        //--- Return false
   areaSize.diagonal = (int)MathCeil(MathSqrt((double)(areaSize.width * areaSize.width + areaSize.height * areaSize.height))); //--- Calculate diagonal
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Erase Legend String                                              |
//+------------------------------------------------------------------+
bool CGaugeBase::EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr) {
   Struct_LabelAreaSize areaSize;          //--- Declare area size
   if(!GetLabelAreaSize(areaSize, legendString)) //--- Get area size
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.FillRectangle((int)legendString.x - (areaSize.width / 2) - 4, (int)legendString.y - (areaSize.height / 2), (int)legendString.x + (areaSize.width / 2) + 4, (int)legendString.y + (areaSize.height / 2), ColorToARGB(eraseClr, scaleLayer.transparency)); //--- Fill rectangle
   return true;                            //--- Return true
}

//+------------------------------------------------------------------+
//| Redraw Value Display                                             |
//+------------------------------------------------------------------+
bool CGaugeBase::RedrawValueDisplay(double value) {
   if(StringLen(scaleLayer.gaugeLabel.value.text) > 0) { //--- Check text length
      if(!EraseLegendString(scaleLayer.gaugeLabel.value, scaleLayer.gaugeLabel.value.backgroundColor)) //--- Erase string
         return false;                     //--- Return false
   }
   scaleLayer.gaugeLabel.value.text = DoubleToString(value, (int)scaleLayer.gaugeLabel.value.decimalPlaces); //--- Set text
   if(!scaleLayer.obj_Canvas.FontSet(scaleLayer.gaugeLabel.value.fontName, scaleLayer.gaugeLabel.value.fontSize, scaleLayer.gaugeLabel.value.fontFlags, 0)) //--- Set font
      return false;                        //--- Return false
   scaleLayer.obj_Canvas.TextOut(scaleLayer.gaugeLabel.value.x, scaleLayer.gaugeLabel.value.y, scaleLayer.gaugeLabel.value.text, ColorToARGB(scaleLayer.gaugeLabel.value.textColor, scaleLayer.transparency), TA_CENTER | TA_VCENTER); //--- Draw text
   scaleLayer.obj_Canvas.Update(true);     //--- Update canvas
   return true;                            //--- Return true
}

Zunächst implementieren wir die Methode „RedrawScaleMarks“, um die Skalenstriche und Beschriftungen der Skala neu zu generieren. Wir deklarieren Indizes und Winkel, setzen den Multiplikator aus einem vordefinierten Array oder standardmäßig auf 1, wenn er ungültig ist, weisen Minimal- und Maximalwerte aus den Eingaben zu und legen die Dezimalstellen als 0 fest. Wir prüfen, ob die Skala aufsteigend ist, um die Vorwärtsrichtung festzulegen, und berechnen den Wertebereich entsprechend. Wir setzen die Position der Nullmarke auf 1 fest, weisen dem Skalenbogen Minimal- und Maximalwinkel zu (wobei wir Anfang und Ende vertauschen) und berechnen den Winkelbereich mit „NormalizeRadians“, wobei wir bei Bedarf eine Umkehrung vornehmen. Wir initialisieren die Anzahl der linken und rechten Markierungen auf 0, erstellen ein Pufferfeld mit 361 Einträgen für die Markierungen, zentrieren es bei Index 180 und setzen den Wert und den Winkel der Nullmarkierung.

Wir bereiten ein Vorzeichen vor, das auf der Richtung basiert, und führen eine Schleife durch, um die Hauptmarkierungen auf der rechten Seite zu füllen, wobei wir die Werte, die durch Multiplikator und Winkel angepasst werden, proportional berechnen und die Anzahl erhöhen, bis der Bereich überschritten wird. Wir berechnen die Winkelschritte für Major, Medium (wenn pro Major eingestellt ist), und kleinere (Minor) Skalenstriche (Anpassung an das Vorhandensein von Medium). Wir rufen „CalculateRanges“ und „DrawRanges“ auf, um zuerst die Farbzonen zu behandeln.

Wir setzen die Schriftart auf der Leinwand mit „FontSet“ und erhöhen die richtige Anzahl. Für rechte Markierungen werden in einer Schleife jeder Winkel ermittelt, innere und äußere Radien für die großen Skalenstriche berechnet, Start-, End- und Textpositionen mit MathCos und MathSin mit Pi-Anpassung berechnet, die Linien der Skalenstriche mit „LineAA“ in ARGB-Farbe gezeichnet, Ziffern bestimmt (0 für Nullwert), der Markierungswert mit DoubleToString in eine Zeichenfolge umgewandelt und die Beschriftung mit „TextOut“ zentriert gezeichnet. Wenn es mittlere Schritte gibt, werden vor jedem mittleren Schritt kleine Skalenstriche gezeichnet, dann mittlere Skalenstriche und danach kleine Skalenstriche; andernfalls werden nur kleine Skalenstriche gezeichnet, wobei „DrawSkalenstrich“ verwendet und bei einem Fehler abgebrochen wird. Für linke Markierungen (obwohl die Anzahl hier 0 ist, ist die Logik symmetrisch), machen wir eine ähnliche Schleife, passen aber die Winkel positiv an und fügen eine Bedingung ein, dass nur gezeichnet wird, wenn die Nullmarkierung nicht die Position 3 ist, wobei minor/medium in die entgegengesetzte Richtung gezeichnet wird.

Wir definieren die Methode „CalculateRanges“, um Farbzonen vorzubereiten, innere und äußere Radien für die wichtigsten Skalenstriche zu berechnen, dann in einer Schleife durch vier Bereiche zu gehen und „CalculateRangePie“ aufzurufen, wenn „IsValidRange“ den Wert „true“ zurückgibt, wobei die angepassten Parameter, einschließlich des Randabstands als radialer Abstand, übergeben werden. Die anderen Methoden sind einfach zu handhaben. Wir haben der Klarheit halber Kommentare hinzugefügt. Schließlich müssen wir nur noch diese Methoden aufrufen, um die schwere Arbeit zu erledigen, wie unten beschrieben.

//+------------------------------------------------------------------+
//| Redraw Gauge                                                     |
//+------------------------------------------------------------------+
void CGaugeBase::Redraw() {
   Draw();                                 //--- Call draw
   initializationComplete = true;          //--- Set initialization complete
}

//+------------------------------------------------------------------+
//| Draw Scale and Needle                                            |
//+------------------------------------------------------------------+
void CGaugeBase::Draw() {
   double diameter = m_radius * 2.0;       //--- Calculate diameter
   scaleLayer.scaleMarks.majorTickLength = (int)((diameter * 10.0) / 100.0); //--- Set major tick length
   scaleLayer.scaleMarks.mediumTickLength = (int)((diameter * 7.5) / 100.0); //--- Set medium tick length
   scaleLayer.scaleMarks.minorTickLength = (int)((diameter * 5.0) / 100.0); //--- Set minor tick length
   scaleLayer.scaleMarks.tickFontName = inputParams.tickFontName; //--- Set tick font name
   scaleLayer.scaleMarks.tickFontFlags = 0; //--- Initialize tick font flags
   if(inputParams.tickFontItalic)          //--- Check italic flag
      scaleLayer.scaleMarks.tickFontFlags |= FONT_ITALIC; //--- Add italic flag
   if(inputParams.tickFontBold)            //--- Check bold flag
      scaleLayer.scaleMarks.tickFontFlags |= FW_BOLD; //--- Add bold flag
   scaleLayer.scaleMarks.tickFontSize = (int)((diameter * 6.5) / 100.0); //--- Set tick font size
   scaleLayer.scaleMarks.tickFontGap = GetTickFontGap(scaleLayer.scaleMarks, 3); //--- Set tick font gap
   scaleLayer.externalLabelArea = 0;       //--- Set external label area
   scaleLayer.internalLabelArea = 0;       //--- Set internal label area
   GetTickLabelAreaSize(scaleLayer.internalLabelArea, scaleLayer.scaleMarks, 3); //--- Get tick label area size
   scaleLayer.borderSize = (int)((diameter * 2) / 100.0); //--- Set border size
   scaleLayer.borderGap = (int)((diameter * 3.0) / 100.0); //--- Set border gap
   scaleLayer.externalScaleGap = 0;        //--- Set external scale gap
   scaleLayer.internalScaleGap = scaleLayer.scaleMarks.majorTickLength; //--- Set internal scale gap
   if(inputParams.scaleAngleRange < 30)    //--- Check min angle range
      inputParams.scaleAngleRange = 30;    //--- Set min angle range
   if(inputParams.scaleAngleRange > 320)   //--- Check max angle range
      inputParams.scaleAngleRange = 320;   //--- Set max angle range
   int halfAngleRange = inputParams.scaleAngleRange / 2; //--- Calculate half angle range
   int startAngle = 90 + halfAngleRange + inputParams.rotationAngle; //--- Calculate start angle
   int endAngle = 90 - halfAngleRange + inputParams.rotationAngle; //--- Calculate end angle
   scaleLayer.scaleArc.centerX = m_radius + 5; //--- Set scale arc center X
   scaleLayer.scaleArc.centerY = m_radius + 5; //--- Set scale arc center Y
   scaleLayer.scaleArc.radius = m_radius - (scaleLayer.borderSize + scaleLayer.borderGap + scaleLayer.externalLabelArea + scaleLayer.externalScaleGap); //--- Set scale arc radius
   scaleLayer.scaleArc.startAngle = NormalizeRadians(DegreesToRadians(endAngle)); //--- Set start angle
   scaleLayer.scaleArc.endAngle = NormalizeRadians(DegreesToRadians(startAngle) - 0.0001); //--- Set end angle
   scaleLayer.scaleArc.clr = inputParams.scaleColor; //--- Set scale arc color
   needleLayer.needleCenter.radius = (int)((diameter * 5) / 100.0); //--- Set needle center radius
   needleLayer.needleCenter.display = true; //--- Set display flag
   needleLayer.needleCenter.centerX = scaleLayer.scaleArc.centerX; //--- Set needle center X
   needleLayer.needleCenter.centerY = scaleLayer.scaleArc.centerY; //--- Set needle center Y
   needleLayer.needleCenter.clr = inputParams.needleCenterColor; //--- Set needle center color
   int maxLegendRadius = m_radius - (scaleLayer.borderSize + scaleLayer.borderGap); //--- Calculate max legend radius
   int minLegendRadius = needleLayer.needleCenter.radius; //--- Set min legend radius
   int legendRadiusDelta = maxLegendRadius - minLegendRadius; //--- Calculate legend radius delta
   SetLegendStringParams(scaleLayer.gaugeLabel.description, inputParams.description, minLegendRadius, legendRadiusDelta); //--- Set description params
   SetLegendStringParams(scaleLayer.gaugeLabel.units, inputParams.units, minLegendRadius, legendRadiusDelta); //--- Set units params
   SetLegendStringParams(scaleLayer.gaugeLabel.multiplier, inputParams.multiplier, minLegendRadius, legendRadiusDelta); //--- Set multiplier params
   SetLegendStringParams(scaleLayer.gaugeLabel.value, inputParams.value, minLegendRadius, legendRadiusDelta); //--- Set value params
   CalculateCaseElements(scaleLayer.externalCase, scaleLayer.internalCase, scaleLayer.borderSize, scaleLayer.borderGap); //--- Calculate case elements
   scaleLayer.caseColor = inputParams.caseColor; //--- Set case color
   DrawCaseElements(scaleLayer.externalCase, scaleLayer.internalCase); //--- Draw case elements
   if(inputParams.displayScaleArc)         //--- Check display scale arc
      scaleLayer.obj_Canvas.Arc(scaleLayer.scaleArc.centerX, scaleLayer.scaleArc.centerY, scaleLayer.scaleArc.radius, scaleLayer.scaleArc.radius, scaleLayer.scaleArc.startAngle, scaleLayer.scaleArc.endAngle, ColorToARGB(scaleLayer.scaleArc.clr, scaleLayer.transparency)); //--- Draw scale arc
   RedrawScaleMarks(scaleLayer.internalCase, scaleLayer.scaleArc, scaleLayer.borderGap); //--- Redraw scale marks
   CalculateAndDrawLegends();              //--- Calculate and draw legends
   CalculateNeedle();                      //--- Calculate needle
   scaleLayer.obj_Canvas.Update(true);     //--- Update scale canvas
   needleLayer.obj_Canvas.Update(true);    //--- Update needle canvas
}

Wir implementieren die Methode „Redraw“ in der Klasse „CGaugeBase“, um die Visualisierung der Messuhr zu aktualisieren, indem wir die „Draw“-Methode aufrufen und dann das Flag „initializationComplete“ auf true setzen, was anzeigt, dass die Einrichtung abgeschlossen ist. Anschließend definieren wir die Methode „Draw“, um das vollständige Rendering der Skalen- und Nadelebenen zu übernehmen.

Wir berechnen den Durchmesser als das Zweifache des Radius und legen dann die Länge der Skalenstriche proportional fest: Major bei 10% des Durchmessers, Medium bei 7,5% und Minor bei 5%. Wir weisen den Namen der Skalenstrich-Schriftart aus den Eingaben zu, initialisieren die Schrift-Flags auf 0 und fügen FONT_ITALIC oder „FW_BOLD“ hinzu, wenn die entsprechenden Flags gesetzt sind. Wir skalieren die Skalenstrich-Schriftgröße auf 6,5 % des Durchmessers und berechnen den Schriftabstand mit „GetTickFontGap“ bei einer Stringlänge von 3. Wir setzen die externen und internen Labelbereiche auf 0 zurück und aktualisieren dann den internen Bereich mit „GetTickLabelAreaSize“. Wir setzen die Randgröße auf 2 % und den Abstand auf 3 % des Durchmessers, den äußeren Skalenabstand auf 0 und den inneren auf die Skalenstrichlänge. Wir begrenzen den Skalenwinkelbereich zwischen 30 und 320 Grad ein. Wenn er außerhalb der Grenzen liegt, berechnen wir die Hälfte des Bereichs und leiten Start- und Endwinkel ab, die um 90 Grad plus Drehung zentriert sind.

Wir positionieren den Mittelpunkt des Skalenbogens bei Radius plus 5 für x und y, berechnen seinen Radius, indem wir Rand, Lücke, externe Beschriftung und Skalenlücke vom Hauptradius subtrahieren, setzen Start- und Endwinkel in normalisiertem Bogenmaß über „NormalizeRadians“ und „DegreesToRadians“ (mit einer kleinen Anpassung am Ende) und weisen die Skalenfarbe zu. Für die Nadelmitte setzen wir ihren Radius auf 5 % des Durchmessers, aktivieren die Anzeige, passen ihre Mitte an den Skalenbogen an und wenden die Farbe der eingegebenen Mitte an. Wir bestimmen den maximalen Legendenradius, indem wir Rand und Lücke vom Hauptradius abziehen, min als den Radius der Nadelmitte und delta als ihre Differenz, und konfigurieren dann jede Legendenzeichenfolge (Beschreibung, Einheiten, Multiplikator, Wert) mit „SetLegendStringParams“ unter Verwendung dieser Radien.

Wir bereiten Case-Elemente mit „CalculateCaseElements“ vor, wobei wir Randgröße und Abstand übergeben, die Case-Farbe einstellen und sie mit „DrawCaseElements“ darstellen. Wenn die Anzeige des Skalenbogens aktiviert ist, zeichnen wir ihn auf der Leinwand mit „Bogen“ in ARGB-Farbe. Wir zeichnen die Markierungen mit „RedrawScaleMarks“ neu, wobei wir den internen Fall, den Skalenbogen und den Randabstand übergeben, berechnen und zeichnen die Legenden mit „CalculateAndDrawLegends“, bereiten die Nadel mit „CalculateNeedle“ vor und aktualisieren sowohl die Skalen- als auch die Nadelleinwände mit „Update“, das auf true gesetzt wird. Damit ist unsere Klasse vollständig, und wir können sie verwenden, um die Eigenschaften nach Bedarf einzustellen, indem wir die entsprechenden Methoden aufrufen, aber zuerst müssen wir die Skalenmultiplikatoren und String-Arrays im globalen Bereich wie unten beschrieben einstellen.

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

Wir definieren das Array „scaleMultipliers“ als ein globales Double-Array mit 9 Elementen, die Skalierungsfaktoren enthalten: 10.000, 1.000, 100, 10, 1, 0,1, 0,01, 0,001 und 0,0001, zum Einstellen der Markierungswerte auf der Messskala. Außerdem definieren wir das Array „scaleMultiplierStrings“ als globales String-Array mit 9 Elementen, die die Anzeigebezeichnungen enthalten: „x10k“, „x1k“, „x100“, „x10“, ein Leerzeichen, „/10“, „/100“, „/1k“ und „/10k“, entsprechend den Multiplikatoren für die visuelle Darstellung in Legenden. Wir können nun mit der Initialisierungsfunktion fortfahren und unsere Messuhr mit aktualisierten Eigenschaften zeichnen.

gauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 1); //--- Set case parameters
gauge.SetScaleParameters(250, 0, 0, 100, 4, 0, clrBlack, false); //--- Set scale parameters
gauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set tick parameters
gauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set tick label font
gauge.SetRangeParameters(0, true, 0, 30, clrLimeGreen); //--- Set range 0
gauge.SetRangeParameters(1, true, 70, 100, clrCoral); //--- Set range 1
gauge.SetRangeParameters(2, true, 30, 70, clrYellow); //--- Set range 2
gauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set range 3
gauge.SetLegendParameters(0, true, "RSI", 8, -180, 20, "Arial", false, false, clrBlueViolet); //--- Set legend 0
gauge.SetLegendParameters(3, true, "2", 4, 180, 13, "Arial", true, false, clrRed); //--- Set legend 3
gauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1); //--- Set needle parameters
gauge.Redraw();                         //--- Redraw gauge
gauge.NewValue(0);                      //--- Set new value 0

In der Ereignisbehandlung von OnInit konfigurieren wir das Gehäuse der Messuhr durch den Aufruf von „gauge.SetCaseParameters“ mit „mint cream“ als Gehäusefarbe, Rahmenstil 1, „light sky blue“ Rahmenfarbe und Lückengröße 1. Wir legen die Skalierungseigenschaften mit „gauge.SetScaleParameters“ mit einem Winkelbereich von 250 Grad, keiner Drehung, Minimalwert 0, Maximalwert 100, Multiplikatorindex 4 (entspricht 1), Stil 0, schwarzer Farbe und Bogenanzeige false fest. Für Skalenstriche rufen wir „gauge.SetTickParameters“ mit Stil 0, Größe 2, Hauptintervall 10, 1 Mittelwert pro großem Intervall und 4 Unterwerten pro Intervall auf. Wir wenden die Schriftart für das Skalenstrich-Label über „gauge.SetTickLabelFont“ mit der Größe 1, dem Namen „Arial“, nicht kursiv oder fett und der Farbe Schwarz an. Wir definieren Bereiche mit „gauge.SetRangeParameters“: Index 0 aktiviert von 0 bis 30 in Lindgrün, Index 1 von 70 bis 100 in Korallenrot, Index 2 von 30 bis 70 in Gelb, und Index 3 deaktiviert mit Dummy-Werten in Grau.

Für Legenden verwenden wir „gauge.SetLegendParameters“, um die Beschreibung (Typ 0) mit dem Text „RSI“, Radius 8, Winkel -180, Schriftgröße 20, „Arial“, nicht kursiv oder fett, blauviolette Farbe, und den Wert (Typ 3) mit dem Text „2“ (für Dezimalzahlen), Radius 4, Winkel 180, Größe 13, „Arial“, kursiv true, nicht fett, rote Farbe zu aktivieren. Wir konfigurieren die Nadel mit „gauge.SetNeedleParameters“ unter Verwendung des Zentrumsstils 1, der schwarzen Zentrumsfarbe, der dunkelgrauen Nadelfarbe und des Füllstils 1. Schließlich rufen wir „gauge.Redraw“ auf, um die Anzeige zu rendern, und „gauge.NewValue“ mit 0, um die Zeigerposition zu initialisieren. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ITIALISIERUNG DER MESSUHR

Anhand der Visualisierung können wir sehen, dass wir die Messuhr mit allen Eigenschaften eingestellt haben. Was bleibt, ist, ihr Leben einzuhauchen, damit sie auf Daten reagiert, wenn neue Werte berechnet werden. Dies wird durch den Aufruf der entsprechenden Funktion in der Ereignisbehandlung von OnCalculate erreicht.

static datetime lastBarTime = 0;        //--- Declare last bar time
bool isNewBar = (ratesTotal > 0 && time[ratesTotal - 1] != lastBarTime); //--- Check new bar
if(isNewBar)                            //--- If new bar
   lastBarTime = time[ratesTotal - 1];  //--- Update last bar time
if(isNewBar) {                          //--- If new bar
   int barsCalculated = BarsCalculated(rsiHandle); //--- Get bars calculated
   if(barsCalculated > 0) {             //--- Check calculated
      double currentRsiValue[1];        //--- Declare current value
      if(CopyBuffer(rsiHandle, 0, 0, 1, currentRsiValue) < 0) //--- Copy buffer
         Print("RSI CopyBuffer error for gauge"); //--- Print error
      else                              //--- Else
         gauge.NewValue(currentRsiValue[0]); //--- Set new value
   }
}

Hier wird die statische Variable „lastBarTime“ mit dem Typ Datetime deklariert, die mit 0 initialisiert wird, um den Zeitstempel des zuletzt verarbeiteten Balkens über die Aufrufe der Funktion OnCalculate hinweg zu verfolgen. Wir bestimmen, ob sich ein neuer Balken gebildet hat, indem wir „isNewBar“ auf true setzen, wenn „ratesTotal“ größer als 0 ist und der Zeitstempel bei „time[ratesTotal – 1]“ sich von „lastBarTime“ unterscheidet. Wenn „isNewBar“ wahr ist, aktualisieren wir „lastBarTime“ auf den Zeitstempel des aktuellen Balkens. In einer separaten Prüfung für „isNewBar“ wird die Anzahl der berechneten Balken für den Relative-Strength-Index mit „BarsCalculated“ abgefragt. Wenn dieser Wert größer als 0 ist, erstellen wir ein Einzelelement-Double-Array „currentRsiValue“ und versuchen, den neuesten Wert aus dem Puffer 0 des Handles ab Position 0 mit CopyBuffer zu kopieren, geben einen Fehler aus, wenn das Kopieren fehlschlägt, oder übergeben den Wert an „gauge.NewValue“, um die Anzeige zu aktualisieren. Wenn Sie die Werte pro Skalenstrich anzeigen möchten, können Sie die neue Balkenlogik ignorieren. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

LIVE RSI-MESSUHR

Nachdem wir der Messuhr Leben eingehaucht haben, müssen wir nur noch die Messuhr löschen, um die gerenderten Objekte zu entfernen.

//+------------------------------------------------------------------+
//| Deinitialize Indicator                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   gauge.Delete();                         //--- Delete gauge
   ChartRedraw();                          //--- Redraw chart
}

In der Ereignisbehandlung von OnDeinit, das aufgerufen wird, wenn der Indikator aus dem Chart entfernt oder wenn das Terminal heruntergefahren wird, rufen wir die Methode „gauge.Delete“ auf, um die Skalen- und Nadelebenenobjekte des Indikators zu entfernen und die grafischen Ressourcen zu bereinigen. Anschließend rufen wir ChartRedraw auf, um die Chartanzeige zu aktualisieren und alle Überbleibsel der dargestellten Messuhr zu entfernen. Wir kommen zu folgendem Ergebnis.

ENTFERNEN DER MESSUHR GIF

Anhand der Visualisierung können wir sehen, dass wir den Indikator berechnen, die Messuhr zeichnen, Parameter festlegen und es löschen, wenn es nicht benötigt wird, 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.

RSI LEINWAND BACKTEST DER MESSUHR GIF


Schlussfolgerung

Abschließend haben wir in MQL5 den Indikator Relative-Strength-Index im Stil einer Messuhr erstellt, der die Momentum-Werte auf einer kreisförmigen Skala mit einer dynamischen Nadel, farbcodierten Bereichen für überkaufte und überverkaufte Zonen, Skalenstrich-Markierungen für Präzision und Legenden für den Kontext visualisiert, während er gleichzeitig ein traditionelles Liniendiagramm integriert und die Aktualisierungen bei neuen Balken über die integrierte Funktion iRSI optimiert. Dieser Indikator bietet ein ansprechendes Instrument für die Marktanalyse mit flexiblen Parametern für Skalen, Schriftarten und Grafiken. In den nächsten Teilen werden wir uns mit der Modularisierung des Codes befassen, damit wir damit fortschrittlichere und elegantere Messuhren erstellen können. Bleiben Sie dran.

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

Letzte Kommentare | Zur Diskussion im Händlerforum (3)
Clemence Benjamin
Clemence Benjamin | 18 Dez. 2025 in 20:36
Vielen Dank, Allan, für diese kreative Idee.
Allan Munene Mutiiria
Allan Munene Mutiiria | 20 Dez. 2025 in 15:56
Clemence Benjamin #:
Vielen Dank, Allan, für diese kreative Idee.
Willkommen @Clemence Benjamin für das freundliche Feedback.
Brian Mutuku Mwanthi
Brian Mutuku Mwanthi | 22 Dez. 2025 in 05:50
Vielen Dank für diesen Artikel, Herr Allan

Unabhängig davon, scheint der Chart einen anderen EA zu haben, der mehrere Trades macht

Jede Kerze hat mehrere Trades, welcher EA ist das?
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.
Datenwissenschaft und ML (Teil 47): Marktprognosen mithilfe des DeepAR-Modells in Python Datenwissenschaft und ML (Teil 47): Marktprognosen mithilfe des DeepAR-Modells in Python
In diesem Artikel werden wir versuchen, den Markt mit einem soliden Modell für Zeitreihenprognosen namens DeepAR vorherzusagen. Ein Modell, das eine Kombination aus tiefen neuronalen Netzen und autoregressiven Eigenschaften darstellt, die in Modellen wie ARIMA und Vector Autoregressive (VAR) zu finden sind.
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.
Larry Williams Marktgeheimnisse (Teil 3): Nachweis eines nicht zufälligen Marktverhaltens mit MQL5 Larry Williams Marktgeheimnisse (Teil 3): Nachweis eines nicht zufälligen Marktverhaltens mit MQL5
Untersuchen wir, ob Finanzmärkte wirklich zufällig sind, indem wir die Experimente zum Marktverhalten von Larry Williams mit MQL5 nachstellen. Dieser Artikel zeigt, wie einfache Preis-Aktions-Tests statistische Marktverzerrungen aufdecken können, indem ein nutzerdefinierter Expert Advisor verwendet wird.