English Русский
preview
Larry Williams’ Marktgeheimnisse (Teil 12): Kontextbezogener Handel von „Smash Day“-Umkehrungen

Larry Williams’ Marktgeheimnisse (Teil 12): Kontextbezogener Handel von „Smash Day“-Umkehrungen

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

Einführung

Smash Day-Muster sind zwar attraktiv, doch der Handel mit ihnen ohne klare Struktur führt oft zu unzuverlässigen Signalen und unnötigen Verlusten. Dieses Muster kann häufig auftreten, und ohne klare Regeln hinsichtlich Kontext, Zeitpunkt und Gültigkeit wird es schwierig, aussagekräftige Trendwenden von zufälligen Kursschwankungen zu unterscheiden. Larry Williams betonte wiederholt, dass Smash Day-Muster nicht isoliert, sondern als Teil eines umfassenderen Entscheidungsprozesses gehandelt werden sollten.

Dieser Artikel befasst sich mit diesem Problem, indem er die Smash Day-Reversals in ein objektives, regelbasiertes System umwandelt, das automatisiert und getestet werden kann. Anstatt sich auf visuelle Interpretationen zu stützen, wird jede Entscheidung in einer messbaren, konsistenten Logik ausgedrückt, die über verschiedene Märkte und Zeitrahmen hinweg ausgewertet werden kann.

Dieser Artikel richtet sich an algorithmische Trader und MQL5-Entwickler, die Smash Day-Muster automatisieren, testen und objektiv untersuchen möchten. Es eignet sich auch für Trader, die verstehen möchten, wie sich die Ideen von Larry Williams in präzise Ausführungsregeln ohne Ermessensspielraum umsetzen lassen.

In diesem Artikel wird ein vollständiger Expert Advisor erstellt, der Smash Day-Umkehrbewegungen mit optionalen Kontextfiltern handelt. Die Trendrichtung kann mithilfe des Supertrend-Indikators als Filter vorgegeben werden, Trades können nach Wochentagen begrenzt werden, und für jedes Setup wird ein festes Gültigkeitsfenster festgelegt. Diese Elemente sollen das Rauschen reduzieren, unnötige Trades begrenzen und die Interpretation der Testergebnisse erleichtern. Am Ende des Artikels werden Hidden Smash Day-Muster nicht mehr als eigenständige Signale betrachtet, sondern als Teil eines strukturierten Rahmens, der getestet, angepasst und erweitert werden kann.


Strategie-Regeln des Smash Day-Expert Advisors

Dieser Expert Advisor wendet Larry Williams’ Smash Day-Umkehrkonzept unter Verwendung strenger, überprüfbarer Regeln an. Das Ziel besteht nicht darin, jedes erkennbare Muster zu handeln, sondern Unklarheiten zu beseitigen und Bedingungen zu definieren, die über verschiedene Märkte und Zeitrahmen hinweg einheitlich bewertet werden können.

Das Smash Day-Muster dient als Signalauslöser. Alle zusätzlichen Regeln dienen dazu, den Zeitpunkt, den Kontext und das Risiko zu steuern, damit die Ergebnisse eine strukturierte Entscheidungsfindung widerspiegeln und nicht das zufällige Vorgehen.

Definition des Smash Day-Musters

Das System unterstützt zwei Mustertypen: Smash Day-Buy-Umkehr und Smash Day-Sell-Umkehr. Ein Smash Day Buy-Setup wird erkannt, wenn eine Bar unterhalb der Tiefs einer benutzerdefinierten Anzahl vorheriger Bars schließt. Diese Rückblicktiefe ist konfigurierbar und ermöglicht es, zu testen, wie sich die Intensität solcher Durchbrüche nach unten auf Umkehrungen auswirkt. Die Bar darf keine Outside-Bar sein, da sie sich in beide Richtungen ausdehnen kann und oft eher Rauschen als eine bestimmte Richtung signalisiert.

Ein Smash Day Sell-Setup folgt derselben Logik in umgekehrter Richtung. Die Bar muss über den Höchstständen einer benutzerdefinierten Anzahl vorheriger Bars schließen und darf zudem keine Outside-Bar sein. Sobald eine Smash Day-Bar erkannt wird, stellt sie einen potenziellen Umkehrpunkt dar, nicht jedoch einen unmittelbaren Handelsanlass.

Setup-Gültigkeit und Zeitsteuerung

Es wird nicht davon ausgegangen, dass Smash Day-Setups auf unbestimmte Zeit gültig bleiben. Jedem erkannten Setup wird eine begrenzte Lebensdauer zugewiesen, die durch eine vom Benutzer festgelegte Anzahl zukünftiger Bars definiert ist. Sollte der Kurs innerhalb dieses Zeitfensters keinen Einstieg auslösen, wird das Setup automatisch verworfen. Dies spiegelt das tatsächliche Marktverhalten wider, bei dem der Umkehrdruck mit der Zeit nachlässt und späte Einstiegszeitpunkte oft ihren Vorteil verlieren. Diese Regel ermöglicht zudem eine objektive Überprüfung der Zeitsensitivität, ohne das Kernmuster zu verändern.
Einstiegsbedingungen und Bestätigung.

Ein Handel kommt erst dann in Betracht, wenn der Kurs das Smash-Day-Niveau in der erwarteten Richtung durchbricht. Bei einer Buy-Umkehr muss der Kurs über das Hoch der Smash Day-Bar steigen. Bei einer Sell-Umkehr muss der Kurs unter das Tief der Smash Day-Bar fallen. Es werden zwei Einstiegsmodi unterstützt. Der Erste wird sofort ausgelöst, sobald der Kurs das Ausbruchsniveau überschreitet. Der zweite wartet auf den bestätigten Schlusskurs einer Bar jenseits des Ausbruchsniveaus. Auf diese Weise lässt sich die schnellere Ausführung mit den bestätigten Einstiegen vergleichen, während die Musterdefinition unverändert bleibt.

Richtungssteuerung

Das System kann so konfiguriert werden, dass es ausschließlich Buy-Setups, ausschließlich Sell-Setups oder beides handelt. Unabhängig vom gewählten Modus hält sich der EA strikt an die Regel, jeweils nur eine Position gleichzeitig zu halten. Smash Day-Umkehrungen sind einzelne Ereignisse, und die Zulassung mehrerer Positionen würde den Zusammenhang zwischen Ursache und Wirkung verschleiern. Durch diesen Aufbau bleiben die Ergebnisse übersichtlich und lassen sich während der Tests leichter auswerten.

Kontextfilter

Die kontextbezogene Filterung ist optional, aber klar definiert. Ein Supertrend-Filter kann aktiviert werden, um Trades an der vorherrschenden Marktrichtung auszurichten. Wenn diese Funktion aktiv ist, sind Buy-Setups nur bei einem bullischen Supertrend zulässig, während Sell-Setups nur bei einem bärischen Supertrend zulässig sind. Der Supertrend-Zeitrahmen, die ATR-Periode und der ATR-Multiplikator lassen sich alle über Eingabeparameter konfigurieren.

