English 日本語
preview
Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur

Larry Williams Marktgeheimnisse (Teil 2): Automatisierung eines Handelssystems der Marktstruktur

MetaTrader 5Handel |
161 3
Chacha Ian Maroa
Chacha Ian Maroa

Einführung

Viele Händler verstehen die Marktstruktur visuell, haben aber Schwierigkeiten, dieses Verständnis in einen präzisen, wiederholbaren Handelsprozess umzusetzen. Umkehrpunkte sind im Nachhinein auf einem Chart leicht zu erkennen, aber konsequente Entscheidungen in Echtzeit zu treffen, ist viel schwieriger. Diese Herausforderung wird noch größer, wenn ein Händler seinen Ermessensspielraum aufgeben und sich auf objektive Regeln verlassen möchte, die getestet und automatisiert werden können.

Im ersten Artikel dieser Serie haben wir einen Teil dieses Problems angegangen, indem wir einen nutzerdefinierten Marktstrukturindikator in MQL5 erstellt haben, der auf Konzepten aus dem Buch „Long-Term Secrets to Short-Term Trading“ von Larry Williams basiert. Dieser Indikator identifiziert kurz- und mittelfristige Umkehrpunkte direkt auf dem Chart und bietet Händlern eine klare, strukturierte Sicht auf das Kursverhalten. In diesem zweiten Artikel gehen wir den nächsten logischen Schritt. Wir gehen von der visuellen Analyse zur vollständigen Automatisierung über. Mit MQL5 entwickeln wir einen Expert Advisor, der die Marktstrukturdaten aus dem Indikator ausliest und in umsetzbare Handelsentscheidungen umwandelt. Ziel ist es zu zeigen, wie eine diskretionäre Idee als klare Regeln ausgedrückt und automatisch ohne emotionale Einmischung ausgeführt werden kann.

Dieser Artikel ist Teil der Reihe über die Geheimnisse des Marktes von Larry Williams, in der jede Folge ein Konzept aus Larry Williams' Arbeit in einer praktischen, überprüfbaren Weise umsetzt. In diesem Teil konzentrieren wir uns auf die kurz- und mittelfristigen Umkehrpunkte und zeigen, wie sie genutzt werden können, um unmittelbar nach der Bestätigung der Struktur Handelsgeschäfte zu tätigen. Am Ende dieses Artikels wird der Leser ein funktionierendes Handelssystem haben, das die Lücke zwischen der Theorie über Marktstrukturen und der realen Automatisierung mit MQL5 schließt.


Wer ist Larry Williams?

Larry Williams ist einer der angesehensten Namen im Handel. Er ist ein Aktien- und Rohstoffhändler mit einer langen Erfolgsgeschichte. Er ist auch Autor zahlreicher Handelsbücher. Eine seiner bekanntesten Veröffentlichungen ist „Long-Term Secrets to Short-Term Trading“ (Langfristige Geheimnisse für den kurzfristigen Handel). Viele Händler studieren dieses Buch wegen seiner praktischen Herangehensweise an die Marktstruktur und Umkehrpunktanalyse, die als Grundlage für diesen Artikel dient.

Larry Williams erlangte große Anerkennung, nachdem er 1987 den World Cup Championship of Futures Trading gewonnen hatte. Bei diesem Wettbewerb machte er innerhalb von zwölf Monaten aus zehntausend Dollar (10.000 $) mehr als eine Million Dollar (1.000.000 $). Diesen Rekord hat seither niemand gebrochen. Zehn Jahre später nahm seine Tochter Michelle Williams an demselben Wettbewerb teil und gewann ebenfalls. Dies zeigte, dass seine Ideen von anderen gelernt und erfolgreich angewendet werden konnten.


Überblick über die Strategie

Bevor wir irgendetwas automatisieren, ist es wichtig, kurz auf die Marktstrukturkonzepte einzugehen, die dieser Strategie zugrunde liegen. Diese Ideen werden in Teil 1 dieser Serie ausführlich erläutert, sodass wir uns hier nur auf das konzentrieren, was zum Verständnis der Handelslogik notwendig ist.

Der folgende Screenshot zeigt den in Teil 1 entwickelten Marktstrukturindikator in einem Live-Markt.

Larry Williams‘ Marktstrukturindikator

Diese visuelle Referenz wird Ihnen helfen, klar zu erkennen, wie kurz- und mittelfristige Umkehrpunkte identifiziert werden, und sie wird es vereinfachen, zu verfolgen, wie dieselben Punkte später vom Expert Advisor verwendet werden, um Handelssignale zu generieren.

Larry Williams definiert die Marktstruktur anhand von Umkehrpunkten, die sich auf natürliche Weise aus den Preisbewegungen ergeben. Ein kurzfristiger tiefer Umkehrpunkt entsteht, wenn der Kurs ein Tief bildet, das von höheren Tiefs auf beiden Seiten umgeben ist.

kurzfristiges Tief

Dies deutet darauf hin, dass der Verkaufsdruck nachgelassen und der Kurs eine Aufwärtsbewegung eingeleitet hat. Ein kurzfristiger hoher Umkehrpunkt ist das Gegenteil.

kurzfristiges Hoch

Er bildet sich, wenn der Kurs einen Höchststand erreicht, der von niedrigeren Hochs auf beiden Seiten umgeben ist, was darauf hindeutet, dass der Kaufdruck nachgelassen und der Kurs begonnen hat, nach unten zu drehen. Larry bezeichnete diese ursprünglich als „ringed highs and lows“, weil die Händler sie auf den Charts einkreisten, um sie hervorzuheben.

Die Märkte bleiben nicht auf einer Strukturebene stehen. Nach Larry Williams verbinden sich kurzfristige Umkehrpunkte zu mittelfristigen Umkehrpunkten. Ein mittelfristiges Tief ist ein kurzfristiges Tief, das niedriger ist als die kurzfristigen Tiefs auf beiden Seiten.

mittelfristiges Tief

Ein mittelfristiges Hoch ist ein kurzfristiges Hoch, das höher ist als die kurzfristigen Hochs auf beiden Seiten.

mittelfristiges Hoch

Diese Verschachtelung von Umkehrpunkten ermöglicht es uns, die Marktbewegung mechanisch und objektiv zu beschreiben, ohne subjektive Chartinterpretation.

Eine der wichtigsten Beobachtungen, die Larry Williams in „Long-Term Secrets to Short-Term Trading“ macht, ist, dass diese Umkehrpunkte nicht nur beschreibend sind. Sie sind umsetzbar. Er erklärt, dass er konsequent Gewinne erzielte, indem er die Bildung und Verletzung dieser Umkehrpunkte als Einstiegs-, Ausstiegs- und Stop-Loss-Niveaus nutzte. Nach seinen Worten stellen diese Punkte die wichtigsten Unterstützungs- und Widerstandsniveaus auf dem Markt dar. Wenn sie halten, bestätigen sie die Fortsetzung des Trends. Wenn sie brechen, warnen sie vor einem Trendwechsel.

In diesem Artikel geht es darum, diese Idee in ein vollautomatisches Handelssystem umzusetzen. In Teil 2 der Serie beschränken wir uns bewusst auf kurz- und mittelfristige Umkehrpunkte. Langfristige Umkehrpunkte werden in einem späteren Artikel vorgestellt und automatisiert, sobald die Grundlagen dafür geschaffen sind.

Die grundlegende Handelslogik ist einfach und lehnt sich eng an die Überlegungen von Larry Williams an. Wir warten darauf, dass ein mittelfristiger Umkehrpunkt durch die Bildung eines kurzfristigen Umkehrpunktes bestätigt wird. Wenn diese Bestätigung erfolgt, steigt der EA sofort in den Markt ein, sofern keine aktive Position vorhanden ist.

Eine Kaufposition wird eröffnet, wenn ein kurzfristiger tiefer Umkehrpunkt einen mittelfristigen tiefen Umkehrpunkt bestätigt. Dies deutet darauf hin, dass der Preis wahrscheinlich eine Korrekturphase abgeschlossen hat und in eine neue Aufwärtsphase eintreten könnte. Der EA prüft diese Bedingung bei jedem neuen Balken und eröffnet eine Marktkauforder, sobald die Bestätigung erkannt wird.

Eine Verkaufsposition folgt der umgekehrten Logik. Wenn ein kurzfristiger hoher Umkehrpunkt einen mittelfristigen hohen Umkehrpunkt bestätigt, interpretiert der EA dies als den Beginn einer Abwärtsbewegung. Wenn es keine aktive Position gibt, wird sofort mit einer Marktorder eine Verkaufsposition eröffnet.

Alle Signalerkennungen werden nur bei der Eröffnung eines neuen Balkens ausgewertet. Dadurch wird sichergestellt, dass die Umkehrpunkte vollständig ausgebildet sind und nicht auf unvollständige Kursdaten reagiert wird.

Neben der zentralen Einstiegslogik enthält die Strategie mehrere Funktionen, die sie unter realen Handelsbedingungen praktisch und robust machen.

Der Nutzer kann die Handelsrichtung kontrollieren. Der EA kann so konfiguriert werden, dass er nur kauft, nur verkauft oder beides tätigt, je nach Marktbedingungen oder persönlicher Präferenz.

Die Positionsbestimmung kann vollautomatisch oder manuell erfolgen. Der EA kann die Losgröße auf der Grundlage eines nutzerdefinierten Risikoprozentsatzes des aktuellen Kontostands berechnen oder eine vom Händler festgelegte Losgröße verwenden.

