English 日本語
preview
Workshop zu benutzerdefinierten Indikatoren (Teil 2): Entwicklung eines praxistauglichen Supertrend-Expert-Advisors in MQL5

Workshop zu benutzerdefinierten Indikatoren (Teil 2): Entwicklung eines praxistauglichen Supertrend-Expert-Advisors in MQL5

MetaTrader 5Handelssysteme |
22 0
Chacha Ian Maroa
Chacha Ian Maroa

Einführung

Das manuelle Trendtrading mit dem Supertrend-Indikator ist theoretisch einfach, in der Praxis jedoch anspruchsvoll. Eine Supertrend-Linie dreht um, der Kurs bestätigt die Richtung, und die Entscheidung, zu kaufen oder zu verkaufen, ist klar. Auf den Live-Märkten kommt diese Klarheit jedoch oft zum falschen Zeitpunkt. Die Ausführung verzögert sich, Emotionen kommen ins Spiel, und was eigentlich ein sauberer Einstieg hätte sein sollen, artet in Zögern oder zu viel Nachdenken aus.

Gerade in dieser Lücke zwischen dem Erkennen eines Signals und dem darauffolgenden Handeln kommt der Automatisierung ihre Bedeutung zu. Nicht, um die Analyse zu ersetzen, sondern um die Ausführung zügig und konsistent zu gestalten. Der Supertrend-Indikator eignet sich gut für diese Aufgabe. Er liefert eine klare Definition des Trendzustands und einen genauen Zeitpunkt, zu dem sich dieser Zustand ändert. Was oft fehlt, ist eine klare und flexible Methode, diese Signale in konkrete Trades umzusetzen.

In diesem Artikel konzentrieren wir uns genau darauf. Wir entwickeln einen Expert Advisor in MQL5, der auf die Signale eines Supertrend-Indikators reagiert und sofort Trades ausführt, sobald ein bestätigter Trendwechsel erkannt wird. Die Logik wird explizit und transparent dargestellt. Jede Entscheidung, die der EA trifft, lässt sich auf eine klar definierte Marktbedingung zurückführen.

Über die Handelslogik hinaus dokumentiert dieser Artikel den gesamten Entwicklungsprozess. Wir gehen Schritt für Schritt von der Signalerkennung über das Positionsmanagement und die Risikokontrolle bis zu historische Tests und erläutern dabei jeden Schritt anhand praktischer Beispiele. Am Ende werden wir über einen vollständigen und funktionsfähigen Supertrend-Expert-Advisor verfügen – und, was noch wichtiger ist, über ein wiederverwendbares Framework, das die Leser anpassen und erweitern können, während sie ihre Suche nach einem nachhaltigen Handelsvorteil fortsetzen.


Die Logik des Supertrend-Signals verstehen

Der Supertrend-Indikator befindet sich immer in einem von zwei Zuständen: bullisch oder bärisch. In einer bullischen Marktphase wird die Supertrend-Linie unterhalb des Kurses eingezeichnet und fungiert als nachlaufende Unterstützung. In einer bärische Phase wird die Linie oberhalb des Kurses eingezeichnet und fungiert als nachlaufender Widerstand. Ein Signal wird nur dann erzeugt, wenn der Indikator von einem Zustand in den anderen übergeht.

Ein bullisches Signal liegt vor, wenn die zuletzt abgeschlossene Kerze über dem oberen Supertrend-Band schließt.

Bullisches Signal

Dieser Schlusskurs bestätigt, dass die bärische Phase beendet und der Indikator nun in eine Aufwärtsbewegung umgeschlagen ist. An diesem Punkt wird das obere Band inaktiv, und das untere Band bildet sich unterhalb des Kurses. Dieser einzelne Übergang entspricht genau einem bestätigten Kaufsignal.

Ein bärisches Signal folgt derselben Logik in umgekehrter Richtung.

Bärisches Signal

Wenn die letzte Kerze unterhalb des unteren Supertrend-Bandes schließt, endet die bullische Phase, und der Indikator wechselt in eine bärische Phase. Das untere Band wird inaktiv, und das obere Band erscheint oberhalb des Kurses. Diese Entwicklung stellt ein bestätigtes Verkaufssignal dar.

Nur zur Information: Signale werden ausschließlich anhand abgeschlossener Kerzen ermittelt. Der EA versucht niemals, eine noch nicht abgeschlossene Kerze zu interpretieren. Indem wir das Schließen einer Kerze abwarten, stellen wir sicher, dass der Supertrend-Status endgültig ist und unter realen Marktbedingungen keine Nachberechnungen oder Signalinstabilitäten auftreten.

Bei diesem Ansatz ist jedes Supertrend-Signal eindeutig, deterministisch und tritt nur einmal pro Trendwechsel auf. Während eines laufenden Trends treten keine wiederholten Signale auf, und es besteht keine Unklarheit hinsichtlich der Marktlage. Diese klare Signaldefinition bildet die Grundlage für die weitere Logik des Expert Advisors.


Handelsregeln und Gestaltungsentscheidungen

Nachdem die Signal-Logik des Supertrend klar definiert ist, besteht der nächste Schritt darin, zu entscheiden, wie der Expert Advisor unter realen Marktbedingungen auf diese Signale reagieren soll. Das Ziel dieses Entwurfs ist nicht Komplexität, sondern Kontrolle. Jede Handelsentscheidung ist so gestaltet, dass sie Flexibilität ermöglicht, ohne dabei die Klarheit oder Sicherheit zu beeinträchtigen.

Wenn ein bullisches Supertrend-Signal auftritt, wertet der EA dies als bestätigten Übergang von einem bärischen zu einem bullischen Marktzustand. Die erste Aufgabe in diesem Moment ist es, die Positionierung an den neuen Trendzustand anzupassen. Falls eine von dieser EA-Instanz eröffnete Short-Position besteht, wird diese sofort geschlossen. Positionen in entgegengesetzten Richtungen würden dem Indikatorstatus widersprechen und die Ergebnisse verfälschen. Sobald die Übereinstimmung wiederhergestellt ist, prüft der EA, ob Long-Positionen zulässig sind. Ist dies der Fall, wird unverzüglich eine Marktkauforder ausgeführt.

Die gleiche Logik gilt umgekehrt auch für bärische Signale. Ein bestätigter Schlusskurs unterhalb des unteren Supertrend-Bandes markiert den Übergang in eine bärische Phase. Zunächst wird jede aktive Long-Position geschlossen, die von diesem EA eröffnet wurde. Ist der Leerverkauf aktiviert, wird daraufhin eine Marktverkaufsorder platziert. Diese Abfolge stellt sicher, dass der EA stets im Einklang mit dem aktuellen Supertrend-Status arbeitet und niemals widersprüchliche Positionen eingeht.

Das Risikomanagement wird als konfigurierbare Komponente und nicht als feste Regel behandelt. Mit dem EA lässt sich die Verwendung von Stop-Loss-Aufträgen vollständig aktivieren oder deaktivieren. Diese Option richtet sich sowohl an Trader, die rein signalbasierte Ausstiegspunkte bevorzugen, als auch an diejenigen, die vordefinierte Risikogrenzen benötigen.