Es kann auch ein Filter für den Handelstag der Woche angewendet werden. Trades werden nur an ausgewählten Tagen ausgeführt, was eine Analyse des sitzungs- oder kalenderbasierten Verhaltens ermöglicht. Beide Filter lassen sich unabhängig voneinander ein- oder ausschalten.

Risiko- und Handelsmanagement

Das Risikomanagement folgt dem Smash Day-Modell. Bei einer Buy-Umkehr wird der Stop-Loss auf das Tief der Smash Day-Bar gesetzt. Bei einer Sell-Umkehr wird der Stop-Loss auf das Hoch der Smash Day-Bar gesetzt. Der Take-Profit-Wert wird anhand eines vom Nutzer festgelegten festen Risiko-Ertrags-Verhältnisses berechnet. Dadurch bleiben die Ausstiegsstrategien marktübergreifend objektiv und skalierbar.

Die Positionsgröße kann manuell festgelegt oder automatisch als Prozentsatz des Kontoguthabens berechnet werden. Im Automatikmodus wird die Positionsgröße aus der Stop-Distanz abgeleitet, wodurch unabhängig von der Volatilität ein konstantes Risiko gewährleistet wird.


Was ist neu in diesem Expert Advisor?

Diese Version des Smash Day-Expert Advisors baut auf früheren Implementierungen auf und legt den Schwerpunkt auf Timing-Steuerung, kontextbezogene Filterung und Reproduzierbarkeit. Die wichtigste Neuerung ist der Mechanismus zur Überprüfung des Setups. Smash Day-Umkehrungen werden nicht mehr als unmittelbare Chancen betrachtet. Jedes Setup bleibt nur für eine festgelegte Anzahl von Bars aktiv; danach wird es automatisch ungültig, sofern kein Einstieg erfolgt. Dadurch wird eine verspätete Teilnahme verhindert und eine strukturierte Untersuchung des Impulsabbaus ermöglicht.

Die Kontextfilterung wurde durch den optionalen Supertrend-Filter erweitert. Trades können nun auf die vorherrschende Marktrichtung beschränkt werden, wobei Sie die volle Kontrolle über den Zeitrahmen und die Berechnungsparameter des Supertrends haben. Dadurch wird das Konzept des Kontexts explizit und überprüfbar und ist nicht mehr willkürlich.

Die zeitbasierte Filterung steht weiterhin über den Mechanismus des Handelstags der Woche zur Verfügung. Trades können auf bestimmte Tage beschränkt werden, ohne die Kernlogik zu ändern, was gezielte Experimente ermöglicht.

Die Ausführungslogik aus früheren Versionen bleibt erhalten. Der Einstieg kann bei einem Niveaudurchbruch oder nach einem bestätigten Bar-Schlusskurs erfolgen. Die Richtungssteuerung ermöglicht reine Kauf-, reine Verkaufs- oder kombinierte Transaktionen. Das Risikomanagement unterstützt sowohl die Festlegung der Positionsgröße als auch die prozentuale Positionsgröße.

Zusammen verwandeln diese Erweiterungen den EA von einem Muster-Ausführungswerkzeug in ein strukturiertes Research-Tool. Das Smash Day-Signal bleibt weiterhin der Auslöser, doch Zeitpunkt, Kontext und Risiko bestimmen nun, wann und wie Trades getätigt werden.


Aufbau des Expert Advisors Schritt für Schritt

In diesem Abschnitt beginnen wir mit der praktischen Erstellung des Expert Advisors. Das Ziel dabei ist nicht, sich vorschnell in die Handelslogik zu stürzen, sondern eine klare und zuverlässige Grundlage zu schaffen, auf der alles, was später folgt, aufbauen kann. Jeder Codeabschnitt, der in dieser Phase eingeführt wird, hat seinen Grund, auch wenn dieser Grund erst im weiteren Verlauf des Entwicklungsprozesses deutlich wird.

Bevor wir fortfahren, wollen wir zunächst einige Voraussetzungen klären. Wir setzen Kenntnisse der Sprache MQL5 und ihrer grundlegenden Syntax voraus. Begriffe wie Variablen, Funktionen, Enumerationen, Schleifen und Standardbibliotheken sollten Ihnen bereits vertraut sein. Wir setzen außerdem Vorkenntnisse im Umgang mit der MetaTrader-5-Plattform voraus, einschließlich des Einbindens von Programmen in Charts und der Durchführung von Tests. Schließlich sind praktische Kenntnisse im Umgang mit dem MetaEditor erforderlich, um bei Bedarf Quelldateien zu erstellen, zu kompilieren und zu debuggen.

Um das Lernen durch praktische Übungen zu unterstützen, ist der vollständige Quellcode für den fertigen Expert Advisor diesem Artikel als Datei lwSmashDayTrendFilteredExpert.mq5 beigefügt. Wenn man parallel zum Tutorial programmiert und den Fortschritt mit der endgültigen Version vergleicht, lässt sich viel leichter nachvollziehen, wie die einzelnen Teile ins Gesamtbild passen.

Nachdem dies erledigt ist, erstellen wir zunächst eine neue, leere Expert-Advisor-Datei im MetaEditor und fügen den folgenden Ausgangsquellcode ein.

//+------------------------------------------------------------------+
//|                                lwSmashDayTrendFilteredExpert.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"
#resource "\\Indicators\\supertrend.ex5"

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

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TDW_MODE
{
   TDW_ALL_DAYS,     
   TDW_SELECTED_DAYS
};

enum ENUM_SMASH_ENTRY_MODE{
   ENTRY_ON_LEVEL_CROSS,
   ENTRY_ON_BAR_CLOSE
};

enum ENUM_SMASH_TRADE_MODE
{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

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

input group "Smash Day Pattern Rules"
input int smashBuyLookbackBars   = 1;
input int smashSellLookbackBars  = 1;
input int smashSetupValidityBars = 3;

input group "Smash Day Entry Settings"
input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS;
input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH;

input group "Supertrend configuration parameters"
input bool useSupertrendFilter            = false;
input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT;
input int32_t supertrendAtrPeriod         = 10;
input double  supertrendAtrMultiplier     = 1.5;

input group "TDW filters"
input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS;
input bool tradeSunday           = false;
input bool tradeMonday           = true;
input bool tradeTuesday          = false;
input bool tradeWednesday        = false;
input bool tradeThursday         = false;
input bool tradeFriday           = false;
input bool tradeSaturday         = false;

input group "Trade and Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;
input double riskRewardRatio                = 3.0;

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

//--- To hep track current market prices for Buying (Ask) and Selling (Bid)
double askPrice;
double bidPrice;

//--- To store current time
datetime currentTime;

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

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

struct MqlSmashDayPatternState{

   //--- Pattern detection flags
   bool hasBuySmashSetup;
   bool hasSellSmashSetup;