Die Platzierung des Stop-Loss basiert ausschließlich auf der Marktstruktur. Der Nutzer kann wählen, ob er den Stop-Loss am letzten kurzfristigen Umkehrpunkt oder am letzten bestätigten mittelfristigen Umkehrpunkt platzieren möchte. Dadurch bleibt das Risikomanagement auf dieselbe Struktur ausgerichtet, die auch die Eröffnungen generiert.

Um unrealistische oder unerwünschte Handelsgeschäfte zu vermeiden, definiert der Nutzer minimale und maximale Stop-Loss-Abstände. Dadurch werden Handelsgeschäfte mit Stopps verhindert, die zu eng sind, um die normalen Kursbewegungen zu überstehen, oder zu weit sind und das Risiko nicht rechtfertigen.

Die Profit-Targets werden über ein konfigurierbares Risiko-Ertrags-Verhältnis gesteuert. So kann die Strategie über verschiedene Märkte und Zeiträume hinweg konsistent bleiben.

Schließlich enthält der EA einen optionalen stufenbasierten Trailing-Stop-Mechanismus. Wenn diese Funktion aktiviert ist, werden Gewinne gesichert, wenn sich der Kurs zugunsten des Handels bewegt, während sich der Trend weiter entwickeln kann. 

Zusammen verwandeln diese Komponenten die Marktstrukturkonzepte von Larry Williams von einem visuellen Analyseinstrument in eine vollständige und systematische Handelsstrategie. In den folgenden Abschnitten werden wir aufschlüsseln, wie jeder Teil in MQL5 implementiert ist und wie der Indikator und der Expert Advisor kommunizieren, um zuverlässige, wiederholbare Handelsentscheidungen zu treffen.



Logik der Signalerzeugung

Öffnen Sie zu Beginn den MetaEditor 5 und erstellen eine neue Expert Advisor-Datei, die Sie larryWilliamsMarketStructureExpert.mq5 nennen. Sobald die Datei erstellt ist, entfernen Sie den Code der Standardvorlage und ersetzen ihn durch die unten gezeigte Textvorlage.

//+------------------------------------------------------------------+
//|                           larryWilliamsMarketStructureExpert.mq5 |
//|          Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/en/users/chachaian |
//+------------------------------------------------------------------+

#property copyright "Copyright 2025, 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>

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

//+------------------------------------------------------------------+
//| 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(){

   //--- Scope variables
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID);

}

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
{
}

//--- UTILITY FUNCTIONS

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

Dieser erste Code gibt uns eine saubere und zuverlässige Struktur, auf der wir Schritt für Schritt aufbauen werden.

Der Header-Abschnitt enthält grundlegende Informationen über die Datei. Es enthält den EA-Namen, Angaben zum Autor, die Versionsnummer und einen Link zum MQL5-Profil. Dies ist eine gängige Praxis und hilft bei der Identifizierung, Versionskontrolle und zukünftigen Wartung.

Als Nächstes wird die Standard-Handelsbibliothek eingebunden. Die Datei Trade.mqh enthält die Klasse CTrade, die wir später verwenden werden, um Positionen auf sichere und strukturierte Weise zu öffnen, zu verwalten und zu schließen.

Danach definieren wir Nutzereingabevariablen. Diese Eingaben ermöglichen es dem Händler, wichtige Einstellungen zu steuern, wenn er den EA an einen Chart anhängt. Im Moment definieren wir nur eine magische Zahl, um die von diesem EA eröffneten Handelsgeschäfte eindeutig zu identifizieren, und einen Zeitrahmenparameter, der Flexibilität bei der Arbeit mit verschiedenen Charts ermöglicht.

Es folgt der Abschnitt über die globalen Variablen. Hier erstellen wir ein CTrade-Objekt, das alle Handelsoperationen abwickeln wird. Wir deklarieren auch Variablen zum Speichern der aktuellen Geld- und Briefkurse, die bei jedem Tick aktualisiert und im gesamten EA wiederverwendet werden.

Die Funktion OnInit wird nur einmal ausgeführt, dann, wenn der EA an ein Chart angehängt wird. In diesem Stadium weisen wir dem CTrade-Objekt nur die magische Zahl zu. Dadurch wird sichergestellt, dass alle von diesem EA eröffneten Handelsgeschäfte von diesem Expert Advisor unabhängig verfolgt, identifiziert und verwaltet werden können.

Die Funktion OnDeinit wird aufgerufen, wenn der EA entfernt oder gestoppt wird. Im Moment wird lediglich eine Meldung ausgegeben, die erklärt, warum das Programm abgebrochen wurde. Dies ist beim Testen und Debuggen nützlich.

Die Funktion OnTick wird jedes Mal ausgeführt, wenn neue Kursdaten am Terminal eintreffen. In diesem frühen Stadium aktualisieren wir nur die Geld- und Briefkurse. Die gesamte Signalerkennung und Handelslogik wird später hinzugefügt, aber im Moment halten wir sie minimal und sauber.

Schließlich wird die Funktion OnTradeTransaction als Platzhalter eingetragen. Wir verwenden sie noch nicht, aber sie wird später bei der Bearbeitung von Handelsereignissen wie Ausführungen, Änderungen oder Schließungen nützlich sein.

An dieser Stelle macht der EA noch nichts von sich aus. Das ist beabsichtigt. Wir haben jetzt eine solide und lesbare Grundlage, die den bewährten MQL5-Verfahren folgt. In den nächsten Schritten werden wir damit beginnen, eine Logik zur Signalerzeugung hinzuzufügen und diesen EA mit dem in Teil 1 erstellten Marktstrukturindikator zu verbinden.

Nachdem nun die grundlegende Struktur des Expert Advisors festgelegt ist, kann die eigentliche Arbeit beginnen. Bei dieser Strategie wird die Marktstruktur nicht intern berechnet. Stattdessen werden Signale direkt aus dem in Teil 1 dieser Serie entwickelten Marktstrukturindikator ausgelesen.

Der vollständige Quellcode für diesen Indikator ist diesem Artikel als larryWilliamsMarketStructureIndicator.mq5 beigefügt. Um mit der gleichen Einrichtung zu arbeiten, sollte der Leser zunächst sicherstellen, dass der Indikator im Terminal verfügbar ist.

Es gibt zwei einfache Möglichkeiten, dies zu tun. Die erste Möglichkeit besteht darin, die beigefügte Quelldatei herunterzuladen, MetaEditor 5 zu öffnen, eine neue leere Indikator-Datei mit dem Namen larryWilliamsMarketStructureIndicator.mq5 zu erstellen, den Quellcode einzufügen und zu kompilieren. Die zweite Möglichkeit ist noch einfacher. Nachdem Sie die Datei heruntergeladen haben, kopieren Sie sie direkt in den Ordner Indicators im MQL5-Datenverzeichnis. Nach dem Neustart des Terminals wird der Indikator normal angezeigt und kann bei Bedarf bearbeitet oder kompiliert werden.

Da dieser Expert Advisor von einem externen Indikator abhängt, ist es sinnvoll, diese Ressource zusammen mit dem EA zu verpacken. Dadurch wird sichergestellt, dass der EA den Indikator immer korrekt finden und laden kann. Dazu fügen wir die folgende Zeile direkt unter den bestehenden Eigenschaftsdirektiven ein.

#resource "\\Indicators\\larryWilliamsMarketStructureIndicator.ex5"

Diese Direktive bettet die kompilierte Indikator-Datei in den Expert Advisor ein. Wenn der EA läuft, weiß das Terminal genau, wo der Indikator zu finden ist, ohne auf manuelle Installationswege angewiesen zu sein. Dies macht die Verteilung und Wiederverwendung viel zuverlässiger.

Als Nächstes benötigen wir eine Möglichkeit, wie der EA mit dem Indikator kommunizieren kann. In MQL5 erfolgt diese Kommunikation über einen Indikator-Handle. im Abschnitt der globalen Variablen deklarieren wir folgende Variablen.

//--- The Larry Williams Market Structure Indicator handle
int larryWilliamsMarketStructureIndicatorHandle;

Das Handle eines Indikators ist einfach eine Referenz. Sie stellt eine Live-Verbindung zwischen dem Expert Advisor und der im Hintergrund laufenden Indikatorinstanz dar. Ohne dieses Handle kann der EA keine Daten aus den Indikatorpuffern anfordern.

Danach deklarieren wir vier Arrays im globalen Bereich, um die aus dem Indikator gelesenen Marktstrukturwerte zu speichern.

//--- Arrays to track market structure data
double shortTermLows [];
double shortTermHighs[];
double intermediateTermLows [];
double intermediateTermHighs[];

Diese Arrays fungieren als Container. Sie werden mit den letzten vom Indikator generierten Umkehrpunkt-Werten gefüllt. Später wird unsere Signallogik diese Arrays untersuchen, um zu entscheiden, wann ein Handelsgeschäft eröffnet werden soll.

In der Funktion OnInit teilen wir MQL5 nun mit, dass es diese Arrays als Zeitreihen behandeln soll.

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

   ...
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(shortTermLows,  true);
   ArraySetAsSeries(shortTermHighs, true);
   ArraySetAsSeries(intermediateTermLows,  true);
   ArraySetAsSeries(intermediateTermHighs, true);
   ArraySetAsSeries(closePriceMinutesData, true);

   return(INIT_SUCCEEDED);
}