Wenn Stop-Loss-Aufträge aktiviert sind, kann ihre Platzierung nach einem von zwei logischen Modellen erfolgen. Der erste Ansatz ist strukturbasiert. Bei Long-Positionen wird der Stop-Loss auf das Tief der zuletzt abgeschlossenen Kerze gesetzt. Bei Short-Positionen wird er auf das Hoch der Kerze gesetzt. Dieser Ansatz verknüpft das Risiko direkt mit der jüngsten Kursentwicklung. Das zweite Modell basiert auf der Volatilität. In diesem Fall wird der Stop-Loss auf den Supertrend-Bandwert der letzten abgeschlossenen Kerze gesetzt, wobei der Indikator selbst als dynamisches Schutzniveau dient.

Die Richtungssteuerung ist ein weiterer wichtiger Bestandteil des Designs. Der EA kann im Long-Only-, Short-Only- oder vollständig bidirektionalen Modus betrieben werden. Dadurch können diskretionärer Richtungsbias und Automatisierung nebeneinander bestehen. Wenn eine umfassendere Marktanalyse eine bestimmte Richtung nahelegt, kann der EA so eingestellt werden, dass er ausschließlich in dieser Richtung handelt, während Ausführung und Risikomanagement weiterhin konsistent automatisiert werden.

Auch die Positionsgrößenbestimmung ist mit derselben Flexibilität ausgelegt. Im Modus mit fester Losgröße wird bei allen Trades unabhängig von den Marktbedingungen ein konstantes Volumen verwendet. Alternativ berechnet ein Automatikmodus die Losgröße auf der Grundlage eines vordefinierten Prozentsatzes des Kontoguthabens. Dadurch wird sichergestellt, dass sich das Risiko mit dem Wachstum oder Rückgang des Kontostands auf natürliche Weise anpasst, ohne dass manuelle Anpassungen erforderlich sind.

Zusammen bilden diese Gestaltungsentscheidungen ein strukturiertes und zugleich anpassungsfähiges Handelssystem. Die Signale bleiben klar und objektiv, während Ausführung, Risiko und Marktengagement über transparente Konfigurationsebenen gesteuert werden. Durch diese Trennung von Logik und Design eignet sich der EA sowohl für den praktischen Einsatz als auch für die einfache Erweiterung im Rahmen weiterer Forschungs- und Versuchsarbeiten. 


Aufbau des Grundgerüsts des Expert Advisors

In diesem Abschnitt geht es darum, die Standardvorlage für den Expert Advisor einzurichten und dessen Struktur zu verstehen. Alles, was nun folgt, baut auf dieser Grundlage auf, daher muss dieser Schritt korrekt ausgeführt werden.

Voraussetzungen und Einrichtung der Umgebung

An dieser Stelle gehen wir davon aus, dass Sie mit den Grundlagen der Programmiersprache MQL5 vertraut sind. Außerdem setzen wir einen sicheren Umgang mit der Plattform MetaTrader 5 voraus, was das Anfügen von Programmen an Charts, das Konfigurieren von Eingabeparametern und die Navigation durch Kernmodule wie den Strategietester und den Navigator betrifft.

Grundlegende Kenntnisse in MetaEditor 5 sind ebenso wichtig. Dazu gehören das Erstellen neuer Quelldateien, das Kompilieren von Code und das Überprüfen von Compiler-Meldungen. Diese Fähigkeiten sind für einen reibungslosen Entwicklungsprozess unerlässlich.

Dieser Expert Advisor basiert auf dem im vorherigen Artikel entwickelten Supertrend-Indikator. Um den Lernprozess konsistent zu halten, wurde der gesamte Quellcode des Indikators diesem Artikel erneut unter dem Dateinamen supertrend.mq5 beigefügt. Es ist wichtig, diese Datei herunterzuladen und lokal zu kompilieren, bevor Sie fortfahren.

Die Quelldatei des Indikators sollte im Standardverzeichnis für (.../MQL5/Indicators) abgelegt werden. Es wird dringend empfohlen, die Standardverzeichnisstruktur zu verwenden. Dadurch werden Probleme mit den Dateipfaden vermieden und es wird sichergestellt, dass der Expert Advisor den Indikator während der Ausführung korrekt finden und laden kann.

Sobald die Quelldatei des Indikators bereitliegt, sollte sie im MetaEditor geöffnet, kompiliert und auf Fehler überprüft werden. Dieser Schritt ist entscheidend, da der Expert Advisor die Signale direkt aus diesem Indikator ausliest. Falls der Indikator fehlt oder nicht kompiliert wurde, arbeitet der EA entweder stillschweigend fehlerhaft oder verhält sich unvorhersehbar.

Außerdem haben wir die vollständige Quelldatei für den Expert Advisor mit dem Namen supertrendExpert.mq5 beigefügt. Es wird empfohlen, diese Datei herunterzuladen und zur weiteren Verwendung geöffnet zu lassen. Der Vergleich einer sich weiterentwickelnden Implementierung mit einer vollständigen, funktionsfähigen Version ist eine der effektivsten Methoden, um die Programmstruktur und den Entscheidungsablauf zu verstehen.

Erstellen der EA-Standardvorlage

Nachdem die Umgebung nun vorbereitet ist, können wir das Grundgerüst für den Expert Advisor erstellen. In MetaEditor sollte eine neue, leere Expert-Advisor-Quelldatei erstellt und mit einem Namen Ihrer Wahl versehen werden. In diese Datei fügen wir den folgenden Standardcode ein, der als Grundlage für die gesamte weitere Entwicklung dienen wird.

//+------------------------------------------------------------------+
//|                                             supertrendExpert.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"

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TRADE_DIRECTION  
{ 
   ONLY_LONG, 
   ONLY_SHORT, 
   TRADE_BOTH 
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

enum ENUM_STOP_LOSS_MODE
{
   SL_MODE_NONE,
   SL_MODE_SWING_EXTREME,
   SL_MODE_SUPERTREND_VOLATILITY
};

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Supertrend configuration parameters"
input int32_t atrPeriod         = 10;
input double  atrMultiplier     = 1.5;

input group "Trade and Risk Management"
input ENUM_TRADE_DIRECTION direction        = ONLY_LONG;
input ENUM_STOP_LOSS_MODE stopLossMode      = SL_MODE_SWING_EXTREME; 
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- Bid and Ask
double   askPrice;
double   bidPrice;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID);  
}

//--- UTILITY FUNCTIONS

//+------------------------------------------------------------------+

Diese erste Struktur führt noch keine Trades durch und liest auch noch keine Indikatorwerte aus. Sein Zweck besteht darin, Konfigurationsoptionen zu definieren, Funktionen für den Programmlebenszyklus festzulegen und die Grundlagen für zukünftige Logik zu schaffen.

Dateikopf und Eigenschaften

//+------------------------------------------------------------------+
//|                                             supertrendExpert.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"

Der Dateikopf und die Eigenschaftsdeklarationen legen Eigentumsverhältnisse, Versionsangaben und Identität fest. Diese Eigenschaften werden im MetaTrader angezeigt und helfen dabei, diesen EA von anderen zu unterscheiden, insbesondere wenn mehrere Systeme gleichzeitig ausgeführt werden. Sie wirken sich zwar nicht direkt auf das Handelsverhalten aus, sind jedoch für die Übersichtlichkeit, die Pflege und die Verbreitung unerlässlich.

Einbindung der Standardbibliothek

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