   //--- Reference breakout levels from smash bar
   double buyBreakoutLevel;
   double sellBreakoutLevel;

   //--- Pattern bar reference data
   datetime smashBarTime;

   //--- Entry status tracking
   bool entryPending;
   
   //--- Tracks the number of bars elapsed after the occurence of the smash bar
   int barsSinceSmash;
   
   //--- Stop-Loss levels
   double buyStopLoss;
   double sellStopLoss;
   
};

MqlSmashDayPatternState smashState;

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

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

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(closePriceMinutesData, true);
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);
   
   //--- Initialize global variables
   lastBarOpenTime = 0;
   
   //--- Reset
   ZeroMemory(smashState);

   return(INIT_SUCCEEDED);
}

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

   //--- Release Supertrend
   if(supertrendIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(supertrendIndicatorHandle);
   }

   //--- 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);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }   
}

//--- UTILITY FUNCTIONS

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

Dieser Code bildet das strukturelle Rückgrat des Systems.

Dateikopf und Programmidentität

//+------------------------------------------------------------------+
//|                                lwSmashDayTrendFilteredExpert.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"
#resource "\\Indicators\\supertrend.ex5"

Im einleitenden Abschnitt werden der Programmname, Angaben zum Autor sowie Versionsinformationen festgelegt. Dies ist in MQL5 gängige Praxis und erleichtert die langfristige Wartung, insbesondere wenn mehrere Versionen eines Expert Advisors vorliegen.

Der Supertrend-Indikator ist ebenfalls als Ressource integriert. Dadurch wird sichergestellt, dass der EA jederzeit intern auf den Indikator zugreifen kann, ohne auf externe Chart-Anhänge angewiesen zu sein. Da die Trendfilterung ein Kernmerkmal dieser Version ist, ist es unerlässlich, den Indikator auf dieser Ebene zur Verfügung zu stellen.

Standardbibliotheken und Zugriff auf Handelsfunktionen

Als Nächstes binden wir die Trade-Bibliothek ein.

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

Dadurch erhalten Sie Zugriff auf die CTrade-Klasse, die die Auftragsausführung, die Positionsverwaltung und das Fehlermanagement vereinfacht. Durch die Verwendung dieser Bibliothek bleibt die Handelslogik lesbar und in den verschiedenen Teilen des Systems einheitlich.

Zum jetzigen Zeitpunkt wurden noch keine Trades getätigt. Wir bereiten lediglich die Werkzeuge vor, die später eine sichere und kontrollierte Ausführung ermöglichen werden.

Enumerationen für eine übersichtliche Konfiguration

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TDW_MODE
{
   TDW_ALL_DAYS,     
   TDW_SELECTED_DAYS
};

enum ENUM_SMASH_ENTRY_MODE{
   ENTRY_ON_LEVEL_CROSS,
   ENTRY_ON_BAR_CLOSE
};

enum ENUM_SMASH_TRADE_MODE
{
   SMASH_TRADE_BUY_ONLY,
   SMASH_TRADE_SELL_ONLY,
   SMASH_TRADE_BOTH
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

Zu Beginn der Datei werden mehrere benutzerdefinierte Enumerationen deklariert. Diese Enumerationen legen fest, wie sich der EA unter verschiedenen Konfigurationen verhält. Der TDW-Modus legt fest, ob der Handel an allen Tagen oder nur an ausgewählten Tagen zulässig ist. Der Smash-Einstiegsmodus legt fest, ob Einstiegspositionen sofort bei Kursdurchbrüchen oder erst nach Bestätigung des Schlusskurses ausgelöst werden. Der Smash-Handelsmodus steuert das Richtungsverhalten und ermöglicht den reinen Kauf, den reinen Verkauf oder beides. Der Lotgrößenmodus legt fest, ob die Positionsgröße manuell oder risikobasiert festgelegt wird. Die Verwendung von Enumerationen anstelle von reinen Zahlenwerten verbessert die Lesbarkeit und beugt späteren Konfigurationsfehlern vor.

Vom Benutzer einzugebende Parameter

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

input group "Smash Day Pattern Rules"
input int smashBuyLookbackBars   = 1;
input int smashSellLookbackBars  = 1;
input int smashSetupValidityBars = 3;

input group "Smash Day Entry Settings"
input ENUM_SMASH_ENTRY_MODE smashEntryMode = ENTRY_ON_LEVEL_CROSS;
input ENUM_SMASH_TRADE_MODE smashTradeMode = SMASH_TRADE_BOTH;

input group "Supertrend configuration parameters"
input bool useSupertrendFilter            = false;
input ENUM_TIMEFRAMES supertrendTimeframe = PERIOD_CURRENT;
input int32_t supertrendAtrPeriod         = 10;
input double  supertrendAtrMultiplier     = 1.5;

input group "TDW filters"
input ENUM_TDW_MODE tradeDayMode = TDW_SELECTED_DAYS;
input bool tradeSunday           = false;
input bool tradeMonday           = true;
input bool tradeTuesday          = false;
input bool tradeWednesday        = false;
input bool tradeThursday         = false;
input bool tradeFriday           = false;
input bool tradeSaturday         = false;

input group "Trade and Risk Management"
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;
input double riskRewardRatio                = 3.0;

Der Eingabebereich stellt dem Benutzer alle konfigurierbaren Funktionen auf strukturierte und übersichtliche Weise dar. Die grundlegenden Eingabeinformationen legen die MagicNumber und den Arbeitszeitraum fest. Die Smash Day-Musterregeln legen fest, wie Umkehrmuster erkannt werden und wie lange ein Setup nach seinem Auftreten gültig bleibt. Die Einstiegseinstellungen legen fest, wie und wann Trades ausgelöst werden. Die Supertrend-Eingaben legen fest, ob die Trendfilterung aktiv ist und wie der Trend berechnet wird. TDW-Eingaben legen fest, an welchen Tagen gehandelt werden kann. Die Parameter des Risikomanagements bestimmen die Positionsgröße und die Renditeerwartungen. In dieser Phase wird noch nichts ausgeführt. Diese Eingaben legen die Regeln des Systems fest, bevor es auf Marktdaten reagiert.

Globale Variablen und gemeinsam genutzter Zustand

Globale Variablen werden deklariert, um Preise, Zeitangaben, Indikatorwerte und den internen Zustand zu speichern.

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

//--- To hep track current market prices for Buying (Ask) and Selling (Bid)
double askPrice;
double bidPrice;

//--- To store current time
datetime currentTime;

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

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

struct MqlSmashDayPatternState{

   //--- Pattern detection flags
   bool hasBuySmashSetup;
   bool hasSellSmashSetup;

   //--- Reference breakout levels from smash bar
   double buyBreakoutLevel;
   double sellBreakoutLevel;

   //--- Pattern bar reference data
   datetime smashBarTime;

   //--- Entry status tracking
   bool entryPending;
   
   //--- Tracks the number of bars elapsed after the occurence of the smash bar
   int barsSinceSmash;
   
