Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen
Einführung
In unserem vorangegangenen Artikel (Teil 2) haben wir eine Relative Strength Index-Anzeige im Messuhren-Stil in MetaQuotes Language 5 (MQL5) entwickelt, die Canvas- und Nadelmechanik verwendet und Relative Strength Index-Werte durch eine kreisförmige Messuhr (Gauge) mit einer dynamischen Nadel, farbcodierten Bereichen, die überkaufte und überverkaufte Niveaus anzeigen, und anpassbaren Legenden visualisiert, wobei die traditionelle Liniendarstellung für eine umfassende Momentumanalyse integriert wird. In Teil 3 entwickeln wir Multi-Messuhren Erweiterungen mit den Versionen Sector und Round Styles. Dieses Modell unterstützt mehrere Oszillatoren, wie z. B. den Relative Strength Index (RSI), den Commodity Channel Index (CCI) und den Money Flow Index (MFI), durch vom Nutzer wählbare Kombinationen. Es werden abgeleitete Klassen für sektorielle und runde Messuhrendesigns eingeführt, mit verbesserter Darstellung von Gehäusen unter Verwendung von Bögen, Polygonen und relativer Positionierung, was zu einer ausgefeilten Anzeige mit mehreren Indikatoren führt. Wir werden die folgenden Themen behandeln:
Am Ende haben Sie einen funktionsfähigen MQL5-Indikator für eine verbesserte Visualisierung des Multi-Messuhren-Oszillators. Sie ist bereit für weitere Anpassungen – fangen wir an!
Multi-Messuhren mit Sektor- und Rundstilen
Das System mehrerer Messuhren baut auf einer Basisklasse für anpassbare Messuhren auf und führt abgeleitete Klassen für runde und sektorale Stile ein, um Oszillatoren wie den Relative-Stärke-Index, den Commodity-Channel-Index und den Money-Flow-Index zu visualisieren, indem wir einzelne oder kombinierte Anzeigen über eine Enumeration für eine flexible Momentum-Analyse über Indikatoren hinweg auswählen. Es wird der runde Stil eines kreisförmigen Gehäuses mit gefüllten Kreisen beibehalten, während der Sektorstil die visuelle Darstellung mit bogenbasierten Sektoren, Rundbögen, Verbindungslinien und Polygonen für partielle Zifferblattformen verbessert, sich an Winkelbereiche anpasst und die relative Positionierung unterstützt, um mehrere Messuhren horizontal auf dem Chart auszurichten. Diese Idee entstand aus der Tatsache, dass man in manchen Fällen nur einen kleinen Teil einer Messuhr benötigt, um einige Informationen anzuzeigen, und so hielten wir es für eine großartige Idee, die volle runde Messuhr in eine halbgroße und eine viertelgroße zu unterteilen, sodass Sie wählen können, was zu Ihrem Stil passt. In unserem Fall werden wir die 3 in bedingter Weise erstellen.
Um dies zu erreichen, planen wir, die Basisklasse für Messuhren um rein virtuelle Methoden für die Fallberechnung und das Zeichnen zu erweitern und Überschreibungen in abgeleiteten Klassen für stil-spezifische Logik zu ermöglichen. Wir werden eine Enumeration für die Auswahl von Messuhren hinzufügen, um Instanzen für den Relative Strength Index (Runde), den Commodity Channel Index (Sektor) und den Money Flow Index (Sektor) bedingt zu initialisieren und zu positionieren und ihre Handles und Puffer für das Kopieren von Daten zu integrieren. Wir benötigen diese, um visuelle Daten zu erhalten, aber in Ihrem Fall können Sie alles andere verwenden, wie z. B. die Anzeige von Gewinnen, Indikatorfluss und Fortschritt oder sogar Kontometrien, die nicht auf andere Indikatordaten beschränkt sind. Die Architektur trennt Ebenen für Skala (mit verbesserter Markierungspopulation für Nullpositionen) und Nadel (mit einstellbaren Nadelmultiplikatoren), was effiziente Neuzeichnungen und eine relative Verankerung auf der Grundlage früherer Messwerte gewährleistet. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