Mit dieser Einstellung wird sichergestellt, dass der Index Null immer den jüngsten Balken darstellt. Dies ist für die Signalerkennung äußerst wichtig, da wir neu gebildete Umkehrpunkte auswerten wollen, ohne jedes Mal die gesamte Historie durchsuchen zu müssen.

Nachdem das Handle deklariert wurde, können wir nun den Indikator selbst initialisieren. Dies geschieht ebenfalls innerhalb von OnInit.

int OnInit(){

   ...

   //--- Initialize larryWilliamsMarketStructureIndicator
   larryWilliamsMarketStructureIndicatorHandle    = iCustom(_Symbol, timeframe, "::Indicators\\larryWilliamsMarketStructureIndicator.ex5");
   if(larryWilliamsMarketStructureIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing Larry Williams' Market Structure Indicator: ", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

Hier lädt iCustom den Indikator und gibt bei Erfolg ein Handle zurück. Die Parameter Symbol und Zeitrahmen stellen sicher, dass der Indikator im gleichen Chartkontext wie der EA läuft. Wenn der Handle ungültig ist, hält der EA sofort an. Dadurch wird verhindert, dass die Strategie ohne zuverlässige Daten läuft.

Da der Indikator nun läuft, benötigen wir eine saubere Methode, um seine Pufferwerte zu lesen. Zu diesem Zweck definieren wir eine nutzerdefinierte Hilfsfunktion.

//--- UTILITY FUNCTIONS
//+-------------------------------------------------------------------------------+
//| Copies the latest swing high and low data from the market structure indicator |
//+-------------------------------------------------------------------------------+
void RefreshMarketStructureBuffers(){

   //--- Get the last 200 short-term swing low points
   int copiedShortTermSwingLows = CopyBuffer(larryWilliamsMarketStructureIndicatorHandle, 0, 0, 200, shortTermLows);
   if(copiedShortTermSwingLows == -1){
      Print("Error while copying short-term swing lows: ", GetLastError());
      return;
   }
   
   //--- Get the last 200 short-term swing high points
   int copiedShortTermSwingHighs = CopyBuffer(larryWilliamsMarketStructureIndicatorHandle, 1, 0, 200, shortTermHighs);
   if(copiedShortTermSwingHighs == -1){
      Print("Error while copying short-term swing highs: ", GetLastError());
      return;
   }
   
   //--- Get the last 200 intermediate swing low points
   int copiedIntermediateSwingLows = CopyBuffer(larryWilliamsMarketStructureIndicatorHandle, 2, 0, 200, intermediateTermLows);
   if(copiedIntermediateSwingLows == -1){
      Print("Error while copying intermediate swing lows: ", GetLastError());
      return;
   }
   
   //--- Get the last 200 intermediate swing high points
   int copiedIntermediateSwingHighs = CopyBuffer(larryWilliamsMarketStructureIndicatorHandle, 3, 0, 200, intermediateTermHighs);
   if(copiedIntermediateSwingHighs == -1){
      Print("Error while copying intermediate swing highs: ", GetLastError());
      return;
   }
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(shortTermLows,  true);
   ArraySetAsSeries(shortTermHighs, true);
   ArraySetAsSeries(intermediateTermLows,  true);
   ArraySetAsSeries(intermediateTermHighs, true);
      
}

Diese Funktion verwendet CopyBuffer, um die neuesten Umkehrpunkt-Daten aus dem Indikator abzurufen. Jeder Aufruf kopiert bis zu den letzten 200 Werten aus einem bestimmten Puffer in das entsprechende Array.

Kurzfristige Tiefs und Hochs werden zuerst gelesen, gefolgt von mittelfristigen Tiefs und Hochs. Nach jedem Aufruf von CopyBuffer wird auf Fehler geprüft, um sicherzustellen, dass die Daten gültig sind. Wenn das Kopieren fehlschlägt, wird die Funktion vorzeitig beendet, um zu vermeiden, dass mit unvollständigen Informationen gearbeitet wird.

Am Ende der Funktion werden die Arrays wieder als Zeitreihen behandelt. Damit ist gewährleistet, dass der Index Null immer auf den letzten Balken zeigt, auch wenn sich das interne Speicherlayout ändert.

Diese Funktion hält die EA-Logik sauber. Anstatt Indikatordaten über mehrere Stellen zu kopieren, aktualisieren wir alles in einem einzigen, kontrollierten Schritt, wann immer es nötig ist.

Marktstruktursignale sind nur gültig, wenn ein Balken geschlossen wird. Aus diesem Grund sollte der EA nur reagieren, wenn ein neuer Balken geöffnet wird. Um dies zu erkennen, definieren wir die folgende 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;
   
}

Diese Funktion vergleicht die Öffnungszeit des aktuellen Balkens mit einem gespeicherten Wert. Unterscheiden sich die Zeitangaben, hat sich ein neuer Balken gebildet. Die Funktion aktualisiert dann den gespeicherten Wert und gibt true zurück.

Um diese Logik zu unterstützen, deklarieren wir eine globale Variable.

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

Diese Variable speichert die Öffnungszeit des zuletzt verarbeiteten Balkens. In OnInit wird er auf Null initialisiert, sodass der erste Balken immer korrekt erkannt wird.

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

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

   return(INIT_SUCCEEDED);
}

Mit den neuen Indikatordaten können wir nun unsere Signallogik definieren. Die erste Funktion prüft, ob ein Kaufsignal vorliegt.

//+---------------------------------------------------------------------------+
//| Checks whether current market structure conditions generate a buy signal  |
//+---------------------------------------------------------------------------+
bool IsBuySignal(){

   if(shortTermLows[2] == EMPTY_VALUE){
      return false;
   }
   
   int commonIndex = -1;
   for(int i = 3; i < ArraySize(shortTermLows); i++){
      if(shortTermLows[i] != EMPTY_VALUE){
         commonIndex = i;
         break;
      }
   }
   
   if(commonIndex == -1){
      return false;
   }
   
   if(intermediateTermLows[commonIndex] != EMPTY_VALUE){
      return true;
   }
   
   return false;
   
}

Die Funktion überprüft zunächst, ob sich gerade ein kurzfristiges Tief gebildet hat. Dies geschieht durch die Überprüfung eines aktuellen Balkenindexes. Wenn kein kurzfristiges Tief existiert, wird die Funktion sofort beendet.

bool IsBuySignal(){

   if(shortTermLows[2] == EMPTY_VALUE){
      return false;
   }
   
   ...
   
}

Anschließend sucht die Funktion rückwärts nach dem letzten bestätigten kurzfristigen Tief. Sobald der Index identifiziert ist, wird er mit dem Array der mittelfristigen Tiefs abgeglichen.

bool IsBuySignal(){

   ...
   
   int commonIndex = -1;
   for(int i = 3; i < ArraySize(shortTermLows); i++){
      if(shortTermLows[i] != EMPTY_VALUE){
         commonIndex = i;
         break;
      }
   }
   
   if(commonIndex == -1){
      return false;
   }
   
   ...
   
}

Wenn an dieser Stelle ein mittelfristiges Tief besteht, bedeutet dies, dass die kurzfristige Struktur ein mittelfristiges Tief bestätigt hat. In diesem Moment gibt die Funktion true zurück. Andernfalls wird false zurückgegeben.

bool IsBuySignal(){

   ...
   
   if(intermediateTermLows[commonIndex] != EMPTY_VALUE){
      return true;
   }
   
   return false;
   
}

Diese Logik spiegelt Larry Williams' Idee der verschachtelten Schaukeln wider. Ein übergeordneter Umkehrpunkt ist nur dann umsetzbar, wenn eine untergeordnete Struktur ihn bestätigt.

Die Funktion des Verkaufssignals folgt der gleichen Struktur, jedoch mit umgekehrter Logik. Statt mit Tiefs arbeitet sie mit Hochs.

//+---------------------------------------------------------------------------+
//| Checks whether current market structure conditions generate a sell signal |
//+---------------------------------------------------------------------------+
bool IsSelSignal(){

   if(shortTermHighs[2] == EMPTY_VALUE){
      return false;
   }
   
   int commonIndex = -1;
   for(int i = 3; i < ArraySize(shortTermHighs); i++){
      if(shortTermHighs[i] != EMPTY_VALUE){
         commonIndex = i;
         break;
      }
   }
   
   if(commonIndex == -1){
      return false;
   }
   
   if(intermediateTermHighs[commonIndex] != EMPTY_VALUE){
      return true;
   }
   
   return false;
   
}

Sie prüft, ob sich ein kurzfristiges Hoch gebildet hat, findet das letzte gültige Hoch und prüft dann, ob ein mittelfristiges Hoch bei demselben Index existiert. Ist dies der Fall, bestätigt die Funktion ein Verkaufssignal.

Da die Logik symmetrisch ist, wird durch das Verständnis der Kaufsignalfunktion das Verkaufssignal sofort klar.

Bevor Sie echte Handelsgeschäfte platzieren, sollten Sie sich vergewissern, dass die Signalerkennung korrekt funktioniert. Aus diesem Grund rufen wir die Funktionen innerhalb von OnTick auf und drucken Nachrichten anstelle von Eröffnungspositionen.

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

   ...

   //--- Execute logic only when a new bar opens
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      //--- Get updated market structure data
      RefreshMarketStructureBuffers();
      
      //--- Handle Buy signals
      if(IsBuySignal()){
         Print("Intermediate low confirmed!");
      }
      
      //--- Handle Sell signals
      if(IsSelSignal()){
         Print("Intermediate high confirmed!");
      }      
   }      
}