   //--- Stop-Loss levels
   double buyStopLoss;
   double sellStopLoss;
   
};

MqlSmashDayPatternState smashState;

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

Das CTrade-Objekt wird einmal angelegt und für alle Handelsvorgänge wiederverwendet. Ask- und Bid-Kurse werden zur Gewährleistung einer präzisen Ausführung gespeichert. Supertrend-Puffer dienen zur Speicherung der vom Indikator abgerufenen Trenddaten.

Außerdem wird eine benutzerdefinierte Struktur eingeführt, um den Status des Setups des Smash Day zu verfolgen. Diese Struktur speichert Informationen über erkannte Setups, Ausbruchsniveaus, Stop-Loss-Niveaus und die Anzahl der Bars seit der Bildung der Smash-Bar. Dieser Ansatz sorgt für eine übersichtliche Logik und verhindert verstreute globale Flags.

Ein kleines Array wird reserviert, um Schlusskurse auf Minutenebene zu speichern. Dies wird später zur präzisen Erkennung von Crossovers verwendet.

Initialisierungslogik für Experten

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

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);
   
   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, supertrendTimeframe, "::Indicators\\supertrend.ex5", supertrendAtrPeriod, supertrendAtrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator", GetLastError());
      return(INIT_FAILED);
   }
   
   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(closePriceMinutesData, true);
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);
   
   //--- Reset
   ZeroMemory(smashState);

   return(INIT_SUCCEEDED);
}

In der Initialisierungsfunktion bereitet sich der EA vor, bevor er Marktdaten empfängt. Die MagicNumber wird dem Trade-Objekt zugewiesen, um die von diesem EA eröffneten Positionen zuverlässig zu identifizieren. Der Supertrend-Indikator wird mithilfe von iCustom initialisiert und sofort validiert. Wenn der Indikator nicht geladen werden kann, stoppt der EA, um den Handel ohne Trendkontext zu vermeiden. Arrays sind als Zeitreihen konfiguriert, sodass der Index Null stets den aktuellsten Wert darstellt. Die Zustandsstruktur von Smash Day wird zurückgesetzt, um einen sauberen Ausgangszustand zu gewährleisten.

Am Ende dieses Prozesses ist der EA vollständig vorbereitet, aber noch inaktiv. Es werden keine Entscheidungen getroffen, bis die Ticks eintreffen.

Deinitialisierung und Bereinigung von Ressourcen

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

   //--- Release Supertrend
   if(supertrendIndicatorHandle != INVALID_HANDLE){
      IndicatorRelease(supertrendIndicatorHandle);
   }
to identify positions opened by this EA reliably
   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);
}

Wenn der EA nicht mehr ausgeführt wird, wird die Deinitialisierungsfunktion aufgerufen. Hier geben wir den Supertrend-Indikator-Handle frei und protokollieren den Grund für die Beendigung. Dadurch werden Speicherlecks verhindert und sichergestellt, dass Ressourcen ordnungsgemäß an das Terminal zurückgegeben werden.

Skelett zur Handhabung von Ticks

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

   //--- Retrieve current market prices for trade execution
   askPrice    = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice    = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }   
}

Die Funktion OnTick wird als letzter Baustein eingeführt. Derzeit werden lediglich aktuelle Kurse und die Daten der letzten Minuten abgerufen. Es wird noch keine Handelslogik angewendet. Diese Trennung ist beabsichtigt. Indem wir die ursprüngliche OnTick-Funktion einfach halten, schaffen wir einen übersichtlichen Einstiegspunkt, um nach und nach Logik hinzuzufügen, ohne dass Verwirrung entsteht.

Zu diesem Zeitpunkt verfügt der Expert Advisor über eine solide strukturelle Grundlage. Die Konfiguration, die Zustandsverwaltung, der Zugriff auf Indikatoren und die Datenaufbereitung sind bereits eingerichtet. In den nächsten Schritten wird diese Struktur um Mustererkennung, Setup-Validierung, Trendfilterung und kontrollierte Handelsausführung erweitert.

Jede neue Funktion, die von nun an hinzugefügt wird, wird direkt an die bereits in dieser Grundlage vorgestellten Konzepte anknüpfen.


Erkennung der neuen Bar und Steuerung der täglichen Ausführung

Der Expert Advisor ist so konzipiert, dass er zu Beginn eines neuen Tagesbar wichtige Entscheidungen trifft. Aus diesem Grund stellen wir als Allererstes ein Tool vor, mit dem sich zuverlässig erkennen lässt, wann sich auf dem ausgewählten Zeitrahmen eine neue Kerze gebildet hat. Zunächst fügen wir eine Hilfsfunktion hinzu, die prüft, ob sich der Zeitstempel der letzten Bar geändert hat.

//+------------------------------------------------------------------+
//| 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 sollte im Abschnitt Hilfsfunktionen der Quelldatei platziert werden. Die Funktion vergleicht die aktuelle Öffnungszeit der Bar mit einer gespeicherten Referenzzeit. Wenn sich die beiden Werte unterscheiden, hat sich eine neue Bar gebildet, und die Referenzzeit wird aktualisiert. Dieser einfache Mechanismus ermöglicht es dem EA, die tägliche Logik nur einmal pro Bar auszuführen, wodurch doppelte Auswertungen bei jedem Tick verhindert werden.

Um diese Logik zu unterstützen, wird eine globale Datums- und Zeitvariable eingeführt, in der die zuletzt bekannte Eröffnungszeit der Bar gespeichert wird.

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

Diese Variable muss über mehrere Ticks hinweg erhalten bleiben, weshalb sie global deklariert wird. Bei der Initialisierung des Experten wird diese Variable ausdrücklich auf Null gesetzt.

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

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

   return(INIT_SUCCEEDED);
}

Dadurch wird sichergestellt, dass der erste nach dem Start erkannte Bar als neue Bar behandelt wird, wodurch von Beginn an ein korrektes Verhalten gewährleistet ist. Dieser Mechanismus bildet das Rückgrat des gesamten Ausführungsablaufs, da die Erkennung von Smash-Mustern und die Verwaltung der Setups später vollständig von der präzisen Erkennung neuer Bars abhängen.

Filterung ungültiger Smash-Bars mithilfe der Erkennung äußerer Bars

Eine der zentralen Gestaltungsentscheidungen besteht darin, dass eine Smash Bar keine Outside-Bar sein darf. Outside-Bars stehen oft für übermäßige Volatilität und eine unzuverlässige Preisbildung, was dem Zweck eines Smash Reversal -Setups widerspricht. Um diese Regel durchzusetzen, führen wir eine Funktion ein, die prüft, ob eine Bar den Bereich der vorherigen Bar vollständig umfasst.