Implementation in MQL5
Um mit der Implementierung der Erweiterungen zu beginnen, müssen wir zunächst die Plots und Puffer des Indikators anpassen und weitere Indikatoreigenschaften für die zusätzlichen Indikatoren hinzufügen, die wir hinzufügen wollen, insbesondere die CCI- und MFI-Indikatoren. Hier ist, wie wir das neu machen.
#property indicator_buffers 3 #property indicator_plots 3 #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 2 #property indicator_label1 "RSI" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGreen #property indicator_style2 STYLE_SOLID #property indicator_width2 2 #property indicator_label2 "CCI" #property indicator_type3 DRAW_LINE #property indicator_color3 clrBlue #property indicator_style3 STYLE_SOLID #property indicator_width3 2 #property indicator_label3 "MFI" #property indicator_minimum 0 #property indicator_maximum 100 #property indicator_level1 30 #property indicator_level2 70 #property indicator_level3 -100 #property indicator_level4 100 #property indicator_level5 0 #property indicator_levelcolor clrGray #property indicator_levelstyle STYLE_DOT
Zunächst definieren wir die Metadaten des Indikators mit #property-Direktiven neu, weisen 3 Puffer mit „indicator_buffers“ für die Datenspeicherung zu und konfigurieren 3 Plots mit indicator_plots, da wir es nun mit 3 Indikatoren zu tun haben. Für das erste Plot setzen wir den Typ auf DRAW_LINE, die Farbe auf dodger blue, den Stil auf solid, die Breite auf 2 und die Beschriftung auf „RSI“, so wie sie war. Das zweite Plot ist eine grüne durchgezogene Linie mit der Breite 2, die mit „CCI“ beschriftet ist, und das dritte ist eine blaue durchgezogene Linie mit der Breite 2, die mit „MFI“ beschriftet ist.
Wir legen die vertikale Skala von 0 bis 100 über „indicator_minimum“ und „indicator_maximum“ fest und fügen fünf gepunktete Graustufen bei 30, 70, -100, 100 und 0 hinzu, indem wir „indicator_level1“ bis „indicator_level5“, „indicator_levelcolor“ und „indicator_levelstyle“ verwenden, um Schwellenwerte für die Oszillatoren zu referenzieren. Diese Eigenschaften ermöglichen die gleichzeitige Darstellung von Linien für den Relative Strength Index, den Commodity Channel Index und den Money Flow Index in einem separaten Fenster.
Damit wir anpassen können, welche Messuhr (RSI, CCI, MFI oder Kombinationen) über den Indikator-Eingabe-Dialog angezeigt werden, um den Indikator flexibler und ressourceneffizienter zu machen und eine erweiterte Skalierungsdarstellung zu unterstützen, insbesondere für Indikatoren wie den CCI (der negative Werte und eine Null in der Mitte haben kann), müssen wir einige Enumerationen deklarieren, um zu bestimmen, wo die Marke „Null“ platziert wird, was die Genauigkeit für nicht-positive Skalierungen verbessert.
//+------------------------------------------------------------------+ //| Gauge Selection Enum | //+------------------------------------------------------------------+ enum ENUM_GAUGE_SELECTION { // Define gauge selection enum RSI_ONLY, // RSI Only CCI_ONLY, // CCI Only MFI_ONLY, // MFI Only RSI_CCI, // RSI CCI RSI_MFI, // RSI MFI CCI_MFI, // CCI MFI ALL // All }; // Inputs input ENUM_GAUGE_SELECTION inpGaugeSelection = ALL; // Gauge Selection //+------------------------------------------------------------------+ //| Null Mark Position Enum | //+------------------------------------------------------------------+ enum ENUM_NULLMARK_POS { // Define null mark position enum NULLMARK_NONE=0, // None NULLMARK_LEFT=1, // Left NULLMARK_MIDDLE=2, // Middle NULLMARK_RIGHT=3 // Right };
Wir definieren die Enumeration „ENUM_GAUGE_SELECTION“, um Optionen für die Auswahl der anzuzeigenden Messuhren bereitzustellen, einschließlich individueller Auswahlmöglichkeiten wie „RSI_ONLY“, „CCI_ONLY“ oder „MFI_ONLY“, Kombinationen wie „RSI_CCI“, „RSI_MFI“ oder „CCI_MFI“ und „ALL“ für die Anzeige aller Werte. Wir deklarieren einen Eingabeparameter „inpGaugeSelection“ vom Typ „ENUM_GAUGE_SELECTION“ mit dem Standardwert „ALL“, sodass der Nutzer die Messuhrenkonfiguration direkt aus den Indikatoreinstellungen auswählen kann. Als Nächstes erstellen wir die Enumeration „ENUM_NULLMARK_POS“, um die Positionen für die Nullmarke auf der Skala festzulegen, wobei die Werte „NULLMARK_NONE“ auf 0, „NULLMARK_LEFT“ auf 1, „NULLMARK_MIDDLE“ auf 2 und „NULLMARK_RIGHT“ auf 3 gesetzt werden, um eine flexible Handhabung von Skalierungslayouts zu ermöglichen, insbesondere für Indikatoren mit negativen Bereichen. Wir hielten dies für wichtig, um alle möglichen Szenarien abzudecken.
Als Nächstes werden wir die Gehäusestruktur so erweitern, dass sie den neuen Sektor oder Teilsektor, wie Sie es nennen möchten, Kreise oder Messuhren unterstützt, und auch die Eingabeparameter für die Messuhren erweitern, um die benötigten Multiplikatoren für die Nadelspitzen (needle tail) aufzunehmen. Beginnen wir mit der Struktur des Gehäuses.
Alte Struktur des Gehäuses
//+------------------------------------------------------------------+ //| Case Structure | //+------------------------------------------------------------------+ struct Struct_Case { // Define case structure bool display; // Store display flag Struct_Circle circle; // Store circle structure };
Neue Struktur des Gehäuses
//+------------------------------------------------------------------+ //| Case Structure | //+------------------------------------------------------------------+ struct Struct_Case { // Define case structure bool display; // Store display flag Struct_Circle circle; // Store circle structure int mode; // Store mode Struct_Arc mainArc; // Store main arc Struct_Arc secondaryArc; // Store secondary arc Struct_Arc centerArc; // Store center arc Struct_Arc leftRoundingArc; // Store left rounding arc Struct_Arc rightRoundingArc; // Store right rounding arc Struct_Line leftConnectLine; // Store left connect line Struct_Line rightConnectLine; // Store right connect line Struct_Dot fillDot; // Store fill dot };
Hier wird die Struktur „Struct_Case“ erweitert, um anspruchsvollere Messuhrengehäuse, insbesondere für Sektoren, zu unterstützen, indem ein Anzeigekennzeichen und ein eingebettetes „Struct_Circle“ für einfache kreisförmige Elemente aufgenommen werden. Wir fügen einen ganzzahligen „Modus“ hinzu, um die Gehäusekonfiguration auf der Grundlage von Winkelbereichen zu bestimmen, zusammen mit Instanzen von „Struct_Arc“ für den Hauptbogen, den sekundären Bogen (für größere Winkel), den mittleren Bogen sowie die linken und rechten Rundungsbögen zur Glättung der Kanten. Zusätzlich haben wir „Struct_Line“ für linke und rechte Verbindungslinien zu Brückenkomponenten und einen „Struct_Dot“ für einen Füllpunkt eingebaut, um eine vollständige Abdeckung von Polygonfüllungen während des Renderings zu gewährleisten. Was die Struktur der Parameter betrifft, so fügen wir nur den Multiplikator am Ende wie folgt ein. Wir haben sie zur Verdeutlichung hervorgehoben.
//+------------------------------------------------------------------+ //| Gauge Input Parameters Structure | //+------------------------------------------------------------------+ struct Struct_GaugeInputParams { // Define gauge input parameters structure int xOffset; // Store x offset int yOffset; // Store y offset int anchorCorner; // Store anchor corner int relativeMode; // Store relative mode string relativeObjectName; // Store relative object name int scaleAngleRange; // Store scale angle range int rotationAngle; // Store rotation angle color scaleColor; // Store scale color int scaleStyle; // Store scale style bool displayScaleArc; // Store display scale arc flag double minScaleValue; // Store minimum scale value double maxScaleValue; // Store maximum scale value int scaleMultiplier; // Store scale multiplier int tickStyle; // Store tick style int tickSize; // Store tick size double majorTickInterval; // Store major tick interval int mediumTicksPerMajor; // Store medium ticks per major int minorTicksPerInterval; // Store minor ticks per interval int tickFontSize; // Store tick font size string tickFontName; // Store tick font name bool tickFontItalic; // Store tick font italic flag bool tickFontBold; // Store tick font bold flag color tickFontColor; // Store tick font color Struct_RangeParams ranges[4]; // Store ranges array color caseColor; // Store case color int borderStyle; // Store border style color borderColor; // Store border color int borderGapSize; // Store border gap size Struct_GaugeLegendParams description; // Store description Struct_GaugeLegendParams units; // Store units Struct_GaugeLegendParams multiplier; // Store multiplier Struct_GaugeLegendParams value; // Store value int needleCenterStyle; // Store needle center style color needleCenterColor; // Store needle center color color needleColor; // Store needle color int needleFillStyle; // Store needle fill style double needleTailMultiplier; // Store needle tail multiplier };
Die Variable, die wir für den Multiplikator hinzugefügt haben, ermöglicht es, die Länge der Nadelspitze anzupassen (z. B. kürzer für Sektorenlehren), die visuelle Ästhetik zu verbessern und verschiedene Formen der Messuhren anzupassen. Wir werden die Klassen nun grundlegend überarbeiten, sodass wir Polymorphismus und Vererbung unterstützen, um eine bessere Anpassung, Klassifizierung und zukünftige Anpassung zu ermöglichen. Wir fügen zwei abgeleitete Klassen mit privaten Helfern wie folgt hinzu.
//+------------------------------------------------------------------+ //| Base Gauge Class | //+------------------------------------------------------------------+ class CGaugeBase // Define base gauge class { private: int relativeX; //--- Store relative X int relativeY; //--- Store relative Y int centerX; //--- Store center X int centerY; //--- Store center Y double currentValue; //--- Store current value bool initializationComplete; //--- Store initialization complete flag void Draw(); //--- Declare draw method void CalculateNeedle(); //--- Declare calculate needle method void RedrawNeedle(double value); //--- Declare redraw needle method void CalculateAndDrawLegends(); //--- Declare calculate and draw legends method void CalculateAndDrawLegendString(Struct_GaugeLegendString &legendString); //--- Declare calculate and draw legend string method void RedrawScaleMarks(Struct_Case &internalCase, Struct_Arc &scaleArc, int borderGap); //--- Declare redraw scale marks method void CalculateRanges(int borderGap); //--- Declare calculate ranges method bool IsValidRange(int index); //--- Declare check valid range method void NormalizeRangeValues(double &minValue, double &maxValue, double val0, double val1); //--- Declare normalize range values method void CalculateRangePie(Struct_Range &range, int innerRadius, int radialGap, int outerRadius, double rangeStart, double rangeEnd, color rangeClr, color caseClr); //--- Declare calculate range pie method void DrawRanges(); //--- Declare draw ranges method void DrawRange(Struct_Range &range); //--- Declare draw range method void CalculateInnerOuterRadii(int &innerRadius, int &outerRadius, int baseRadius, int tickLength, int tickStyle); //--- Declare calculate inner outer radii method bool DrawTick(double angle, int length, Struct_Arc &scaleArc); //--- Declare draw tick method double CalculateAngleDelta(double angle1, double angle2, int direction); //--- Declare calculate angle delta method bool GetLabelAreaSize(Struct_LabelAreaSize &areaSize, Struct_GaugeLegendString &legendString); //--- Declare get label area size method bool EraseLegendString(Struct_GaugeLegendString &legendString, color eraseClr); //--- Declare erase legend string method bool RedrawValueDisplay(double value); //--- Declare redraw value display method void SetLegendStringParams(Struct_GaugeLegendString &legendString, Struct_GaugeLegendParams ¶m, int minRadius, int radiusDelta); //--- Declare set legend string params method protected: Struct_GaugeInputParams inputParams; //--- Store input parameters Struct_ScaleLayer scaleLayer; //--- Store scale layer Struct_NeedleLayer needleLayer; //--- Store needle layer int m_radius; //--- Store radius virtual void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) = 0; //--- Declare calculate case elements method virtual void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) = 0; //--- Declare draw case elements method public: bool Create(string name, int x, int y, int size, string relativeObjectName, int relativeMode, int corner, bool background, uchar scaleTransparency, uchar needleTransparency); //--- Declare create method bool CalculateLocation(); //--- Declare calculate location method void Redraw(); //--- Declare redraw method void NewValue(double value); //--- Declare new value method void Delete(); //--- Declare delete method void SetScaleParameters(int angleRange, int rotation, double minValue, double maxValue, int multiplier, int style, color scaleClr, bool displayArc = false); //--- Declare set scale parameters method void SetTickParameters(int style, int size, double majorInterval, int mediumPerMajor, int minorPerInterval); //--- Declare set tick parameters method void SetTickLabelFont(int fontSize, string fontName, bool italic, bool bold, color fontClr = clrBlack); //--- Declare set tick label font method void SetCaseParameters(color caseClr, int borderStyle, color borderClr, int borderGapSize); //--- Declare set case parameters method void SetLegendParameters(int legendType, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend parameters method void SetLegendParam(Struct_GaugeLegendParams &legendParam, bool enable, string text, int radius, double angle, uint fontSize, string fontName, bool italic, bool bold, color textClr = clrDarkGray); //--- Declare set legend param method void SetRangeParameters(int index, bool enable, double start, double end, color rangeClr); //--- Declare set range parameters method void SetNeedleParameters(int centerStyle, color centerClr, color needleClr, int fillStyle, double tailMultiplier = 2.0); //--- Declare set needle parameters method }; //+------------------------------------------------------------------+ //| Round Gauge Class | //+------------------------------------------------------------------+ class CRoundGauge : public CGaugeBase // Define round gauge class { protected: void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements { if(borderSize > 0) { //--- Check border size externalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set external center X externalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set external center Y externalCase.circle.radius = m_radius; //--- Set external radius externalCase.circle.clr = inputParams.borderColor; //--- Set external color externalCase.display = true; //--- Set display flag } else //--- Handle no border externalCase.display = false; //--- Set display flag false internalCase.circle.centerX = scaleLayer.scaleArc.centerX; //--- Set internal center X internalCase.circle.centerY = scaleLayer.scaleArc.centerY; //--- Set internal center Y internalCase.circle.radius = m_radius - borderSize; //--- Set internal radius internalCase.circle.clr = inputParams.caseColor; //--- Set internal color internalCase.display = true; //--- Set display flag } void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements { if(externalCase.display) //--- Check external display scaleLayer.obj_Canvas.FillCircle(externalCase.circle.centerX, externalCase.circle.centerY, externalCase.circle.radius, ColorToARGB(externalCase.circle.clr, scaleLayer.transparency)); //--- Fill external circle if(internalCase.display) //--- Check internal display scaleLayer.obj_Canvas.FillCircle(internalCase.circle.centerX, internalCase.circle.centerY, internalCase.circle.radius, ColorToARGB(internalCase.circle.clr, scaleLayer.transparency)); //--- Fill internal circle } }; //+------------------------------------------------------------------+ //| Sector Gauge Class | //+------------------------------------------------------------------+ class CSectorGauge : public CGaugeBase // Define sector gauge class { private: void CaseCalculateSector(Struct_Case &caseStruct, int gap) //--- Declare calculate sector method { double fi0,fi1,fi2; //--- Declare angles double sa; //--- Declare scale range Struct_Arc referenceArc = scaleLayer.scaleArc; //--- Set reference arc if(referenceArc.endAngle > referenceArc.startAngle) //--- Check end > start sa = NormalizeRadians(referenceArc.endAngle - referenceArc.startAngle); //--- Set sa else //--- Handle wrap sa = NormalizeRadians(referenceArc.endAngle + (2 * M_PI - referenceArc.startAngle)); //--- Set sa if(sa > M_PI) //--- Check > PI caseStruct.mode = 1; //--- Set mode 1 else //--- Handle <= PI caseStruct.mode = 0; //--- Set mode 0 if(sa > M_PI) { //--- Check > PI caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.startAngle + sa * 0.55); //--- Set main end angle caseStruct.mainArc.clr = clrNONE; //--- Set main color caseStruct.secondaryArc.display = true; //--- Set secondary display caseStruct.secondaryArc.centerX = referenceArc.centerX; //--- Set secondary center X caseStruct.secondaryArc.centerY = referenceArc.centerY; //--- Set secondary center Y caseStruct.secondaryArc.radius = referenceArc.radius + gap; //--- Set secondary radius caseStruct.secondaryArc.startAngle = NormalizeRadians(referenceArc.endAngle - sa * 0.55); //--- Set secondary start angle caseStruct.secondaryArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set secondary end angle caseStruct.secondaryArc.clr = clrNONE; //--- Set secondary color } else { //--- Handle <= PI caseStruct.mainArc.centerX = referenceArc.centerX; //--- Set main center X caseStruct.mainArc.centerY = referenceArc.centerY; //--- Set main center Y caseStruct.mainArc.radius = referenceArc.radius + gap; //--- Set main radius caseStruct.mainArc.startAngle = NormalizeRadians(referenceArc.startAngle); //--- Set main start angle caseStruct.mainArc.endAngle = NormalizeRadians(referenceArc.endAngle); //--- Set main end angle caseStruct.mainArc.clr = clrNONE; //--- Set main color } caseStruct.leftRoundingArc.radius = gap; //--- Set left rounding radius caseStruct.leftRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.endAngle)); //--- Set left rounding center X caseStruct.leftRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.endAngle)); //--- Set left rounding center Y if(caseStruct.mode == 1) //--- Check mode 1 fi1 = referenceArc.endAngle + (2 * M_PI - sa) / 2; //--- Set fi1 else //--- Handle mode 0 fi1 = referenceArc.endAngle + M_PI * 0.5; //--- Set fi1 caseStruct.leftRoundingArc.startAngle = referenceArc.endAngle; //--- Set left start angle caseStruct.leftRoundingArc.endAngle = NormalizeRadians(fi1); //--- Set left end angle caseStruct.leftRoundingArc.clr = clrNONE; //--- Set left color caseStruct.rightRoundingArc.radius = gap; //--- Set right rounding radius caseStruct.rightRoundingArc.centerX = (int)(referenceArc.centerX - referenceArc.radius * MathCos(M_PI - referenceArc.startAngle)); //--- Set right rounding center X caseStruct.rightRoundingArc.centerY = (int)(referenceArc.centerY - referenceArc.radius * MathSin(M_PI - referenceArc.startAngle)); //--- Set right rounding center Y if(caseStruct.mode == 1) //--- Check mode 1 fi0 = referenceArc.startAngle - (2 * M_PI - sa) / 2; //--- Set fi0 else //--- Handle mode 0 fi0 = referenceArc.startAngle - M_PI * 0.5; //--- Set fi0 caseStruct.rightRoundingArc.startAngle = NormalizeRadians(fi0); //--- Set right start angle caseStruct.rightRoundingArc.endAngle = referenceArc.startAngle; //--- Set right end angle caseStruct.rightRoundingArc.clr = clrNONE; //--- Set right color caseStruct.centerArc.centerX = referenceArc.centerX; //--- Set center arc center X caseStruct.centerArc.centerY = referenceArc.centerY; //--- Set center arc center Y caseStruct.centerArc.radius = needleLayer.needleCenter.radius + gap; //--- Set center arc radius fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2 fi1 = NormalizeRadians(referenceArc.endAngle + fi2); //--- Set fi1 caseStruct.centerArc.startAngle = fi1; //--- Set center start angle fi1 = NormalizeRadians(referenceArc.startAngle - fi2); //--- Set fi1 caseStruct.centerArc.endAngle = fi1; //--- Set center end angle caseStruct.centerArc.clr = clrNONE; //--- Set center color if(caseStruct.mode == 1) { //--- Check mode 1 double angleOffset = M_PI - (sa / 2); //--- Calculate offset caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start X caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos((caseStruct.secondaryArc.endAngle + angleOffset) - M_PI * 1.5)); //--- Set left start Y caseStruct.leftConnectLine.endX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end X caseStruct.leftConnectLine.endY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos((caseStruct.mainArc.startAngle - angleOffset) - M_PI * 1.5)); //--- Set left end Y caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color } else { //--- Handle mode 0 caseStruct.leftConnectLine.startX = (int)(caseStruct.leftRoundingArc.centerX + caseStruct.leftRoundingArc.radius * MathSin(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start X caseStruct.leftConnectLine.startY = (int)(caseStruct.leftRoundingArc.centerY + caseStruct.leftRoundingArc.radius * MathCos(caseStruct.leftRoundingArc.startAngle - M_PI)); //--- Set left start Y fi2 = MathArcsin(((double)caseStruct.leftRoundingArc.radius / (double)caseStruct.centerArc.radius)); //--- Calculate fi2 fi1 = NormalizeRadians(caseStruct.mainArc.endAngle + fi2); //--- Set fi1 caseStruct.leftConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set left end X caseStruct.leftConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set left end Y caseStruct.leftConnectLine.clr = clrNONE; //--- Set left color caseStruct.rightConnectLine.startX = (int)(caseStruct.rightRoundingArc.centerX + caseStruct.rightRoundingArc.radius * MathSin(caseStruct.rightRoundingArc.endAngle)); //--- Set right start X caseStruct.rightConnectLine.startY = (int)(caseStruct.rightRoundingArc.centerY + caseStruct.rightRoundingArc.radius * MathCos(caseStruct.rightRoundingArc.endAngle)); //--- Set right start Y fi1 = NormalizeRadians(caseStruct.mainArc.startAngle - fi2); //--- Set fi1 caseStruct.rightConnectLine.endX = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(M_PI - fi1)); //--- Set right end X caseStruct.rightConnectLine.endY = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(M_PI - fi1)); //--- Set right end Y caseStruct.rightConnectLine.clr = clrNONE; //--- Set right color } fi1 = M_PI - NormalizeRadians(referenceArc.endAngle - (sa / 2)); //--- Set fi1 caseStruct.fillDot.x = (int)(referenceArc.centerX - caseStruct.centerArc.radius * MathCos(fi1)); //--- Set fill dot X caseStruct.fillDot.y = (int)(referenceArc.centerY - caseStruct.centerArc.radius * MathSin(fi1)); //--- Set fill dot Y caseStruct.fillDot.clr = clrNONE; //--- Set fill dot color } void RedrawSectorCase(Struct_Case &caseStruct) //--- Declare redraw sector case method { int polygonX[5]; //--- Declare polygon X int polygonY[5]; //--- Declare polygon Y scaleLayer.obj_Canvas.Pie(caseStruct.mainArc.centerX, caseStruct.mainArc.centerY, caseStruct.mainArc.radius, caseStruct.mainArc.radius, caseStruct.mainArc.startAngle, caseStruct.mainArc.endAngle, ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.mainArc.clr, scaleLayer.transparency)); //--- Draw main pie if(caseStruct.secondaryArc.display) //--- Check secondary display scaleLayer.obj_Canvas.Pie(caseStruct.secondaryArc.centerX, caseStruct.secondaryArc.centerY, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.radius, caseStruct.secondaryArc.startAngle, caseStruct.secondaryArc.endAngle, ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency), ColorToARGB(caseStruct.secondaryArc.clr, scaleLayer.transparency)); //--- Draw secondary pie scaleLayer.obj_Canvas.FillCircle(caseStruct.leftRoundingArc.centerX, caseStruct.leftRoundingArc.centerY, caseStruct.leftRoundingArc.radius, ColorToARGB(caseStruct.leftRoundingArc.clr, scaleLayer.transparency)); //--- Fill left rounding scaleLayer.obj_Canvas.FillCircle(caseStruct.rightRoundingArc.centerX, caseStruct.rightRoundingArc.centerY, caseStruct.rightRoundingArc.radius, ColorToARGB(caseStruct.rightRoundingArc.clr, scaleLayer.transparency)); //--- Fill right rounding if(caseStruct.mode == 0) //--- Check mode 0 scaleLayer.obj_Canvas.FillCircle(caseStruct.centerArc.centerX, caseStruct.centerArc.centerY, caseStruct.centerArc.radius, ColorToARGB(caseStruct.centerArc.clr, scaleLayer.transparency)); //--- Fill center arc if(caseStruct.mode == 0) { //--- Check mode 0 caseStruct.secondaryArc.display = false; //--- Set secondary display false polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1 polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0 polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2 polygonX[4] = caseStruct.mainArc.centerX; //--- Set polygonX4 polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3 polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1 polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0 polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2 polygonY[4] = caseStruct.mainArc.centerY; //--- Set polygonY4 polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill left polygon polygonX[3] = caseStruct.rightConnectLine.startX; //--- Set polygonX3 polygonX[4] = caseStruct.rightConnectLine.endX; //--- Set polygonX4 polygonX[2] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX2 polygonX[0] = caseStruct.mainArc.centerX; //--- Set polygonX0 polygonX[1] = caseStruct.fillDot.x; //--- Set polygonX1 polygonY[3] = caseStruct.rightConnectLine.startY; //--- Set polygonY3 polygonY[4] = caseStruct.rightConnectLine.endY; //--- Set polygonY4 polygonY[2] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY2 polygonY[0] = caseStruct.mainArc.centerY; //--- Set polygonY0 polygonY[1] = caseStruct.fillDot.y; //--- Set polygonY1 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.rightConnectLine.clr, scaleLayer.transparency)); //--- Fill right polygon } else { //--- Handle mode 1 polygonX[0] = caseStruct.leftConnectLine.endX; //--- Set polygonX0 polygonX[1] = caseStruct.leftConnectLine.startX; //--- Set polygonX1 polygonX[2] = caseStruct.leftRoundingArc.centerX; //--- Set polygonX2 polygonX[3] = caseStruct.fillDot.x; //--- Set polygonX3 polygonX[4] = caseStruct.rightRoundingArc.centerX; //--- Set polygonX4 polygonY[0] = caseStruct.leftConnectLine.endY; //--- Set polygonY0 polygonY[1] = caseStruct.leftConnectLine.startY; //--- Set polygonY1 polygonY[2] = caseStruct.leftRoundingArc.centerY; //--- Set polygonY2 polygonY[3] = caseStruct.fillDot.y; //--- Set polygonY3 polygonY[4] = caseStruct.rightRoundingArc.centerY; //--- Set polygonY4 scaleLayer.obj_Canvas.FillPolygon(polygonX, polygonY, ColorToARGB(caseStruct.leftConnectLine.clr, scaleLayer.transparency)); //--- Fill polygon } } protected: void CalculateCaseElements(Struct_Case &externalCase, Struct_Case &internalCase, int borderSize, int borderGap) override //--- Override calculate case elements { int totalGap = scaleLayer.externalScaleGap + scaleLayer.externalLabelArea + scaleLayer.borderGap; //--- Calculate total gap if(borderSize > 0) { //--- Check border size CaseCalculateSector(externalCase, totalGap + borderSize); //--- Calculate external sector externalCase.mainArc.clr = inputParams.borderColor; //--- Set main color externalCase.secondaryArc.clr = inputParams.borderColor; //--- Set secondary color externalCase.centerArc.clr = inputParams.borderColor; //--- Set center color externalCase.leftRoundingArc.clr = inputParams.borderColor; //--- Set left rounding color externalCase.rightRoundingArc.clr = inputParams.borderColor; //--- Set right rounding color externalCase.leftConnectLine.clr = inputParams.borderColor; //--- Set left connect color externalCase.rightConnectLine.clr = inputParams.borderColor; //--- Set right connect color externalCase.fillDot.clr = inputParams.borderColor; //--- Set fill dot color externalCase.display = true; //--- Set display flag } else //--- Handle no border externalCase.display = false; //--- Set display flag false CaseCalculateSector(internalCase, totalGap); //--- Calculate internal sector internalCase.mainArc.clr = inputParams.caseColor; //--- Set main color internalCase.secondaryArc.clr = inputParams.caseColor; //--- Set secondary color internalCase.centerArc.clr = inputParams.caseColor; //--- Set center color internalCase.leftRoundingArc.clr = inputParams.caseColor; //--- Set left rounding color internalCase.rightRoundingArc.clr = inputParams.caseColor; //--- Set right rounding color internalCase.leftConnectLine.clr = inputParams.caseColor; //--- Set left connect color internalCase.rightConnectLine.clr = inputParams.caseColor; //--- Set right connect color internalCase.fillDot.clr = inputParams.caseColor; //--- Set fill dot color internalCase.display = true; //--- Set display flag } void DrawCaseElements(Struct_Case &externalCase, Struct_Case &internalCase) override //--- Override draw case elements { if(externalCase.display) //--- Check external display RedrawSectorCase(externalCase); //--- Redraw external case if(internalCase.display) //--- Check internal display RedrawSectorCase(internalCase); //--- Redraw internal case } };
Hier definieren wir die Klasse „CGaugeBase“ mit privaten Mitgliedern für Positionen, den aktuellen Wert und das Initialisierungs-Flag, zusammen mit deklarierten Methoden für das Zeichnen, Nadelberechnungen, Legende, Skalierungsmarkierungen, Bereiche, Skalierungsstriche und Beschriftungen wie zuvor, belassen aber „CalculateCaseElements“ und „DrawCaseElements“ als rein virtuell, um eine Implementierung in Unterklassen zu erfordern, während wir das Standardverhalten für andere Setter und Aktualisierungen in öffentlichen und geschützten Abschnitten bereitstellen. Wir erstellen die Klasse „CRoundGauge“, die von „CGaugeBase“ erbt, überschreiben „CalculateCaseElements“, um externe und interne Kreise auf der Grundlage der Randgröße, der Zentren aus dem Skalierungsbogen, der Radiusanpassungen, der Farben aus den Eingaben und der Anzeige-Flags zu konfigurieren, und überschreiben „DrawCaseElements“, um diese Kreise auf der Skalierungsleinwand mit „FillCircle“ unter Verwendung von ARGB-konvertierten Farben zu füllen, falls angezeigt.
Für die Klasse „CSectorGauge“, die ebenfalls von „CGaugeBase“ erbt, fügen wir die private Methode „CaseCalculateSector“ zur Berechnung von Sektorelementen hinzu: Bestimmung des Modus auf der Grundlage des Winkelbereichs, der Pi übersteigt, Festlegung von Haupt- und Nebenbögen für große Winkel, Berechnung von linken und rechten Rundungsbögen mit Trig-Funktionen wie MathArcsin und „NormalizeRadians“, Mittelpunktsbogen mit Versatz, Verbindungslinien mit Sinus- und Kosinusanpassungen, die je nach Modus variieren, und ein Füllpunkt in der Mitte des Winkels. Wir überschreiben „CalculateCaseElements“ in „CSectorGauge“, um den Gesamtabstand von externen Elementen und der Umrandung zu berechnen, rufen „CaseCalculateSector“ für externe (wenn die Umrandung positiv ist) und interne Elemente mit den entsprechenden Abständen auf, weisen allen Bogen-, Linien- und Punktkomponenten Umrandungs- oder Gehäusefarben zu und aktivieren die Anzeigen.
Schließlich überschreiben wir „DrawCaseElements“, um bedingt „RedrawSectorCase“ für externe und interne, falls angezeigt, aufzurufen, wobei „RedrawSectorCase“ Haupt- und Nebentortendiagramme mit „Pie“ zeichnet, Rundungen und Mittelkreise mit „FillCircle“, und Polygone für Füllverbindungen (unter Verwendung von Arrays für Koordinaten) mit „FillPolygon“ in ARGB-Farben, wobei Arrays für Modus 0 (getrennte linke/rechte Polygone) oder Modus 1 (einzelnes Polygon) angepasst werden.
Im Allgemeinen ist es so, dass die rein virtuellen Methoden Polymorphismus ermöglichen: Der RSI verwendet eine runde Skala, während der CCI/MFI aus Gründen der Abwechslung und besseren Anpassung einen Sektor verwendet (z. B. die symmetrische Skala des CCI um den Nullpunkt). Die relative Positionierung in „Erstellen“ ermöglicht die automatische Ausrichtung von Messuhren nebeneinander, und die Überarbeitung von „RedrawScaleMarks“ unterstützt Indikatoren mit negativen Bereichen (CCI) oder invertierter Skalierung (MFI: 100 bis 0), um eine genaue Platzierung und Beschriftung der Skalierungsstriche zu gewährleisten. Die Funktion „calc_digits“ setzt dynamisch Dezimalstellen für eine saubere Anzeige. Hier ist die Logik der Funktionsimplementierung, die wir verwendet haben.
//+------------------------------------------------------------------+ //| Calculate Digits | //+------------------------------------------------------------------+ int calc_digits(double value) { int i, j, max_nulls = 0, nulls = 0; //--- Declare variables if(value == 0) return(0); //--- Return 0 if zero ulong v = ulong(MathAbs(value) * 100000000); //--- Calculate v ulong vtmp; //--- Declare vtmp for(j = -5; j <= 5; j++) { //--- Loop j nulls = 0; //--- Reset nulls vtmp = v + (ulong)j; //--- Set vtmp for(i = 0; i < 8; i++) { //--- Loop i if(vtmp % 10 == 0) { //--- Check mod 10 == 0 vtmp = vtmp / 10; //--- Divide vtmp nulls++; //--- Increment nulls } else break; //--- Break else } if(max_nulls < nulls) max_nulls = nulls; //--- Update max nulls } return(8 - max_nulls); //--- Return digits }
Die Funktion „calc_digits“ bestimmt die Anzahl der Dezimalstellen, die für die Anzeige eines Wertes erforderlich sind, und behandelt Fragen der Fließkommagenauigkeit. Wenn der Wert null ist, wird sofort 0 zurückgegeben. Wir berechnen einen vorzeichenlosen ganzzahligen Wert vom Typ long „v“, indem wir den absoluten Wert mit 100.000.000 multiplizieren, um den Dezimalteil in die Ganzzahl zu verschieben. Dann wird eine Schleife über die kleinen Offsets „j“ von -5 bis 5 gezogen, die jeweils zu „v“ addiert werden, um „vtmp“ zu erstellen, und es werden die nachgestellten Nullen gezählt, indem wiederholt durch 10 geteilt wird, während der Rest Null ist, und zwar bis zu 8 Mal, wobei die maximale Anzahl solcher Nullen „max_nulls“ über die Offsets hinweg verfolgt wird. Schließlich geben wir 8 minus „max_nulls“ als effektive Stellenzahl zurück. Schließlich müssen wir die Messuhren während der Initialisierung unter bestimmten Bedingungen erstellen. Zuerst müssen wir die globalen Variablen ändern.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CRoundGauge rsiGauge; //--- Declare RSI gauge CSectorGauge cciGauge; //--- Declare CCI gauge CSectorGauge mfiGauge; //--- Declare MFI gauge int rsiHandle = INVALID_HANDLE; //--- Initialize RSI handle int cciHandle = INVALID_HANDLE; //--- Initialize CCI handle int mfiHandle = INVALID_HANDLE; //--- Initialize MFI handle double scaleMultipliers[9] = {10000, 1000, 100, 10, 1, 0.1, 0.01, 0.001, 0.0001}; //--- Define scale multipliers array string scaleMultiplierStrings[9] = {"x10k", "x1k", "x100", "x10", " ", "/10", "/100", "/1k", "/10k"}; //--- Define multiplier strings array double rsiBuffer[], cciBuffer[], mfiBuffer[]; //--- Declare buffers
Hier deklarieren wir globale Instanzen der Messuhren-Klassen: „rsiGauge“ als „CRoundGauge“ für die Visualisierung des Relative-Stärke-Index mit einem kreisförmigen Stil, „cciGauge“ und „mfiGauge“ als „CSectorGauge“ für den Commodity Channel Index und den Money Flow Index mit Sektordesigns. Wir initialisieren die ganzzahligen Handles „rsiHandle“, „cciHandle“ und „mfiHandle“ auf INVALID_HANDLE, um später auf die jeweiligen technischen Indikatoren zu verweisen. Wir definieren das Double-Array „scaleMultipliers“ mit 9 Skalierungsfaktoren von 10000 bis hinunter zu 0,0001 zur Anpassung der Wertanzeigen auf den Messuhren. Das String-Array „scaleMultiplierStrings“ enthält entsprechende Bezeichnungen für diese Multiplikatoren, die in Legenden zur visuellen Darstellung verwendet werden. Schließlich deklarieren wir Double-Arrays „rsiBuffer“, „cciBuffer“ und „mfiBuffer“, um die berechneten Daten der einzelnen Indikatoren für die Darstellung und die Aktualisierung der Messuhren zu speichern. Wir können nun die Initialisierung der Messuhren vornehmen.
//+------------------------------------------------------------------+ //| Initialize Indicator | //+------------------------------------------------------------------+ int OnInit() { bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI string prevName = ""; //--- Initialize prev name int baseX = 30; //--- Set base X int baseY = 30; //--- Set base Y IndicatorSetInteger(INDICATOR_LEVELS, 5); //--- Set levels SetIndexBuffer(0, rsiBuffer, INDICATOR_DATA); //--- Set RSI buffer SetIndexBuffer(1, cciBuffer, INDICATOR_DATA); //--- Set CCI buffer SetIndexBuffer(2, mfiBuffer, INDICATOR_DATA); //--- Set MFI buffer PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set RSI empty PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set CCI empty PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set MFI empty PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14 - 1); //--- Set RSI draw begin PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, 14 - 1); //--- Set CCI draw begin PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, 14 - 1); //--- Set MFI draw begin if(showRSI) { //--- Check show RSI if(!rsiGauge.Create("rsi_gauge", baseX, baseY, 230, "", 0, 0, false, 0, 0)) return(INIT_FAILED); //--- Create RSI gauge rsiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set RSI case rsiGauge.SetScaleParameters(250, 0, 0, 100, 4, 0, clrBlack, false); //--- Set RSI scale rsiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set RSI ticks rsiGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set RSI font rsiGauge.SetRangeParameters(0, true, 0, 30, clrLimeGreen); //--- Set RSI range 0 rsiGauge.SetRangeParameters(1, true, 70, 100, clrCoral); //--- Set RSI range 1 rsiGauge.SetRangeParameters(2, true, 30, 70, clrYellow); //--- Set RSI range 2 rsiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set RSI range 3 rsiGauge.SetLegendParameters(0, true, "RSI", 8, -180, 20, "Arial", false, false, clrBlueViolet); //--- Set RSI legend 0 rsiGauge.SetLegendParameters(3, true, "2", 4, 180, 13, "Arial", true, false, clrRed); //--- Set RSI legend 3 rsiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 2.0); //--- Set RSI needle rsiGauge.Redraw(); //--- Redraw RSI rsiGauge.NewValue(0); //--- Set RSI value 0 prevName = "rsi_gauge"; //--- Set prev name rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE); //--- Get RSI handle if(rsiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check RSI handle } if(showCCI) { //--- Check show CCI string relName = prevName; //--- Set rel name int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode int posX = (prevName != "") ? 0 : baseX; //--- Set pos X int posY = baseY + 90; //--- Set pos Y if(!cciGauge.Create("cci_gauge", posX, posY, 230, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create CCI gauge cciGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 1); //--- Set CCI case cciGauge.SetScaleParameters(200, 0, -200, 200, 4, 0, clrBlack, false); //--- Set CCI scale cciGauge.SetTickParameters(0, 2, 100, 1, 4); //--- Set CCI ticks cciGauge.SetTickLabelFont(1, "Arial", false, false, clrBlack); //--- Set CCI font cciGauge.SetRangeParameters(0, true, -200, -100, clrCoral); //--- Set CCI range 0 cciGauge.SetRangeParameters(1, true, -100, 100, clrDodgerBlue); //--- Set CCI range 1 cciGauge.SetRangeParameters(2, true, 100, 200, clrLimeGreen); //--- Set CCI range 2 cciGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set CCI range 3 cciGauge.SetLegendParameters(0, true, "CCI", 4, 0, 16, "Arial", false, false, clrBlueViolet); //--- Set CCI legend 0 cciGauge.SetLegendParameters(3, true, "1", 1, 0, 12, "Arial", true, false, clrRed); //--- Set CCI legend 3 cciGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.5); //--- Set CCI needle cciGauge.Redraw(); //--- Redraw CCI cciGauge.NewValue(0); //--- Set CCI value 0 prevName = "cci_gauge"; //--- Set prev name cciHandle = iCCI(_Symbol, _Period, 14, PRICE_TYPICAL); //--- Get CCI handle if(cciHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check CCI handle } if(showMFI) { //--- Check show MFI string relName = prevName; //--- Set rel name int relMode = (prevName != "") ? 1 : 0; //--- Set rel mode int posX = (prevName != "") ? 0 : baseX; //--- Set pos X int posY = baseY + 90; //--- Set pos Y if(!mfiGauge.Create("mfi_gauge", posX, posY, 250, relName, relMode, 0, false, 0, 0)) return(INIT_FAILED); //--- Create MFI gauge mfiGauge.SetCaseParameters(clrMintCream, 1, clrLightSkyBlue, 3); //--- Set MFI case mfiGauge.SetScaleParameters(120, -35, 100, 0, 3, 0, clrBlack, false); //--- Set MFI scale mfiGauge.SetTickParameters(0, 2, 10, 1, 4); //--- Set MFI ticks mfiGauge.SetTickLabelFont(0, "Arial", false, false, clrBlack); //--- Set MFI font mfiGauge.SetRangeParameters(0, true, 80, 100, clrRed); //--- Set MFI range 0 mfiGauge.SetRangeParameters(1, true, 20, 80, clrMagenta); //--- Set MFI range 1 mfiGauge.SetRangeParameters(2, true, 0, 20, clrGreen); //--- Set MFI range 2 mfiGauge.SetRangeParameters(3, false, 0, 0, clrGray); //--- Set MFI range 3 mfiGauge.SetLegendParameters(0, true, "MFI", 4, -15, 14, "Arial", false, false, clrBlueViolet); //--- Set MFI legend 0 mfiGauge.SetLegendParameters(2, true, "", 4, -50, 10, "Arial", false, false, clrDimGray); //--- Set MFI legend 2 mfiGauge.SetLegendParameters(3, true, "0", 3, -80, 16, "Arial", true, false, clrRed); //--- Set MFI legend 3 mfiGauge.SetNeedleParameters(1, clrBlack, clrDimGray, 1, 1.2); //--- Set MFI needle mfiGauge.Redraw(); //--- Redraw MFI mfiGauge.NewValue(0); //--- Set MFI value 0 mfiHandle = iMFI(_Symbol, _Period, 14, VOLUME_TICK); //--- Get MFI handle if(mfiHandle == INVALID_HANDLE) return(INIT_FAILED); //--- Check MFI handle } return(INIT_SUCCEEDED); //--- Return succeeded }
In OnInit legen wir zunächst die Sichtbarkeits-Flags für jeden Indikator auf der Grundlage der Eingabe „inpGaugeSelection“ fest: „showRSI“ ist wahr für Auswahlen, die den Relative Strength Index enthalten, ebenso für „showCCI“ und „showMFI“, um die entsprechenden Indikatoren bedingt anzuzeigen. Wir initialisieren die leere Zeichenkette „prevName“ für die relative Positionierung, setzen die Basiskoordinaten „baseX“ und „baseY“ auf 30, konfigurieren fünf Indikatorstufen mit „IndicatorSetInteger“ und binden Puffer mit „SetIndexBuffer“ an den Index 0 für „rsiBuffer“, 1 für „cciBuffer“ und 2 für „mfiBuffer“ als Indikatordaten. Wir setzen leere Plot-Werte mit PlotIndexSetDouble für alle drei Plots auf EMPTY_VALUE und legen den Zeichenbeginn mit PlotIndexSetInteger auf den Balken-Index 13 fest, um die Berechnungen auf 14 Perioden abzustimmen. Ihre Logik könnte sich ändern, wenn Sie einen anderen Ansatz wählen.
Wenn „showRSI“ auf „true“ gesetzt ist, erstellen wir die Instanz „rsiGauge“ mit „Create“ an der Basisposition und einer Größe von 230 (keine relative Größe) und konfigurieren sie anschließend über „SetCaseParameters“ mit einem mintfarbenen Hintergrund, Rahmenstil 1, hellblauem Rand und einem Abstand von 3; „SetScaleParameters“ für einen Bereich von 250 Grad, Werte von 0 bis 100, Multiplikator 4, Farbe Schwarz, kein Bogen; „SetTickParameters“ Stil 0, Größe 2, groß 10, mittel 1, klein 4; „SetTickLabelFont“ Größe 1, Arial, keine Stile, schwarz; Bereiche mit „SetRangeParameters“ 0–30 limettengrün, 70–100 korallenrot, 30–70 gelb, deaktiviert grau; Legende mit „SetLegendParameters“ Beschreibung „RSI“ bei Radius 8, Winkel -180, Größe 20, Arial, keine Stile, blau-violett; Wert „2“ bei Radius 4, Winkel 180, Größe 13, Arial, kursiv, keine Fettschrift, rot; „SetNeedleParameters“ Mitte 1 schwarz, Füllung 1 mattgrau, Nadel 2,0; „Redraw“ und „NewValue“ 0 aufrufen; „prevName“ auf „rsi_gauge“ aktualisieren; „rsiHandle“ mit iRSI auf Symbolperiodenlänge 14 Schlusskursen initialisieren und, falls ungültig, INIT_FAILED zurückgeben.
Wenn „showCCI“ wahr ist, setzen wir den relativen Namen auf „prevName“ und den Modus auf 1, sofern nicht leer, Position x 0 oder base, y base+90; erstellen „cciGauge“ relativ; konfigurieren das Gehäuse wie beim RSI, jedoch mit einem Abstand von 1; Skalierung 200 Grad von -200 bis 200; Skalierungsstriche gleich; Schriftart gleich; Bereiche -200--100 korallenrot, -100-100 dodgerblau, 100-200 limettengrün, deaktiviert; Legenden „CCI“ Radius 4 Winkel 0 Größe 16 Arial keine Stile blau-violett, Wert „1“ Radius 1 Winkel 0 Größe 12 Arial kursiv keine Fettschrift rot; Nadelspitze 1,5; neu zeichnen und auf 0 initialisieren; prevName aktualisieren; „cciHandle“ mit iCCI und der Periodenlänge 14 und dem typischen Preis abrufen, einschließlich der Prüfung auf Ungültigkeit.
Für „showMFI“: Falls true, verwenden wir eine ähnliche relative Konfiguration mit Position y: Basis + 90; „mfiGauge“ erstellen, Größe 250; Abstand 3; Skalierung: 120 Grad, 100 bis 0 (umgekehrt), Multiplikator 3; Markierungen wie oben; Schriftgröße 0; Bereiche 80–100 rot, 20–80 magenta, 0–20 grün, deaktiviert; Beschriftungen „MFI“ Radius 4 Winkel -15 Größe 14 Arial keine Stile blauviolett, Multiplikator leer Radius 4 Winkel -50 Größe 10 dunkelgrau, Wert „0“ Radius 3 Winkel -80 Größe 16 Arial kursiv keine Fettschrift rot; Nadelspitze 1,2; Neuzeichnen auf 0 initialisieren; „mfiHandle“ mit Abrufen des iMFI, Periodenlänge 14, Tick-Volumen und der Prüfung auf Ungültigkeit.
Wir geben INIT_SUCCEEDED zurück, um die Initialisierung abzuschließen. Nach der Initialisierung erhalten wir das folgende Ergebnis.

