Workshop für nutzerdefinierte Indikatoren (Teil 1): Aufbau des Supertrend-Indikators in MQL5
Einführung
Stellen Sie sich Folgendes vor: Es ist Anfang 2022. Der Krypto-Winter ist hart, und Bitcoin ist gerade unter 20.000 Dollar gefallen. Überall herrscht Panik – die Foren sind überschwemmt von Verzweiflung, die Portfolios bluten rot, und die meisten Händler sind entweder in Angst erstarrt oder verkaufen verzweifelt am Boden.
Auf einem ruhigen Chart jedoch wechselt eine einzelne dicke Linie ruhig von Rot zu Grün und verläuft eng unter dem Preis, als ob sie flüstern würde: „Entspann dich, der Abwärtstrend ist gerade zu Ende.“ Diese Linie war der Supertrend-Indikator.
Während die meisten Händler mutmaßen, lieferte er eine klare, eindeutige Botschaft: Der Trend hat sich geändert. Wer dem Indikator vertraute, überstand nicht nur das Marktrauschen, sondern erwischte auch die gesamte darauffolgende Bewegung.
Trotz seiner Einfachheit und langjährigen Beliebtheit ist der Supertrend nach wie vor einer der am meisten missverstandenen und schlecht implementierten Indikatoren auf Handelsplattformen. Viele Versionen repainten die Signale, verhalten sich im Live-Handel inkonsistent oder verbergen ihre interne Logik hinter undurchsichtigen Berechnungen. Für Entwickler ergibt sich daraus ein wiederkehrendes Problem: Händler fordern immer wieder Supertrend-basierte Tools an, doch zuverlässige, erweiterbare und gut dokumentierte Implementierungen sind erstaunlich selten.
Dieser Artikel greift genau dieses Problem auf. Anstatt Supertrend wie eine Blackbox zu behandeln, werden wir einen Supertrend-Indikator in MQL5 ohne Repainting entwerfen und zwar von Grund auf entwickeln und implementieren – mit einem Fokus auf Klarheit, Korrektheit und einer professionelle Struktur. Der Indikator wird seine internen Puffer in dokumentierter, entwicklerfreundlicher Form bereitstellen, sodass er sich nicht nur für die visuelle Analyse, sondern auch für kommerzielle EAs, Strategien und weitere Erweiterungen eignet.
Gleichzeitig erhalten Studenten und angehende MQL5-Entwickler einen praktischen Einblick in die saubere Architektur von Indikatoren, das richtige Puffermanagement und sichere Live-Markt-Berechnungstechniken – Fähigkeiten, die weit über Supertrend selbst hinausgehen.
Willkommen zum ersten Teil des Custom-Indicator-Workshops, einer neuen Serie, in der wir aufhören, passive Konsumenten von vorgefertigten Tools zu sein, und anfangen, selbst kreativ zu werden. Wir nehmen beliebte Indikatoren auseinander, bauen sie neu auf und machen sie zu unseren eigenen – wir geben uns nicht mehr mit den festen Parametern oder der klobigen Darstellung eines anderen zufrieden. Indem wir sie selbst in MQL5 kodieren, erhalten wir die volle Kontrolle und ein viel tieferes Verständnis dafür, wie sie wirklich funktionieren.
Lassen Sie uns zu Beginn des Workshops verstehen, was Supertrend wirklich ist und warum es wichtig ist, ihn richtig aufzubauen.
Was der Supertrend-Indikator wirklich ist
Bevor Sie eine einzige Linie MQL5-Code schreiben, ist es wichtig, zu verstehen, was der Supertrend-Indikator eigentlich darstellt und was nicht.
Der Supertrend-Indikator wurde erstmals von Olivier Seban vorgestellt, der ihn zunächst auf der TradingView-Charting-Plattform implementierte und verbreitete. Seitdem hat er sich zu einem der am weitesten verbreiteten Trendfolge-Werkzeuge auf Retail-Handelsplattformen entwickelt, insbesondere für Kryptowährungen, Devisen und Indizes.
Im Kern ist Supertrend ein volatilitätsbasierter Trendindikator. Er sagt keine Preisrichtung voraus, prognostiziert keine Umkehrungen und versucht auch nicht, Höchst- und Tiefststände zu ermitteln. Stattdessen beantwortet er eine viel einfachere – und viel praktischere – Frage: „Ist der Markt angesichts der aktuellen Volatilität eher im Auf- oder im Abwärtstrend?“ Dies wird durch eine Kombination erreicht:
- Average True Range (ATR) zur Messung der Volatilität
- Ein Preis-Mittelpunkt-Referenzwert
- Ein dynamisches Trailing-Band, das die Seite wechselt, wenn sich der Trend ändert
Bleibt der Preis über dem Trailing-Band, geht der Indikator von einem Aufwärtstrend aus und zeichnet die Linie unterhalb des Preises ein. Wenn der Preis unter dem Band schließt, kehrt sich die Annahme um, und die Linie bewegt sich über dem Preis, was einen Abwärtstrend signalisiert.
Diese einzige Umkehrung, ob über oder unter dem Preis, verleiht Supertrend seine Klarheit. Es gibt keine Schwingungen, keine konkurrierenden Signale und keine Interpretationsgymnastik. Eine Linie. Eine Richtung. Ein Status. Aufgrund dieser Einfachheit ist Supertrend besonders beliebt bei Trendfolgestrategien, Trailing-Stop-Systemen und algorithmischen Entscheidungen. In vielen Online-Diskussionen geht es jedoch vor allem um Handelsstrategien, Parameteroptimierung und Leistungsvergleiche. Diese Themen werden an anderer Stelle ausführlich behandelt und sind nicht der Schwerpunkt dieses Workshops.
Für Leser, die an einer tiefergehenden konzeptionellen Erklärung interessiert sind, bietet Investopedia einen soliden, neutralen Überblick über die Theorie und Verwendung des Indikators.
Wie unser Supertrend aussehen und sich verhalten wird
Bevor wir an den Code gehen, sollten wir uns darüber im Klaren sein, was wir erstellen und wie es sich im Chart verhalten soll. Der Supertrend-Indikator stützt sich ausschließlich auf die ATR, um zu bestimmen, wo seine Trendlinie gezogen werden sollte. Anstatt das Rad neu zu erfinden, werden wir die Tatsache nutzen, dass MetaTrader 5 bereits einen hoch optimierten ATR-Indikator bietet. Unser nutzerdefinierter Supertrend verwendet ein Indikator-Handle, um ATR-Werte zu verwenden, sodass wir uns auf die Trendlogik und -visualisierung und nicht auf die Volatilitätsberechnungen konzentrieren können.
Dieser Indikator wird als nutzerdefinierter MQL5-Indikator implementiert und direkt im Hauptchartfenster geplottet. Der Chart verwendet einen weißen Hintergrund, um den Kontrast zu erhöhen und die visuelle Klarheit zu verbessern. Zu jedem Zeitpunkt behält der Indikator einen einzigen internen Zustand bei, der das aktuelle Marktumfeld beschreibt. Dieser Zustand kann nur einen von zwei Werten annehmen: bullisch oder bärisch.
In einem bullischen Umfeld wird eine dünne seegrüne Linie unterhalb des Preises gezogen. Diese Linie wird berechnet, indem man den Mittelpunkt der Kerze nimmt und die ATR, multipliziert mit dem nutzerdefinierten Multiplikator, abzieht, sodass sie den Preisen dynamisch folgen kann, wenn sich die Volatilität ändert. Gleichzeitig nehmen alle Preiskerzen auf dem Chart die gleiche Farbe an: SeaGreen. Dies ist eine unmittelbare visuelle Bestätigung dafür, dass der Markt in einen Aufwärtstrend übergeht.
In einem bärischen Umfeld kehrt sich das Verhalten um. Eine dünne, schwarze Linie wird oberhalb des Preises eingezeichnet, bis sich der Trend ändert. Alle Kerzen werden ebenfalls schwarz gefärbt. In dem Moment, in dem der Preis das Trailing-Band in die entgegengesetzte Richtung durchbricht, kehrt sich der Trendzustand um und der Indikator wird entsprechend aktualisiert.
Sehen Sie sich dazu bitte die folgende Abbildung an. Sie zeigt das endgültige Aussehen des Indikators in einem Live-Chart. Sie wissen also, dass der Trendstatus ohne zusätzliche Indikatoren oder Interpretationen mitgeteilt wird. Eine Linie. Eine Farbe. Eine Richtung.