Durch das Einbinden der Trade-Bibliothek wird Zugriff auf die Klasse CTrade gewährt. Diese Klasse vereinfacht die Ausführung und Verwaltung der Trades, indem sie Low-Level-Handelsanfragen in übersichtliche und sichere Methoden bündelt. Die Klasse CTrade wird später verwendet, um mithilfe ihrer integrierten Handelsfunktionen die Handelsausführung und das Positionsmanagement abzuwickeln.

Benutzerdefinierte Enumerationen

Die benutzerdefinierten Enumerationen legen kontrollierte Auswahlmöglichkeiten für wichtige Konfigurationsbereiche fest.

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TRADE_DIRECTION  
{ 
   ONLY_LONG, 
   ONLY_SHORT, 
   TRADE_BOTH 
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

enum ENUM_STOP_LOSS_MODE
{
   SL_MODE_NONE,
   SL_MODE_SWING_EXTREME,
   SL_MODE_SUPERTREND_VOLATILITY
};

Die Angabe der Handelsrichtung legt fest, ob der EA long, short oder beides handelt. Dies ermöglicht eine flexible Filterung der Trades, ohne die Logik zu ändern. Der Eingabemodus für die Losgröße legt fest, ob die Positionsgröße feststeht oder automatisch auf der Grundlage des Risikos berechnet wird. Der Stop-Loss-Modus legt fest, wie Schutz-Stops gehandhabt werden, einschließlich ihrer vollständigen Deaktivierung oder ihrer Platzierung auf der Grundlage der Kursstruktur oder der Supertrend-Werte. Die Verwendung von Enumerationen sorgt für mehr Übersichtlichkeit bei den Eingabeparametern und verhindert ungültige Konfigurationen.

Benutzereingaben

Die Eingabevariablen machen dem Benutzer über das EA-Einstellungsfenster alle konfigurierbaren Funktionen zugänglich.

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Supertrend configuration parameters"
input int32_t atrPeriod         = 10;
input double  atrMultiplier     = 1.5;

input group "Trade and Risk Management"
input ENUM_TRADE_DIRECTION direction        = ONLY_LONG;
input ENUM_STOP_LOSS_MODE stopLossMode      = SL_MODE_SWING_EXTREME; 
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;

Informationsgruppe

magicNumber

Dieser Wert identifiziert eindeutig alle von dieser EA-Instanz eröffneten Positionen. Dadurch kann der EA seine eigenen Trades von manuellen Trades oder von Trades unterscheiden, die von anderen EAs eröffnet wurden, die auf demselben Konto laufen. 

timeframe

Hiermit wird festgelegt, welchen Chart-Zeitrahmen der EA zum Auswerten der Supertrend-Signale verwendet. Selbst wenn der EA an ein Chart mit einem niedrigeren Zeitrahmen gekoppelt ist, kann er dennoch auf der Grundlage von Signalen aus einem Chart mit einem höheren Zeitrahmen handeln. Dadurch sind Strategien mit mehreren Zeitrahmen möglich, ohne dass der Code geändert werden muss.

Konfigurationsparameter für den Supertrend

atrPeriod

Hiermit wird der vom Supertrend-Indikator verwendete ATR-Rückblickzeitraum festgelegt. Ein niedrigerer Wert lässt den Indikator stärker auf Kursänderungen reagieren, während ein höherer Wert die Signale glättet und Störsignale reduziert. Da der EA Signale direkt vom Indikator ausliest, muss dieser Wert mit der Indikator-Konfiguration übereinstimmen, um Signalinkongruenzen zu vermeiden.

atrMultiplier

Hiermit wird der Abstand der Supertrend-Bänder zum Kurs festgelegt. Ein höherer Multiplikator führt zu breiteren Bändern, weniger Signalen und längeren Trends. Ein niedrigerer Multiplikator führt zu schmaleren Bändern und häufigeren Signalen. Dies wirkt sich direkt darauf aus, wie oft der EA Positionen eröffnet und schließt.

Handel und Risikomanagement

direction

Diese Einstellung legt fest, in welche Handelsrichtungen der EA Trades ausführen darf. ONLY_LONG erlaubt ausschließlich Long-Positionen, ONLY_SHORT ausschließlich Short-Positionen und TRADE_BOTH den Handel in beide Richtungen. Dies ist nützlich, wenn die Einschätzung des Anwenders auf eine starke Richtungsneigung hindeutet, sodass sich der EA an den allgemeinen Marktkontext anpassen kann.

stopLossMode

Hier wird festgelegt, ob und wie Stop-Loss-Aufträge platziert werden. SL_MODE_NONE deaktiviert die Platzierung von Stop-Loss-Aufträgen vollständig. Positionen werden erst geschlossen, wenn ein entgegengesetztes Supertrend-Signal auftritt. SL_MODE_SWING_EXTREME setzt den Stop-Loss auf das Hoch oder Tief der zuletzt abgeschlossenen Kerze, basierend auf der aktuellen Marktstruktur. SL_MODE_SUPERTREND_VOLATILITY setzt den Stop-Loss auf das Niveau des Supertrend-Bandes und passt ihn dynamisch an die Volatilität an.

lotSizeMode

Diese Eingabe bestimmt, wie die Positionsgröße berechnet wird. Im MANUAL_MODE wird für alle Trades eine feste Losgröße verwendet. AUTO_MODE berechnet die Losgröße auf der Grundlage des Kontostands und des Risikoprozentsatzes. Dadurch kann der EA die Positionsgröße automatisch anpassen, wenn das Konto wächst oder schrumpft.

riskPerTradePercent

Dieser Wert wird nur verwendet, wenn die automatische Losgrößenbestimmung aktiviert ist. Es legt fest, wie viel des Kontoguthabens bei jedem Trade riskiert wird. Der EA verwendet diesen Wert zusammen mit der Stop-Loss-Distanz zur Berechnung der Positionsgröße. Dadurch wird ein gleichbleibendes Risikoengagement unter verschiedenen Marktbedingungen gewährleistet.

Positionsgröße

Dieser Wert wird nur verwendet, wenn die manuelle Losgrößenbestimmung ausgewählt ist. Der EA eröffnet alle Positionen mit dieser festen Losgröße, unabhängig von der Volatilität oder der Stop-Loss-Distanz.

Globale Variablen

Globale Variablen speichern Werte, auf die über verschiedene Funktionen hinweg zugegriffen werden muss.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- Bid and Ask
double   askPrice;
double   bidPrice;

Das CTrade-Objekt wird einmal angelegt und für alle Handelsvorgänge wiederverwendet. Bid und Ask werden gespeichert, um wiederholte Symbolabfragen zu vermeiden und die Ausführungslogik übersichtlich zu halten. Zum jetzigen Zeitpunkt sind noch keine Indikator-Handles oder Signalpuffer deklariert. Diese werden später vorgestellt.

Funktionen im Programmlebenszyklus

Die Funktion OnInit wird einmalig aufgerufen, wenn der EA geladen wird.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);

   return(INIT_SUCCEEDED);
}

Hier weisen wir dem Handelsobjekt die magicNumber zu. Diese eine Zeile gewährleistet, dass die Handelsverantwortung während der gesamten Laufzeit des EAs korrekt zugewiesen wird.

Die Funktion OnDeinit wird ausgelöst, wenn der EA getrennt oder das Terminal heruntergefahren wird.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);

}

Derzeit wird lediglich der Grund für das Herunterfahren angezeigt. Später wird es auch die Freigabe der Ressourcen übernehmen.