//+-----------------------------------------------------------------------------+
//| Checks whether the bar at the given index fully engulfs the prior bar range |
//+-----------------------------------------------------------------------------+
bool IsOutsideBar(string symbol, ENUM_TIMEFRAMES tf, int index){

   double high0 = iHigh(symbol, tf, index);
   double low0  = iLow (symbol, tf, index);

   double high1 = iHigh(symbol, tf, index + 1);
   double low1  = iLow (symbol, tf, index + 1);

   return (high0 > high1 && low0 < low1);
}

Die Funktion vergleicht das aktuelle Hoch und Tief der Bar mit den Werten der vorherigen Bar. Wenn sich die aktuelle Bar sowohl über als auch unter der vorherigen Bar erstreckt, wird sie als Outside-Bar klassifiziert und von der weiteren Auswertung ausgeschlossen. Diese Funktion ist bewusst klein und fokussiert gehalten. Sie erkennt Smash-Muster nicht direkt, sondern fungiert als Filter, auf den sich andere Erkennungsfunktionen stützen. Indem wir diese Logik auslagern, sorgen wir für mehr Übersichtlichkeit und vermeiden Code-Wiederholungen.

Erkennen von Umkehrmustern am Smash Day

Nachdem die Outside-Bars herausgefiltert wurden, können wir nun die Logik zur Identifizierung gültiger Smash Day-Umkehrmuster definieren. Die Funktion zur Erkennung einer Kaufumkehr prüft, ob der Schlusskurs des Smash Bars unter eine konfigurierbare Anzahl vorheriger Tiefststände fällt.

//+-----------------------------------------------------------------------------------+
//| Detects a Smash Day Buy Reversal where the close breaks below multiple prior lows |
//+-----------------------------------------------------------------------------------+
bool IsSmashDayBuyReversal(string symbol,
                           ENUM_TIMEFRAMES tf,
                           int index,
                           int lookbackBars)
{
   // Bar must not be an outside bar
   if(IsOutsideBar(symbol, tf, index))
      return false;

   double close1 = iClose(symbol, tf, index);

   // Validate close breaks below N prior lows
   for(int i = 2; i <= lookbackBars + 1; i++)
   {
      double priorLow = iLow(symbol, tf, i);

      if(close1 >= priorLow)
         return false;
   }

   return true;
}

Dieser Rückblickzeitraum wird vom Benutzer festgelegt, was Flexibilität hinsichtlich der Strenge der Durchsetzung der Musterdefinition ermöglicht. Die Funktion prüft zunächst, ob es sich bei der Bar um eine Outside-Bar handelt. Anschließend wird überprüft, ob der Schlusskurs unter jedem vorherigen Tiefststand innerhalb des Rückblickzeitraums liegt. Wird ein vorheriges Tief nicht unterschritten, gilt das Muster als widerlegt.

Die Funktion zur Erkennung von Verkaufsumkehrungen spiegelt diese Logik exakt wider, allerdings in umgekehrter Richtung.

//+-------------------------------------------------------------------------------------+
//| Detects a Smash Day Sell Reversal where the close breaks above multiple prior highs |
//+-------------------------------------------------------------------------------------+
bool IsSmashDaySellReversal(string symbol,
                            ENUM_TIMEFRAMES tf,
                            int index,
                            int lookbackBars)
{
   // Bar must not be an outside bar
   if(IsOutsideBar(symbol, tf, index))
      return false;

   double close1 = iClose(symbol, tf, index);

   // Validate close breaks above N prior highs
   for(int i = 2; i <= lookbackBars + 1; i++)
   {
      double priorHigh = iHigh(symbol, tf, i);

      if(close1 <= priorHigh)
         return false;
   }

   return true;
}

Es wird geprüft, ob sich die Smash-Bar oberhalb einer Reihe früherer Höchststände schließt. Diese beiden Funktionen bilden den Kern der Logik zur Signalerzeugung. Jede Handelsentscheidung, die später im EA getroffen wird, lässt sich auf diese Musterprüfer zurückführen.

Aktualisierung und Auswertung von Supertrend-Daten

Da dieser Expert Advisor die Trendfilterung unterstützt, müssen wir die Werte des Supertrend-Indikators abrufen und auswerten. Es wird eine spezielle Funktion eingeführt, um die aktuellen Werte des oberen und unteren Bandes des Supertrend-Indikators mithilfe des bei der Initialisierung erstellten Indikator-Handles abzurufen.

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

Diese Funktion aktualisiert interne Arrays, in denen die aktuellsten Indikatorwerte gespeichert sind. Es wird sofort eine Datenvalidierung durchgeführt, um sicherzustellen, dass genügend Werte abgerufen wurden. Dadurch werden falsche Trendsignale verhindert, die durch unvollständige Indikator-Daten verursacht werden.

Anschließend werden zwei kleine Hilfsfunktionen definiert, um den Supertrend-Status zu interpretieren.

//+------------------------------------------------------------------+
//| Returns true if Supertrend is currently in a bullish trend state |
//+------------------------------------------------------------------+
bool IsSupertrendCurrentlyBullish(){

   if(lowerBandValues[1] != EMPTY_VALUE){
      return true;
   }

   return false;
}

//+------------------------------------------------------------------+
//| Returns true if Supertrend is currently in a bearish trend state |
//+------------------------------------------------------------------+
bool IsSupertrendCurrentlyBearish(){

   if(upperBandValues[1] != EMPTY_VALUE){
      return true;
   }

   return false;
}

Ein Aufwärtstrend wird erkannt, wenn das untere Band aktiv ist, während ein Abwärtstrend erkannt wird, wenn das obere Band aktiv ist. Durch die Trennung von Datenabfrage und -auswertung bleibt die Trendlogik übersichtlich und wiederverwendbar. Anschließend prüft die Einstiegslogik, ob der Trend bullisch oder bärisch ist, ohne dabei zu berücksichtigen, wie diese Entscheidung zustande gekommen ist.

Filterung nach Handelstag der Woche

Larry Williams legte oft großen Wert auf das Timing und den Marktrhythmus. Um das zu berücksichtigen, bietet der EA eine optionale Filterung nach Wochentag an. Eine Hilfsfunktion wandelt einen Datums- und Zeitwert in einen numerischen Wochentag um.

//+------------------------------------------------------------------------------------+
//| Returns the day of the week (0 = Sunday, 6 = Saturday) for the given datetime value|                               
//+------------------------------------------------------------------------------------+
int TimeDayOfWeek(datetime time){
   MqlDateTime timeStruct = {};
   if(!TimeToStruct(time, timeStruct)){
      Print("TimeDayOfWeek: TimeToStruct failed");
      return -1;
   }      
   return timeStruct.day_of_week;
}

Dieser Wert wird anschließend von einer anderen Funktion verwendet, die anhand der Benutzereingabe prüft, ob der Handel zulässig ist.

