Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 1): Erstellen eines Pivot-basierten Trendindikators mit Canvas-Gradient
Einführung
In diesem Artikel entwickeln wir einen Pivot-basierten Trendindikator in MetaQuotes Language 5 (MQL5), der schnelle/langsame Pivot-Linien berechnet, Trends mit Richtungspfeilen erkennt, Pivot-Linien auf dem Chart nach vorn verlängert und zur besseren Lesbarkeit optionale Canvas-Gradienten bietet, die steigende oder fallende Bereiche hervorheben. In diesem Artikel werden wir unter anderem folgende Themen behandeln:
Am Ende werden Sie einen voll funktionsfähigen MQL5-Indikator für die Pivot-Trend-Erkennung mit flexiblen visuellen Einstellungen haben. Beginnen wir mit dem Entwicklungsprozess.
Das Verständnis des Pivot-basierten Trendindikators
Der Indikator Pivot-Trend-Detector ist ein technisches Hilfsmittel, das schnelle und langsame Pivot-Linien auf der Grundlage von Hoch-/Tiefstkursen über definierte Zeiträume verwendet, um Trendrichtungen und potenzielle Umkehrungen zu identifizieren, indem es Kursdaten glättet und gleichzeitig Verschiebungen durch farbcodierte Linien und Pfeile hervorhebt. Es besteht aus drei Hauptlinien: einer langsamen Linie, die als primäre Trendreferenz dient (aufwärts oder abwärts, je nach Kurslage), einer schnellen gepunkteten Linie, die bei Trendumkehrungen die Farbe ändert, und Pfeilen, die den Beginn neuer Trends markieren, wenn der Kurs beide Linien kreuzt.
Diese Konstellation hilft uns, Veränderungen des Momentums zu erkennen, wobei die langsame Linie Unterstützung/Widerstand und die schnelle Linie frühe Signale liefert, die sich durch Periodenanpassungen an die Volatilität anpassen lassen. In der Praxis hilft der Indikator bei der Trendfolge, indem er Aufwärtstrends bestätigt, wenn der Kurs oberhalb der langsamen Linie (in der Farbe für aufwärts) bleibt, und Abwärtstrends, wenn der Kurs unterhalb der langsamen Linie (in der Farbe für abwärts) bleibt, wobei Pfeile Einstiegspunkte bei Kreuzungen und optionale Erweiterungen, die aus den Linien herausragen, für zukünftige Projektionen signalisieren. Die dynamische Linienfüllung visualisiert die Trendstärke mit einem Deckkraftgradienten, der von langsam bis schnell überblendet und so intuitiv Bereiche hervorhebt.
Wir werden die Architektur des Indikators auf einer klaren Trennung der Verantwortlichkeiten aufbauen: Eingabeparameter, die Puffer der Indikatoren und grafische Eigenschaften. Wir beginnen mit der Definition der Schlüsseleingaben, wie schnelle/langsame Periodenlängen, Farben, Deckkraft, Pfeilcode und Erweiterungen, die das Verhalten des Indikators bestimmen werden. Anschließend werden wir acht Puffer deklarieren, um langsame Auf-/Abwärtslinien, schnelle Linien mit Farben, Trendpfeile mit Farben sowie interne Berechnungen der Werte für „trend“ und „slow“ zu speichern. Diese Puffer werden mit grafischen Zeichnungen verknüpft, deren Eigenschaften wie Typ (Linie/Farbe, Linie/Pfeil), Farbe, Breite und Verschiebung mit den in MQL5 integrierten Funktionen konfiguriert werden. Außerdem werden wir die Klasse Canvas verwenden, um den Raum zwischen den Linien mit Farbverläufen zu füllen, damit sich der Indikator dynamisch an die Marktvolatilität anpasst. Hier ist ein Beispiel für das, was wir bekommen werden.

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 Zeichnungen und die Eigenschaften der einzelnen Linien, wie Farbe, Breite und Beschriftung.
//+------------------------------------------------------------------+ //| 1. Pivot Trend Detector.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_chart_window #property indicator_buffers 8 #property indicator_plots 4 #property indicator_label1 "PTD slow line up" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 #property indicator_label2 "PTD slow line down" #property indicator_type2 DRAW_LINE #property indicator_color2 clrCrimson #property indicator_width2 2 #property indicator_label3 "PTD fast line" #property indicator_type3 DRAW_COLOR_LINE #property indicator_color3 clrDodgerBlue,clrCrimson #property indicator_style3 STYLE_DOT #property indicator_label4 "PTD trend start" #property indicator_type4 DRAW_COLOR_ARROW #property indicator_color4 clrDodgerBlue,clrCrimson #property indicator_width4 2
Wir beginnen die Implementierung, indem wir die Metadaten des Indikators mit den Direktiven von property definieren, mit „indicator_chart_window“ festlegen, dass er im Hauptchartfenster zeichnet, mit indicator_buffers 8 Puffer zuweisen und mit „indicator_plots“ 4 Zeichnungen konfigurieren. Der ersten Zeichnung geben wir die Bezeichnung „PTD slow line up“ ein, den Typ DRAW_LINE, die Farbe dodger blue, die Breite 2. Die zweite Zeichnung ist gekennzeichnet mit „PTD slow line down“, Typ Line, Farbe karminrot und der Breite 2, die dritte Darstellung mit „PTD fast line“, Typ farbige Linie, Farben „dodger blue“ und „crimson“, Stil gepunktet und die vierte Darstellung mit „PTD trend start“, Typ Farbpfeil, Farben „dodger blue“ und „crimson“, Breite 2. Diese Eigenschaften bilden die visuelle Struktur für langsame Auf-/Abwärtslinien, farbwechselnde schnelle Linien und Trendstartpfeile. Dann werden wir einige Eingabeparameter und globale Variablen für die Verwendung im Programm definieren.
#include <Canvas/Canvas.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ CCanvas obj_Canvas; //--- Canvas object //--- input parameters input int fastPeriod = 5; // Fast period input int slowPeriod = 10; // Slow period input color upColor = clrDodgerBlue; // Up trend color input color downColor = clrCrimson; // Down trend color input int fillOpacity = 128; // Fill opacity (0-255) input int arrowCode = 77; // Arrow code for trend start input bool showExtensions = true; // Show line extensions input bool enableFilling = true; // Enable canvas fill (disable for speed) input int extendBars = 1; // Extension bars to protrude lines/fill //--- indicator buffers double slowLineUpBuffer[],slowLineDownBuffer[],slowLineBuffer[],fastLineBuffer[],fastLineColorBuffer[],trendArrowColorBuffer[],trendArrowBuffer[],trendBuffer[]; //--- Indicator buffers //--- chart properties int currentChartWidth = 0; //--- Current chart width int currentChartHeight = 0; //--- Current chart height int currentChartScale = 0; //--- Current chart scale int firstVisibleBarIndex = 0; //--- First visible bar index int visibleBarsCount = 0; //--- Visible bars count double minPrice = 0.0; //--- Minimum price double maxPrice = 0.0; //--- Maximum price //--- optimization flags static datetime lastRedrawTime = 0; //--- Last redraw time static double previousTrend = -1; //--- Previous trend string objectPrefix = "PTD_"; //--- Object prefix
Hier binden wir die Canvas-Bibliothek mit „#include <Canvas/Canvas.mqh>“ ein, um nutzerdefinierte grafische Zeichnungen zu ermöglichen, z. B. Farbverläufe zwischen Indikatorlinien zur besseren Visualisierung. Dann deklarieren wir „obj_Canvas“ als eine globale Instanz der Klasse CCanvas, um die Bitmap-Leinwand zum Füllen von Bereichen zu verwalten. Wir definieren Eingabeparameter für die Anpassung: „fastPeriod“ mit dem Standardwert 5 für das Berechnungsfenster des schnellen Pivots, „slowPeriod“ auf 10 für das langsame, „upColor“ als „Dodger Blue“ für Aufwärtstrends, „downColor“ als „Crimson“ für Abwärtstrends, „fillOpacity“ auf 128 (halbtransparent) für Flächenfüllungen im Bereich von 0 bis 255, „arrowCode“ auf 77 für Wingdings-Symbole zum Trendbeginn, „showExtensions“ auf „true“, um Linien über den aktuellen Balken hinausragen zu lassen, „enableFilling“ auf „true“, um Flächenfüllungen zu aktivieren (deaktivieren für bessere Leistung), „extendBars“ auf 1 für die Anzahl der zu verlängernden Balken. Sie können den Pfeilcode so ändern, dass ein beliebiger aus der in MQL5 definierten Wingdings-Schriftart verwendet werden kann (siehe unten).