Die OnTick-Funktion wird ausgeführt, sobald neue Kursdaten eintreffen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID); 
   
}

Derzeit werden lediglich die Bid und Ask aktualisiert. Dies ist beabsichtigt. Die Handelslogik wird schrittweise hinzugefügt, damit das Verhalten vorhersehbar und leicht zu debuggen bleibt.

Nachdem die Grundlagen nun gelegt sind, können wir im nächsten Abschnitt den Supertrend-Indikator vorstellen und mit der Implementierung der eigentlichen signalgesteuerten Ausführungslogik beginnen.


Implementierung der Logik zur Erkennung von Supertrend-Signalen

In dieser Entwicklungsphase verlagert sich unser Fokus von der Konfiguration und Struktur hin zur Signalerfassung. Da dieser Expert Advisor darauf ausgelegt ist, Trades direkt auf Basis des Supertrend-Indikators auszuführen, muss der Indikator selbst ein zentraler Bestandteil des Algorithmus werden. Der EA versucht nicht, den Supertrend intern nachzubilden. Stattdessen liest er die Signale direkt aus dem ursprünglichen Indikator aus. Durch diesen Ansatz bleibt die Logik im Einklang mit dem, was Händler visuell auf dem Chart sehen, und eine manuelle Ausführung ist nicht mehr erforderlich.

Den Supertrend-Indikator im EA verfügbar machen

Der erste Schritt besteht darin, den Supertrend-Indikator mithilfe einer Compiler-Direktive als Ressource in die ausführbare EA-Datei einzubinden.

#resource "\\Indicators\\supertrend.ex5"

Diese Zeile weist den Compiler an, den Supertrend-Indikator in die endgültige EA-Datei einzubinden. Daher ist der EA zur Laufzeit nicht auf eine externe Indikator-Datei angewiesen. Dies vereinfacht die Verteilung, das Testen und die Bereitstellung erheblich, da nur eine ausführbare Datei benötigt wird. Sobald der EA geladen ist, steht der Indikator intern bereits zur Verfügung und kann ohne zusätzliche Einrichtung initialisiert werden.

Globalen Speicher für Indikatorendaten deklarieren

Um programmgesteuert mit dem Supertrend-Indikator zu arbeiten, müssen wir Variablen definieren, die auf den Indikator verweisen, und dessen Ausgabe speichern.

//--- Supertrend values 
int    supertrendIndicatorHandle;
double upperBandValues[];
double lowerBandValues[];

Der Indikator-Handle dient als eindeutige Referenz auf die im Hintergrund laufende Supertrend-Instanz. Ohne diesen Handle kann der EA nicht mit dem Indikator kommunizieren oder dessen Daten abrufen. Die Arrays des oberen und des unteren Bands sind dynamische Container, die die aktuellen Werte aus den Indikatorpuffern speichern. Diese Bänder stellen die aktiven Supertrend-Niveaus dar und sind entscheidend für die Bestimmung des aktuellen Trendzustands. Durch die Speicherung der letzten Werte kann der EA vergangene und aktuelle Bedingungen vergleichen, um zu erkennen, wann sich ein Trend umkehrt.

Initialisierung des Supertrend-Indikators

Innerhalb der Initialisierungsfunktion wird der Supertrend-Indikator mithilfe der Funktion iCustom erstellt.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, timeframe, "::Indicators\\supertrend.ex5", atrPeriod, atrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

Dieser Aufruf lädt den eingebetteten Indikator, wendet ihn auf das ausgewählte Symbol und den ausgewählten Zeitrahmen an und übergibt dieselben Parameter, die auch vom visuellen Indikator verwendet werden. Dadurch wird sichergestellt, dass der EA Signale aus einer identischen Konfiguration ausliest. Das von iCustom zurückgegebene Handle gibt an, ob der Indikator erfolgreich initialisiert wurde. Ist der Handle ungültig, kann der EA nicht fortfahren, da eine Signalerfassung unmöglich wäre. Aus diesem Grund führt ein Fehler bei der Initialisierung zu einer ordnungsgemäßen Beendigung des EA. Dadurch wird verhindert, dass das System in einem undefinierten Zustand läuft.

Verwendung der Zeitreihenreihenfolge für Indikator-Arrays

Standardmäßig speichern MQL5-Arrays Daten in aufsteigender Reihenfolge, wobei ältere Werte an erster Stelle stehen. Für die Handelslogik ist dies unpraktisch, da häufig auf die aktuellen Werte zugegriffen wird.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);

   return(INIT_SUCCEEDED);
}

Da diese Arrays als Zeitreihen behandelt werden, entspricht der Index Null immer der zuletzt abgeschlossenen Bar. Index 1 bezieht sich auf die vorherige Bar, und so weiter. Diese Reihenfolge entspricht der Art und Weise, wie an anderer Stelle in MQL5 auf Marktdaten zugegriffen wird, wodurch die Signallogik übersichtlicher und weniger fehleranfällig wird.

Pufferwerte des Indikators auslesen

Nachdem der Indikator initialisiert und die Speicher-Arrays vorbereitet wurden, besteht die nächste Aufgabe darin, die aktuellen Supertrend-Werte abzurufen. Die Funktion UpdateSupertrendBandValues kopiert Daten aus den Indikatorpuffern in die EA-Arrays.

//--- UTILITY FUNCTIONS

//+------------------------------------------------------------------+
//| Fetches recent Supertrend upper and lower band values            |
//+------------------------------------------------------------------+
void UpdateSupertrendBandValues(){

   //--- Get a few Supertrend upper band values
   int copiedUpper = CopyBuffer(supertrendIndicatorHandle, 5, 0, 5, upperBandValues);
   if(copiedUpper == -1)
   {
      Print("Error while copying Supertrend upper band values: ", GetLastError());
      return;
   }

   //--- Get a few Supertrend lower band values
   int copiedLower = CopyBuffer(supertrendIndicatorHandle, 6, 0, 5, lowerBandValues);
   if(copiedLower == -1)
   {
      Print("Error while copying Supertrend lower band values: ", GetLastError());
      return;
   }
   
   if(copiedUpper < 5 || copiedLower < 5){
      Print("Insufficient Supertrend indicator data!");
      return;
   }   
}

Intern fordert CopyBuffer über dessen Handle einen bestimmten Puffer vom Indikator an. Der Pufferindex gibt an, auf welchen internen Datenstrom gerade zugegriffen wird. Es wird eine festgelegte Anzahl aktueller Werte kopiert, wodurch sichergestellt wird, dass der EA stets über genügend historische Daten verfügt, um Trendwechsel zu erkennen. Nach jedem Kopiervorgang werden Fehlerprüfungen durchgeführt. Wenn der Indikator keine Daten zurückgibt oder weniger Werte als erwartet zurückgibt, wird die Funktion vorzeitig beendet. Dadurch wird verhindert, dass der EA Entscheidungen auf der Grundlage unvollständiger oder ungültiger Daten trifft. Sobald diese Funktion erfolgreich abgeschlossen ist, verfügt der EA über synchronisierte Werte für das obere und untere Band, die den aktuellen Supertrend-Zustand widerspiegeln.

Erkennen neuer Bars

Handelsentscheidungen werden erst bei Eröffnung einer neuen Kerze ausgewertet. Um diese Logik zu veranschaulichen, definieren wir die folgende benutzerdefinierte Funktion:

//+------------------------------------------------------------------+
//| Function to check if there's a new bar on a given chart timeframe|
//+------------------------------------------------------------------+
bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &lastTm){

   datetime currentTm = iTime(symbol, tf, 0);
   if(currentTm != lastTm){
      lastTm       = currentTm;
      return true;
   }  
   return false;
}

Dadurch wird eine wiederholte Ausführung bei jedem Tick vermieden und es wird sichergestellt, dass die Signale auf bestätigten Kerzendaten basieren. Die Funktion IsNewBar vergleicht den Eröffnungszeitpunkt der aktuellen Bar mit einem gespeicherten Zeitstempel, dessen Variable im globalen Bereich definiert ist.

//--- To help track new bar open
datetime lastBarOpenTime;

Diese Variable erfasst die zuletzt verarbeitete Bar. Er wird beim Start auf Null gesetzt, damit die erste erkannte Bar immer als neu behandelt wird.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   //--- Initialize global variables
   lastBarOpenTime = 0;

   return(INIT_SUCCEEDED);
}

Die Initialisierung auf Null gewährleistet einen sauberen Ausgangspunkt und verhindert, dass der erste Auswertungszyklus übersprungen wird. Wenn sich die Barzeit ändert, aktualisiert die Funktion den gespeicherten Wert und signalisiert, dass sich eine neue Bar gebildet hat. Dieser Mechanismus gewährleistet, dass die Signalerfassung einmal pro Kerze erfolgt und sowohl beim Backtesting als auch im Live-Handel konsistent bleibt.

Erkennen von bullischen und bärischen Supertrend-Signalen

Die Signalerkennung basiert darauf, wie sich die Supertrend-Bänder von einer Bar zur nächsten verändern. Ein bullisches Signal liegt vor, wenn das untere Band bei der zuletzt abgeschlossenen Bar aktiv wird, während das obere Band bei der vorherigen Bar aktiv war. Dieser Übergang bestätigt, dass sich der Kurs von einer bärische Phase in einen Aufwärtstrend gewandelt hat.

Die Funktion zur Erkennung von Aufwärtstrends prüft genau diese Bedingung, indem sie die aktuellen Bandwerte untersucht und sicherstellt, dass an den erwarteten Stellen gültige Daten vorliegen.

//+--------------------------------------------------------------------------------+
//| Detects a bullish Supertrend signal when price confirms an upward trend change |
//+--------------------------------------------------------------------------------+
bool IsSupertrendBullishSignal(){

   if(lowerBandValues[1] != EMPTY_VALUE && upperBandValues[2] != EMPTY_VALUE){
      return true;
   }
   
   return false;
}

Die Logik zur Erkennung von Abwärtstrends folgt derselben Struktur, jedoch in umgekehrter Reihenfolge. Ein bärisches Signal liegt vor, wenn das obere Band aktiv wird, nachdem zuvor das untere Band aktiv gewesen ist. Dies bestätigt eine Trendwende nach unten.

//+---------------------------------------------------------------------------------+
//| Detects a bearish Supertrend signal when price confirms a downward trend change |
//+---------------------------------------------------------------------------------+
bool IsSupertrendBearishSignal(){

   if(upperBandValues[1] != EMPTY_VALUE && lowerBandValues[2] != EMPTY_VALUE){
      return true;
   }
   
   return false;
}

Durch die Aufteilung der Erkennung von Aufwärts- und Abwärtstrends in separate Funktionen bleibt die Logik übersichtlich, gut lesbar und leicht zu überprüfen.

Überprüfung der Logik anhand eines Testfalls

Bevor mit der Handelsausführung begonnen wird, muss die Signallogik validiert werden. Dies geschieht, indem die erkannten Signale innerhalb der Tick-Funktion protokolliert werden, sobald sich eine neue Bar bildet.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   ...
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      UpdateSupertrendBandValues();
      
      if(IsSupertrendBullishSignal()){   
         Print("Bullish Signal");
      }
      
      if(IsSupertrendBearishSignal()){
         Print("Bearish Signal");
      }      
   }   
}

Bei jeder neuen Bar aktualisiert der EA die Supertrend-Werte und wertet sowohl bullische als auch bärische Bedingungen aus. Wenn sich ein Trend umkehrt, wird eine Meldung in das Journal geschrieben. Während des Tests werden diese Meldungen in wechselnder Reihenfolge angezeigt, je nachdem, wie sich die Trends entwickeln.

Journal-Testprotokoll

Dieses Verhalten bestätigt, dass der EA die Supertrend-Übergänge korrekt interpretiert und die Erkennungslogik wie vorgesehen funktioniert.

Das Erreichen dieses Punktes ist ein wichtiger Meilenstein. Der EA kann nun Indikatorendaten auswerten, Veränderungen der Marktstruktur verfolgen und Trendwechsel zuverlässig erkennen.


Entwicklung der Handels- und Positionsmanagement-Logik

Nachdem die Signalerfassung abgeschlossen ist, gehen wir nun in die Handelsphase über. In dieser Phase geht es darum, Supertrend-Signale in kontrollierte Marktmaßnahmen umzusetzen. Das Ziel ist einfach und bewusst gewählt. Der EA darf zu jedem Zeitpunkt nur Positionen halten, die der aktuellen Trendrichtung entsprechen. Wenn sich der Trend ändert, müssen Positionen in die entgegengesetzte Richtung sofort geschlossen werden, bevor ein neuer Trade in Betracht gezogen wird.

Um dieses Verhalten zu erreichen, benötigen wir zunächst zuverlässige Methoden zur Überprüfung bestehender Positionen, zur Verwaltung von Ausstiegspunkten, zur Berechnung von Positionsgrößen, zur Ermittlung adaptiver Stop-Loss-Niveaus und schließlich zur Ausführung von Trades. Jede dieser Komponenten wird Schritt für Schritt vorgestellt und später innerhalb der Haupt-Handelsschleife kombiniert.

Prüfung auf bestehende Kaufpositionen

Die erste Nutzfunktion, die wir vorstellen, beantwortet eine einfache, aber entscheidende Frage. Hat dieser EA bereits eine aktive Kaufposition?

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active buy position.  |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveBuyPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", GetLastError());
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   
   return false;
}

Die Logik funktioniert so, dass alle derzeit offenen Positionen im Handelskonto durchgesehen werden. Für jede Position ruft der EA das entsprechende Ticket ab und überprüft zwei Dinge. Zunächst muss die Position zu dieser EA-Instanz gehören, was durch die magicNumber bestätigt wird. Zweitens muss die Positionsart eine Kaufposition sein. Sobald eine solche Position gefunden wird, gibt die Funktion true zurück. Wird die Schleife beendet, ohne dass eine passende Position gefunden wurde, wird false zurückgegeben. Diese Funktion dient dazu, eine wichtige Konstruktionsregel durchzusetzen. Der EA darf niemals mehrere Positionen in derselben Richtung stapeln.

Prüfung auf bestehende Verkaufspositionen

Als Nächstes definieren wir eine zweite Funktion zur Erkennung aktiver Verkaufspositionen.

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active sell position. |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveSellPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", GetLastError());
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            return true;
         }
      }
   }
   
   return false;
}