//+-----------------------------------------------------------------------------------------------------+
//| Determines whether trading is permitted for the given datetime based on the selected trade-day mode |                               
//+-----------------------------------------------------------------------------------------------------+
bool IsTradingDayAllowed(datetime time)
{
   // Baseline mode: no filtering
   if(tradeDayMode == TDW_ALL_DAYS){
      return true;
   }

   int day = TimeDayOfWeek(time);

   switch(day)
   {
      case 0: return tradeSunday;
      case 1: return tradeMonday;
      case 2: return tradeTuesday;
      case 3: return tradeWednesday;
      case 4: return tradeThursday;
      case 5: return tradeFriday;
      case 6: return tradeSaturday;
   }

   return false;
}

Die Filterlogik unterstützt zwei Modi. Der Handel kann entweder an allen Tagen erlaubt sein oder auf ausgewählte Tage beschränkt werden. Dieses Design ermöglicht es, dass ein und derselbe EA sowohl diskretionäre Timing-Präferenzen als auch systematische Tests unterstützt. Diese Logik wurde bewusst isoliert, damit später im Code die Tagesfilterung einheitlich auf alle Einstiegsbedingungen angewendet werden kann.

Erkennen von Preisniveau-Durchbrüchen zur Bestimmung des Einstiegszeitpunkts

Eine der wichtigsten Designentscheidungen in diesem EA besteht darin, den Benutzern die Wahl zu lassen, wie Einstiege ausgelöst werden. Manche Trader ziehen es vor, sofort einzusteigen, sobald der Kurs ein bestimmtes Niveau überschreitet. Andere erfordern eine Bestätigung durch einen Schlusskurs, der deutlich über diesem Niveau liegt. Zur Unterstützung der ersten Option führen wir Funktionen zur Erkennung von Über- und Unterquerungen 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;
}

Diese Funktionen arbeiten mit geschlossenen Daten auf Minutenebene und erkennen, wann der Kurs ein bestimmtes Niveau von unten oder von oben durchbricht. Sie sind einfach, aber präzise, und stellen sicher, dass Einstiege nur einmal pro Überquerungsereignis ausgelöst werden. Diese Funktionen sind direkt in die Einstiegslogik integriert und bleiben inaktiv, solange der Benutzer nicht den Einstiegsmodus beim Niveaudurchbruch auswählt.

Nur eine Position gleichzeitig zulassen

Um eine zu hohe Exponierung zu vermeiden, ist der EA so konzipiert, dass er jeweils nur eine aktive Position hält. Es werden zwei Hilfsfunktionen definiert, um zu prüfen, ob für die angegebene MagicNumber bereits eine Kauf- oder Verkaufsposition besteht.

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

Jede Funktion durchsucht alle offenen Positionen und gibt true zurück, wenn eine passende Position gefunden wird. Bevor eine Position eröffnet wird, werden diese Überprüfungen durchgeführt. Dies gewährleistet eine strenge Positionskontrolle und verhindert ein versehentliches Stapeln von Handelspositionen.

Risikobasierte Positionsgrößenbestimmung und der Gewinnziele

Das Risikomanagement spielt im System eine zentrale Rolle. Eine spezielle Funktion berechnet die Positionsgröße auf der Grundlage eines festen Prozentsatzes des Kontoguthabens.

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

Bei der Berechnung werden die Stop-Loss-Distanz und die Kontraktgröße herangezogen, um ein Volumen zu ermitteln, das dem gewünschten Risiko entspricht. Anschließend werden separate Funktionen definiert, um die Take-Profit-Niveaus für Kauf- und Verkaufspositionen anhand eines konfigurierbaren Risiko-Ertrags-Verhältnisses zu berechnen.

//+--------------------------------------------------------------------------------------------------+
//| Computes the bullish take profit level based on entry price, stop loss, and risk to reward ratio |
//+--------------------------------------------------------------------------------------------------+
double GetBuyTakeProfit(double entryPrice, double stopLoss){
   double riskDistance = entryPrice - stopLoss;
   double rewardDistance = riskDistance * riskRewardRatio;
   rewardDistance = MathAbs(rewardDistance);
   return NormalizeDouble((entryPrice + rewardDistance), Digits());
}

//+--------------------------------------------------------------------------------------------------+
//| Computes the bearish take profit level based on entry price, stop loss, and risk to reward ratio |
//+--------------------------------------------------------------------------------------------------+
double GetSellTakeProfit(double entryPrice, double stopLoss){
   double riskDistance = stopLoss - entryPrice;
   double rewardDistance = riskDistance * riskRewardRatio;
   rewardDistance = MathAbs(rewardDistance);
   return NormalizeDouble((entryPrice - rewardDistance), Digits());
}

Diese Funktionen gewährleisten Konsistenz bei allen Trades und verhindern fest programmierte Gewinnziele. Durch die Auslagerung dieser Berechnungen bleibt der EA flexibel und lässt sich leicht erweitern.

Ausführung von Marktorders

Zwei Ausführungsfunktionen bearbeiten Kauf- und Verkaufsaufträge.

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

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

Jede Funktion unterstützt sowohl die manuelle als auch die automatische Losgrößenbestimmung. Wenn der Automatikmodus aktiviert ist, wird die Positionsgröße dynamisch anhand der zuvor definierten risikobasierten Funktion berechnet. Ausführungsfehler werden übersichtlich protokolliert, um die Fehlersuche und das Testen zu erleichtern. Diese Funktionen bilden den letzten Schritt in der Handelspipeline: Sie nehmen validierte Signale entgegen und führen Trades sicher aus.