In diesem Stadium meldet der EA nur erkannte Signale im Terminalprotokoll. Auf diese Weise kann der Leser die gedruckten Meldungen visuell mit dem Indikator auf dem Chart vergleichen und sich vergewissern, dass alles korrekt ausgerichtet ist.

Sobald dieses Verhalten verifiziert ist, können wir im nächsten Abschnitt mit der Handelsausführungslogik fortfahren.



Von den Signalen zu den Handelsgeschäften

Da wir nun zuverlässig Signale der Umkehrpunkte aus der Marktstruktur erkennen können, ist der nächste logische Schritt die Umwandlung dieser Signale in echte Handelsgeschäfte. In diesem Abschnitt stellen wir die Handelslogik des Expert Advisors vor. Ziel ist es, dem Nutzer die vollständige Kontrolle darüber zu geben, wie Handelsgeschäfte ausgeführt werden, während die interne Logik sauber und strukturiert bleibt.

Bevor wir überhaupt Handelsfunktionen schreiben, müssen wir zunächst die Variablen und Konfigurationen definieren, die sie unterstützen. Mit diesen Einstellungen kann der Nutzer Handelsrichtung, Positionsgröße, Risiko, Stop-Loss-Platzierung und Renditeziele steuern.

Kontrolle der Handelsrichtung

Die Marktbedingungen sind nicht immer neutral. Ein Händler kann beschließen, nur in die Richtung des vorherrschenden Trends zu handeln. Um dies zu unterstützen, führen wir eine nutzerdefinierte Enumeration ein, die die zulässige Handelsrichtung definiert.

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

ONLY_LONG erlaubt nur Kaufpositionen. ONLY_SHORT erlaubt nur Verkaufspositionen. TRADE_BOTH erlaubt sowohl Kauf- als auch Verkaufspositionen.

Diese Auswahl wird dann dem Nutzer als Eingabeparameter zur Verfügung gestellt.

...

input group "Trade and Risk Management"
input ENUM_TRADE_DIRECTION            direction  = TRADE_BOTH;

Standardmäßig ist es dem EA erlaubt, in beide Richtungen zu handeln. Wenn der EA an einen Chart angehängt wird, kann der Nutzer dieses Verhalten je nach seiner Marktvorliebe ändern. Dies bietet Flexibilität, ohne den Quellcode zu ändern.

Modi zur Berechnung der Losgröße

Als Nächstes geben wir dem Nutzer die Kontrolle darüber, wie die Positionsgröße berechnet wird. Einige Händler bevorzugen eine feste Losgröße, während andere eine risikobasierte Positionsgröße bevorzugen. Wir definieren eine weitere Enumeration mit zwei Modi.

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

MODE_MANUAL verwendet eine feste Losgröße. MODE_AUTO berechnet die Losgröße anhand des Kontorisikos.

Wenn der automatische Modus ausgewählt ist, verwendet der EA einen nutzerdefinierten Risikoprozentsatz des Kontosaldos. Dieser Prozentsatz stellt den maximalen Verlust dar, den der Händler bereit ist zu tragen, wenn der Stop-Loss ausgelöst wird. Wenn der manuelle Modus ausgewählt ist, verwendet der EA die vom Nutzer vorgegebene feste Losgröße.

input ENUM_LOT_SIZE_INPUT_MODE      lotSizeMode  = MODE_AUTO;
input double                 riskPerTradePercent = 1.0;
input double                             lotSize = 5.0;

Dieser Ansatz ermöglicht es sowohl konservativen als auch aggressiven Händlern, denselben EA bequem zu nutzen.

Stop-Loss-Platzierung auf Basis der Marktstruktur

Da dieser EA auf Basis der Marktstruktur handelt, sollte auch der Stop-Loss logisch platziert werden. Der Nutzer kann wählen, ob der Stop-Loss auf den letzten kurzfristigen Umkehrpunkt oder den letzten bestätigten mittelfristigen Umkehrpunkt gesetzt werden soll.

enum ENUM_STOP_LOSS_STRUCTURE{
   SL_AT_SHORT_TERM_SWING,
   SL_AT_INTERMEDIATE_SWING
};

Diese Wahl beeinflusst, wie eng oder weit der Stop-Loss sein wird. Kurzfristige Umkehrpunkte führen zu engeren Stopps, während mittelfristige Umkehrpunkten mehr Spielraum für Kursbewegungen lassen. Der Nutzer kann wählen, was am besten zu seinem Handelsstil passt.

input ENUM_STOP_LOSS_STRUCTURE stopLossStructure = SL_AT_INTERMEDIATE_SWING;

Gültiger Bereich für die Stopps

Da die Marktstruktur dynamisch ist, können die Stoppabstände manchmal zu kurz oder zu lang sein. Beide Fälle können unerwünscht sein.

Um dies zu kontrollieren, definiert der Nutzer einen minimal und maximal zulässigen Stoppabstand in Punkten. Jeder Handel, der außerhalb dieses Bereichs liegt, wird ignoriert. Dies schützt den EA davor, Handelsgeschäfte mit schlechten Risikoeigenschaften einzugehen.

input int              minimumStopDistancePoints = 100;
input int              maximumStopDistancePoints = 600;

Risiko-Ertrags-Verhältnis

Außerdem kann der Nutzer ein vordefiniertes Risiko-Ertrags-Verhältnis wählen.

enum ENUM_RISK_REWARD_RATIO   
{ 
   ONE_TO_ONE, 
   ONE_TO_ONEandHALF, 
   ONE_TO_TWO, 
   ONE_TO_THREE, 
   ONE_TO_FOUR, 
   ONE_TO_FIVE, 
   ONE_TO_SIX 
};

Jede Option gibt an, wie viele Belohnungseinheiten für jede Risikoeinheit angestrebt werden. Dieser Wert wird später zur automatischen Berechnung der Gewinnmitnahme verwendet.

input ENUM_RISK_REWARD_RATIO     riskRewardRatio = ONE_TO_TWO;

Verfolgung von Informationen über offene Handelsgeschäfte

Sobald ein Handelsgeschäft eröffnet ist, müssen wir seine Details verfolgen. So können wir künftige Funktionen wie Trailing-Stops und ein Handels-Management effektiv umsetzen. 

Zu diesem Zweck definieren wir eine Struktur, die wesentliche Informationen über die aktive Position speichert. Dazu gehören Einstiegskurs, Stop-Loss, Take-Profit, Losgröße und Eröffnungszeit. Wir deklarieren dann eine globale Instanz dieser Struktur, um die aktuellen Handelsdaten zu speichern.

//+------------------------------------------------------------------+
//| Data Structures                                                  |
//+------------------------------------------------------------------+
struct MqlTradeInfo
{
   ulong orderTicket;                 
   ENUM_ORDER_TYPE type;
   ENUM_POSITION_TYPE posType;
   double entryPrice;
   double takeProfitLevel;
   double stopLossLevel;
   datetime openTime;
   double lotSize;   
};

//--- Instantiate the trade information data structure
MqlTradeInfo tradeInfo

Initialisierung der Punktwerte

Die Validierung des Stoppabstands erfordert die Kenntnis der Punktgröße des Instruments. Da verschiedene Symbole unterschiedliche Punktwerte haben, speichern wir diesen Wert in einer globalen Variablen.

//--- The size of a point for this financial security
double pointValue;

In der Funktion OnInit wird sie mithilfe der Symboleigenschaften initialisiert. Dadurch wird sichergestellt, dass die Abstandsberechnung des Stopps für das aktuelle Instrument immer genau ist.

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

   ...
   
   pointValue      = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

   return(INIT_SUCCEEDED);
}

Eröffnung einer Kaufposition

Die Funktion OpenBuy eröffnet einen Marktkaufauftrag. Es führt mehrere wichtige Schritte auf strukturierte Weise durch.

//+------------------------------------------------------------------+
//| Function used to open a market buy order.                        |   
//+------------------------------------------------------------------+
bool OpenBuy(const double askPr){

   ENUM_ORDER_TYPE action          = ORDER_TYPE_BUY;
   ENUM_POSITION_TYPE positionType = POSITION_TYPE_BUY;
   datetime currentTime            = TimeCurrent();
   double contractSize             = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double accountBalance           = AccountInfoDouble(ACCOUNT_BALANCE);
   double rewardValue              = 1.0;
   
   switch(riskRewardRatio){
      case ONE_TO_ONE: 
         rewardValue = 1.0;
         break;
      case ONE_TO_ONEandHALF:
         rewardValue = 1.5;
         break;
      case ONE_TO_TWO: 
         rewardValue = 2.0;
         break;
      case ONE_TO_THREE: 
         rewardValue = 3.0;
         break;
      case ONE_TO_FOUR: 
         rewardValue = 4.0;
         break;
      case ONE_TO_FIVE: 
         rewardValue = 5.0;
         break;
      case ONE_TO_SIX: 
         rewardValue = 6.0;
         break;
      default:
         rewardValue = 1.0;
         break;
   }
   
   double stopLevel = 0;
   
   if(stopLossStructure == SL_AT_SHORT_TERM_SWING  ){
      stopLevel = NormalizeDouble(shortTermLows[2], Digits());
   }
   
   if(stopLossStructure == SL_AT_INTERMEDIATE_SWING){
   
      for(int i = 0; i < ArraySize(intermediateTermLows); i++){
         if(intermediateTermLows[i] != EMPTY_VALUE){
            stopLevel = NormalizeDouble(intermediateTermLows[i], Digits());
            break;
         }
      }      
   }
   
   double stopDistance = NormalizeDouble(askPr - stopLevel, Digits());
   if(stopDistance > (maximumStopDistancePoints * pointValue) || stopDistance < (minimumStopDistancePoints * pointValue)){
      Print("The Stop Distance falls outside desired distance range");
      return false;
   }
   
   double targetLevel  = NormalizeDouble(askPr + (rewardValue * stopDistance), Digits());
   
   double volume       = NormalizeDouble(lotSize, 2);
   if(lotSizeMode == MODE_AUTO){
      double amountAtRisk = (riskPerTradePercent / 100.0) *  accountBalance;
      volume              = amountAtRisk / (contractSize * stopDistance);
      volume              = NormalizeDouble(volume, 2);
   }
   
   if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){
      Print("Error while opening a long position, ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }else{
      MqlTradeResult result = {};
      Trade.Result(result);
      tradeInfo.orderTicket                 = result.order;
      tradeInfo.type                        = action;
      tradeInfo.posType                     = positionType;
      tradeInfo.entryPrice                  = result.price;
      tradeInfo.takeProfitLevel             = targetLevel;
      tradeInfo.stopLossLevel               = stopLevel;
      tradeInfo.openTime                    = currentTime;
      tradeInfo.lotSize                     = result.volume;
      
      return true;
   }
   
   return false;
}