Die interne Logik entspricht vollständig der vorherigen Funktion. Der einzige Unterschied besteht in der Art der markierten Position. Anstatt nach Kaufpositionen zu suchen, sucht es nach Verkaufspositionen, die von diesem EA eröffnet wurden. Da Struktur und Zweck identisch sind, werden mit dieser Funktion keine neuen Konzepte eingeführt. Dadurch wird die Logik zur Positionserkennung einfach vervollständigt, indem die entgegengesetzte Handelsrichtung abgedeckt wird. Zusammen bieten diese beiden Funktionen dem EA einen vollständigen Überblick über sein aktuelles Risikoengagement.

Positionen schließen, wenn sich der Trend ändert

Sobald wir bestehende Positionen erkennen können, brauchen wir eine saubere Möglichkeit, diese zu schließen. Da dieser EA derzeit keine Take-Profit-Logik verwendet, müssen Positionen manuell geschlossen werden, sobald sich der Markt umkehrt. Um diese Logik zu unterstützen, definieren wir die folgende benutzerdefinierte Funktion:

//+------------------------------------------------------------------+
//| To close all position with a specified magic number              |   
//+------------------------------------------------------------------+
void ClosePositionsByMagic(ulong magic) {
    
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket)) {
            if (PositionGetInteger(POSITION_MAGIC) == magic) {
                ulong positionType = PositionGetInteger(POSITION_TYPE);
                double volume = PositionGetDouble(POSITION_VOLUME);
                if (positionType == POSITION_TYPE_BUY) {
                    Trade.PositionClose(ticket);
                } else if (positionType == POSITION_TYPE_SELL) {
                    Trade.PositionClose(ticket);
                }
            }
        }
    }    
}

Die Funktion durchläuft alle offenen Positionen und wählt nur diejenigen aus, die mit der magicNumber des EAs übereinstimmen. Dadurch wird sichergestellt, dass weder manuelle Trades noch Trades anderer EAs davon betroffen sind. Für jede übereinstimmende Position prüft der EA, ob es sich um eine Kauf- oder Verkaufsposition handelt, und sendet über das CTrade-Objekt einen Auftrag zum Schließen der Position. Diese Funktion dient als Rücksetzmechanismus. Sobald sich der Trend ändert, schließt der EA alle veralteten Positionen, bevor er einen neuen Einstieg in Betracht zieht.

Berechnung der Positionsgröße anhand des Risikoprozentsatzes

Zur Unterstützung der automatischen Positionsgrößenbestimmung führen wir eine Funktion ein, die die Losgröße auf der Grundlage des Kontorisikos berechnet.

//+------------------------------------------------------------------+
//| Calculates position size based on a fixed percentage risk of the account balance |
//+------------------------------------------------------------------+
double CalculatePositionSizeByRisk(double stopDistance){
   double amountAtRisk = (riskPerTradePercent / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE);
   double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double volume       = amountAtRisk / (contractSize * stopDistance);
   return NormalizeDouble(volume, 2);
}

Zunächst wird ermittelt, wie viel Geld bei einem einzelnen Trade riskiert werden kann. Dieser Betrag ergibt sich aus dem Kontostand und dem benutzerdefinierten Risikoprozentsatz. Anschließend berücksichtigt die Funktion die Kontraktgröße des gehandelten Wertpapiers und die Stop-Loss-Distanz. Dadurch wird sichergestellt, dass das berechnete Volumen das tatsächliche Marktrisiko widerspiegelt und nicht auf festen Annahmen beruht. Abschließend wird der Wert auf eine gültige Handelsgenauigkeit normiert. Der Rückgabewert stellt eine Positionsgröße dar, die das Risiko an die Marktvolatilität anpasst. Diese Funktion ermöglicht es dem EA, sich automatisch an verschiedene Symbole, Kontogrößen und Stop-Loss-Abstände anzupassen, ohne dass eine manuelle Neukalibrierung erforderlich ist.

Berechnung adaptiver Stop-Loss-Niveaus

Bevor ein Trade eröffnet wird, muss der EA wissen, wo der Stop-Loss logischerweise angesetzt werden sollte. Auch wenn der Nutzer sich dafür entscheidet, keinen Stop-Loss zu setzen, wird das Kursniveau dennoch für die Risikoberechnung benötigt. Diese Funktion sollte unmittelbar nach der Funktion zur Positionsgrößenbestimmung eingefügt werden.

//+-----------------------------------------------------------------------------------------------+
//| Calculates the appropriate stop loss price based on position type and selected stop loss mode |
//+-----------------------------------------------------------------------------------------------+
double CalculateAdaptiveStopLossPrice(ENUM_POSITION_TYPE positionType){
   
   double stopLossPrice = 0.000000;
   
   if(positionType == POSITION_TYPE_BUY){
   
      if(stopLossMode == SL_MODE_SWING_EXTREME){
         stopLossPrice = NormalizeDouble(iLow(_Symbol, timeframe, 1), Digits());
      }
      
      if(stopLossMode == SL_MODE_SUPERTREND_VOLATILITY){
         stopLossPrice = NormalizeDouble(lowerBandValues[1], Digits());
      }
      
      if(stopLossMode == SL_MODE_NONE){
         stopLossPrice = NormalizeDouble(lowerBandValues[1], Digits());
      }      
   }
   
   if(positionType == POSITION_TYPE_SELL){
   
      if(stopLossMode == SL_MODE_SWING_EXTREME){
         stopLossPrice = NormalizeDouble(iHigh(_Symbol, timeframe, 1), Digits());
      }
      
      if(stopLossMode == SL_MODE_SUPERTREND_VOLATILITY){
         stopLossPrice = NormalizeDouble(upperBandValues[1], Digits());
      }
      
      if(stopLossMode == SL_MODE_NONE){
         stopLossPrice = NormalizeDouble(upperBandValues[1], Digits());
      }   
   }   
   return stopLossPrice;  
}

Die Funktion nimmt den gewünschten Positionstyp entgegen und wählt auf Grundlage des ausgewählten Stop-Loss-Modus einen Stop-Loss-Kurs aus. Bei Kaufpositionen werden als Swing-Extreme das Tief der vorherigen Kerze herangezogen, während bei der volatilitätsbasierten Logik das untere Supertrend-Band der vorherigen Kerze verwendet wird. Bei Verkaufspositionen wird diese Logik anhand des Höchstkurses der vorherigen Kerze oder des oberen Supertrend-Bandes gespiegelt. Wenn kein Stop-Loss ausgewählt ist, gibt die Funktion dennoch einen auf dem Supertrend basierenden Wert zurück. Dies ist beabsichtigt. Die automatische Positionsgrößenbestimmung stützt sich auf eine Risikodistanz, wobei die Volatilität die stabilste Bezugsgröße darstellt.

Eröffnung von Kaufpositionen

Nachdem nun alle erforderlichen Hilfsfunktionen vorhanden sind, definieren wir nun die Funktion, die für die Eröffnung von Kaufgeschäften zuständig ist.