Alles in der Tick-Funktion zusammenführen

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

   //--- Retrieve current market prices for trade execution
   askPrice    = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice    = SymbolInfoDouble (_Symbol, SYMBOL_BID);
   currentTime = TimeCurrent();
   
   //--- Get some minutes data
   if(CopyClose(_Symbol, PERIOD_M1, 0, 7, closePriceMinutesData) == -1){
      Print("Error while copying minutes datas ", GetLastError());
      return;
   }
   
   //--- Update Supertrend
   UpdateSupertrendBandValues();
   
   //--- Execute this block on new bar formation
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
      
      //---
      if(!smashState.hasBuySmashSetup && !smashState.hasSellSmashSetup){
         if(IsSmashDayBuyReversal(_Symbol, timeframe, 1, smashBuyLookbackBars)){
            smashState.hasBuySmashSetup = true;
            smashState.buyBreakoutLevel = iHigh(_Symbol, timeframe, 1);
            smashState.smashBarTime     = iTime(_Symbol, timeframe, 1);
            smashState.entryPending     = true;
            smashState.buyStopLoss      = iLow(_Symbol, timeframe, 1);
         }
               
         //---
         if(IsSmashDaySellReversal(_Symbol, timeframe, 1, smashSellLookbackBars)){
            smashState.hasSellSmashSetup = true;
            smashState.sellBreakoutLevel = iLow(_Symbol, timeframe, 1);
            smashState.smashBarTime      = iTime(_Symbol, timeframe, 1);
            smashState.entryPending      = true;
            smashState.sellStopLoss      = iHigh(_Symbol, timeframe, 1);
         }
      }else{
      
         smashState.barsSinceSmash = smashState.barsSinceSmash + 1;
      
         if(smashState.barsSinceSmash > smashSetupValidityBars){
            ZeroMemory(smashState);
         }
      
      }
      
      //---
      if(smashState.hasBuySmashSetup && smashState.entryPending){
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            if(smashTradeMode == SMASH_TRADE_BUY_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
               if(askPrice > smashState.buyBreakoutLevel){
                  if(useSupertrendFilter){
                     if(IsSupertrendCurrentlyBullish()){
                        if(tradeDayMode == TDW_SELECTED_DAYS){
                           if(IsTradingDayAllowed(currentTime)){
                              OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                              ZeroMemory(smashState);
                           }
                        }else{
                           OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }
                  }else{
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }else{
                        OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                        ZeroMemory(smashState);
                     }
                  }
               }
            }
         }
      }
      
      //---
      if(smashState.hasSellSmashSetup && smashState.entryPending){
         if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
            if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
               if(bidPrice < smashState.sellBreakoutLevel){
                  if(useSupertrendFilter){
                     if(IsSupertrendCurrentlyBearish()){
                        if(tradeDayMode == TDW_SELECTED_DAYS){
                           if(IsTradingDayAllowed(currentTime)){
                              OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                              ZeroMemory(smashState);
                           }
                        }else{
                              OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                              ZeroMemory(smashState);
                        }
                     }
                  }else{
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }else{
                        OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                        ZeroMemory(smashState);
                     }
                  }
               }
            }
         }
      }     
   }
   
   //---
   if(smashEntryMode == ENTRY_ON_LEVEL_CROSS){
   
      //---
      if(smashState.hasBuySmashSetup && smashState.entryPending){
         // ---
         if(IsCrossOver(smashState.buyBreakoutLevel, closePriceMinutesData)){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_BUY_ONLY  || smashTradeMode == SMASH_TRADE_BOTH){
                  if(useSupertrendFilter){
                     if(IsSupertrendCurrentlyBullish()){
                        if(tradeDayMode == TDW_SELECTED_DAYS){
                           if(IsTradingDayAllowed(currentTime)){
                              OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                              ZeroMemory(smashState);
                           }
                        }else{
                           OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }
                  }else{
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }else{
                        OpenBuy(askPrice, smashState.buyStopLoss, GetBuyTakeProfit(askPrice, smashState.buyStopLoss), positionSize);
                        ZeroMemory(smashState);
                     }
                  }
               }
            }
         }
      }
      
      //---
      if(smashState.hasSellSmashSetup && smashState.entryPending){
         // ---
         if(IsCrossUnder(smashState.sellBreakoutLevel, closePriceMinutesData)){
            if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){
               if(smashTradeMode == SMASH_TRADE_SELL_ONLY || smashTradeMode == SMASH_TRADE_BOTH){
                  if(useSupertrendFilter){
                     if(IsSupertrendCurrentlyBearish()){
                        if(tradeDayMode == TDW_SELECTED_DAYS){
                           if(IsTradingDayAllowed(currentTime)){
                              OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                              ZeroMemory(smashState);
                           }
                        }else{
                           OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }
                  }else{
                     if(tradeDayMode == TDW_SELECTED_DAYS){
                        if(IsTradingDayAllowed(currentTime)){
                           OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                           ZeroMemory(smashState);
                        }
                     }else{
                        OpenSel(bidPrice, smashState.sellStopLoss, GetSellTakeProfit(bidPrice, smashState.sellStopLoss), positionSize);
                        ZeroMemory(smashState);
                     }
                  }
               }
            }
         }
      }   
   }   
}

Zum jetzigen Zeitpunkt sind alle erforderlichen Bausteine vorhanden. Die Tick-Funktion wurde nun erweitert, um die gesamte Strategie zu steuern.

Dadurch lassen sich Smash-Bars und das Kursverhalten während des Testens leichter beobachten. Die Funktion wird bei der Initialisierung des Expert Advisors aufgerufen, um bei jedem Start des EA ein einheitliches Erscheinungsbild zu gewährleisten.

Abschluss der Entwicklungsphase

Nun ist der Expert Advisor vollständig erstellt und bereit für den Test. Der vollständige Quellcode ist als lwSmashDayReversalExpert.mq5 beigefügt, sodass die Leser die endgültige Umsetzung einsehen können, ohne dass der Artikel durch eine vollständige Code-Auflistung überladen wird. Damit ist die Entwicklungsphase abgeschlossen. Jede neu eingeführte Funktion erfüllt einen bestimmten Zweck und fügt sich logisch in das übrige System ein.


Testen der Strategie anhand historischer Daten

Um zu beurteilen, wie sich der Expert Advisor unter realen Marktbedingungen verhält, wurde ein Backtest unter Verwendung historischer Kursdaten durchgeführt. Diese Phase ist wichtig, da sie es uns ermöglicht, Leistungsmerkmale wie Rentabilität, Beständigkeit und Drawdown ohne jegliche subjektive Interpretation zu beobachten.

Der Test wurde mit Gold unter Verwendung des Symbols XAUUSD durchgeführt. Der Tages-Zeitrahmen wurde gewählt, um der ursprünglichen Smash Day-Logik, wie sie von Larry Williams beschrieben wurde, treu zu bleiben. Der Testzeitraum erstreckte sich über ein volles Kalenderjahr, vom 1. Januar 2025 bis zum 30. Dezember 2025. Dies liefert einen umfassenden Überblick über die Marktbedingungen innerhalb eines einzigen Jahres.

Bei diesem Durchlauf waren ausschließlich Smash Day Buy-Setups aktiviert. Das bedeutet, dass das System ausschließlich auf Long-Positionen beschränkt war, wodurch wir das Verhalten an bullisch Smash Days isoliert und bewertet konnten, ohne dass Short-Positionen dieses Bild verfälschten. Die Positionsgröße wurde auf den automatischen Modus eingestellt, wobei bei jedem Trade genau 1,0 Prozent des Kontoguthabens riskiert wurde.

Testbericht

Der anfängliche Kontostand wurde auf 10.000 Dollar festgelegt. Am Ende des Testzeitraums erzielte das System einen Nettogewinn von insgesamt 832,97 Dollar, was einer Kapitalrendite von knapp über 8 % entspricht. Die verzeichnete Gewinnquote betrug 75,00 Prozent. Auch wenn die Gewinnsumme bescheiden erscheinen mag, gibt die Kapitalentwicklung einen aussagekräftigeren Einblick.

Sie verläuft über den gesamten Testzeitraum hinweg gleichmäßig und weist weder starke Rückgänge noch instabile Schwankungen des Eigenkapitals auf, was auf ein kontrolliertes Risiko und eine stabile Ausführung hindeutet. In diesem Abschnitt ist ein Screenshot der Wertentwicklungskurve enthalten, um dieses Verhalten visuell zu veranschaulichen und die Ergebnisse auf einen Blick leichter interpretierbar zu machen.

Kapitalkurven