Standardmäßig wird der Indikator unter Verwendung des Zeitrahmens des Charts berechnet und dargestellt. In dieser Phase wird keine Logik für mehrere Zeitrahmen eingeführt, um das Verhalten vorhersehbar und transparent zu halten.
Um die Empfindlichkeit des Supertrends zu steuern, werden zwei Nutzereingabeparameter angezeigt:
- atrPeriod, das das ATR-Rückblickfenster definiert
- atrMultiplier, der steuert, wie eng die Supertrend-Linie dem Preis folgt
Schließlich, und das ist das Wichtigste, arbeitet diese Implementierung ohne Repainting. Sobald eine Kerze schließt und eine Trendentscheidung getroffen wird, wird sie nachträglich nicht mehr verändert. Damit eignet sich der Indikator nicht nur für die diskretionäre Analyse, sondern auch für den Einsatz in Expert Advisors, Signalgeneratoren und kommerziellen Handelssystemen.
Implementierung von Supertrend in MQL5
Nun gehen wir in die eigentliche Implementierung. In diesem Abschnitt beginnen wir mit der Umsetzung des Supertrend-Konzepts in einen funktionierenden, nutzerdefinierten Indikator, der in MQL5 geschrieben wurde.
Bevor der Leser fortfährt, muss er einige Grundvoraussetzungen erfüllen. Sie sollten bereits ein Grundverständnis der MQL5-Programmierung haben. Sie sollten auch mit der Navigation im MetaTrader 5 Desktop-Terminal vertraut sein, einschließlich des Anhängens von Indikatoren an Charts. Schließlich sollten Sie mit dem MetaEditor vertraut sein und wissen, wie Sie neue Dateien erstellen, Code kompilieren, Fehler überprüfen und gegebenenfalls debuggen können. Wenn Ihnen diese Grundlagen vertraut sind, können Sie fortfahren.
Der vollständige Quellcode für den endgültigen Indikator ist diesem Artikel beigefügt. Um die besten Ergebnisse zu erzielen, wird dringend empfohlen, dass Sie mitcodieren. Den Code selbst zu schreiben und ihn mit dem zur Verfügung gestellten Quellcode zu vergleichen, ist eine der effektivsten Methoden, um die Entwicklung von Indikatoren zu erlernen. Wenn Sie dabei auf Fehler stoßen, können Sie Ihre Arbeit jederzeit mit der beigefügten Implementierung abgleichen.
Anlegen der ersten Indikatorstruktur
Öffnen Sie MetaEditor und erstellen Sie eine neue leere nutzerdefinierte Indikatordatei. Nennen Sie die Datei supertrend.mq5, und fügen Sie den folgenden Quellcode in diese Datei ein.
//+------------------------------------------------------------------+ //| supertrend.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input int32_t atrPeriod = 10; input double atrMultiplier = 1.5; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; //+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { //--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ } //--- This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ } return rates_total; } //+------------------------------------------------------------------+ //| Deinitiatialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- } //--- CUSTOM UTILITY FUNCTIONS //+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrWhite)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
Diese Datei dient als Grundlage für den kompletten Supertrend-Indikator. Auf den ersten Blick mag es nach viel aussehen, aber es ist absichtlich so strukturiert, dass der Code übersichtlich, lesbar und einfach zu erweitern ist. Gehen wir ihn Abschnitt für Abschnitt durch.
Dateieigenschaften und Metadaten
Ganz oben werden die Metadaten des Indikators definiert.
//+------------------------------------------------------------------+ //| supertrend.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00"
Dazu gehören der Dateiname, die Angaben zum Autor, die Versionsnummer und der Verweislink. Diese Eigenschaften werden im MetaTrader-Terminal angezeigt und helfen dem Nutzer, den Indikator, seinen Autor und seine Herkunft zu identifizieren. Auch wenn dieser Abschnitt keine Auswirkungen auf die Logik hat, so ist er doch eine gute fachliche Praxis, insbesondere für Indikatoren, die später öffentlich zugänglich gemacht oder kommerziell verkauft werden können.
Nutzer-Eingabevariablen
Als Nächstes definieren wir die Parameter für die Nutzereingabe. Dies sind Werte, die der Händler direkt im Fenster mit den Indikatoreinstellungen ändern kann.
//+------------------------------------------------------------------+ //| User input variables | //+------------------------------------------------------------------+ input group "Information" input int32_t atrPeriod = 10; input double atrMultiplier = 1.5;
Hier legen wir zwei Eingänge frei. Die ATR-Periode steuert, wie die Volatilität gemessen wird. Der ATR-Multiplikator steuert, wie empfindlich die Supertrend-Linie auf Preisbewegungen reagiert. Mit diesen Parametern kann der Nutzer eine Feinabstimmung des Indikators vornehmen, ohne den Code zu verändern. Im Laufe der Entwicklung des Indikators werden hier auch künftig alle vom Nutzer konfigurierbaren Einstellungen zusammengefasst.
Globale Variablen
Der Abschnitt für globale Variablen definiert Werte, die von mehreren Funktionen gemeinsam genutzt werden. Im Moment definieren wir nur die Timeframe-Variable, die standardmäßig auf den aktuellen Chart-Zeitrahmen eingestellt ist.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;Im weiteren Verlauf wird dieser Abschnitt um Indikatorhandles, Trendstatusverfolgung und andere für die Berechnung erforderliche gemeinsame Werte erweitert.
Indikator-Puffer
//+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+
Dieser Abschnitt ist absichtlich noch leer. Später im Artikel werden wir alle Puffer deklarieren und binden, die zum Speichern von Kerzendaten, Supertrend-Bändern und Trendzuständen verwendet werden. Durch die Isolierung der Pufferdeklarationen an dieser Stelle wird die Struktur leicht nachvollziehbar und ein Durcheinander an anderen Stellen des Codes vermieden.
Die Funktion OnInit()
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; }
Die Funktion OnInit wird einmal ausgeführt, wenn der Indikator an einen Chart angehängt wird. Seine Hauptaufgabe ist die Initialisierung und Konfiguration.
In unserem Fall konfigurieren wir zunächst das Erscheinungsbild des Charts, indem wir die Utility-Funktion ConfigureChartAppearance aufrufen. Wenn diese Konfiguration aus irgendeinem Grund fehlschlägt, wird die Initialisierung des Indikators ordnungsgemäß abgebrochen.
Konfiguration der Chartdarstellung
Die Funktion ConfigureChartAppearance bereitet den Chart-Hintergrund für den Supertrend-Indikator vor.
//+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrWhite)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; }
Es stellt einen weißen Hintergrund ein, entfernt das Raster, passt die Kerzenfarben an und stellt sicher, dass Aufwärts- und Abwärtsbewegungen visuell erkennbar sind. So entsteht eine saubere, kontrastreiche Umgebung, in der die Supertrend-Linie und die farbigen Kerzen deutlich hervortreten können.
Jede Einstellung wird defensiv angewandt. Wenn eine Chartänderung fehlschlägt, meldet die Funktion den Fehler und bricht die Ausführung ab. Dies erleichtert die Fehlersuche und verhindert stille Fehler.
OnCalculate
Die Funktion OnCalculate ist der Ort, an dem letztlich alle Berechnungen des Indikators stattfinden. Sie wird immer dann aufgerufen, wenn neue Preisdaten verfügbar sind.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t 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 int32_t &spread[]) { //--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ } //--- This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ } return rates_total; }
Für den Moment trennen wir die beiden logischen Szenarien. Die erste tritt auf, wenn der Indikator zum ersten Mal in das Chart eingefügt wird.
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ }
Der zweite tritt auf, wenn neue Kerzen gebildet werden.
//--- This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ }
Diese Struktur ermöglicht es uns, die Initialisierungslogik klar von den laufenden Berechnungen zu unterscheiden, was besonders bei Indikatoren wie Supertrend wichtig ist, die nicht neu gezeichnet werden.
OnDeinit
//+------------------------------------------------------------------+ //| Deinitiatialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- }
Die Funktion OnDeinit wird aufgerufen, wenn der Indikator aus dem Chart entfernt wird. Im Moment ist sie leer, aber sie ist vorhanden, um später bei Bedarf eine ordnungsgemäße Bereinigung zu ermöglichen. Wenn wir Indikator-Handles und dynamische Objekte hinzufügen, wird diese Funktion für die Freigabe von Ressourcen hilfreich sein.
Hilfsfunktionen
Schließlich gruppieren wir alle Hilfs- und Dienstprogramme am Ende der Datei.
//--- CUSTOM UTILITY FUNCTIONS
---
Auf diese Weise bleibt die Hauptlogik des Indikators leicht zu lesen, während wiederverwendbare Funktionen an einem Ort untergebracht werden können. Wenn der Indikator wächst, werden hier weitere nützliche Funktionen hinzugefügt, ohne die Kernlogik zu überladen.
Initialisierung des ATR-Indikators
Wie bereits erwähnt, basiert der Supertrend-Indikator auf dem Average True Range. Da MetaTrader 5 bereits einen zuverlässigen, gut getesteten ATR-Indikator bietet, müssen wir ihn nicht von Grund auf neu erstellen. Stattdessen werden wir seine Werte direkt aus dem Terminal lesen und in unsere Logik integrieren.
Um dies zu ermöglichen, benötigen wir zunächst zwei globale Variablen. Das erste ist ein Indikator-Handle. Dieses Handle stellt eine Live-Verbindung zwischen unserem nutzerdefinierten Indikator und dem integrierten ATR-Indikator dar, der im Terminal läuft. Das zweite ist ein dynamisches Array, das die vom MetaTrader berechneten ATR-Werte speichert.
Wir deklarieren sie im globalen Bereich wie folgt:
int atrHandle; double atrValues [];
atrHandle enthält den Verweis auf den ATR-Indikator, während atrValues[] die vom Terminal zurückgegebenen ATR-Werte speichert. Wenn wir sie global deklarieren, können wir überall in unserer Indikatorlogik auf die ATR-Werte zugreifen, sowohl während der Initialisierung als auch während der Berechnungen.
Erstellen des ATR-Indikator-Handles
Nachdem die Variablen eingerichtet sind, initialisieren wir den ATR-Indikator in der OnInit-Funktion.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Initialize the ATR indicator atrHandle = iATR(_Symbol, timeframe, atrPeriod); if(atrHandle == INVALID_HANDLE){ Print("Error while initializing the ATR indicator ", GetLastError()); return(INIT_FAILED); } return INIT_SUCCEEDED; }
Dies geschieht mithilfe der Funktion iATR. Die Funktion teilt MetaTrader mit, welches Symbol zu verwenden ist, welcher Zeitrahmen zu lesen ist und wie viele Perioden bei der Berechnung der ATR zu verwenden sind. In unserem Fall verwenden wir das aktuelle Symbol, den aktuellen Zeitrahmen und die vom Nutzer angegebene ATR-Periode.
Wenn iATR erfolgreich aufgerufen wird, wird ein gültiges Handle zurückgegeben. Wenn etwas schiefgeht, wird ein ungültiges Handle zurückgegeben. Aus diesem Grund wird der Rückgabewert sofort überprüft. Wenn der Handle ungültig ist, wird eine Fehlermeldung ausgegeben und das Laden des Indikators abgebrochen. Dadurch wird verhindert, dass unsere Logik ohne gültige ATR-Daten läuft, was später zu falschen Ergebnissen führen würde.
Zu diesem Zeitpunkt läuft der ATR-Indikator unauffällig im Hintergrund, und der MetaTrader ist bereit, uns die ATR-Werte zu liefern, sobald wir sie anfordern.
Freigeben des ATR-Indikators
Jeder Indikator-Handle, der erstellt wird, muss auch wieder freigegeben werden, wenn der Indikator aus dem Chart entfernt wird. Dies wird in der Funktion OnDeinit behandelt.
//+------------------------------------------------------------------+ //| Deinitiatialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Release ATR if(atrHandle != INVALID_HANDLE){ IndicatorRelease(atrHandle); } }
Hier wird geprüft, ob der ATR-Handle gültig ist. Ist dies der Fall, wird er mit IndicatorRelease freigegeben. Dieser Schritt ist für die Speicherverwaltung unerlässlich. Sie sorgt dafür, dass MetaTrader die vom ATR-Indikator verwendeten Ressourcen freigibt, sobald unser Supertrend-Indikator nicht mehr benötigt wird. MetaTrader ist zwar gut darin, Ressourcen zu verwalten, aber die explizite Freigabe von Handles gilt als Best Practice und trägt dazu bei, unseren Code sauber und professionell zu halten.
Auflösen von Kompilierungswarnungen
Bei der Kompilierung des Indikators treten einige Warnungen auf. Diese Warnungen werden erwartet und zeigen an, dass einige erforderliche Eigenschaften noch nicht definiert wurden. Die erste Warnung besagt, dass keine Eigenschaft des Indikatorfensters angegeben ist. In MetaTrader müssen Indikatoren ausdrücklich angeben, wo sie gezeichnet werden sollen.
Es gibt zwei gängige Anzeigetypen. Einige Indikatoren werden direkt auf dem Chart eingezeichnet, wie z. B. gleitende Durchschnitte und Bollinger-Bänder. Andere werden in einem separaten Unterfenster unterhalb des Charts angezeigt, z. B. RSI und ATR.
Da Supertrend ein Overlay-Indikator ist, der dem Preis folgt, soll er direkt im Hauptchartfenster erscheinen. Wir erklären dies durch Hinzufügen der folgenden Richtlinie:
#property indicator_chart_window
Nach dem Hinzufügen verschwindet die erste Warnung.
Verstehen von grafischen Plots
Die zweite Warnung besagt, dass kein Indikatorplot definiert wurde. Ein grafischer Plot ist eine visuelle Ausgabe, die MetaTrader unter Verwendung von Daten aus Indikatorpuffern auf dem Chart zeichnet. Jedes Plot stellt ein visuelles Element dar, wie z. B. eine Linie, ein Histogramm oder eine farbige Kerze. MQL5 unterstützt bis zu achtzehn verschiedene Plot-Typen und bietet Entwicklern damit Flexibilität bei der Visualisierung von Indikatordaten. Jeder Plot muss explizit deklariert werden, damit das Terminal weiß, wie viele visuelle Elemente vorbereitet werden müssen.
In unserem Supertrend-Indikator werden wir drei Plots verwenden. Ein Plot wird für die nutzerdefinierten farbigen Kerzen verwendet, und zwei Plots werden für die Supertrend-Linien verwendet. Um dies zu deklarieren, fügen wir die folgende Richtlinie hinzu:
#property indicator_plots 3
Dadurch wird die zweite Warnung entfernt.
Indikatorpuffer und ihr Zweck
Nachdem Sie die Anzahl der Plots angegeben haben, zeigt MetaTrader eine weitere Warnung an, die besagt, dass die Puffergröße des Indikators nicht ausreicht. Indikatorpuffer sind spezielle Arrays, in denen Daten gespeichert werden, die später im Chart dargestellt werden. Jeder Puffer enthält einen Datenstrom, z. B. offene Preise, Linienwerte oder Farbindizes. MetaTrader liest automatisch aus diesen Puffern und verwendet deren Inhalt, um die entsprechenden Plots zu erstellen.
Da unser Supertrend-Indikator mehrere Datenströme verwendet, darunter Kerzenwerte, Trendfarben, Bandwerte und interne Trendzustände, müssen wir im Voraus genügend Puffer reservieren. Dazu geben wir die Gesamtzahl der Puffer an, die wir verwenden wollen:
#property indicator_buffers 8
Damit wird MetaTrader angewiesen, bis zu 8 Indikatorpuffer zuzuweisen. Sobald diese Richtlinie hinzugefügt wird, verschwinden alle Kompilierungswarnungen.
Deklaration von Indikator-Puffer-Arrays
Nachdem die Anzahl der Puffer definiert wurde, können wir nun die eigentlichen Arrays deklarieren, die als unsere Indikatorpuffer fungieren werden. Diese Arrays müssen vom Typ double sein und im globalen Bereich deklariert werden. Sie werden als dynamische Arrays deklariert, da MetaTrader ihre Größe automatisch an die Anzahl der verfügbaren Chart-Kerzen anpasst. Wir deklarieren Puffer für die nutzerdefinierten Kerzendaten, die Supertrend-Bänder und den internen Trendstatus.
//+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double bufCandleOpen[]; double bufCandleHigh[]; double bufCandleLow[]; double bufCandleClose[]; double bufCandleTrendColor[]; double bufSupertrendUpperBand[]; double bufSupertrendLowerBand[]; double bufTrendState[];
In diesem Stadium deklarieren wir nur die Arrays. Es reicht nicht aus, sie zu erklären. Im nächsten Schritt werden wir jedes Array an einen bestimmten Pufferindex innerhalb der OnInit-Funktion binden, damit MetaTrader sie als echte Indikatorpuffer erkennt.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Bind arrays to indicator buffers SetIndexBuffer(0, bufCandleOpen, INDICATOR_DATA); SetIndexBuffer(1, bufCandleHigh, INDICATOR_DATA); SetIndexBuffer(2, bufCandleLow, INDICATOR_DATA); SetIndexBuffer(3, bufCandleClose, INDICATOR_DATA); SetIndexBuffer(4, bufCandleTrendColor, INDICATOR_COLOR_INDEX); SetIndexBuffer(5, bufSupertrendUpperBand, INDICATOR_DATA); SetIndexBuffer(6, bufSupertrendLowerBand, INDICATOR_DATA); SetIndexBuffer(7, bufTrendState, INDICATOR_DATA); return INIT_SUCCEEDED; }
In diesem Stadium haben wir bereits alle Indikatorpuffer deklariert und korrekt gebunden. Diese Puffer dienen als Speicherbehälter für Kerzen, Supertrend-Bänder und Trendzustände. Zurzeit enthalten sie jedoch keine aussagekräftigen Daten. Solange wir sie nicht strukturiert ausfüllen, kann nichts auf dem Chart dargestellt werden.
Um diese Puffer aufzufüllen, implementieren wir nun den Supertrend-Algorithmus. Wie bei den meisten Indikatoren in MQL5 befindet sich die zentrale Berechnungslogik in der Funktion OnCalculate. Diese Funktion wird immer dann ausgeführt, wenn neue Preisdaten im Terminal verfügbar sind.
Unser Ziel ist ein zweifaches. Zunächst berechnen wir die Supertrend-Werte auf der Grundlage von Preis und ATR. Zweitens färben wir die Kerzen entsprechend dem erkannten Trend. Bullische Kerzen erscheinen in SeaGreen, während bärische Kerzen in Schwarz angezeigt werden. Zur Unterstützung der Kerzenfärbung definieren wir eine einfache Farbpalette.
#property indicator_color1 clrBlack, clrSeaGreen
Der Farbpuffer verwendet numerische Indizes für die Zuordnung zu diesen Farben. Ein Wert von 0 entspricht Schwarz, während ein Wert von 1 SeaGreen entspricht.
Vermeidung von doppeltem Code mit Hilfsfunktionen
Bei der Konstruktion von Kerzen tauchen immer wieder die gleichen Pufferzuweisungen auf. Diese Logik inline zu schreiben, würde schnell zu Doppelarbeit und geringerer Lesbarkeit führen. Um dies zu vermeiden, definieren wir zwei Hilfsfunktionen. Die erste Funktion füllt die Kerzenpuffer für eine bullische Kerze und weist den bullischen Farbindex zu.
//+---------------------------------------------------------------------------------------------------+ //| Populates candle buffers for a bullish bar and assigns the bullish trend color at the given index | //+---------------------------------------------------------------------------------------------------+ void SetBullishCandle(int index, double &open[], double &high[], double &low[], double &close[]){ bufCandleOpen [index] = open [index]; bufCandleHigh [index] = high [index]; bufCandleLow [index] = low [index]; bufCandleClose[index] = close[index]; bufCandleTrendColor[index] = 1; }
Mit dieser Funktion werden die Werte für die Eröffnung, das Hoch, das Tief und den Abschluss der angegebenen Kerze in die Kerzenpuffer des Indikators kopiert. Der Trendfarbpuffer wird dann auf 1 gesetzt, was der bullischen Farbe entspricht. Die zweite Funktion erfüllt dieselbe Aufgabe für bärische Kerzen.
//+---------------------------------------------------------------------------------------------------+ //| Populates candle buffers for a bearish bar and assigns the bearish trend color at the given index | //+---------------------------------------------------------------------------------------------------+ void SetBearishCandle(int index, double &open[], double &high[], double &low[], double &close[]){ bufCandleOpen [index] = open [index]; bufCandleHigh [index] = high [index]; bufCandleLow [index] = low [index]; bufCandleClose[index] = close[index]; bufCandleTrendColor[index] = 0; }
Der einzige Unterschied besteht darin, dass der Farbpuffer auf 0 gesetzt ist, was auf eine bärische Kerze hinweist. Diese beiden Funktionen ermöglichen es uns, Kerzen einheitlich zu konstruieren und zu färben, wobei die Hauptlogik von Supertrend übersichtlich und kompakt bleibt.
Sicheres Arbeiten mit Preisarrays
Die Funktion OnCalculate liefert Arrays von Preisreihen, wie Open, High, Low und Close. Diese Arrays sind konstant und können nicht per Referenz an nutzerdefinierte Funktionen übergeben werden. Um diese Einschränkung zu umgehen, erstellen wir lokale Kopien dieser Arrays in OnCalculate.
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, ... { //--- Temporary buffers used to store price data for rendering custom colored candles double sOpen []; double sHigh []; double sLow []; double sClose[]; ... return rates_total; }
Diese Arrays dienen als Arbeitspuffer. Wir kopieren die Preisdaten in sie und übergeben sie sicher an unsere Hilfsfunktionen, ohne die ursprünglichen Preisreihen zu verändern.
Erstberechnung beim Start des Indikators
Wenn der Indikator zum ersten Mal an ein Chart angehängt wird, wird der Parameter prev_calculated auf Null gesetzt. Dies signalisiert, dass es keine vorherigen Berechnungen gibt und wir alle verfügbaren historischen Daten verarbeiten müssen. Wir erkennen diese Bedingung mit:
if(prev_calculated == 0)
In diesem Block werden zunächst alle Indikatorpuffer mit EMPTY_VALUE initialisiert.
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ //--- Start with clean buffers at first calculation ArrayInitialize(bufCandleOpen, EMPTY_VALUE); ArrayInitialize(bufCandleHigh, EMPTY_VALUE); ArrayInitialize(bufCandleLow, EMPTY_VALUE); ArrayInitialize(bufCandleClose, EMPTY_VALUE); ArrayInitialize(bufSupertrendUpperBand, EMPTY_VALUE); ArrayInitialize(bufSupertrendLowerBand, EMPTY_VALUE); }
In MQL5 teilt EMPTY_VALUE dem Terminal mit, dass es an diesem Index nichts zeichnen soll. Dieses Verhalten ist für Supertrend-Bänder von Vorteil, da je nach Trendrichtung immer nur ein Band sichtbar ist.
Definieren und Initialisieren von Trendzuständen
Die Logik des Supertrends beruht auf der Trendrichtung. Bei jeder Kerze kann der Trend bullisch, bärisch oder noch in der Entwicklung sein. Um diese Zustände klar darzustellen, definieren wir drei Konstanten:
const double TREND_BULLISH = 1.0; const double TREND_BEARISH = 0.0; const double TREND_NEUTRAL = -1.0;
Diese Werte werden in einem speziellen Trendstatuspuffer gespeichert. Bevor die Berechnungen beginnen, wird der gesamte Puffer auf den neutralen Zustand initialisiert: Dadurch wird sichergestellt, dass der Algorithmus weiß, dass es zu Beginn keinen Trend gibt.
Kopieren von Preisdaten und Validierung der Historie
Noch innerhalb des anfänglichen Berechnungsblocks kopieren wir die Endpreis-Arrays in unsere lokalen Arbeitspuffer:
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ ... //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(sOpen, open); ArrayCopy(sHigh, high); ArrayCopy(sLow, low); ArrayCopy(sClose, close); }
Als Nächstes überprüfen wir, ob genügend historische Daten für die Berechnung der ATR vorliegen.
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ ... //--- Ensure sufficient historical data is available to calculate the ATR period. if(rates_total < atrPeriod){ Print("Insufficient bars to initialize Supertrend: ATR period requires more historical data."); return rates_total; } }
Da der Supertrend von der ATR abhängt, können wir nicht fortfahren. Ohne mindestens atrPeriod Kerzen kann die Berechnung nicht fortgesetzt werden. Wenn die Daten nicht ausreichen, beenden wir die Berechnung vorzeitig und warten, bis weitere Kerzen geladen werden.
Abrufen von ATR-Werten
Die Supertrend-Bänder werden von den ATR-Werten abgeleitet. Wir rufen die gesamte ATR-Serie mit CopyBuffer ab:
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ ... //--- Get all ATR values int numberOfCopiedATRValues = CopyBuffer(atrHandle, 0, 0, rates_total, atrValues); if(numberOfCopiedATRValues == -1){ Print("Error while copying ATR values: ", GetLastError()); return rates_total; } }
Wenn dieser Vorgang fehlschlägt, brechen wir die Verarbeitung ab, um die Arbeit mit ungültigen Daten zu vermeiden.
Berechnung des Supertrends auf historischen Kerzen
Sobald alle Voraussetzungen erfüllt sind, werden die historischen Balken durchlaufen, um die Supertrend-Werte zu berechnen.
//--- This block is executed when the indicator is initially attached to a chart if(prev_calculated == 0){ ... //--- Iterate through historical price data to calculate Supertrend values starting from the first valid ATR window. for(int i = atrPeriod - 1; i < rates_total - 1; i++){ //--- Calculate the current bar midpoint and derive the raw Supertrend upper and lower bands using the ATR multiplier double barMidpoint = (high[i] + low[i]) / 2.0; double upperBandValue = barMidpoint + atrValues[i] * atrMultiplier; double lowerBandValue = barMidpoint - atrValues[i] * atrMultiplier; //--- Initialize the first valid Supertrend bands once enough ATR data is available and skip further processing for this bar if(i == (atrPeriod - 1)){ bufSupertrendUpperBand[atrPeriod - 1] = upperBandValue; bufSupertrendLowerBand[atrPeriod - 1] = lowerBandValue; continue; } //--- Handle initial trend resolution when no prior trend has been established if(bufTrendState[i - 1] == TREND_NEUTRAL){ if(close[i] > bufSupertrendUpperBand[i - 1] || close[i] < bufSupertrendLowerBand[i - 1]){ if(close[i] > bufSupertrendUpperBand[i - 1]){ bufTrendState[i] = TREND_BULLISH; SetBullishCandle(i, sOpen, sHigh, sLow, sClose); bufSupertrendUpperBand[i] = EMPTY_VALUE; bufSupertrendLowerBand[i] = lowerBandValue; } if(close[i] < bufSupertrendLowerBand[i - 1]){ bufTrendState[i] = TREND_BEARISH; SetBearishCandle(i, sOpen, sHigh, sLow, sClose); bufSupertrendLowerBand[i] = EMPTY_VALUE; bufSupertrendUpperBand[i] = upperBandValue; } }else{ if(upperBandValue < bufSupertrendUpperBand[i - 1]){ bufSupertrendUpperBand[i] = upperBandValue; }else{ bufSupertrendUpperBand[i] = bufSupertrendUpperBand[i - 1]; } if(lowerBandValue > bufSupertrendLowerBand[i - 1]){ bufSupertrendLowerBand[i] = lowerBandValue; }else{ bufSupertrendLowerBand[i] = bufSupertrendLowerBand[i - 1]; } } } //--- Maintain or invalidate the bullish trend based on price interaction with the lower Supertrend band if(bufTrendState[i - 1] == TREND_BULLISH){ if(close[i] < bufSupertrendLowerBand[i - 1]){ bufTrendState[i] = TREND_BEARISH; SetBearishCandle(i, sOpen, sHigh, sLow, sClose); bufSupertrendLowerBand[i] = EMPTY_VALUE; bufSupertrendUpperBand[i] = upperBandValue; }else{ if(lowerBandValue > bufSupertrendLowerBand[i - 1]){ bufSupertrendLowerBand[i] = lowerBandValue; SetBullishCandle(i, sOpen, sHigh, sLow, sClose); bufTrendState[i] = TREND_BULLISH; }else{ bufSupertrendLowerBand[i] = bufSupertrendLowerBand[i - 1]; SetBullishCandle(i, sOpen, sHigh, sLow, sClose); bufTrendState[i] = TREND_BULLISH; } } } //--- Maintain or invalidate the bearish trend based on price interaction with the upper Supertrend band if(bufTrendState[i - 1] == TREND_BEARISH){ if(close[i] > bufSupertrendUpperBand[i - 1]){ bufTrendState[i] = TREND_BULLISH; SetBullishCandle(i, sOpen, sHigh, sLow, sClose); bufSupertrendUpperBand[i] = EMPTY_VALUE; bufSupertrendLowerBand[i] = lowerBandValue; }else{ if(upperBandValue < bufSupertrendUpperBand[i - 1]){ bufSupertrendUpperBand[i] = upperBandValue; SetBearishCandle(i, sOpen, sHigh, sLow, sClose); bufTrendState[i] = TREND_BEARISH; }else{ bufSupertrendUpperBand[i] = bufSupertrendUpperBand[i - 1]; SetBearishCandle(i, sOpen, sHigh, sLow, sClose); bufTrendState[i] = TREND_BEARISH; } } } } }
Die Schleife beginnt bei atrPeriod – 1, dem ersten Index, bei dem ATR gültig wird. Für jede Kerze berechnen wir den Mittelpreis und leiten die rohen oberen und unteren Supertrend-Bänder unter Verwendung des ATR-Multiplikators ab. Bei der ersten gültigen Kerze werden beide Bänder initialisiert und der Rest der Logik übersprungen. Von diesem Punkt an beginnt die Trendauflösung.
Wenn der vorherige Trend neutral ist, bestimmt die Interaktion des Preises mit den vorherigen Bändern, ob sich ein Aufwärts- oder Abwärtstrend etabliert. Sobald ein Trend vorliegt, bleibt nur ein Band aktiv, während das gegenüberliegende Band mit EMPTY_VALUE unterdrückt wird.
Wenn der vorherige Trend bullisch ist, löst das Unterschreiten des unteren Bandes eine rückläufige Trendwende aus. Andernfalls setzt sich der Aufwärtstrend fort, und das untere Band wird angepasst oder fortgeschrieben. Der Abwärtstrend folgt der gleichen Logik in umgekehrter Richtung. Ein Schlusspreis oberhalb des oberen Bandes signalisiert eine bullische Trendwende, während eine Fortsetzung des Trends das obere Band verengt oder bewahrt. Bei jedem Schritt werden die Kerzen mithilfe der Hilfsfunktionen konstruiert und gefärbt, um die visuelle Konsistenz zu gewährleisten.
Supertrend in Echtzeit aktualisieren
Nach der ersten Berechnung wird OnCalculate erneut aufgerufen, wenn sich eine neue Kerze bildet. In diesem Fall ist prev_calculated weder null noch gleich rates_total.
//--- This block is executed on every new bar open if(prev_calculated != rates_total && prev_calculated != 0){ //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays ArrayCopy(sOpen, open); ArrayCopy(sHigh, high); ArrayCopy(sLow, low); ArrayCopy(sClose, close); //--- Start with clean buffers bufSupertrendLowerBand[rates_total - 1] = EMPTY_VALUE; bufSupertrendLowerBand[rates_total - 2] = EMPTY_VALUE; bufSupertrendUpperBand[rates_total - 1] = EMPTY_VALUE; bufSupertrendUpperBand[rates_total - 2] = EMPTY_VALUE; //--- Get all ATR values int numberOfCopiedATRValues = CopyBuffer(atrHandle, 0, 0, rates_total, atrValues); if(numberOfCopiedATRValues == -1){ Print("Error while copying ATR values: ", GetLastError()); return rates_total; } //--- Define indices for the most recently closed bar and the bar immediately preceding it int elapsedBarIndex = rates_total - 2; int priorElapsedBarIndex = rates_total - 3; //--- Compute the Supertrend midpoint and derive the upper and lower bands using the ATR multiplier double barMidpoint = (high[elapsedBarIndex] + low[elapsedBarIndex]) / 2.0; double upperBandValue = barMidpoint + atrValues[elapsedBarIndex] * atrMultiplier; double lowerBandValue = barMidpoint - atrValues[elapsedBarIndex] * atrMultiplier; //--- Handle Supertrend continuation or reversal logic when the previous bar was bullish if(bufTrendState[priorElapsedBarIndex] == TREND_BULLISH){ if(close[elapsedBarIndex] < bufSupertrendLowerBand[priorElapsedBarIndex]){ bufTrendState[elapsedBarIndex] = TREND_BEARISH; SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufSupertrendUpperBand[elapsedBarIndex] = upperBandValue; bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE; }else{ if(lowerBandValue > bufSupertrendLowerBand[priorElapsedBarIndex]){ bufSupertrendLowerBand[elapsedBarIndex] = lowerBandValue; bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE; SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufTrendState[elapsedBarIndex] = TREND_BULLISH; }else{ bufSupertrendLowerBand[elapsedBarIndex] = bufSupertrendLowerBand[priorElapsedBarIndex]; bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE; SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufTrendState[elapsedBarIndex] = TREND_BULLISH; } } } //--- Handle Supertrend continuation or reversal logic when the previous bar was bearish if(bufTrendState[priorElapsedBarIndex] == TREND_BEARISH){ if(close[elapsedBarIndex] > bufSupertrendUpperBand[priorElapsedBarIndex]){ bufTrendState[elapsedBarIndex] = TREND_BULLISH; SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufSupertrendLowerBand[elapsedBarIndex] = lowerBandValue; bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE; }else{ if(upperBandValue < bufSupertrendUpperBand[priorElapsedBarIndex]){ bufSupertrendUpperBand[elapsedBarIndex] = upperBandValue; bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE; SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufTrendState[elapsedBarIndex] = TREND_BEARISH; }else{ bufSupertrendUpperBand[elapsedBarIndex] = bufSupertrendUpperBand[priorElapsedBarIndex]; bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE; SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose); bufTrendState[elapsedBarIndex] = TREND_BEARISH; } } } }
Mit diesem Block wird nur die zuletzt geschlossene Kerze aktualisiert, dadurch werden rückwirkende Änderungen bzw. Repaints vermieden. Wir beginnen damit, die Preisdaten erneut in die lokalen Puffer zu kopieren, und löschen dann die Werte der Supertrend-Bänder für die letzten beiden Balken, um übrig gebliebene Zeichnungen zu vermeiden. Die ATR-Werte werden aktualisiert, und wir ermitteln zwei Schlüsselindizes: die zuletzt geschlossene Kerze und die Kerze davor. Anhand dieser Indizes berechnen wir die neuen Supertrend-Bänder und wenden dieselbe Fortsetzungs-/Umkehrlogik an, die bei der historischen Verarbeitung verwendet wird. Nur die neueste Kerze wird aktualisiert, damit die Berechnungen effizient und die Ergebnisse stabil bleiben.
Konfigurieren von Chartplots für das Rendering
Zu diesem Zeitpunkt sind alle Indikatorpuffer korrekt definiert, und die Supertrend-Logik ist vollständig implementiert. Wenn der Indikator jedoch dem Chart hinzugefügt wird, wird noch nichts angezeigt. Dies ist ein erwartetes Verhalten. In MQL5 sind das Berechnen von Werten und das Rendern von Grafiken zwei getrennte Aufgaben. Obwohl unsere Puffer mit gültigen Daten gefüllt werden, kann das Terminal sie immer noch nicht darstellen. Um den Indikator sichtbar zu machen, müssen wir explizit grafische Plots konfigurieren.
Grafische Plots werden mit der Funktion PlotIndexSetInteger und verwandten Funktionen konfiguriert. Diese Konfiguration wird üblicherweise in der Funktion OnInit vorgenommen, unmittelbar nachdem die Indikatorpuffer an ihre jeweiligen Plot-Indizes gebunden wurden.
Konfigurieren des farbigen Kerzen-Plot
Unser erstes Plot zeigt die Preiskerzen mit dynamischen Farben je nach Trendrichtung an. Dieses Plot verwendet den Zeichenmodus DRAW_COLOR_CANDLES. In OnInit konfigurieren wir den ersten Plot wie folgt:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Configure Graphic Plots PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE); return INIT_SUCCEEDED; }
Hier bezieht sich der Plot-Index Null auf den ersten Indikatorplot. Indem wir den Zeichnungstyp auf DRAW_COLOR_CANDLES setzen, weisen wir das Terminal an, die ersten fünf Puffer als Kerzendaten zu behandeln. Dieser Zeichenmodus erwartet fünf Puffer in einer bestimmten Reihenfolge – offen, hoch, niedrig, geschlossen und Farbindex. Die Konfiguration des leeren Wertes stellt sicher, dass ein als EMPTY_VALUE markierter Kerzenpufferwert nicht gerendert wird. Dies ermöglicht es uns, Kerzen nur dann zu zeichnen, wenn unsere Logik sie ausdrücklich selektiv auffüllt.
Benennung von Kerzenpuffern für das erste Plot
Um mehrere Puffer korrekt mit einem einzigen Kerzen-Plot zu verknüpfen, definieren wir ihre Bezeichnungen mit einer Eigenschaftsrichtlinie:
#property indicator_label1 "SupertrendOpen;SupertrendHigh;SupertrendLow;SupertrendClose"
Diese Richtlinie weist den Kerzendatenpuffern für das erste Plot Namen zu. Die Reihenfolge ist entscheidend. Das Terminal verwendet diese Reihenfolge, um jeden Puffer der entsprechenden Kerzenkomponente zuzuordnen. Auf diese Weise versteht MQL5, dass diese Puffer zusammen ein einziges farbiges Kerzen-Plot bilden und nicht separate grafische Objekte. Dies verbessert auch die Übersichtlichkeit bei der Überprüfung von Indikatorpuffern im Datenfenster.
Konfigurieren des Abwärtstrend-Supertrend-Bands
Der folgende Plot rendert das obere Supertrend-Band, das bei Abwärtstrends sichtbar ist. Dieses Band wird als einfache Linie gezeichnet. Die Konfiguration wird wie folgt vorgenommen:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Configure Graphic Plots ... PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID); PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 1); PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrBlack); PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (1, PLOT_LABEL, "downtrend"); return INIT_SUCCEEDED; }
Plot-Index 1 bezieht sich auf den zweiten Indikator-Plot. Wir definieren sie als eine durchgezogene Linie mit einer Breite von einem Pixel. Die Farbe ist auf Schwarz eingestellt, um den bärischen Bedingungen visuell zu entsprechen. Die Einstellung des Leerwertes spielt hier eine wichtige Rolle. Wenn der Puffer EMPTY_VALUE enthält, wird die Linie für diese Kerze nicht gezeichnet. Auf diese Weise wird das Abwärtsband nur dann sichtbar, wenn der Markt rückläufig ist, und bleibt ansonsten verborgen. Die Beschriftung legt fest, wie diese Linie im Datenfenster und in der Legende erscheint.
Konfigurieren des Aufwärtstrend-Supertrend-Bands
Das Aufwärtstrendband wird auf die gleiche Weise konfiguriert wie das Abwärtstrendband. Die einzigen Unterschiede sind der Plot-Index, die Farbe und die Bezeichnung.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Configure Graphic Plots ... PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_SOLID); PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 1); PlotIndexSetInteger(2, PLOT_LINE_COLOR, clrSeaGreen); PlotIndexSetDouble (2, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetString (2, PLOT_LABEL, "uptrend"); return INIT_SUCCEEDED; }
Dieser Plot zeigt das untere Supertrend-Band bei Aufwärtstrends. Da die Logik die Abwärtstrend-Konfiguration widerspiegelt, gelten die gleichen Rendering-Regeln. Je nach Trendstatus ist jeweils nur ein Band sichtbar.
Allgemeine Indikatoreigenschaften einstellen
Schließlich konfigurieren wir allgemeine Indikatoreigenschaften, die sich darauf auswirken, wie der Indikator im Terminal angezeigt wird.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- General indicator configurations IndicatorSetString(INDICATOR_SHORTNAME, "Supertrend Indicator"); IndicatorSetInteger(INDICATOR_DIGITS, Digits()); return INIT_SUCCEEDED; }
Der Kurzname bestimmt, wie der Indikator im Chart und in der Indikatorliste aufgeführt wird. Eine knappe Formulierung verbessert die Lesbarkeit, wenn mehrere Indikatoren verwendet werden. Die Einstellung der Nachkommastellen stellt sicher, dass alle gezeichneten Werte die Preisgenauigkeit des Symbols einhalten. Dies ist wichtig für die Konsistenz, insbesondere bei der Arbeit mit Symbolen, die unterschiedliche Dezimalformate verwenden.
Mit diesen Plot-Konfigurationen weiß das Terminal nun, wie es unsere Daten darstellen soll. Farbige Kerzen spiegeln den aktuellen Trend wider, Supertrend-Bänder erscheinen nur, wenn sie relevant sind, und leere Werte verhindern unerwünschte Zeichnungen. In diesem Stadium ist der Supertrend-Indikator voll funktionsfähig, visuell korrekt und ohne Repainting. Die Indikatorlogik, die Pufferverwaltung und die Rendering-Konfiguration arbeiten jetzt als ein vollständiges und kohärentes System zusammen.
Tests
In diesem Stadium sollte der Indikator ohne Fehler kompiliert und korrekt im Chart dargestellt werden. Wir können nun den Indikator an ein beliebiges Symbol und einen Zeitrahmen unserer Wahl anhängen, um zu beobachten, wie die farbigen Kerzen und Supertrend-Bänder in Echtzeit auf veränderte Marktbedingungen reagieren.
Unten sehen Sie einen Screenshot des fertigen Supertrend-Indikators, der auf das Gold-Chart auf vier Stunden angewendet wurde.