//+------------------------------------------------------------------+
//| Function to open a market buy position                           |
//+------------------------------------------------------------------+
bool OpenBuy(double entryPrice, double stopLoss, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(entryPrice - stopLoss);
   }
   
   if(stopLossMode == SL_MODE_NONE){
      if(!Trade.Buy(lotSize, _Symbol, entryPrice, 0.000000, 0.000000)){
         Print("Error while executing a market buy order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }else{
      if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, 0.000000)){
         Print("Error while executing a market buy order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }
   return true;
}

Die Funktion prüft zunächst, ob die automatische Losgrößenbestimmung aktiviert ist. In diesem Fall berechnet das System die Losgröße anhand der Differenz zwischen dem Einstiegskurs und dem Stop-Loss. Anschließend ermittelt die Funktion, ob ein Stop-Loss an den Broker gesendet werden soll. Ist der Stop-Loss-Modus deaktiviert, wird die Position ohne Stop-Loss eröffnet. Andernfalls wird der berechnete Stop-Loss-Kurs in den Handelsauftrag aufgenommen. Die gesamte Handelsausführung wird vom CTrade-Objekt abgewickelt. Sollte der Befehl fehlschlagen, werden detaillierte Fehlerinformationen protokolliert, um die Diagnose von Ausführungsproblemen zu erleichtern. Bei erfolgreicher Ausführung wird true zurückgegeben, was bestätigt, dass der Handel korrekt ausgeführt wurde.

Eröffnung von Verkaufspositionen

Die Funktion zur Ausführung von Verkaufsaufträgen wird direkt unterhalb der Funktion zur Ausführung von Kaufaufträgen eingefügt.

//+------------------------------------------------------------------+
//| Function to open a market sell position                          |
//+------------------------------------------------------------------+
bool OpenSel(double entryPrice, double stopLoss , double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(stopLoss - entryPrice);
   }
   
   if(stopLossMode == SL_MODE_NONE){
      if(!Trade.Sell(lotSize, _Symbol, entryPrice, 0.000000, 0.000000)){
         Print("Error while executing a market sell order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }else{
      if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, 0.000000)){
         Print("Error while executing a market sell order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }
   
   return true;
}

Die Logik entspricht voll und ganz der Kauf-Funktion. Die einzigen Unterschiede bestehen in der Richtung des Handels und der Art und Weise, wie die Stop-Loss-Distanz berechnet wird. Diese Symmetrie sorgt dafür, dass die Handelslogik konsistent und leicht zu warten ist. Alle Verbesserungen, die an einer Funktion vorgenommen werden, lassen sich mit minimalem Aufwand auch auf die andere Funktion übertragen.

Alles in OnTick vereinen

Nachdem nun alle Handelsfunktionen bereitstehen, aktualisieren wir die Funktion OnTick. An dieser Stelle laufen die Signalerfassung und die Handelsausführung schließlich zusammen. Wir entfernen die bisherige, auf Journal-Ausgaben basierende Testlogik und ersetzen die Funktion OnTick durch die aktualisierte Version.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID); 
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      UpdateSupertrendBandValues();
      
      if(IsSupertrendBullishSignal()){   
         if(IsThereAnActiveSellPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_LONG){
            OpenBuy(askPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_BUY), positionSize);
         }
      }
      
      if(IsSupertrendBearishSignal()){
         if(IsThereAnActiveBuyPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_SHORT){
            OpenSel(bidPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_SELL), positionSize);
         }
      }      
   }   
}

Bei jeder neuen Bar aktualisiert der EA die Supertrend-Werte und ermittelt die Trendrichtung. Wird ein bullisches Signal erkannt, werden zunächst alle offenen Verkaufspositionen geschlossen. Erst dann erwägt der EA die Eröffnung einer Kaufposition, sofern die gewählte Handelsrichtung dies zulässt. Bei bärischen Signalen gilt derselbe Vorgang in umgekehrter Reihenfolge. Diese Struktur gewährleistet eine einheitliche Ausrichtung. Der EA nimmt niemals widersprüchliche Positionen ein und handelt niemals gegen den erkannten Trend.

Konfigurieren der Chart-Darstellung

Bevor wir mit den Tests beginnen, fügen wir noch eine letzte Funktion hinzu, die den Bedienkomfort verbessert. Die automatische Chart-Gestaltung hilft, auf einen Blick zu erkennen, ob der EA ordnungsgemäß läuft. Die Funktion zur Konfiguration der Charts sollte am Ende der Quelldatei platziert werden.

//+------------------------------------------------------------------+
//| 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_CANDLES)){
      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, clrSeaGreen)){
      Print("Error while setting bullish candles color, ", GetLastError());
      return false;
   }
      
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){
      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;
}

Mit dieser Funktion lassen sich die Hintergrundfarbe, die Farben der Kerzen, die Sichtbarkeit des Rasters und der Chart-Modus anpassen. Jede Einstellung verbessert die Klarheit und den Kontrast, wodurch sich das Handelsverhalten leichter beobachten lässt. Sollte eine Chart-Einstellung fehlschlagen, wird ein Fehler protokolliert und die Initialisierung abgebrochen. Dadurch werden unbemerkte Fehler vermieden und das Verhalten bleibt vorhersehbar.

Abschließend aktivieren wir die Logik zur Chart-Konfiguration innerhalb der Funktion OnInit.

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

Dadurch wird sichergestellt, dass die visuelle Umgebung automatisch vorbereitet wird, sobald der EA an ein Chart angehängt wird. Damit ist die EA-Entwicklungsphase abgeschlossen. Das System kann Trends erkennen, Positionen verwalten, Risiken berechnen, Trades ausführen und eine übersichtliche Handelsoberfläche bereitstellen.


Tests und Leistungsbewertung

Nachdem die gesamte Handelslogik nun feststeht, besteht der nächste Schritt darin, zu überprüfen, wie sich der Expert Advisor unter historischen Marktbedingungen verhält. Es wurde ein strukturierter Backtest durchgeführt, um die Performance, die Stabilität und das Risikoverhalten in einem realistischen Marktszenario zu bewerten.

Der Test wurde mit Gold unter Verwendung des Symbols XAUUSD durchgeführt. Als Zeitrahmen wurde H1 (Stundenbasis) verwendet, und der Testzeitraum umfasste ein volles Jahr, vom ersten Tag des Januars 2025 bis zum letzten Tag des Dezembers 2025. Dieser Zeitraum bietet einen ausgewogenen Querschnitt durch verschiedene Marktbedingungen, einschließlich Trend- und Konsolidierungsphasen.

Bei diesem Test war die Handelsrichtung ausschließlich auf Long-Positionen beschränkt. Das bedeutet, dass der EA ausschließlich in Haussephasen zum Einsatz kommen durfte, während er Baisse-Signale vollständig ignorierte. Die Positionsgröße wurde automatisch berechnet, wobei pro Trade ein Prozent des aktuellen Kontostands riskiert wurde. Die Stop-Loss-Platzierung wurde aktiviert und so eingestellt, dass der Extremwert der zuvor abgeschlossenen Kerze verwendet wird, wodurch eine einheitliche und objektive Risikokontrolle bei allen Trades gewährleistet wird.

Um diesen Test vollständig reproduzierbar zu machen, wurden zwei Begleitdateien beigefügt. Die erste Datei, configurations.ini, enthält die Konfiguration der Strategie-Tester-Umgebung, und die zweite Datei, parameters.set, enthält die genauen Eingabeparameter, die während des Tests verwendet wurden. Zusammen ermöglichen diese Dateien die Nachbildung derselben Bedingungen, ohne dass manuelle Schätzungen erforderlich sind.

Die Leistungsergebnisse sind bemerkenswert.

Testbericht

Ausgehend von einem Anfangsguthaben von 10.000 US-Dollar erzielte das System einen Nettogewinn von 21.380,79 US-Dollar. Dies entspricht einer Rendite von mehr als 200 % über den einjährigen Testzeitraum. Trotz einer relativ niedrigen Gewinnquote von 34,92 % verlief die Kapitalkurve während des gesamten Tests gleichmäßig und stabil.