Zunächst wird das gewählte Risiko-Ertrags-Verhältnis ermittelt und in einen numerischen Ertragswert umgewandelt. Dieser Wert wird später zur Berechnung der Gewinnmitnahme verwendet.

Als Nächstes bestimmt die Funktion das Stop-Loss-Niveau. Je nach Wahl des Nutzers wird der Stopp entweder beim letzten kurzfristigen Tief oder beim letzten mittelfristigen Tief gesetzt.

Sobald das Stopp-Niveau bekannt ist, wird der Abstand des Stopps berechnet. Diese Entfernung wird dann mit dem vom Nutzer definierten Mindest- und Höchstabstand verglichen. Wenn die Entfernung nicht akzeptabel ist, wird das Handelsgeschäft übersprungen.

Das Take-Profit-Niveau wird anhand des Gewinnwertes und der Stop-Distanz berechnet. Dadurch wird sichergestellt, dass jedes Handelsgeschäft dem gewählten Risiko-Ertrags-Profil entspricht.

Die Funktion bestimmt dann das Handelsvolumen. Wenn der manuelle Modus gewählt wird, wird die feste Losgröße verwendet. Wenn der automatische Modus ausgewählt ist, wird die Losgröße auf der Grundlage des Kontostands, des Risikoprozentsatzes, der Kontraktgröße und des Stoppabstands berechnet.

Schließlich sendet die Funktion einen Kaufauftrag an den Server. Wenn das Handelsgeschäft erfolgreich ist, werden alle relevanten Handelsdetails in der Handelsinformationsstruktur zur späteren Verwendung gespeichert.

Eröffnung einer Verkaufsposition

Die Funktion OpenSel folgt der gleichen Logik wie die Funktion zum Kaufen, allerdings in umgekehrter Richtung. Die Stop-Loss-Niveaus werden von den hohen Umkehrpunkten statt von den tiefen Umkehrpunkten abgenommen, und die Preisberechnungen werden entsprechend invertiert.

//+------------------------------------------------------------------+
//| Function used to open a market sell order.                       |   
//+------------------------------------------------------------------+
bool OpenSel( const double bidPr){

   ENUM_ORDER_TYPE action          = ORDER_TYPE_SELL;
   ENUM_POSITION_TYPE positionType = POSITION_TYPE_SELL;
   datetime currentTime            = TimeCurrent();   
   double contractSize             = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double accountBalance           = AccountInfoDouble(ACCOUNT_BALANCE);
   double rewardValue              = 1.0;
   
   switch(riskRewardRatio){
      case ONE_TO_ONE: 
         rewardValue = 1.0;
         break;
      case ONE_TO_ONEandHALF:
         rewardValue = 1.5;
         break;
      case ONE_TO_TWO: 
         rewardValue = 2.0;
         break;
      case ONE_TO_THREE: 
         rewardValue = 3.0;
         break;
      case ONE_TO_FOUR: 
         rewardValue = 4.0;
         break;
      case ONE_TO_FIVE: 
         rewardValue = 5.0;
         break;
      case ONE_TO_SIX: 
         rewardValue = 6.0;
         break;
      default:
         rewardValue = 1.0;
         break;
   }
   
   double stopLevel = 0;
   
   if(stopLossStructure == SL_AT_SHORT_TERM_SWING  ){
      stopLevel = NormalizeDouble(shortTermHighs[2], Digits());
   }
   
   if(stopLossStructure == SL_AT_INTERMEDIATE_SWING){
   
      for(int i = 0; i < ArraySize(intermediateTermHighs); i++){
         if(intermediateTermHighs[i] != EMPTY_VALUE){
            stopLevel = NormalizeDouble(intermediateTermHighs[i], Digits());
            break;
         }
      }
      
   }
   
   double stopDistance = NormalizeDouble(stopLevel - bidPr, Digits());
   if(stopDistance > (maximumStopDistancePoints * pointValue) || stopDistance < (minimumStopDistancePoints * pointValue)){
      Print("The Stop Distance falls outside desired distance range");
      return false;
   }
   
   double targetLevel  = NormalizeDouble(bidPr - (rewardValue * stopDistance), Digits());
   double volume       = NormalizeDouble(lotSize, 2);
   if(lotSizeMode == MODE_AUTO){
      double amountAtRisk = (riskPerTradePercent / 100.0) *  accountBalance;
      volume              = amountAtRisk / (contractSize * stopDistance);
      volume              = NormalizeDouble(volume, 2);
   }
   
   if(!Trade.Sell(volume, _Symbol, bidPr, stopLevel, targetLevel)){
      Print("Error while opening a short position, ", GetLastError());
      Print(Trade.ResultRetcode());
      Print(Trade.ResultComment());
      return false;
   }else{ 
      MqlTradeResult result = {};
      Trade.Result(result);
      tradeInfo.orderTicket                 = result.order;
      tradeInfo.type                        = action;
      tradeInfo.posType                     = positionType;
      tradeInfo.entryPrice                  = result.price;
      tradeInfo.takeProfitLevel             = targetLevel;
      tradeInfo.stopLossLevel               = stopLevel;
      tradeInfo.openTime                    = currentTime;
      tradeInfo.lotSize                     = result.volume;
      
      return true;
   }
   
   return false;   
}

Da die Struktur die Kauflogik widerspiegelt, bleibt das Verhalten konsistent, sodass gleichzeitig doppelte Komplexität vermieden wird.

Prüfung auf vorhandene Positionen

Bevor der EA ein neues Handelsgeschäft eröffnet, muss er sicherstellen, dass aktuell keine aktive Position vorhanden ist. Um dies zu erkennen, führen wir zwei Nutzenfunktionen ein.

//+------------------------------------------------------------------+
//| 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 ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| 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 ", _LastError);
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            return true;
         }
      }
   }
   
   return false;
}

Eine Funktion prüft, ob eine aktive Kaufposition vorliegt. Der andere prüft, ob eine aktive Verkaufsposition vorliegt. Jede Funktion durchsucht alle offenen Positionen und filtert sie nach magischer Nummer und Positionstyp.

Dadurch wird sichergestellt, dass der EA jeweils nur eine Position eröffnet und widersprüchliche Handelsgeschäfte vermieden werden.

Ausführen von Handelsgeschäften innerhalb von OnTick

Wenn alle Komponenten vorhanden sind, muss die Handelslogik mit der Hauptausführungsschleife verbunden werden.

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

   //--- Scope variables
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID);

   //--- Execute logic only when a new bar opens
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      //--- Get updated market structure data
      RefreshMarketStructureBuffers();
      
      //--- Handle Buy signals
      if(IsBuySignal()){
      
         //--- Open a long position if there is no active position
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            OpenBuy(askPrice);
         }
      }
      
      //--- Handle Sell signals
      if(IsSelSignal()){
         
         //--- Open a short position if there is no active position
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            OpenSel(bidPrice);
         }
      }
            
   }
   
}

In der Funktion OnTick werden zunächst die aktuellen Geld- und Briefkurse aktualisiert. Anschließend wird geprüft, ob ein neuer Balken eröffnet wurde. Die Handelslogik wird nur bei neuen Balken ausgeführt, um doppelte Signale zu vermeiden.

Wenn ein Kaufsignal erkannt wird, prüft der EA, ob keine aktive Position vorhanden ist. Wenn die Bedingungen erfüllt sind, wird ein Kaufauftrag eröffnet.

Die gleiche Logik gilt für Verkaufssignale. Damit ist der gesamte Zyklus von der Erkennung der Marktstruktur bis zur Handelsausführung abgeschlossen.

Zu diesem Zeitpunkt ist der Expert Advisor voll funktionsfähig und in der Lage, Marktstruktursignale in einer kontrollierten, konfigurierbaren Weise zu handeln.



Hinzufügen eines dynamischen stufenweisen Trailing-Stops