Um die Nachbildung zu vereinfachen, wurden diesem Artikel zwei Dateien beigefügt. Die erste Datei enthält die Konfiguration der Testumgebung, während die zweite Datei den genauen Satz von Eingabeparametern enthält, der bei diesem Test verwendet wurde. Mit diesen Dateien lassen sich die gleichen Testbedingungen eindeutig reproduzieren.

Dieser Test bezieht sich lediglich auf eine Konfiguration und einen Markt. Der Expert Advisor wurde bewusst so konzipiert, dass er durch seine Eingabeparameter flexibel ist. Verschiedene Rückblickzeiträume, Einstiegsstrategien, Risikoeinstellungen und Filter lassen sich anpassen, um alternative Ideen zu prüfen. Durch die Durchführung weiterer Experimente mit anderen Symbolen oder Zeiträumen lassen sich möglicherweise Verhaltensmuster aufdecken, die zu unterschiedlichen Handelsstilen oder Risikopräferenzen passen.

Alle Erkenntnisse, Verbesserungen oder Beobachtungen, die im Rahmen unabhängiger Tests gewonnen werden, sind wertvoll. Wenn solche Erkenntnisse in den Kommentaren zum Artikel geteilt werden, trägt dies dazu bei, die Forschung über diese einzelne Umsetzung hinaus auszuweiten, und macht das System zu einem Instrument des kollaborativen Lernens statt zu einer feststehenden Lösung.


Schlussfolgerung

Dieser Artikel sollte ein bestimmtes Problem lösen. Der Handel mit Smash Day-Mustern ohne Kontext führt zu unzuverlässigen Signalen und uneinheitlichen Ergebnissen. Das Ziel bestand darin, das Muster in ein strukturiertes, testbares und für die Automatisierung geeignetes System umzuwandeln.

Dieses Ziel wurde erreicht. Der Artikel enthält einen vollständigen Satz objektiver Smash Day-Regeln, darunter Mustererkennung, Bestätigungslogik, Gültigkeitsgrenzen für Setups und kontrolliertes Einstiegsverhalten. Optionale Kontextfilter, wie beispielsweise die Supertrend-Richtung und die Auswahl des Handelstages der Woche, sind direkt in das System integriert und können über Eingabeparameter aktiviert oder deaktiviert werden.

Praktisch gesehen erhält der Leser am Ende einen voll funktionsfähigen MQL5-Expert Advisor, der Smash Day-Umkehrungen genau wie definiert automatisiert. Der EA führt jeweils nur eine Position aus, wendet ein einheitliches Risikomanagement an und legt alle Annahmen über konfigurierbare Eingabewerte offen. Die beigefügten Parameter- und Konfigurationsdateien ermöglichen es, die Ergebnisse zu reproduzieren und weiter zu testen, ohne den Quellcode zu ändern.

Für algorithmische Trader bietet dies ein einsatzbereites Analysewerkzeug, mit dem sich das Verhalten an Smash Days unter verschiedenen Bedingungen untersuchen lässt. Für MQL5-Entwickler zeigt dies, wie subjektiv erkannte Kursmuster in eine klare, testbare Logik umgesetzt werden können. Vor allem aber hält der Artikel, was er zu Beginn versprochen hat: einen klaren Weg von einem isolierten Muster hin zu einem objektiven, kontextbezogenen Handelssystem, das zuverlässig getestet und bewertet werden kann.


Anlagen

Dateiname Beschreibung
lwSmashDayTrendFilteredExpert.mq5 Der in diesem Artikel entwickelte EA
configurations.ini Um die Konfiguration der Strategie-Tester-Umgebung für den in diesem Artikel beschriebenen Testlauf zu erleichtern
parameters.set Um die Konfiguration der Benutzereingabevariablen für den in diesem Artikel verwendeten Test zu erleichtern


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

Erstellen benutzerdefinierter Indikatoren in MQL5 (Teil 8): Integration von Volumendaten für eine tiefere Marktprofilanalyse Erstellen benutzerdefinierter Indikatoren in MQL5 (Teil 8): Integration von Volumendaten für eine tiefere Marktprofilanalyse
In diesem Artikel erweitern wir den hybriden Marktprofil-Indikator Time Price Opportunity (TPO) in MQL5 durch die Einbindung von Volumendaten, um volumenbasierte Kontrollpunkte, Wertbereiche und den volumengewichteten Durchschnittspreis mit anpassbaren Hervorhebungsoptionen zu berechnen. Das System bietet erweiterte Funktionen wie die Erkennung der Initial Balance, Projektionslinien wichtiger Kursniveaus, Split-Profile sowie alternative TPO-Symbole wie Quadrate oder Kreise, um die visuelle Analyse über mehrere Zeitrahmen hinweg zu verbessern.
Behebung von Barrierefreiheitsproblemen bei MQL5-Handelswerkzeugen (Teil II): Einen Expert Advisor per Pythons Text-to-Speech sprachfähig machen Behebung von Barrierefreiheitsproblemen bei MQL5-Handelswerkzeugen (Teil II): Einen Expert Advisor per Pythons Text-to-Speech sprachfähig machen
Lassen Sie uns besprechen, wie wir unsere Expert Advisors mithilfe von Text-to-Speech-Technologie sprachfähig machen können, indem wir Python und MQL5 kombinieren. Nachdem Sie diesen Artikel gelesen haben, verfügen Sie über ein funktionierendes Beispiel für einen EA, der dynamische Marktinformationen ausgibt. Sie werden den Einsatz von TTS und der WebRequest-Funktion beherrschen und lernen, wie sich Python-Bibliotheken in die Sprache MQL5 integrieren lassen, um ein wirklich sprachunterstütztes Trading-Tool zu entwickeln.
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.
Erstellen benutzerdefinierter Indikatoren in MQL5 (Teil 7): Hybride Time-Price-Opportunity-(TPO)-Marktprofile für die Analyse von Handelssitzungen Erstellen benutzerdefinierter Indikatoren in MQL5 (Teil 7): Hybride Time-Price-Opportunity-(TPO)-Marktprofile für die Analyse von Handelssitzungen
In diesem Artikel entwickeln wir einen benutzerdefinierten Indikator in MQL5 für hybride Time Price Opportunity (TPO)-Marktprofile, der mehrere Zeitrahmen wie Intraday, täglich, wöchentlich, monatlich sowie feste Zeitintervalle mit Zeitzonenanpassungen unterstützt. Der Indikator unterteilt die Kurse in ein Raster, erfasst Sitzungsdaten wie Höchst-, Tiefst-, Eröffnungs- und Schlusskurse und berechnet wichtige Kennzahlen wie den Point of Control und den Wertebereich auf der Grundlage von TPO-Zählwerten. Der Indikator stellt Profile visuell im Chart dar, wobei die Farben für TPO-Buchstaben, Single Prints, Wertebereiche, POC und Abschlussmarkierungen individuell angepasst werden können, was eine detaillierte Analyse von Handelssitzungen ermöglicht.