Kapitalkurven

Es gab keine starken Kursrückgänge oder unvorhersehbaren Schwankungen, was auf kontrollierte Verluste und ein diszipliniertes Risikomanagement hindeutet. Der beigefügte Screenshot der Kapitalkurve bestätigt dieses Verhalten anschaulich. Das Wachstum ist stabil, die Verlustphasen sind begrenzt, und die Gesamtentwicklung spiegelt ein System wider, das eher auf ein asymmetrisches Risiko-Ertrags-Verhältnis als auf häufige Gewinne setzt.

In dieser Phase sollten die Tests noch nicht beendet werden. Der Expert Advisor wurde bewusst mit flexiblen Eingabeparametern konzipiert, um weitere Experimente zu ermöglichen. Es können verschiedene Symbole, Zeitrahmen, Risikostufen, Stop-Loss-Modi und Filter für die Handelsrichtung ausprobiert werden. Durch die Anpassung dieser Variablen lässt sich untersuchen, wie sich die Strategie unter verschiedenen Annahmen und Marktbedingungen verhält.

Den Lesern wird empfohlen, eigene Tests durchzuführen, die Standardeinstellungen zu hinterfragen und Varianten zu erkunden, die ihren eigenen Markteinschätzungen entsprechen. Alle Beobachtungen, Verbesserungsvorschläge oder unerwartete Ergebnisse sind es wert, geteilt zu werden. Wenn Sie Ihre Erkenntnisse im Kommentarbereich des Artikels hinterlassen, trägt dies dazu bei, das kollektive Verständnis zu erweitern, und kann neue Ideen oder Verbesserungsvorschläge zutage fördern, die es wert sind, weiterverfolgt zu werden.


Schlussfolgerung

In diesem Artikel wurde der gesamte Prozess der Erstellung eines praktischen, flexiblen Expert Advisors auf Basis des Supertrend-Indikators Schritt für Schritt erläutert. Am Ende dieser Reise haben wir mehr als nur funktionierenden Code erreicht. So erhalten wir ein praxistaugliches Forschungsinstrument, das manuelle Arbeitsschritte überflüssig macht, klare Handelsregeln durchsetzt und die Untersuchung des Marktverhaltens auf disziplinierte und reproduzierbare Weise ermöglicht.

Ein vollautomatisches, auf dem Supertrend basierendes Handelssystem wurde Schritt für Schritt entwickelt – von der Signalauswertung über die Ausführung und das Risikomanagement bis hin zum Testen. Das Endergebnis ist ein kostenloser, konfigurierbarer Expert Advisor, der entweder unverändert verwendet oder erweitert werden kann, um neue Ideen zu erproben. Dank seiner Flexibilität lassen sich verschiedene Stop-Loss-Modelle, Ansätze zur Positionsgrößenbestimmung und Filter für die Handelsrichtung implementieren, ohne den Quellcode ändern zu müssen. Dadurch eignet es sich nicht nur für Handelsexperimente, sondern auch für die strukturierte Strategieforschung.

Neben dem Tool selbst ist der dokumentierte Entwicklungsprozess ein ebenso wichtiges Ergebnis. Der Artikel zeigt, wie man einen Expert Advisor mit einem benutzerdefinierten Indikator verbindet, Indikatorpuffer sicher ausliest, auf neue Marktdaten reagiert und Signale in kontrollierte Handelsaktionen umsetzt. Dies sind Kernkompetenzen in der MQL5-Entwicklung, die weit über den Supertrend-Indikator hinaus Anwendung finden.

Für Leser, die sich noch nicht mit der Entwicklung indikatorbasierter Handelssysteme auskennen, dient dieser Artikel als übersichtliche und praxisnahe Referenz. Erfahrenen Entwicklern bietet es eine übersichtliche Grundlage, die optimiert, erweitert oder mit zusätzlicher Logik kombiniert werden kann. In beiden Fällen ergibt sich daraus eine solide Ausgangsbasis für weitere Untersuchungen im Rahmen der fortlaufenden Suche nach einem beständigen Handelsvorteil.

In der folgenden Tabelle sind alle diesem Artikel beigefügten Dateien aufgeführt, zusammen mit einer kurzen Erläuterung ihres Zwecks und ihrer Verwendung im Rahmen des Projekts.



Dateiname Beschreibung
1 supertrend.mq5 Die Quelldatei des Supertrend-Indikators dient zur Berechnung der Trendrichtung und der auf der Volatilität basierenden Kursbänder. Der Expert Advisor liest diese Werte direkt aus, um Handelssignale zu generieren.
2 supertrendExpert.mq5 Quelldatei für einen Expert Advisor, der die Handelsausführung auf der Grundlage von Supertrend-Signalen, Risikomanagement-Regeln und benutzerdefinierten Eingabeparametern automatisiert.
3 configurations.ini Die Konfigurationsdatei des Strategy Testers definiert die Testumgebung. Damit lassen sich die in diesem Artikel verwendeten Backtest-Bedingungen exakt nachstellen.
4 parameters.set Datei mit den Eingabeparametern, die alle während des Backtests angewendeten Einstellungen des Expert Advisors enthält, darunter Risiko, Handelsrichtung und Stop-Loss-Verhalten.

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

Beigefügte Dateien |
supertrend.mq5 (17.92 KB)
configurations.ini (1.38 KB)
parameters.set (1.26 KB)
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Einführung in MQL5 (Teil 40): Einsteigerleitfaden zur Dateiverarbeitung in MQL5 (II) Einführung in MQL5 (Teil 40): Einsteigerleitfaden zur Dateiverarbeitung in MQL5 (II)
Erstellen Sie in MQL5 ein CSV-Trading-Journal, indem Sie den Kontoverlauf für einen festgelegten Zeitraum auslesen und strukturierte Datensätze in eine Datei schreiben. Der Artikel erläutert die Zählung von Transaktionen, das Abrufen von Tickets, das Zuordnen von Symbolen und Ordertypen sowie die Erfassung von Daten zur Eröffnung (Losgröße, Zeitpunkt, Kurs, SL/TP) und zur Schließung (Zeitpunkt, Kurs, Gewinn, Ergebnis) mithilfe dynamischer Arrays. Das Ergebnis ist ein übersichtliches, dauerhaft verfügbares Trading-Journal, das sich für Analysen und Berichte eignet.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Liquidity-Raids optimieren: Den Unterschied zwischen Liquidity-Raids und Marktstrukturverschiebungen verstehen Liquidity-Raids optimieren: Den Unterschied zwischen Liquidity-Raids und Marktstrukturverschiebungen verstehen
In diesem Artikel geht es um einen spezialisierten, trendfolgenden EA, der deutlich machen soll, wie man Handels-Setups nach Liquidity-Raids nutzen kann. In diesem Artikel wird ein EA ausführlich vorgestellt, der speziell für Trader entwickelt wurde, die daran interessiert sind, Liquiditätsabschöpfungen als Einstiegskriterien für ihre Trades und Handelsentscheidungen zu optimieren und zu nutzen. Außerdem wird untersucht, wie man korrekt zwischen Liquidity-Raids und Marktstrukturwechseln unterscheidet und wie man diese jeweils validiert und nutzt, wenn sie auftreten, um so Verluste zu minimieren, die dadurch entstehen, dass Trader beides miteinander verwechseln.