In diesem Stadium kann unser Expert Advisor Signale erkennen, Handelsgeschäfte eröffnen und das Risiko korrekt verwalten. Der letzte Teil ist der Handelsschutz. In diesem Abschnitt fügen wir einen dynamischen Trailing-Stop hinzu, der stufenweise Gewinne sichert, während sich der Kurs auf das Ziel zubewegt. Der Trailing-Stop ist optional. Der Nutzer kann sie beim Starten des EA in einem Chart deaktivieren oder aktivieren. Dadurch bleibt das System flexibel und es eignet sich für unterschiedliche Handelsstile.

Aktivieren oder Deaktivieren des Trailing-Stops

Wir beginnen mit dem Hinzufügen eines booleschen Eingabeparameters. Dies funktioniert wie ein einfacher Schalter.

input bool                    enableTrailingStop = false;

Wenn der Wert wahr ist, verwaltet der EA aktiv die Trailing-Stops. Wenn er falsch ist, können die Handelsgeschäfte entweder den Stop-Loss oder den Take-Profit ohne Eingreifen erreichen. Diese Entscheidung liegt allein in den Händen des Nutzers.

Struktur-Definition der Trailing-Stops

Der Trailing-Stop ist als stufenweises System implementiert. Anstatt den Stop-Loss kontinuierlich zu verschieben, wird er in vordefinierten Schritten vorverlegt, wenn sich der Kurs dem Ziel nähert.

Um dieses Verhalten zu unterstützen, definieren wir eine Struktur, die alle Informationen über die Trailing-Stops enthält.

//--- Instantiate the trade information data structure
MqlTradeInfo tradeInfo;

struct MqlTrailingStop
{
   double level1;
   double level2;
   double level3;
   double level4;
   double level5;
   
   double stopLevel1;
   double stopLevel2;
   double stopLevel3;
   double stopLevel4;
   double stopLevel5;
   
   bool isLevel1Active;
   bool isLevel2Active;
   bool isLevel3Active;
   bool isLevel4Active;
   bool isLevel5Active;
};

//--- Instantiate the trailing stop structure
MqlTrailingStop trailingStop;

In den ersten fünf Feldern werden Kursniveaus gespeichert, die überschritten werden müssen, bevor eine Aktualisierung des Stopps zulässig ist. Dies sind die Auslöseschwellen. In den folgenden fünf Feldern werden die Stop-Loss-Niveaus gespeichert, die bei Erreichen der einzelnen Auslöser angewendet werden. Diese Niveaus zeigen an, wohin der Stop-Loss verschoben wird. Die letzten fünf booleschen Felder zeigen an, ob der jeweilige Schritt bereits aktiviert wurde. Dadurch wird verhindert, dass der EA dieselbe Stopp-Aktualisierung mehr als einmal anwendet. Vereinfacht ausgedrückt muss der Kurs ein Niveau einmal überschreiten, um ein Stop-Update auszulösen. Danach merkt sich der EA, dass der Schritt bereits abgearbeitet wurde. Die gesamte Strecke des Trailing-Stops ist in sechs gleiche Teile unterteilt. Dadurch entstehen fünf Trailing-Schritte zwischen Einstieg und Gewinnmitnahme.

Erkennen von Kursdurchbrüchen

Die Trailing-Logik hängt davon ab, wann der Kurs bestimmte Niveaus überschreitet. Um dies sauber zu handhaben, führen wir zwei kleine Hilfsfunktionen ein.

//+------------------------------------------------------------------+
//| To detect a crossover at a given price level                     |                               
//+------------------------------------------------------------------+
bool IsCrossOver(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] <= price && closePriceMinsData[0] > price){
      return true;
   }
   return false;
}


//+------------------------------------------------------------------+
//| To detect a crossunder at a given price level                    |                               
//+------------------------------------------------------------------+
bool IsCrossUnder(const double price, const double &closePriceMinsData[]){
   if(closePriceMinsData[1] >= price && closePriceMinsData[0] < price){
      return true;
   }
   return false;
}

Eine Funktion erkennt, wenn der Kurs ein bestimmtes Niveau überschreitet. Der andere erkennt, wenn der Preis unter ein bestimmtes Niveau fällt. Jede Funktion vergleicht den vorherigen Schlusskurs mit dem aktuellen Schlusskurs, um festzustellen, ob ein Crossing stattgefunden hat. Diese Funktionen benötigen zwei Eingaben. Der erste ist das zu prüfende Niveau. Die zweite ist eine Reihe von aktuellen Schlusskursen.

Speicherung von Preisdaten auf Minutenebene

Um ein genaues Erkennen der Durchbrüche zu unterstützen, speichern wir die letzten Minutenschlusskurse in einem globalen Array. Dieses Array wird wie eine Zeitreihe behandelt, sodass der Index Null immer den jüngsten Wert darstellt.

//--- To store minutes data
double closePriceMinutesData [];

In der Funktion OnTick wird dieses Array bei jedem Tick mit den Daten des Minutenzeitrahmens aktualisiert. Dadurch wird sichergestellt, dass Trailing-Stop-Entscheidungen auf aktuellen Kursbewegungen und nicht auf verzögerten Daten der Balken beruhen. Wenn die Datenkopie fehlschlägt, wird der EA frühzeitig beendet, um zu vermeiden, dass Entscheidungen mit ungültigen Informationen getroffen werden.

Vorbereitung von Trailing-Stops bei der Eröffnung eines Handelsgeschäfts

Trailing-Stop-Niveaus müssen sofort bei Eröffnung eines neuen Handelsgeschäfts berechnet werden. Dadurch wird sichergestellt, dass der EA von Anfang an genau weiß, wo jede Stufe der Trailing-Stops liegt. Sowohl in der Kauf- als auch in der Verkaufsorderfunktion weisen wir der Trailing-Stop-Struktur nach einer erfolgreichen Handelsausführung die Werte zu.