Dies bestätigt, dass sowohl die Berechnungslogik als auch die Rendering-Konfiguration wie vorgesehen funktionieren.
Schlussfolgerung
In diesem Artikel haben wir die Theorie hinter uns gelassen und etwas Reales geschaffen. Wir haben einen voll funktionsfähigen Supertrend-Indikator in MQL5 entworfen und implementiert, der im Live-Handel zuverlässig funktioniert, nicht neu gezeichnet wird und seine interne Logik auf saubere und strukturierte Weise offenlegt. Es handelt sich nicht um eine Demo oder eine verkürzte Implementierung. Es handelt sich um ein praktisches Werkzeug, das sich sofort für Trading, Analyse oder als Baustein in Expert Advisors und kommerziellen Systemen einsetzen lässt.
Noch wichtiger ist, dass der Indikator selbst nur ein Teil der Leistung ist. Auf dem Weg dorthin haben wir eine solide Indikatorarchitektur, eine disziplinierte Pufferverwaltung, einen sicheren Umgang mit Live-Daten und eine präzise Trennung zwischen Berechnung und Rendering praktiziert. Dies sind Kernkompetenzen, die für jeden ernsthaften MQL5-Indikator gelten, nicht nur für Supertrend. Wenn man diese Grundlagen einmal verstanden hat, ist die Erstellung fortgeschrittener und angepasster Tools weit weniger einschüchternd.
Indem wir den Indikator selbst geschrieben haben, haben wir das Mysterium beseitigt, das beliebte Handelsinstrumente oft umgibt. Wir haben die Kontrolle über das Verhalten, die Darstellung und die Leistung gewonnen und verstehen jetzt genau, warum sich der Indikator in Echtzeit so verhält, wie er es tut. Dieses Vertrauen ist es, was einen Nutzer von Indikatoren von einem Ersteller von Indikatoren unterscheidet.
Dies ist das Ende der ersten Workshop-Reihe zu nutzerdefinierten Indikatoren. In den nächsten Kapiteln werden wir diesen Ansatz fortsetzen, indem wir andere weit verbreitete Indikatoren aufschlüsseln und sie mit Klarheit und Absicht neu aufbauen. Das Ziel bleibt dasselbe: weniger Black Boxes, bessere Werkzeuge und ein besseres Verständnis der Charts, auf die wir uns täglich verlassen.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20755
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.
Der MQL5 Standard Library Explorer (Teil 6): Optimierung eines generierten Expert Advisors
MQL5-Handelswerkzeuge (Teil 13): Entwicklung eines Canvas-basierten Kurs-Dashboards mit Chart- und Statistik-Panels
MQL5-Handelswerkzeuge (Teil 14): Pixelgenaues, scrollbares Textpanel mit Anti-Aliasing und abgerundeter Scrollleiste
Data Science und ML (Teil 48): Sind Transformer für das Trading wirklich relevant?
- 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.