Dann weisen wir acht globale Arrays als Indikatorpuffer zu: „slowLineUpBuffer“ und „slowLineDownBuffer“ für separate langsame Aufwärts-/Abwärtslinien, „slowLineBuffer“ für interne langsame Berechnungen, „fastLineBuffer“ für die schnelle Linie, „fastLineColorBuffer“ für deren Farben, „trendArrowColorBuffer“ und „trendArrowBuffer“ für Pfeilpositionen/Farben und „trendBuffer“ für Trendzustände. Wir setzen die Globals für die Charteigenschaften „currentChartWidth“/„Height“ auf 0 für die Anfangsgröße, „currentChartScale“ auf 0, „firstVisibleBarIndex“ auf 0 für den Balken ganz links, „visibleBarsCount“ auf 0, „minPrice“ und „maxPrice“ auf 0,0 für den Bereich. Zur Optimierung verwenden wir statisch „lastRedrawTime“ als 0, um Neuzeichnungen zu verhindern, statisch „previousTrend“ als -1 zur Erkennung von Änderungen und „objectPrefix“ als „PTD_“ zur Benennung von Erweiterungen. Nach der Kompilierung erhalten wir das folgende Fenster mit den Eingabeparametern.

Nachdem wir die Eingaben gemacht haben, können wir zur Ereignisbehandlung der Initialisierung übergehen und das Programm initialisieren. Hier ist die Logik, die wir dafür verwenden.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set chart properties currentChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width currentChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height currentChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get chart scale firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get first visible bar visibleBarsCount = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars minPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get min price maxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get max price // Indicator buffers SetIndexBuffer(0,slowLineUpBuffer,INDICATOR_DATA); //--- Set slow up buffer SetIndexBuffer(1,slowLineDownBuffer,INDICATOR_DATA); //--- Set slow down buffer SetIndexBuffer(2,fastLineBuffer,INDICATOR_DATA); //--- Set fast buffer SetIndexBuffer(3,fastLineColorBuffer,INDICATOR_COLOR_INDEX); //--- Set fast color buffer SetIndexBuffer(4,trendArrowBuffer,INDICATOR_DATA); //--- Set arrow buffer SetIndexBuffer(5,trendArrowColorBuffer,INDICATOR_COLOR_INDEX); //--- Set arrow color buffer SetIndexBuffer(6,trendBuffer,INDICATOR_CALCULATIONS); //--- Set trend buffer SetIndexBuffer(7,slowLineBuffer,INDICATOR_CALCULATIONS); //--- Set slow buffer // Plot settings PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,slowPeriod); //--- Set slow draw begin PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,slowPeriod); //--- Set slow draw begin PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,fastPeriod); //--- Set fast draw begin PlotIndexSetInteger(3,PLOT_DRAW_BEGIN,fastPeriod); //--- Set fast draw begin PlotIndexSetInteger(4,PLOT_DRAW_BEGIN,slowPeriod); //--- Set arrow draw begin PlotIndexSetInteger(3,PLOT_ARROW,arrowCode); //--- Set arrow code // Line extensions PlotIndexSetInteger(0,PLOT_SHIFT,extendBars); //--- Set slow up shift PlotIndexSetInteger(1,PLOT_SHIFT,extendBars); //--- Set slow down shift PlotIndexSetInteger(2,PLOT_SHIFT,extendBars); //--- Set fast shift PlotIndexSetInteger(3,PLOT_SHIFT,0); //--- Set arrow shift // Set plot colors dynamically PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, upColor); //--- Set slow up color PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, downColor); //--- Set slow down color PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, upColor); //--- Set fast up color PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, downColor); //--- Set fast down color PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, upColor); //--- Set arrow up color PlotIndexSetInteger(4, PLOT_LINE_COLOR, 1, downColor); //--- Set arrow down color // Short name string shortName = "PTD(" + IntegerToString(fastPeriod) + "," + IntegerToString(slowPeriod) + ")"; //--- Set short name IndicatorSetString(INDICATOR_SHORTNAME, shortName); //--- Set indicator short name return(INIT_SUCCEEDED); //--- Return success }
In der Ereignisbehandlung von OnInit, das ausgeführt wird, wenn der Indikator an das Chart angehängt oder neu geladen wird, rufen wir zunächst die aktuellen Chartabmessungen und Ansichtsparameter ab und speichern sie, da wir sie später im Canvas-Rendering benötigen: Die Breite in Pixeln erhalten wir mit ChartGetInteger unter Verwendung von CHART_WIDTH_IN_PIXELS in „currentChartWidth“, die Höhe mit CHART_HEIGHT_IN_PIXELS in „currentChartHeight“, die Skalierung mit CHART_SCALE in „currentChartScale“, den ersten sichtbaren Balken mit CHART_FIRST_VISIBLE_BAR in „firstVisibleBarIndex“, die Anzahl der sichtbaren Balken mit CHART_VISIBLE_BARS in „visibleBarsCount“, den tiefsten sichtbaren Preis mit ChartGetDouble und „CHART_PRICE_MIN“ in „minPrice“, und Höchstpreis mit CHART_PRICE_MAX in „maxPrice“. Diese Werte ermöglichen ein adaptives Zeichnen auf der Grundlage der aktuellen Ansicht.
Anschließend ordnen wir die acht Puffer den Zeichnungen zu: Wir weisen „slowLineUpBuffer“ den Index 0 für Daten, „slowLineDownBuffer“ den Index 1 für Daten, „fastLineBuffer“ den Index 2 für Daten, „fastLineColorBuffer“ den Index 3 für den Farbindex, „trendArrowBuffer“ den Index 4 für Daten, „trendArrowColorBuffer“ den Index 5 für den Farbindex, „trendBuffer“ den Index 6 für Berechnungen, „slowLineBuffer“ den Index 7 für Berechnungen, unter Verwendung von SetIndexBuffer mit entsprechenden Typen. Wir konfigurieren die Zeichnungsstarts mit PlotIndexSetInteger und PLOT_DRAW_BEGIN: die Zeichnung der ‚langsamen‘ ab „slowPeriod“, der ‚schnellen‘ und die Pfeile ab „fastPeriod“ oder „slowPeriod“. Wir setzen das Symbol des Pfeils mit PLOT_ARROW auf „arrowCode“. Für Erweiterungen wenden wir Verschiebungen mit „PLOT_SHIFT“ an: extendBars für langsame Auf-/Abwärtsbewegungen und die schnellen, 0 für Pfeile. Wir setzen die Zeichenfarben dynamisch mit „PlotIndexSetInteger“ und „PLOT_LINE_COLOR“: Index 0 für „upColor“, 1 für „downColor“, schnelle Linie Index 2 mit „upColor“ auf 0 und „downColor“ auf 1, Pfeile Index 4 in gleicher Weise. Wir erstellen einen kurzen Namensstring als „PTD(“ plus der schnellen und der langsamen Periodenlänge, getrennt durch ein Komma, plus „)“, setzen ihn mit IndicatorSetString und INDICATOR_SHORTNAME. Wir geben INIT_SUCCEEDED zurück, um die erfolgreiche Initialisierung zu bestätigen. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Auf dem Bild ist zu sehen, dass wir den Indikator beim Laden korrekt eingestellt haben. Wir können die Puffer im Datenfenster sehen, und was wir jetzt machen müssen, ist, sie auszufüllen und die Indikatorberechnungen durchzuführen, um die Indikatorwerte mit unserer Strategie zu erhalten. Wir werden dies in der Ereignisbehandlung von OnCalculate wie folgt machen.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { // Always calculate buffers int startBar = prev_calculated - 1; //--- Set start bar if(startBar < 0) startBar = 0; //--- Adjust start bar for(int barIndex = startBar; barIndex < rates_total && !_StopFlag; barIndex++) { int fastStartBar = barIndex - fastPeriod + 1; //--- Calc fast start if(fastStartBar < 0) fastStartBar = 0; //--- Adjust fast start int slowStartBar = barIndex - slowPeriod + 1; //--- Calc slow start if(slowStartBar < 0) slowStartBar = 0; //--- Adjust slow start double slowHigh = high[ArrayMaximum(high, slowStartBar, slowPeriod)]; //--- Get slow high double slowLow = low[ArrayMinimum(low, slowStartBar, slowPeriod)]; //--- Get slow low double fastHigh = high[ArrayMaximum(high, fastStartBar, fastPeriod)]; //--- Get fast high double fastLow = low[ArrayMinimum(low, fastStartBar, fastPeriod)]; //--- Get fast low if(barIndex > 0) { slowLineBuffer[barIndex] = (close[barIndex] > slowLineBuffer[barIndex-1]) ? slowLow : slowHigh; //--- Set slow line fastLineBuffer[barIndex] = (close[barIndex] > fastLineBuffer[barIndex-1]) ? fastLow : fastHigh; //--- Set fast line trendBuffer[barIndex] = trendBuffer[barIndex-1]; //--- Set trend if(close[barIndex] < slowLineBuffer[barIndex] && close[barIndex] < fastLineBuffer[barIndex]) trendBuffer[barIndex] = 1; //--- Set up trend if(close[barIndex] > slowLineBuffer[barIndex] && close[barIndex] > fastLineBuffer[barIndex]) trendBuffer[barIndex] = 0; //--- Set down trend trendArrowBuffer[barIndex] = (trendBuffer[barIndex] != trendBuffer[barIndex-1]) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set arrow slowLineUpBuffer[barIndex] = (trendBuffer[barIndex] == 0) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow up slowLineDownBuffer[barIndex] = (trendBuffer[barIndex] == 1) ? slowLineBuffer[barIndex] : EMPTY_VALUE; //--- Set slow down } else { trendArrowBuffer[barIndex] = slowLineUpBuffer[barIndex] = slowLineDownBuffer[barIndex] = EMPTY_VALUE; //--- Set empties trendBuffer[barIndex] = fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = 0; //--- Set zeros fastLineBuffer[barIndex] = slowLineBuffer[barIndex] = close[barIndex]; //--- Set first lines } fastLineColorBuffer[barIndex] = trendArrowColorBuffer[barIndex] = trendBuffer[barIndex]; //--- Set colors } return(rates_total); //--- Return total rates }
In der Ereignisbehandlung von OnCalculate, der zentralen Rechenfunktion, die bei jedem neuen Tick oder Balken aufgerufen wird, um die Indikatorpuffer mit frischen Preisdaten zu aktualisieren und so sicherzustellen, dass die Zeichnungen die aktuellen Marktbedingungen widerspiegeln, legen wir den Startbalken für die Berechnungen als „prev_calculated – 1“ fest und setzen ihn bei negativen Werten auf 0, um ungültige Indizes zu vermeiden. Dann wird eine Schleife von „startBar“ bis „rates_total – 1“ durchlaufen, falls sie nicht gestoppt wird: Für jeden „barIndex“ wird der Start für die schnelle Periodenlänge mit „barIndex – fastPeriod + 1“ (mindestens 0) berechnet, für die langsame mit „barIndex – slowPeriod + 1“ (mindestens 0). Wir finden das langsame Hoch als maximales Hoch der langsamen Periodenlänge mit ArrayMaximum auf dem hohen Array von „slowStartBar“, das langsame Tief als minimales Tief mit ArrayMinimum, das schnelle Hoch als maximales Hoch über die schnelle Periodenlänge, das schnelle Tief als minimales Tief.
Für „barIndex > 0“ wird „slowLineBuffer[barIndex]“ auf „slowlow“ gesetzt, wenn der Schlusskurs über der vorherigen langsamen Linie liegt (aufwärts), andernfalls auf das „slowhigh“ (abwärts); „fastLineBuffer[barIndex]“ auf „fastlow“, wenn der Schlusskurs über dem vorherigen „fast“ liegt, andernfalls auf „fasthigh“. Wir kopieren den vorherigen Trend in „trendBuffer[barIndex]“ und aktualisieren ihn dann auf 1 (aufwärts), wenn der Schlusskurs sowohl unter der aktuellen langsamen als auch der schnellen Linie liegt, oder auf 0 (abwärts), wenn er über beiden liegt. Wir platzieren einen Pfeil in „trendArrowBuffer[barIndex]“ am Wert von „slowline“, wenn sich der Trend gegenüber dem vorhergehenden geändert hat, ansonsten ist er leer. Wir setzen „slowLineUpBuffer[barIndex]“ auf „slowline“, wenn Trend 0, sonst leer, „slowLineDownBuffer[barIndex]“ auf „slowline“, wenn Trend 1 sonst leer. Für den ersten Balken (“barIndex == 0“) setzen wir die Pfeile und die langsamen Auf-/Abwärtspfeile auf leer, die Trend-/Schnellfarbe/Pfeilfarbe auf 0 und „fastline“/„slowline“ auf close[0] für die Initialisierung. Wir weisen dem Trendwert die Farbindizierung „fastLineColorBuffer[barIndex]“ und „trendArrowColorBuffer[barIndex]“ zu. Wir geben „rates_total“ zurück, um alle verarbeiteten Balken anzuzeigen. Nach der Kompilierung erhalten wir das folgende Ergebnis.

Auf dem Bild können wir sehen, dass der Indikator perfekt berechnet und auf dem Chart und den mit Datenwerten gefüllten Pufferarrays angezeigt wird. Was bleibt, ist das Hinzufügen von Preisen auf der rechten Seite der Indikatorlinien, sodass wir die genauen Linienpreise zur Information kennen. Das ist ganz einfach. Aus Gründen der Modularität werden wir die Logik in einer Funktion unterbringen.
//+------------------------------------------------------------------+ //| Draw right price extension line/label | //+------------------------------------------------------------------+ bool drawRightPrice(string objectName, datetime lineTime, double linePrice, color lineColor, ENUM_LINE_STYLE lineStyle = STYLE_SOLID) { bool objectExists = (ObjectFind(0, objectName) >= 0); //--- Check exists if(!objectExists) { if(!ObjectCreate(0, objectName, OBJ_ARROW_RIGHT_PRICE, 0, lineTime, linePrice)) { Print("Failed to create ", objectName); //--- Log failure return false; //--- Return failure } } else { ObjectSetInteger(0, objectName, OBJPROP_TIME, 0, lineTime); //--- Set time ObjectSetDouble(0, objectName, OBJPROP_PRICE, 0, linePrice); //--- Set price } long currentScale = ChartGetInteger(0, CHART_SCALE); //--- Get scale int lineWidth = 1; //--- Init width if(currentScale <= 1) lineWidth = 1; //--- Set width small else if(currentScale <= 3) lineWidth = 2; //--- Set width medium else lineWidth = 3; //--- Set width large ObjectSetInteger(0, objectName, OBJPROP_COLOR, lineColor); //--- Set color ObjectSetInteger(0, objectName, OBJPROP_WIDTH, lineWidth); //--- Set width ObjectSetInteger(0, objectName, OBJPROP_STYLE, lineStyle); //--- Set style ObjectSetInteger(0, objectName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objectName, OBJPROP_SELECTABLE, false); //--- Set not selectable ObjectSetInteger(0, objectName, OBJPROP_SELECTED, false); //--- Set not selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } // we then call this function in the "OnCalculate" event handler // Draw line extensions if enabled if(showExtensions && rates_total > 0) { int latestBarIndex = rates_total - 1; //--- Get latest index double slowLineValue = slowLineBuffer[latestBarIndex]; //--- Get slow value double fastLineValue = fastLineBuffer[latestBarIndex]; //--- Get fast value double currentTrend = trendBuffer[latestBarIndex]; //--- Get trend color lineColor = (currentTrend == 0.0) ? upColor : downColor; //--- Set line color datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time long timeOffset = (long)extendBars * PeriodSeconds(_Period); //--- Calc offset datetime extensionTime = currentBarTime + (datetime)timeOffset; //--- Calc extension time drawRightPrice(objectPrefix + "SLOW", extensionTime, slowLineValue, lineColor, STYLE_SOLID); //--- Draw slow extension drawRightPrice(objectPrefix + "FAST", extensionTime, fastLineValue, lineColor, STYLE_DOT); //--- Draw fast extension }
Für die Logik zur Darstellung des rechten Preises definieren wir die Funktion „drawRightPrice“, um ein Pfeilobjekt für den rechten Preis zu erstellen oder zu aktualisieren, das die Indikatorlinien horizontal nach rechts verlängert und so einen visuellen Vorsprung für zukünftige Balken auf der Grundlage der Eingabeeinstellungen bietet. Zunächst wird mit ObjectFind geprüft, ob das Objekt existiert. Ist dies nicht der Fall, wird mit ObjectCreate ein OBJ_ARROW_RIGHT_PRICE mit den angegebenen Werten „lineTime“ und „linePrice“ erstellt, wobei Fehler protokolliert werden und false zurückgegeben wird, wenn der Vorgang nicht erfolgreich war. Wenn es existiert, aktualisieren wir seine Zeit- und Preisanker mit ObjectSetInteger für OBJPROP_TIME und ObjectSetDouble für „OBJPROP_PRICE“. Die aktuelle Chartskalierung wird mit ChartGetInteger und CHART_SCALE in „currentScale“ abgerufen, dann wird „lineWidth“ basierend auf der Skalierung eingestellt: 1 für Maßstab <=1, 2 für <=3, 3 für größer, um die Sichtbarkeit bei verschiedenen Zooms zu gewährleisten.
Wir konfigurieren das Objekt: Farbe mit OBJPROP_COLOR auf „lineColor“, Breite auf „lineWidth“, Stil auf „lineStyle“ (standardmäßig solid), Vordergrund mit „OBJPROP_BACK“ false, nicht auswählbar oder ausgewählt mit „OBJPROP_SELECTABLE“ und „OBJPROP_SELECTED“ false. Wir zeichnen das Chart mit ChartRedraw neu und geben bei Erfolg true zurück. Wir rufen diese Funktion in der Ereignisbehandlung von OnCalculate auf, wenn „showExtensions“ wahr ist und Balken existieren: wir holen den letzten Index als „rates_total – 1“, holen langsame und schnelle Werte aus Puffern, Trend aus „trendBuffer“, wählen „lineColor“ als „upColor“, wenn Trend 0.0 sonst „downColor“, holen die aktuelle Barzeit mit iTime bei Shift 0, berechnen Offset als „extendBars * PeriodSeconds(_Period)“ und, „extension time“ als „current“ plus „offset“, dann rufen wir „drawRightPrice“ für slow mit solid style und fast mit dot unter Verwendung von „objectPrefix“ + „SLOW“ oder „FAST“ auf. Nach der Kompilierung, erhalten wir folgendes Ergebnis.

Mit dem richtigen Preis sind wir nun komplett mit dem Hauptindikator. Was bleibt, ist das Rendern der Leinwand, um die Grenzen des Indikators wie gewünscht zu füllen, und das ist alles. Dazu werden wir einige Hilfsfunktionen definieren.
//+------------------------------------------------------------------+ //| Convert chart scale to bar width | //+------------------------------------------------------------------+ int BarWidth(int chartScale) { return (int)MathPow(2.0, chartScale); //--- Return bar width } //+------------------------------------------------------------------+ //| Convert bar shift to x pixel | //+------------------------------------------------------------------+ int ShiftToX(int barShift) { return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1); //--- Return x pixel } //+------------------------------------------------------------------+ //| Convert price to y pixel | //+------------------------------------------------------------------+ int PriceToY(double price) { if(maxPrice - minPrice == 0.0) return 0; //--- Return zero if no range return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1); //--- Return y pixel }
Zunächst wird die Funktion „BarWidth“ definiert, um die Pixelbreite jedes Balkens auf der Grundlage der aktuellen Chartskalierung zu berechnen, wobei eine ganze Zahl aus „MathPow(2.0, chartScale)“ zurückgegeben wird – dies liefert eine exponentielle Schätzung (1 bei Skala 0, 2 bei 1, 4 bei 2 usw.) für die Positionierung in Leinwandkoordinaten. Dann implementieren wir die Funktion „ShiftToX“, um eine Balkenverschiebung (relativ zum äußersten linken sichtbaren Balken) in eine x-Pixel-Position im Chart umzuwandeln, indem wir „(firstVisibleBarIndex – barShift) * BarWidth(currentChartScale) – 1“ in int umwandeln – dies positioniert die Elemente von rechts (neuer) nach links (älter), angepasst um 1 für die Ausrichtung. Schließlich erstellen wir die Funktion „PriceToY“, um einen Preiswert auf eine y-Pixel-Koordinate auf der Leinwand abzubilden, wobei 0 zurückgegeben wird, wenn keine Preisspanne vorhanden ist (“maxPrice – minPrice == 0.0“), andernfalls wird „currentChartHeight * (maxPrice – price) / (maxPrice – minPrice) – 1“ mit MathRound in eine Ganzzahl umgewandelt – dadurch wird die y-Achse invertiert (höhere Preise oben) und für eine präzise Zeichnung um 1 korrigiert. Wir werden diese Funktionen nun verwenden, um die Hauptfunktion zu erstellen, die die schwere Arbeit übernimmt.
//+-----------------------------------------------------------------------------------------+ //| Fill area between two lines using trend for color with gradient alpha from slow to fast | //+-----------------------------------------------------------------------------------------+ void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0) { int firstVisibleBar = firstVisibleBarIndex; //--- Get first visible int totalBarsToDraw = visibleBarsCount + extendShift; //--- Calc bars to draw int bufferSize = (int)ArraySize(slowLineValues); //--- Get buffer size if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return; //--- Return if invalid int previousX = -1; //--- Init previous X int previousY1 = -1; //--- Init previous Y1 int previousY2 = -1; //--- Init previous Y2 for(int offset = 0; offset < totalBarsToDraw; offset++) { int barPosition = firstVisibleBar - offset; //--- Calc bar position int x = ShiftToX(barPosition); //--- Calc x if(x >= currentChartWidth) break; //--- Break if beyond width int dataBarShift = firstVisibleBar - offset + extendShift; //--- Calc data shift int bufferBarIndex = bufferSize - 1 - dataBarShift; //--- Calc buffer index if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize) { previousX = -1; //--- Reset previous X continue; //--- Continue } double value1 = slowLineValues[bufferBarIndex]; //--- Get value1 double value2 = fastLineValues[bufferBarIndex]; //--- Get value2 if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE) { previousX = -1; //--- Reset previous X continue; //--- Continue } int y1 = PriceToY(value1); //--- Calc y1 int y2 = PriceToY(value2); //--- Calc y2 double currentTrend = trendValues[bufferBarIndex]; //--- Get trend uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF); //--- Set base RGB if(previousX != -1 && x > previousX) { double deltaX = x - previousX; //--- Calc delta X int endColumn = MathMin(x, currentChartWidth - 1); //--- Calc end column double maxT = (double)(endColumn - previousX) / deltaX; //--- Calc max T for(int column = previousX; column <= endColumn; column++) { double t = (column - previousX) / deltaX; //--- Calc t double interpolatedY1 = previousY1 + t * (y1 - previousY1); //--- Interpolate Y1 double interpolatedY2 = previousY2 + t * (y2 - previousY2); //--- Interpolate Y2 int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2)); //--- Calc upper Y int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2)); //--- Calc lower Y if(upperY > lowerY) continue; //--- Continue if invalid double slowLineY = interpolatedY1; //--- Set slow Y double height = MathAbs(interpolatedY1 - interpolatedY2); //--- Calc height if(height == 0.0) continue; //--- Continue if no height // Fill per row with gradient from slow (opaque) to fast (transparent) for(int row = upperY; row <= lowerY; row++) { double distanceFromSlow = MathAbs(row - slowLineY); //--- Calc distance double gradientFraction = distanceFromSlow / height; //--- Calc fraction uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction)); //--- Calc alpha if(alphaValue > fillAlpha) alphaValue = fillAlpha; //--- Cap alpha uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB; //--- Set pixel color obj_Canvas.FillRectangle(column, row, column, row, pixelColor); //--- Fill pixel } } } previousX = x; //--- Update previous X previousY1 = y1; //--- Update previous Y1 previousY2 = y2; //--- Update previous Y2 } } //+------------------------------------------------------------------+ //| Redraw the canvas | //+------------------------------------------------------------------+ void Redraw(void) { if(currentChartWidth <= 0 || currentChartHeight <= 0) return; //--- Return if invalid size uint defaultColor = 0; //--- Default color obj_Canvas.Erase(defaultColor); //--- Erase canvas DrawFilling(slowLineBuffer, fastLineBuffer, trendBuffer, upColor, downColor, (uchar)fillOpacity, extendBars); //--- Draw filling obj_Canvas.Update(); //--- Update canvas }
Wir definieren die Funktion „DrawFilling“, um den Bereich zwischen der langsamen und der schnellen Linie auf der Leinwand mit einem Farbverlauf zu füllen, indem wir den Trend verwenden, um die Farben für oben oder unten auszuwählen und die Deckkraft von der langsamen Linie (volles „fillAlpha“) zur schnellen Linie (transparent) zu verblassen, um eine sanfte visuelle Verjüngung zu schaffen, während wir uns um die „extendShift“-Balken erweitern, wenn sie aktiviert sind. Zunächst wird der erste sichtbare Balken ermittelt und „totalBarsToDraw“ als Anzahl der sichtbaren Balken plus „extendShift“ berechnet, die Puffergröße aus „slowLineValues“ geholt und bei Ungültigkeit oder Nichtübereinstimmung mit Fast/Trend-Puffern vorzeitig zurückgegeben. Wir initialisieren die vorherigen X/Y1/Y2 auf -1 für die Interpolationsverfolgung und ziehen dann eine Schleife über die Offsets von 0 bis „totalBarsToDraw – 1“: Für jeden Balken berechnen wir die Position als „firstVisibleBar – offset“, x-Pixel mit „ShiftToX“, Abbruch, wenn größer als die Chartbreite; Berechnung der Datenverschiebung mit sichtbarer Balken minus Offset plus „extendShift“ und des Pufferindex als Größe minus 1 minus Datenverschiebung – wird übersprungen, wenn außerhalb der Grenzen oder bei leeren Werten, Zurücksetzen des vorherigen X.
Dann holen wir uns den langsamen Wert1 und den schnellen Wert2 aus den Puffern, konvertieren in y1/y2 mit „PriceToY“, bestimmen den Trend aus „trendValues“ und setzen das Basis-RGB aus „fillUpColor“ oder „fillDownColor“ mit ColorToARGB maskiert auf RGB. Wenn das vorherige X gültig ist und das aktuelle X > „previousX“, wird interpoliert: Delta X berechnen, Endspalte als Minimum von X und Breite minus 1, Max t als (Ende – previousX) / delta. Für jede „column“ von „previousX“ bis zum Ende wird t als (column – previousX) / delta berechnet, y1 und y2 interpoliert, Min/Max auf das obere/untere Y gerundet und übersprungen, wenn oberes > unteres ist. Wir setzen slowLineY auf interpoliertes y1, Höhe als abs y1 minus y2 – überspringen das, wenn es null ist. Für jede Zeile von oben nach unten berechnen wir den Abstand von slow, fraction als Abstand / Höhe, alpha als „fillAlpha * (1.0 – fraction)“, wandeln es in uchar, beschränken es auf unter „fillAlpha“, kombinieren die Pixelfarbe als alpha-shifted 24 bits oder-ed mit base und füllen ein einzelnes Pixel in Spalte/Zeile mit „obj_Canvas.FillRectangle“ (1x1). Für die nächste Iteration wird das vorherige X zum aktuellen X, Y1 zu Y1 und Y2 zu Y2.
Wir implementieren die Funktion „Redraw“, um die Zeichnung der Leinwand bei Bedarf zu aktualisieren, und kehren frühzeitig zurück, wenn die Breite oder Höhe ungültig ist (<=0). Wir setzen die Standardfarbe auf 0 (transparent), löschen die Leinwand mit „obj_Canvas.Erase“, rufen „DrawFilling“ auf und übergeben dabei die Puffer für langsame/schnelle/Trenddaten, die Farben für Aufwärts-/Abwärtsbewegungen, „fillOpacity“ (als uchar konvertiert) sowie „extendBars“; anschließend aktualisieren wir die Leinwandanzeige mit „obj_Canvas.Update“. Dies ist die Funktion, die wir aufrufen, wenn wir die Grenzen des Indikators füllen wollen, wie unten in der Ereignisbehandlung der Berechnung gezeigt.
if(!enableFilling) return(rates_total); //--- Return if no filling // Canvas logic only if enabled bool isNewBar = (rates_total > prev_calculated); //--- Check new bar bool hasTrendChanged = false; //--- Init trend changed if(rates_total > 0 && trendBuffer[rates_total-1] != previousTrend) { hasTrendChanged = true; //--- Set changed previousTrend = trendBuffer[rates_total-1]; //--- Update previous trend } // Update chart properties (only if changed) bool hasChartChanged = false; //--- Init chart changed int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height int newChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get new scale int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get new min price double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get new max price if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) { obj_Canvas.Resize(newChartWidth, newChartHeight); //--- Resize canvas currentChartWidth = newChartWidth; //--- Update width currentChartHeight = newChartHeight; //--- Update height hasChartChanged = true; //--- Set changed } if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount || newMinPrice != minPrice || newMaxPrice != maxPrice) { currentChartScale = newChartScale; //--- Update scale firstVisibleBarIndex = newFirstVisibleBar; //--- Update first visible visibleBarsCount = newVisibleBars; //--- Update visible bars minPrice = newMinPrice; //--- Update min price maxPrice = newMaxPrice; //--- Update max price hasChartChanged = true; //--- Set changed } // Redraw only on: new bar, trend change, or chart resize/scroll. Debounce to 1x/sec max. datetime currentTime = TimeCurrent(); //--- Get current time if((isNewBar || hasTrendChanged || hasChartChanged) && (currentTime - lastRedrawTime >= 1)) { Redraw(); //--- Redraw canvas lastRedrawTime = currentTime; //--- Update last redraw }
Hier geben wir „rates_total“ frühzeitig zurück, wenn „enableFilling“ falsch ist, und überspringen die Canvas-Logik, um die Leistung zu verbessern, wenn das Ausfüllen deaktiviert ist. Wir führen canvas-spezifische Operationen nur dann aus, wenn das Ausfüllen aktiviert ist: Wir prüfen, ob ein neuer Balken mit „rates_total > prev_calculated“ in „isNewBar“ vorhanden ist, erkennen Trendänderungen, indem wir „trendBuffer[rates_total-1]“ mit „previousTrend“ vergleichen, sofern Balken vorhanden sind, setzen „hasTrendChanged“ auf „true“ und aktualisieren „previousTrend“, falls ein Unterschied besteht. Wir überwachen die Änderungen im Chart: initialisieren „hasChartChanged“ auf false, holen neue Breite/Höhe/Skalierung/erster sichtbare/alle sichtbaren Balken/Min-Preis/Max-Preis mit den Funktionen ChartGetInteger und ChartGetDouble. Wenn die Breite oder Höhe abweicht, wird die Größe des Canvas mit „obj_Canvas.Resize“ an die neuen Abmessungen angepasst, „currentChartWidth“ und „currentChartHeight“ aktualisiert und „hasChartChanged“ auf true gesetzt. Wenn sich die Skalierung, der erste sichtbare Wert, die Anzahl der sichtbaren Werte, der Mindest- oder Höchstpreis geändert haben, aktualisieren wir die Globals entsprechend und setzen „hasChartChanged“ auf true.
Schließlich optimieren wir das Neuzeichnen: Wir holen uns die aktuelle Zeit mit TimeCurrent in „currentTime“, und wenn sich ein neuer Balken, Trend oder Chart ändert, und mindestens 1 Sekunde seit „lastRedrawTime“, rufen wir „Redraw“, um die Leinwand zu aktualisieren, aktualisieren „lastRedrawTime“ mit der aktuellen Zeit. Dies beschränkt auf höchstens einmal pro Sekunde, wodurch unnötige Berechnungen vermieden werden. Jetzt müssen wir nur noch die Änderungen neu rendern, wenn die Chartreignisse erkannt werden, und sie bei der Deinitialisierung löschen, wie unten dargestellt.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(id != CHARTEVENT_CHART_CHANGE || !enableFilling) return; int newChartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get new width int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get new height if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight) { obj_Canvas.Resize(newChartWidth, newChartHeight); //--- Resize canvas currentChartWidth = newChartWidth; //--- Update width currentChartHeight = newChartHeight; //--- Update height Redraw(); //--- Redraw canvas return; //--- Return } int newChartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get new scale int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); //--- Get new first visible int newVisibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get new visible bars double newMinPrice = ChartGetDouble(0, CHART_PRICE_MIN, 0); //--- Get new min price double newMaxPrice = ChartGetDouble(0, CHART_PRICE_MAX, 0); //--- Get new max price if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount || newMinPrice != minPrice || newMaxPrice != maxPrice) { currentChartScale = newChartScale; //--- Update scale firstVisibleBarIndex = newFirstVisibleBar; //--- Update first visible visibleBarsCount = newVisibleBars; //--- Update visible bars minPrice = newMinPrice; //--- Update min price maxPrice = newMaxPrice; //--- Update max price Redraw(); //--- Redraw canvas } } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(enableFilling) obj_Canvas.Destroy(); //--- Destroy canvas if enabled ObjectsDeleteAll(0,objectPrefix,0,OBJ_ARROW_RIGHT_PRICE); //--- Delete right price arrows ChartRedraw(0); //--- Redraw chart }
Hier rufen wir die Ereignisbehandlung von OnChartEvent auf, um Ereignisse im Zusammenhang mit dem Chart zu behandeln; insbesondere reagieren wir nur auf Änderungen, wenn das Ausfüllen mit „enableFilling“ true aktiviert ist; andernfalls kehren wir vorzeitig zurück. Zuerst holen wir die neue Chartbreite und -höhe mit ChartGetInteger unter Verwendung von CHART_WIDTH_IN_PIXELS und CHART_HEIGHT_IN_PIXELS. Wenn einer der beiden Werte von „currentChartWidth“ oder „currentChartHeight“ abweicht, wird die Größe des Canvas mit „obj_Canvas.Resize“ an die neuen Abmessungen angepasst, die Globals aktualisiert, „Redraw“ aufgerufen, um die Füllung zu aktualisieren, und zurückgegeben. Wir erhalten dann die neue Skalierung mit CHART_SCALE, den ersten sichtbaren Balken mit „CHART_FIRST_VISIBLE_BAR“, die sichtbaren Balken mit „CHART_VISIBLE_BARS“, den Mindestpreis mit ChartGetDouble und „CHART_PRICE_MIN“, den Höchstpreis mit CHART_PRICE_MAX. Wenn sich einer der Parameter „scale“, „first visible“, „visible count“, „min“ oder „max price“ gegenüber den gespeicherten globalen Werten geändert hat, aktualisieren wir „currentChartScale“, „firstVisibleBarIndex“, „visibleBarsCount“, „minPrice“, „maxPrice“ und rufen „Redraw“ auf, um die Leinwandfüllung an die neue Ansicht anzupassen.
In der Ereignisbehandlung von OnDeinit, die ausgeführt wird, wenn der Indikator entfernt oder das Terminal geschlossen wird, um die Ressourcen aufzuräumen: Wenn „enableFilling“ wahr ist, zerstören wir die Leinwand mit „obj_Canvas.Destroy“; wir löschen alle Pfeilobjekte mit einem Preis rechts, die mit „objectPrefix“ beginnen, mit ObjectsDeleteAll unter Angabe von Chart 0, Window 0, Typ OBJ_ARROW_RIGHT_PRICE; dann zeichnen wir das Chart mit der Funktion ChartRedraw neu. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Anhand der Visualisierung können wir sehen, dass wir den Indikator berechnen und die Leinwand füllen, wenn dies erlaubt ist, und somit unsere Ziele erreichen. Bleibt nur noch der Backtest des Programms, und das 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
Zusammenfassend lässt sich sagen, dass wir in MQL5 einen pivotbasierten Trendindikator entwickelt haben, der schnelle und langsame Pivot-Linien aus Hoch-/Tief-Bereichen berechnet, Trendrichtungen mit farbcodierten Linien und Pfeilen anzeigt, die Linien optional für Prognosen verlängert und Bereiche mit einem Farbverlauf auf der Canvas-Oberfläche füllt, um visuelle Tiefe zu erzeugen – und das alles bei optimierter Neuzeichnung bei neuen Balken oder Änderungen im Chart. Dieser Indikator bietet ein flexibles Werkzeug zur Trenderkennung mit anpassbaren Eingabeparametern. In den kommenden Teilen werden wir fortgeschrittene Indikatoren wie Volatilitätskanäle oder Momentum-Oszillatoren mit Elementen des maschinellen Lernens untersuchen. Bleiben Sie dran. Bleiben Sie dran.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20610
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 3): Nachweis eines nicht zufälligen Marktverhaltens mit MQL5
Klassische Strategien neu interpretiert (Teil 20): Moderne stochastische Oszillatoren
Datenwissenschaft und ML (Teil 47): Marktprognosen mithilfe des DeepAR-Modells in Python
Codex-Pipelines: Von Python zu MQL5 für die Indikatorauswahl – eine Multi-Quartal-Analyse des FXI ETF
- 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.