//+------------------------------------------------------------------+
//| Function used to open a market buy order.                        |   
//+------------------------------------------------------------------+
bool OpenBuy(const double askPr){

   ...
   
   if(!Trade.Buy(volume, _Symbol, askPr, stopLevel, targetLevel)){
   
   ...
   
   }else{

      ...
      
      //--- Refill the trailing Stop struct
      double targetDistance       = targetLevel - askPr;
      double trailingStep         = NormalizeDouble(targetDistance / 6,   Digits());
      trailingStop.level1         = NormalizeDouble(askPr + trailingStep, Digits());
      trailingStop.level2         = NormalizeDouble(trailingStop.level1 + trailingStep, Digits());      
      trailingStop.level3         = NormalizeDouble(trailingStop.level2 + trailingStep, Digits());
      trailingStop.level4         = NormalizeDouble(trailingStop.level3 + trailingStep, Digits());      
      trailingStop.level5         = NormalizeDouble(trailingStop.level4 + trailingStep, Digits());
      
      trailingStop.stopLevel1     = NormalizeDouble(stopLevel + trailingStep, Digits());
      trailingStop.stopLevel2     = NormalizeDouble(trailingStop.stopLevel1 + trailingStep, Digits());
      trailingStop.stopLevel3     = NormalizeDouble(trailingStop.stopLevel2 + trailingStep, Digits());
      trailingStop.stopLevel4     = NormalizeDouble(trailingStop.stopLevel3 + trailingStep, Digits());
      trailingStop.stopLevel5     = NormalizeDouble(trailingStop.stopLevel4 + trailingStep, Digits());
      
      trailingStop.isLevel1Active = false;
      trailingStop.isLevel2Active = false;
      trailingStop.isLevel3Active = false;
      trailingStop.isLevel4Active = false;
      trailingStop.isLevel5Active = false;
      
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| Function used to open a market sell order.                       |   
//+------------------------------------------------------------------+
bool OpenSel( const double bidPr){

   ...
   
   if(!Trade.Sell(volume, _Symbol, bidPr, stopLevel, targetLevel)){
      
      ...
      
      return false;
   }else{ 

      ...
      
      //--- Refill the trailing Stop struct
      double targetDistance       = bidPr - targetLevel;
      double trailingStep         = NormalizeDouble(targetDistance / 6,   Digits());
      trailingStop.level1         = NormalizeDouble(bidPr - trailingStep, Digits());
      trailingStop.level2         = NormalizeDouble(trailingStop.level1 - trailingStep, Digits());
      trailingStop.level3         = NormalizeDouble(trailingStop.level2 - trailingStep, Digits());
      trailingStop.level4         = NormalizeDouble(trailingStop.level3 - trailingStep, Digits());
      trailingStop.level5         = NormalizeDouble(trailingStop.level4 - trailingStep, Digits());
      
      trailingStop.stopLevel1     = NormalizeDouble(stopLevel - trailingStep, Digits());
      trailingStop.stopLevel2     = NormalizeDouble(trailingStop.stopLevel1 - trailingStep, Digits());
      trailingStop.stopLevel3     = NormalizeDouble(trailingStop.stopLevel2 - trailingStep, Digits());
      trailingStop.stopLevel2     = NormalizeDouble(trailingStop.stopLevel3 - trailingStep, Digits());
      trailingStop.stopLevel3     = NormalizeDouble(trailingStop.stopLevel4 - trailingStep, Digits());
      
      trailingStop.isLevel1Active = false;
      trailingStop.isLevel2Active = false;
      trailingStop.isLevel3Active = false;
      trailingStop.isLevel4Active = false;
      trailingStop.isLevel5Active = false;
      return true;
   }
   
   return false;  
}

Zunächst wird der Abstand zwischen dem Eröffnungspreis und dem Take-Profit berechnet. Dieser Abstand wird in sechs gleiche Teile geteilt, um die Trailing-Stufen zu bilden. Bei Käufen werden die Auslöseschwellen über dem Einstiegskurs platziert. Bei Verkäufen werden die Auslöseschwellen unterhalb des Einstiegskurses platziert. Die entsprechenden Stop-Loss-Niveaus werden in die gleiche Richtung wie der ursprüngliche Stop-Loss verschoben. Alle Aktivierungs-Flags der Stufen werden auf false zurückgesetzt. Dadurch wird die Struktur so vorbereitet, dass sie nur für die neue Position den Fortschritt verfolgt.

Verwaltung des Trailing-Stops in Echtzeit

Sobald ein Handelsgeschäft aktiv ist, wird die Verwaltung des Trailing-Stops von einer speziellen Hilfsfunktion übernommen.

//+------------------------------------------------------------------+
//| To track price action and updates the trailing stop              |   
//+------------------------------------------------------------------+
void ManageTrailingStop(){

   int totalPositions = PositionsTotal();
   //--- Loop through all open positions
   for(int i = totalPositions - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket != 0){
         // Get some useful position properties
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);  
         string symbol                   = PositionGetString (POSITION_SYMBOL);
         ulong magic                     = PositionGetInteger(POSITION_MAGIC);
         double targetLevel              = PositionGetDouble(POSITION_TP);
         if(positionType == POSITION_TYPE_BUY ){
            if(symbol == _Symbol && magic == magicNumber){
            
               if(IsCrossOver(trailingStop.level1, closePriceMinutesData) && !trailingStop.isLevel1Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel1, targetLevel)){
                     Print("Error while trailing SL at level 1: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel1Active = true;
                  }
               }
               
               if(IsCrossOver(trailingStop.level2, closePriceMinutesData) && !trailingStop.isLevel2Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel2, targetLevel)){
                     Print("Error while trailing SL at level 2: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel2Active = true;
                  }
               }
               
               if(IsCrossOver(trailingStop.level3, closePriceMinutesData) && !trailingStop.isLevel3Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel3, targetLevel)){
                     Print("Error while trailing SL at level 3: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel3Active = true;
                  }
               }
               
               if(IsCrossOver(trailingStop.level4, closePriceMinutesData) && !trailingStop.isLevel4Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel4, targetLevel)){
                     Print("Error while trailing SL at level 4: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel4Active = true;
                  }
               }
               
               if(IsCrossOver(trailingStop.level5, closePriceMinutesData) && !trailingStop.isLevel5Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel5, targetLevel)){
                     Print("Error while trailing SL at level 5: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel5Active = true;
                  }
               }
            }
         }
         
               
         if(positionType == POSITION_TYPE_SELL){
            if(symbol == _Symbol && magic == magicNumber){
            
               if(IsCrossUnder(trailingStop.level1, closePriceMinutesData) && !trailingStop.isLevel1Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel1, targetLevel)){
                     Print("Error while trailing SL at level 1: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel1Active = true;
                  }
               }
               
               if(IsCrossUnder(trailingStop.level2, closePriceMinutesData) && !trailingStop.isLevel2Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel2, targetLevel)){
                     Print("Error while trailing SL at level 2: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel2Active = true;
                  }
               }
               
               if(IsCrossUnder(trailingStop.level3, closePriceMinutesData) && !trailingStop.isLevel3Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel3, targetLevel)){
                     Print("Error while trailing SL at level 3: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel3Active = true;
                  }
               }
               
               if(IsCrossUnder(trailingStop.level4, closePriceMinutesData) && !trailingStop.isLevel4Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel4, targetLevel)){
                     Print("Error while trailing SL at level 4: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel4Active = true;
                  }
               }
               
               if(IsCrossUnder(trailingStop.level5, closePriceMinutesData) && !trailingStop.isLevel5Active){
                  if(!Trade.PositionModify(ticket, trailingStop.stopLevel5, targetLevel)){
                     Print("Error while trailing SL at level 5: ", GetLastError());
                     Print(Trade.ResultRetcodeDescription());
                     Print(Trade.ResultRetcode());
                  }else{
                     trailingStop.isLevel5Active = true;
                  }
               }      
            }
         }
      }
   }  
}

Diese Funktion durchläuft alle offenen Positionen und filtert sie nach Symbol und magischer Zahl. Dadurch wird sichergestellt, dass nur Positionen, die zum EA gehören, verwaltet werden.

Bei Kaufpositionen prüft die Funktion, ob der Kurs ein Trailing-Trigger-Niveau von unten überschritten hat. Wenn ein Niveau zum ersten Mal überschritten wird, wird der Stop-Loss auf das entsprechende Stopp-Niveau verschoben und der Schritt als aktiv markiert.

Für Verkaufspositionen gilt die gleiche Logik in umgekehrter Richtung. Die Funktion erkennt das Unterschreiten von Trigger-Levels und aktualisiert den Stop-Loss entsprechend.

Jeder Schritt wird nur einmal durchgeführt. Wenn der Kurs zurückgeht und dasselbe Niveau wieder überschreitet, werden keine weiteren Maßnahmen ergriffen. Dadurch bleibt die Bewegung der Haltestelle geordnet und vorhersehbar.

Wenn eine Änderung des Stopps fehlschlägt, wird eine Fehlermeldung ausgegeben, um die Fehlersuche zu erleichtern.

Integration des Trailing-Stops in den EA

Die Trailing-Stop-Funktion kann nun innerhalb der OnTick-Funktion aufgerufen werden. Sie sollte nur ausgeführt werden, wenn der Nutzer die Trailing-Stop-Funktion aktiviert.

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

   ...
   
   //--- Manage trailing stop
   if(enableTrailingStop){
      ManageTrailingStop();
   }   
}

Mit diesem letzten Zusatz hat der Expert Advisor nun die vollständige Kontrolle über den Lebenszyklus des Handels. Er erkennt strukturbasierte Signale, eröffnet Handelsgeschäfte mit kontrolliertem Risiko und sichert Gewinne durch einen strukturierten Trailing-Stop ab. Damit ist die Kernentwicklung des EA abgeschlossen. Auf dieser soliden Grundlage können nun weitere Verbesserungen, wie z. B. eine Breakeven-Logik oder Teilausstiege, aufgebaut werden.

Der vollständige Quellcode, der in diesem Artikel entwickelt wurde, ist in den Anhängen des Artikels enthalten. Sie kann jederzeit heruntergeladen werden, um die Entwicklung zu verfolgen, Fehler zu korrigieren oder sie mit der eigenen Umsetzung zu vergleichen.



Tests und Ergebnisse

Nachdem die komplette Handelslogik erstellt wurde, besteht der nächste Schritt darin, die Leistung des Expert Advisors unter realen Marktbedingungen zu überprüfen. Zu diesem Zweck wurde der EA mit dem Strategy Tester des MetaTrader 5 auf historischen Kursdaten getestet. Der Backtest wurde für Gold mit dem Zeitrahmen H1 durchgeführt. Der Testzeitraum lief vom 1. Januar 2025 bis zum 30. November 2025. Der anfängliche Kontostand wurde auf 10.000 USD festgelegt, und alle Handelsgeschäfte während des Tests wurden vom EA automatisch und ohne manuelle Eingriffe ausgeführt.

Die für diesen Test verwendete Konfiguration spiegelt die in diesem Artikel besprochene Strategielogik wider. Die genauen Eingabeeinstellungen sind in einer diesem Artikel beigefügten Datei enthalten, damit die Leser die Ergebnisse auf ihren eigenen Terminals reproduzieren können.

Am Ende des Testzeitraums verzeichnete der EA einen Gesamtnettogewinn von 8.950,01 USD. Dies entspricht einem Zuwachs von etwa 80 % in 11 Monaten. Die Erzielung dieses Renditeniveaus über einen solchen Zeitraum verdeutlicht die Stärke der Kombination von strukturierter Marktlogik mit diszipliniertem Risikomanagement und regelbasierter Ausführung.

Abgesehen von der Rentabilität ist es wichtig zu beobachten, wie sich das Kapital während des Tests verhalten hat. Die Wachstumskurve des Kapitals zeigt einen gleichmäßigen und stetigen Verlauf und keine scharfen Ausschläge oder instabilen Umkehrpunkten.

Wachstum der Kapitalkurve

Testbericht

Dies deutet darauf hin, dass im Laufe der Zeit durchgängig Gewinne erzielt und die Drawdowns durch die Regeln der Strategie unter Kontrolle gehalten wurden.

Die Testergebnisse deuten darauf hin, dass der EA nicht auf zufällige Einträge oder isolierte Marktbedingungen angewiesen ist. Stattdessen profitiert er davon, dass er immer wieder sinnvolle Marktstrukturpunkte identifiziert und nur dann handelt, wenn diese Bedingungen gegeben sind. Dies untermauert den Kerngedanken der Marktstruktur von Larry Williams, der besagt, dass Preisbewegungen eher identifizierbaren, wiederholbaren Mustern folgen als reiner Zufälligkeit.


Schlussfolgerung

In diesem Artikel haben wir eine komplette Handelsidee in einen voll funktionsfähigen Expert Advisor mit MQL5 verwandelt. Ausgehend von der objektiven Definition der Marktstruktur von Larry Williams haben wir kurz- und mittelfristige Umkehrpunkte in einen Code übersetzt und sie zur Generierung klarer, wiederholbarer Handelssignale verwendet. Dadurch wird das Rätselraten aus der Marktanalyse entfernt und durch Regeln ersetzt, die getestet, überprüft und verbessert werden können.