Wir können sehen, dass die Puffer leer sind und die Indikatoren nicht gezeichnet werden, aber die Messuhren sind in Ordnung. Wir müssen die Berechnungen im Berechnungs-Ereignishandler für die von uns ausgewählten Indikatoren unter bestimmten Bedingungen durchführen.
//+------------------------------------------------------------------+ //| Calculate Indicator | //+------------------------------------------------------------------+ int OnCalculate(const int ratesTotal, const int prevCalculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tickVolume[], const long &volume[], const int &spread[]) { bool showRSI = (inpGaugeSelection == RSI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == RSI_MFI || inpGaugeSelection == ALL); //--- Set show RSI bool showCCI = (inpGaugeSelection == CCI_ONLY || inpGaugeSelection == RSI_CCI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show CCI bool showMFI = (inpGaugeSelection == MFI_ONLY || inpGaugeSelection == RSI_MFI || inpGaugeSelection == CCI_MFI || inpGaugeSelection == ALL); //--- Set show MFI static datetime lastBarTime = 0; //--- Declare last bar time bool isNewBar = (ratesTotal > 0 && time[ratesTotal - 1] != lastBarTime); //--- Check new bar if(isNewBar) lastBarTime = time[ratesTotal - 1]; //--- Update last bar time if(showRSI) { //--- Check show RSI if(rsiHandle != INVALID_HANDLE && CopyBuffer(rsiHandle, 0, 0, ratesTotal, rsiBuffer) < 0) return(0); //--- Copy RSI buffer } else { //--- Handle no show ArrayFill(rsiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill RSI empty } if(showCCI) { //--- Check show CCI if(cciHandle != INVALID_HANDLE && CopyBuffer(cciHandle, 0, 0, ratesTotal, cciBuffer) < 0) return(0); //--- Copy CCI buffer } else { //--- Handle no show ArrayFill(cciBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill CCI empty } if(showMFI) { //--- Check show MFI if(mfiHandle != INVALID_HANDLE && CopyBuffer(mfiHandle, 0, 0, ratesTotal, mfiBuffer) < 0) return(0); //--- Copy MFI buffer } else { //--- Handle no show ArrayFill(mfiBuffer, 0, ratesTotal, EMPTY_VALUE); //--- Fill MFI empty } if(isNewBar) { //--- Check new bar if(showRSI && rsiHandle != INVALID_HANDLE) { //--- Check RSI double val[1]; //--- Declare val if(CopyBuffer(rsiHandle, 0, 0, 1, val) > 0) rsiGauge.NewValue(val[0]); //--- Set RSI value } if(showCCI && cciHandle != INVALID_HANDLE) { //--- Check CCI double val[1]; //--- Declare val if(CopyBuffer(cciHandle, 0, 0, 1, val) > 0) cciGauge.NewValue(val[0]); //--- Set CCI value } if(showMFI && mfiHandle != INVALID_HANDLE) { //--- Check MFI double val[1]; //--- Declare val if(CopyBuffer(mfiHandle, 0, 0, 1, val) > 0) mfiGauge.NewValue(val[0]); //--- Set MFI value } } return(ratesTotal); //--- Return rates total }
In der Ereignisbehandlung von OnCalculate werden die Sichtbarkeits-Flags „showRSI“, „showCCI“ und „showMFI“ auf der Grundlage der Eingabe „inpGaugeSelection“ neu definiert, um zu bestimmen, welche Indikatoren und Messuhren in diesem Berechnungszyklus verarbeitet werden sollen. Wir verwenden eine statische „datetime“ „lastBarTime“, um neue Balken zu erkennen, setzen „isNewBar“ auf true, wenn der letzte Zeitstempel abweicht, und aktualisieren „lastBarTime“ entsprechend. Wenn „showRSI“ wahr und der Handle gültig ist, kopieren wir den gesamten Verlauf aus dem Puffer des Relative Strength Index mit CopyBuffer in „rsiBuffer“ und geben bei einem Fehlschlag 0 zurück; andernfalls füllen wir „rsiBuffer“ mit EMPTY_VALUE unter Verwendung von ArrayFill, um die Darstellung auszublenden.
Ebenso kopieren wir für den Commodity Channel Index den Wert in „cciBuffer“ oder füllen das Feld mit Nullen; und für den Money Flow Index kopieren wir den Wert in „mfiBuffer“ oder füllen das Feld mit Nullen. Bei einem neuen Balken, wenn die Anzeige und die Handles gültig sind, kopieren wir den letzten Einzelwert in ein temporäres Array „val“ mit „CopyBuffer“ shift 0 count 1, und aktualisieren bei Erfolg die entsprechende Anzeige mit „NewValue“ über „val[0]“. Wir geben „ratesTotal“ zurück, um die Bearbeitung fortzusetzen. Vergessen wir nicht, die Messuhren zu löschen, um ein unübersichtliches Chart zu vermeiden.
//+------------------------------------------------------------------+ //| Deinitialize Indicator | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { rsiGauge.Delete(); //--- Delete RSI gauge cciGauge.Delete(); //--- Delete CCI gauge mfiGauge.Delete(); //--- Delete MFI gauge ChartRedraw(); //--- Redraw chart }
In der Ereignisbehandlung von OnDeinit rufen wir die Methode „Delete“ für „rsiGauge“, „cciGauge“ und „mfiGauge“ auf, um ihre Skalierungs- und Nadelebenenobjekte aus dem Chart zu entfernen. Anschließend rufen wir ChartRedraw auf, um das Chart zu aktualisieren und sicherzustellen, dass nach der Deinitialisierung des Indikators keine visuellen Überbleibsel zurückbleiben. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Anhand der Visualisierung können wir sehen, dass wir die Indikatoren berechnen, die Messuhren zeichnen, Parameter festlegen und sie löschen, wenn sie nicht benötigt werden, und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und der wird im nächsten Abschnitt behandelt.
Backtests
Wir haben die Tests durchgeführt, und unten sehen Sie die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Schlussfolgerung
Abschließend haben wir den Indikator auf Basis von Messuhren in MQL5 verbessert, um mehrere Oszillatoren wie den Relative Strength Index, den Commodity Channel Index und den Money Flow Index durch vom Nutzer auswählbare Kombinationen zu unterstützen und runde und sektorale Stile über abgeleitete Klassen mit fortgeschrittenen Gehäusedarstellungen unter Verwendung von Bögen, Polygonen und relativer Positionierung für ausgerichtete Anzeigen einzuführen. Dieser Indikator ist ein vielseitiges Werkzeug für die Analyse von Multi-Oszillatoren, mit konfigurierbaren Skalierungen, Bereichen, Legenden und Nadeln, die sich an verschiedene Wertebereiche anpassen lassen. Sie können sie erweitern, um nutzerdefinierte Datenindikatoren einzubeziehen, wie Sie möchten.
Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20719
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5
Sigma Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor
Larry Williams Marktgeheimnisse (Teil 1): Aufbau eines Swing-Struktur-Indikators in MQL5
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.