Über die Signalerzeugung hinaus haben wir ein praktisches Handelssystem entwickelt. Der EA umfasst eine konfigurierbare Handelsrichtungskontrolle, ein flexibles Risikomanagement, eine strukturbasierte Stop-Loss-Platzierung, ein festes Risiko-Ertrags-Verhältnis und einen optionalen Trailing-Stop. Jede Funktion wurde mit einem klaren Ziel hinzugefügt und auf modulare Weise implementiert, sodass die Logik einfach zu lesen, zu testen und zu erweitern ist.

Eines der wichtigsten Ergebnisse dieser Arbeit ist nicht nur die Strategie selbst, sondern auch der Prozess, mit dem sie entwickelt wurde. Während des gesamten Artikels haben wir uns an bewährte Praktiken für das Schreiben von wartbarem MQL5-Code gehalten, indem wir Verantwortlichkeiten in kleine Hilfsfunktionen aufteilten, Enumerationen für Nutzeroptionen verwendeten und uns auf klare Datenstrukturen zur Verwaltung von Handelsgeschäften und der Trailing-Logik verließen. Dieser Ansatz erleichtert die Fehlersuche und Änderung des EA und eignet sich als Grundlage für fortgeschrittenere Systeme.

Die Backtest-Ergebnisse zeigen, dass eine regelbasierte Interpretation der Marktstruktur in Verbindung mit einem disziplinierten Risikomanagement zu einer konsistenten Performance führen kann. Noch wichtiger ist, dass die hier vorgestellte EA dem Leser einen vollständigen Rahmen für Experimente bietet. Verschiedene Symbole, Zeitrahmen, Risikoparameter und Stop-Loss-Strukturen können getestet werden, um zu untersuchen, wie sich das Marktverhalten ändert und wo die Strategie am verlässlichsten funktioniert.

Schließlich unterstreicht dieser Artikel einen wichtigen Gedanken hinter Larry Williams' Arbeit. Märkte sind nicht rein zufällig. Der Preis neigt dazu, sich in strukturierten Umkehrpunkten zu bewegen, die objektiv identifiziert werden können. Indem wir diese Konzepte in einem Expert Advisor kodieren, können wir das Marktverhalten systematisch untersuchen und emotionale Verzerrungen bei der Ausführung beseitigen.

Damit ist der zweite Teil der Serie abgeschlossen. Im folgenden Artikel werden wir auf dieser Grundlage aufbauen, indem wir die übergeordnete Marktstruktur und weitere Möglichkeiten zur Analyse und Validierung von nicht zufälligem Preisverhalten mit MQL5 untersuchen.

Alle Quellcodedateien und andere Dateien, die in diesem Artikel verwendet werden, sind unten aufgeführt. In der folgenden Tabelle werden die einzelnen Dateien und ihr Zweck erläutert.

Dateiname Beschreibung
larryWilliamsMarketStructureIndicator.mq5 Nutzerdefinierter Indikator, der auf der Grundlage der Methodik von Larry Williams kurz- und mittelfristige Umkehrpunkte der Marktstruktur identifiziert und darstellt.
larryWilliamsMarketStructureExpert.mq5 Expert Advisor, der Signale vom Marktstrukturindikator liest und automatisch Handelsgeschäfte mit Risikomanagement und Step-Trailing-Stop-Logik ausführt.
setFile.set Konfigurationsdatei mit den genauen Eingabeparametern, die während der Test- und Beispielläufe des Expert Advisors verwendet wurden.

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

Letzte Kommentare | Zur Diskussion im Händlerforum (3)
William Tosolini
William Tosolini | 12 Jan. 2026 in 10:00
Guten Morgen, interessanter Expert Advisor, ich wollte fragen, ob es möglich ist, den kompletten Code für MetaTrader 4 zu haben, bitte.
Da ich nicht weiß, wie man MQL benutzt und schreibt, wollte ich fragen, ob Sie mir die fertige Datei des Expert Advisors für MetaTrader 4 schicken könnten, damit ich sie in die Plattform einfügen kann.
Lassen Sie mich wissen, ob das möglich ist, und nochmals vielen Dank und Glückwunsch zu Ihrer Arbeit.
Chacha Ian Maroa
Chacha Ian Maroa | 12 Jan. 2026 in 14:28
William Tosolini Expert Advisor, ich wollte fragen, ob es möglich ist, den kompletten Code für MetaTrader 4 zu haben, bitte.
Auch, da ich nicht weiß, wie zu verwenden und schreiben in MQL, wollte ich fragen, ob Sie mir die Expert Advisor fertige Datei für MetaTrader 4 senden könnte, so kann ich es in die Plattform einfügen.
Lassen Sie mich wissen, ob das möglich ist, und nochmals vielen Dank und Glückwunsch zu Ihrer Arbeit.

Lieber William,

Vielen Dank für Ihre freundlichen Worte und dafür, dass Sie meine Arbeit verfolgen.

Was Ihre Anfrage betrifft, so bin ich ausschließlich auf die Entwicklung von MQL5 für die Plattform MetaTrader 5 spezialisiert. Da sich die Architektur von MT4 und MT5 erheblich unterscheidet, erfordert die Portierung des Codes eine komplette Neuprogrammierung.

Leider bin ich aufgrund meines aktuellen Projektplans derzeit nicht in der Lage, kundenspezifische Kodierungsanfragen oder Konvertierungen zu erfüllen. Ich danke Ihnen für Ihr Verständnis und hoffe, dass Sie die MT5-Version des Artikels nützlich finden.

William Tosolini
William Tosolini | 12 Jan. 2026 in 20:40
Chacha Ian Maroa #:

Lieber William,

vielen Dank für deine freundlichen Worte und dafür, dass du meine Arbeit verfolgst.

Was Ihre Anfrage betrifft, so bin ich ausschließlich auf die MQL5-Entwicklung für die MetaTrader 5-Plattform spezialisiert. Da die Architektur von MT4 und MT5 deutlich unterschiedlich ist, erfordert die Portierung des Codes eine komplette Neuschreibung.

Leider bin ich aufgrund meines aktuellen Projektplans derzeit nicht in der Lage, kundenspezifische Kodierungsanfragen oder Konvertierungen zu erfüllen. Ich danke Ihnen für Ihr Verständnis und hoffe, dass Sie die MT5-Version des Artikels nützlich finden.

Danke für das Feedback, aber leider verwende ich nur MetaTrader 4, weil ich ihn besser finde als MetaTrader 5.
Das macht nichts, es ist schade, ich hätte den Experten für MT4 wirklich gerne gehabt, aber trotzdem danke. Sie waren so freundlich, zu antworten und Ihre Gründe zu erläutern.
Sigma-Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor Sigma-Score Indikator für MetaTrader 5: Ein einfacher statistischer Anomalie-Detektor
Erstellen Sie einen praktischen MetaTrader 5 „Sigma-Score“ Indikator von Grund auf und lernen Sie, was er wirklich misst: den z-Score der logarithmischen Renditen (wie viele Standardabweichungen die letzte Bewegung vom letzten Durchschnitt abweicht). Der Artikel geht jeden Codeblock in OnInit(), OnCalculate() und OnDeinit() durch und zeigt dann, wie man Schwellenwerte (z. B. ±2) interpretiert und den Sigma-Score als einfaches „Marktstress-Messgerät“ für Mean-Reversion und Momentum-Trading einsetzt.
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 3): Erweiterungen auf Multi-Messuhren mit Sektor- und Rundstilen
In diesem Artikel erweitern wir den Indikator auf Basis von Messuhren in MQL5, um mehrere Oszillatoren zu unterstützen und dem Nutzer die Auswahl durch eine Enumeration für einzelne oder kombinierte Anzeigen zu ermöglichen. Wir führen sektorale und runde Messuhren-Stile über abgeleitete Klassen eines Basis-Messuhren-Systems ein und verbessern die Falldarstellung mit Bögen, Linien und Polygonen für ein verfeinertes visuelles Erscheinungsbild.
Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 8): UI-Polnisch mit Animationen, zeitlichen Metriken und Tools für das Reaktionsmanagement Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 8): UI-Polnisch mit Animationen, zeitlichen Metriken und Tools für das Reaktionsmanagement
In diesem Artikel erweitern wir das KI-gestützte Handelssystem in MQL5 um Verbesserungen der Nutzeroberfläche, einschließlich Ladeanimationen für die Vorbereitungs- und Denkphasen von Anfragen sowie Zeitmesswerte, die in den Antworten für ein besseres Feedback angezeigt werden. Wir fügen Tools zur Verwaltung von Antworten hinzu, wie z. B. Schaltflächen zum erneuten Abfragen der KI und Exportoptionen zum Speichern der letzten Antwort in einer Datei, um die Interaktion zu optimieren.
Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5 Implementierung von praktischen Modulen aus anderen Sprachen in MQL5 (Teil 06): Python-ähnliche Datei-IO-Operationen in MQL5
Dieser Artikel zeigt, wie man komplexe MQL5-Datei-Operationen vereinfachen kann, indem man eine Schnittstelle im Python-Stil für müheloses Lesen und Schreiben erstellt. Es wird erklärt, wie man die intuitiven Dateiverarbeitungsmuster von Python durch nutzerdefinierte Funktionen und Klassen nachbilden kann. Das Ergebnis ist ein sauberer, zuverlässiger Ansatz für MQL5-Datei-E/A.