Larry Williams‘ Geheimnisse des Marktes (Teil 8): Kombination von Volatilitäts-, Struktur- und Zeitfiltern
Einführung
Die meisten kurzfristigen Handelssysteme scheitern aus einem einfachen Grund. Sie behandeln alle Marktbedingungen, jeden Tag und jede Stunde so, als ob sie gleich wären. Die Eingaben werden ohne Kontext ausgelöst. Stopps werden ohne Struktur gesetzt. Die Ausgänge werden ohne Logik gewählt. Das Ergebnis ist eine mechanische Strategie, die auf den Preis reagiert, ihn aber nie wirklich versteht.
Larry Williams hat den Markt aus einem anderen Blickwinkel betrachtet. Er hat nicht mit Indikatoren oder starren Regeln begonnen. Er begann mit dem Verhalten. Wie sich der Preis entwickelt. Wie die Volatilität den Trends vorausgeht. Wie emotionale Extreme Chancen schaffen, wie die Zeit selbst die Marktbewegung beeinflusst. In seiner Arbeit geht es nicht um ein einzelnes System. Es geht darum, eine Handelslogik zu entwickeln, die sich auf das stützt, was die Märkte tatsächlich tun.
In diesem Artikel fassen wir einige der Kernideen von Larry Williams in einem einzigen, überprüfbaren Handelsmodell zusammen. Wir kombinieren kurzfristige Marktstrukturen mit volatilitätsbasierten Eröffnungen. Wir fügen zeitbasierte Filter hinzu, die reale Marktverzerrungen widerspiegeln. Wir führen eine flexible Platzierung der Stopps und adaptive Gewinnmitnahmen ein. Ferner verpacken wir alles in einen vollständig konfigurierbaren Expert Advisor, der untersucht, getestet, verändert und erweitert werden kann.
Dies ist keine weitere starre Strategievorlage. Es ist ein Rahmen. Eine Möglichkeit, über Einstieg, Ausstieg, Risiko und Timing als zusammenhängende Komponenten und nicht als isolierte Regeln nachzudenken. Jeder zentrale Entscheidungspunkt wird als Nutzeroption angezeigt. Jedes Konzept ist als Logik implementiert, die durch Tests validiert werden kann, nicht durch Meinungen.
Unser Ziel ist einfach. Um zu zeigen, wie professionelle Handelsideen in saubere, strukturierte MQL5-Logik umgesetzt werden können. Außerdem soll eine Grundlage geschaffen werden, die sich weiterentwickeln kann, wenn tiefere Filter, bessere Ausstiegsmöglichkeiten und neue Forschungsideen hinzukommen.
Überblick über die Strategie
Dieses Handelsmodell beruht auf einer einfachen Idee. Trends beginnen mit Volatilität. Die Volatilität liefert Eröffnungen. Filter verbessern die Handelsqualität. Selektivität verbessert das Überleben.
Anstatt auf zufällige Kursbewegungen zu reagieren, wartet der EA auf eine offensichtliche Veränderung der Marktstruktur. Eine Aufwärtskonstellation entsteht, wenn drei aufeinanderfolgende Bars ein kurzfristiges Swing-Tief bilden. Eine Abwärtskonstellation entsteht, wenn drei aufeinanderfolgende Bars ein kurzfristiges Swing-Hoch bilden. Diese Swing-Punkte stellen Bereiche dar, in denen sich der Kurs nicht weiter in eine Richtung bewegen konnte und zu rotieren begonnen hat. Sie bilden den strukturellen Rahmen für die nächste mögliche Erweiterung.
Sobald ein gültiges Swing-Signal bei einem neu geöffneten Bar erscheint, steigt der EA nicht sofort ein. Stattdessen wird anhand eines von zwei Volatilitätsmodellen ein Ausbruchsniveau für den Einstieg prognostiziert. Das erste Modell misst die Range der zuvor fertiggestellten Bar und verwendet diese als Working-Range. Das zweite Modell verwendet die Swing-basierte Volatilitätstechnik von Larry Williams, die zwei historische Swing-Distanzen vergleicht und die größere als Working-Range auswählt. Das gewählte Eingabemodell wird durch Nutzereingaben gesteuert.
Aus diesem Working-Range berechnet der EA die voraussichtlichen Kauf- und Verkaufsniveaus. Diese Werte dienen als Preisschwellen. Ein Handel wird nur dann eröffnet, wenn der Kurs über die Kauf- oder unter die Verkaufsschwelle steigt. Erreicht der Kurs während des laufenden Handelszeitraums keines der beiden Niveaus, wird die Konstellation verworfen. Nachmeldungen sind nicht zulässig. Jeder Handel muss sowohl durch die Struktur als auch durch die Volatilitätsausweitung ausgelöst werden.
Die Platzierung des Stop-Loss folgt der gleichen Logik von Flexibilität und Strukturbewusstsein. Der EA unterstützt zwei Modi der Haltestellenplatzierung. Im ersten Modus wird der Stop-Loss als Prozentsatz der Handelsspanne berechnet und relativ zum Einstiegskurs platziert. Im zweiten Modus wird der Stop-Loss am Extremum des Swings platziert. Bei einem bullischen Trade ist dies der Tiefpunkt der mittleren Swing-Bar. Bei einem bärischen Trade ist dies das Hoch der mittleren Swing-Bar. Das ausgewählte Stoppmodell wird durch Nutzereingaben gesteuert.
Die Gewinnmitnahme kann auch als Komponente konfiguriert werden. Der EA unterstützt drei Ausstiegsmodi. Der Erste beendet den Trade bei der ersten gewinnbringenden Eröffnung ab. Der Zweite beendet den Trade nach Ablauf einer vom Nutzer festgelegten Anzahl von Bars. Mit dem Dritten wird ein Take-Profit-Level auf der Grundlage des Risiko-Ertrags-Verhältnisses festgelegt, wobei der Abstand zwischen dem Einstieg und dem Stop-Loss verwendet wird. Mit diesen Ausstiegsmodellen kann dieselbe Strategielogik unter sehr unterschiedlichen Handelsmanagementstilen getestet werden.
Die zeitbasierte Filterung wird als optionale Ebene angewendet. Der Handel kann nach dem Handelstag der Woche oder nach der Tageszeit eingeschränkt werden. Auf diese Weise kann der Nutzer untersuchen, wie sich das Marktverhalten über verschiedene Zeitsegmente hinweg verändert, und Zeiträume isolieren, die qualitativ hochwertigere Signale liefern. Diese Filter können unabhängig voneinander ein- oder ausgeschaltet werden und werden angewendet, bevor ein Handel ausgeführt wird.
Das Risikomanagement ist direkt in die Erfassungslogik integriert. Der EA unterstützt sowohl manuelle als auch automatische Positionsgrößen, die auf einem festen Prozentsatz des Kontostands basieren. Im automatischen Modus wird die Positionsgröße anhand der Stop-Loss-Distanz berechnet, um ein gleichmäßiges Risiko bei allen Geschäften zu gewährleisten.
Es ist jeweils nur eine Position zulässig. Diese Regel vereinfacht das Handelsmanagement und stellt sicher, dass jede Konstellation unabhängig bewertet wird. Der EA kann so konfiguriert werden, dass er nur kauft, nur verkauft oder beide Richtungen handelt.
Was dieses Modell von typischen Breakout-Systemen unterscheidet, ist die Reihenfolge der Logik. Eröffnungen werden nicht allein durch die Volatilität ausgelöst. Sie werden erstens durch die Struktur und zweitens durch die Volatilität ausgelöst. Es werden keine Filter nachträglich installiert. Sie werden als Kernbestandteile behandelt, die die Qualität des Handels prägen. Ein Ende der Trades ist nicht festgelegt. Sie sind anpassungsfähig und überprüfbar.
Das Hauptziel dieses EA ist es nicht, eine einzige optimierte Strategie zu entwickeln. Es soll einen strukturierten, flexiblen Rahmen bieten, um zu testen, wie Volatilität, Struktur und Zeit zusammenwirken. Jeder zentrale Entscheidungspunkt ist für den Nutzer konfigurierbar. Jedes Konzept wird als logisches Konstrukt dargestellt, das verändert und untersucht werden kann. Dies macht den EA nicht nur zu einem Handelswerkzeug, sondern auch zu einer Forschungsplattform für die Entwicklung und Validierung professioneller kurzfristiger Handelsideen.
Aufbau des Expert Advisors Schritt für Schritt
Bevor wir mit dem Schreiben von Code beginnen, müssen wir uns darüber im Klaren sein, welches Hintergrundwissen erforderlich ist, um diesen Abschnitt richtig zu verstehen.
Zunächst setzen wir grundlegende Kenntnisse der Programmiersprache MQL5 voraus. Konzepte wie Variablen, Funktionen, bedingte Anweisungen, Schleifen, Enumerationen, Strukturen und die Verwendung von Standardbibliotheken sollten bereits bekannt sein. Wenn Sie mit diesen Konzepten noch nicht vertraut sind, ist die offizielle MQL5-Referenz ein guter Ausgangspunkt, bevor Sie fortfahren.
Zweitens setzen wir voraus, dass Sie bereits Erfahrung mit der Plattform MetaTrader 5 haben. Dazu gehören die grundlegende Navigation auf der Nutzeroberfläche, das Öffnen von Charts, das Anhängen von Expert Advisors und die Durchführung von Backtests im Strategy Tester.
Drittens setzen wir eine gute Kenntnis des MetaEditors voraus. Wir sollten in der Lage sein, neue Quelldateien zu erstellen, Code zu schreiben, zu kompilieren und Fehler zu überprüfen, wenn sie auftreten.
Dieser Abschnitt ist als praktische Übung konzipiert. Programmieren lernt man am besten, indem man es tut, nicht indem man es nur liest. Um dies zu unterstützen, ist die vollständige Quelldatei lwVolatilityStructureTimeFilterExpert.mq5 diesem Artikel beigefügt. Wir empfehlen Ihnen, die Datei herunterzuladen und in einem anderen Tab zu öffnen, damit Sie sie als Referenz verwenden können, während wir die gleiche Logik Schritt für Schritt aufbauen.
Schaffung der Grundlage des Expert Advisors
Wir beginnen damit, den MetaEditor zu öffnen und eine neue leere Expert Advisor-Quelldatei zu erstellen. Der Name kann beliebig sein, aber aus Gründen der Klarheit und Konsistenz werden wir denselben Namen wie in der angehängten Datei verwenden. Anschließend fügen wir den folgenden Standardcode in die neue Datei ein.
//+------------------------------------------------------------------+ //| lwVolatilityStructureTimeFilterExpert.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //+------------------------------------------------------------------+ //| Standard Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Custom Enumerations | //+------------------------------------------------------------------+ enum ENUM_TRADE_DIRECTION { ONLY_LONG, ONLY_SHORT, TRADE_BOTH }; enum ENUM_VOLATILITY_ENTRY_MODE { VOL_SIMPLE_PREVIOUS_RANGE, VOL_SWING_BASED }; enum ENUM_STOP_LOSS_MODE { SL_BY_RANGE_PERCENT, SL_AT_SWING_EXTREME }; enum ENUM_TAKE_PROFIT_MODE { TP_FIRST_PROFITABLE_OPEN, TP_AFTER_N_CANDLES, TP_BY_RISK_REWARD }; enum ENUM_TDW_MODE { TDW_ALL_DAYS, TDW_SELECTED_DAYS }; 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 "Volatility Breakout Parameters" input ENUM_VOLATILITY_ENTRY_MODE volatilityEntryMode = VOL_SIMPLE_PREVIOUS_RANGE; input double inpBuyRangeMultiplier = 0.50; input double inpSellRangeMultiplier = 0.50; input double inpStopRangeMultiplier = 0.50; input group "TDW filter" 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 "Time of Day filter" input bool useTimeFilter = false; input double startTime = 9.30; input double endTime = 16.00; input group "Trade & Risk Management" input ENUM_TRADE_DIRECTION direction = ONLY_LONG; input ENUM_STOP_LOSS_MODE stopLossMode = SL_BY_RANGE_PERCENT; input ENUM_TAKE_PROFIT_MODE takeProfitMode = TP_BY_RISK_REWARD; input double riskRewardRatio = 3.0; input int exitAfterCandles = 3; input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO; input double riskPerTradePercent = 1.0; input double positionSize = 0.1; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Create a CTrade object to handle trading operations CTrade Trade; //--- Bid and Ask double askPrice; double bidPrice; datetime currentTime; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Assign a unique magic number to identify trades opened by this EA Trade.SetExpertMagicNumber(magicNumber); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Notify why the program stopped running Print("Program terminated! Reason code: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- Retrieve current market prices for trade execution askPrice = SymbolInfoDouble (_Symbol, SYMBOL_ASK); bidPrice = SymbolInfoDouble (_Symbol, SYMBOL_BID); currentTime = TimeCurrent(); } //--- UTILITY FUNCTIONS //+------------------------------------------------------------------+
Diese Grundlage definiert die Identität des Expert Advisors. Sie enthält die Standard-Handelsbibliothek, deklariert nutzerdefinierte Aufzählungen für alle konfigurierbaren Modi und stellt Nutzereingabeparameter bereit, die Volatilitätslogik, Filter, Risikomanagement und Ausführungsverhalten steuern.
Zu diesem Zeitpunkt wird noch nichts gehandelt. Wir haben nur definiert, was der EA tun kann, nicht was er tun wird. Durch die folgenden Schritte erhält dieses Skelett schrittweise eine echte Entscheidungsfähigkeit.
Erkennen des Öffnens einer neuen Bar
Die Logik unserer Strategie basiert auf der Eröffnung einer neuen Bar innerhalb des ausgewählten Zeitrahmens. Dies ist unerlässlich, da Swing-Punkte, Volatilitätsbereiche und projizierte Einstiegsniveaus erst nach Abschluss einer vollständigen Bar neu berechnet werden sollten.
Um dieses Verhalten zu unterstützen, 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 ermittelt die Öffnungszeit der letzten Bar und vergleicht sie mit einem gespeicherten Wert. Wenn sich der Zeitstempel ändert, hat sich eine neue Bar gebildet. Der gespeicherte Wert wird per Referenz aktualisiert, damit der nächste Aufruf den nächsten Bar-Wechsel erkennen kann. Um diese Logik zu unterstützen, deklarieren wir eine globale Variable.
//--- To help track new bar open datetime lastBarOpenTime;
Diese Variable enthält die letzte bekannte Eröffnungszeit der Bar und wird während der Experteninitialisierung auf 0 initialisiert.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Initialize global variables lastBarOpenTime = 0; return(INIT_SUCCEEDED); }
Von diesem Zeitpunkt an kann jede Logik, die nur einmal pro Bar ausgeführt werden soll, sicher in diese Bedingung eingeschlossen werden.
Erstellen eines Containers für Volatilitätsniveaus
Eine der wichtigsten Aktionen bei der Eröffnung einer neuen Bar ist die Berechnung der geplanten Einstiegs-, Stop-Loss- und Take-Profit-Niveaus. Um diese Werte zu organisieren und im gesamten EA wiederzuverwenden, definieren wir eine Struktur für sie.
//--- Holds all price levels derived from Larry Williams' volatility breakout calculations struct MqlLwVolatilityLevels { double range; double buyEntryPrice; double sellEntryPrice; double bullishStopLoss; double bearishStopLoss; double bullishTakeProfit; double bearishTakeProfit; double bullishStopDistance; double bearishStopDistance; };Jedes Feld steht für eine bestimmte Komponente der Volatilitätslogik von Larry Williams.
- Das Feld range enthält den Arbeitsvolatilitätsbereich, der für Hochrechnungen verwendet wird.
- Die Felder buyEntryPrice und sellEntryPrice enthalten Ausbruchsniveaus.
- In den Stop-Loss-Feldern werden die voraussichtlichen Schutzniveaus gespeichert.
- In den Take-Profit-Feldern werden projizierte Ziele gespeichert, wenn der Risiko-Ertrags-Modus aktiv ist.
- In den Feldern für die Stoppdistanz wird die Risikodistanz gespeichert, die für die dynamische Positionsberechnung verwendet wird.
Wir erstellen dann eine einzelne Instanz dieser Struktur direkt unter ihrer Definition.
MqlLwVolatilityLevels lwVolatilityLevels;
Diese Instanz dient als gemeinsamer Speicher für alle Funktionen. Es ermöglicht uns, die Levels einmal pro Bar zu berechnen und sie konsistent für die Einstiegs-, Risiko- und Ausstiegslogik wiederzuverwenden.
Bei der Experteninitialisierung werden alle Felder auf Null gesetzt.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- Reset Larry Williams' volatility levels ZeroMemory(lwVolatilityLevels); return(INIT_SUCCEEDED); }Dadurch wird sichergestellt, dass keine veralteten Werte aus früheren Läufen übrig bleiben.
Aufspüren von Larry Williams‘ kurzfristigen Swing-Punkten
Unsere strategische Logik beginnt mit der Struktur. Ein Aufwärtssignal ist nur dann gültig, wenn sich ein kurzfristiges Swing-Tief bildet. Ein Abwärtssignal ist nur dann gültig, wenn sich ein kurzfristiges Swing-Hoch bildet.
Um ein kurzfristiges Swing-Hoch zu erkennen, definieren wir:
//+------------------------------------------------------------------+ //| Detects a Larry Williams short-term high on the last three bars | //| Bar index 2 must be a swing high with lower highs on both sides | //| Bar 2 must NOT be an outside bar | //| Bar 1 must NOT be an inside bar | //+------------------------------------------------------------------+ bool IsLarryWilliamsShortTermHigh(string symbol, ENUM_TIMEFRAMES tf){ //--- Price data for the three bars double high1 = iHigh(symbol, tf, 1); double low1 = iLow (symbol, tf, 1); double high2 = iHigh(symbol, tf, 2); double low2 = iLow (symbol, tf, 2); double high3 = iHigh(symbol, tf, 3); double low3 = iLow (symbol, tf, 3); //--- Condition 1: Bar 2 must be a swing high bool isSwingHigh = (high2 > high1) && (high2 > high3); if(!isSwingHigh){ return false; } //--- Condition 2: Bar 2 must NOT be an outside bar relative to bar 3 bool isOutsideBar = (high2 > high3) && (low2 < low3); if(isOutsideBar){ return false; } //--- Condition 3: Bar 1 must NOT be an inside bar relative to bar 2 bool isInsideBar = (high1 < high2) && (low1 > low2); if(isInsideBar){ return false; } return true; }
Mit dieser Funktion werden die Höchst- und Tiefstwerte der letzten drei abgeschlossenen Bars abgerufen. Anschließend werden drei strukturelle Bedingungen geprüft. Erstens muss der zweite Bar-Index ein Swing-High sein. Ihr Hoch muss höher sein als die beiden benachbarten Bars. Zweitens darf der Bar-Index zwei im Verhältnis zum Bar-Index drei kein „Outside“-Bar sein. Dadurch werden instabile oder zu große Bars vermieden. Drittens darf die Bar mit Index eins im Verhältnis zu der mit Index zwei keine „Inside“-Bar sein. Dadurch werden komprimierte Fortsetzungsmuster vermieden, die keinen sauberen Swing darstellen. Nur wenn alle drei Bedingungen erfüllt sind, geben wir true zurück.
Die Logik eines kurzfristigen Swing-Lows spiegelt diese Struktur wider.
//+------------------------------------------------------------------+ //| Detects a Larry Williams short-term low on the last three bars | //| Bar index 2 must be a swing low with higher lows on both sides | //| Bar 2 must NOT be an outside bar | //| Bar 1 must NOT be an inside bar | //+------------------------------------------------------------------+ bool IsLarryWilliamsShortTermLow(string symbol, ENUM_TIMEFRAMES tf){ //--- Price data for the three bars double high1 = iHigh(symbol, tf, 1); double low1 = iLow (symbol, tf, 1); double high2 = iHigh(symbol, tf, 2); double low2 = iLow (symbol, tf, 2); double high3 = iHigh(symbol, tf, 3); double low3 = iLow (symbol, tf, 3); //--- Condition 1: Bar 2 must be a swing low bool isSwingLow = (low2 < low1) && (low2 < low3); if(!isSwingLow){ return false; } //--- Condition 2: Bar 2 must NOT be an outside bar relative to bar 3 bool isOutsideBar = (high2 > high3) && (low2 < low3); if(isOutsideBar){ return false; } //--- Condition 3: Bar 1 must NOT be an inside bar relative to bar 2 bool isInsideBar = (high1 < high2) && (low1 > low2); if(isInsideBar){ return false; } return true; }
In diesem Fall muss die Bar des zweiten Barindex ein Tiefpunkt sein. Ihr Tiefstwert muss niedriger sein als der beider benachbarter Bars. Es gelten die gleichen Ausschlüsse für die „Outside“- und „Inside“-Bar. Diese beiden Funktionen definieren, wann ein gültiges Marktstruktursignal vorliegt. Alle weiteren Eröffnungen hängen davon ab, dass diese Bedingungen zuerst erfüllt werden.
Messung der Volatilität mit zwei Larry-Williams-Modellen
Sobald ein strukturelles Signal vorliegt, müssen wir die Volatilität messen, um Einstiegsniveaus zu planen. Wir unterstützen zwei Volatilitätsmodelle. Das erste Modell ist die Swing-basierte Volatilität.
//+------------------------------------------------------------------+ //| Calculates Larry Williams swing-based volatility range | //+------------------------------------------------------------------+ double CalculateLwSwingVolatilityRange(const string symbol, ENUM_TIMEFRAMES tf){ //--- Retrieve required highs and lows double high_3_days_ago = iHigh(symbol, tf, 4); double low_yesterday = iLow (symbol, tf, 1); double high_1_day_ago = iHigh(symbol, tf, 2); double low_3_days_ago = iLow (symbol, tf, 4); //--- Validate data if(high_3_days_ago == 0.0 || low_yesterday == 0.0 || high_1_day_ago == 0.0 || low_3_days_ago == 0.0) { return 0.0; } //--- Calculate swing distances using absolute values double swingRangeA = MathAbs(high_3_days_ago - low_yesterday); double swingRangeB = MathAbs(high_1_day_ago - low_3_days_ago); //--- Select the dominant swing double usableRange = MathMax(swingRangeA, swingRangeB); //--- Normalize for symbol precision return NormalizeDouble(usableRange, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); }
Diese Funktion misst zwei historische Swing-Abstände. Der erste Abstand liegt zwischen dem Höchststand vor drei Tagen und dem gestrigen Tiefststand. Der zweite Abstand ist der zwischen dem Höchststand vor einem Tag und dem Tiefststand vor drei Tagen. Wir nehmen den absoluten Wert der beiden Abstände und wählen den größeren aus. Auf diese Weise wird sichergestellt, dass wir immer die vorherrschende aktuelle Ausdehnung verwenden, unabhängig von der Richtung. Der gewählte Bereich wird zum Maßstab für unsere Arbeitsvolatilität. Das zweite Modell ist die einfache Volatilität der vorherigen Spanne.
//+------------------------------------------------------------------+ //| Returns the price range (high - low) of a bar at the given index | //+------------------------------------------------------------------+ double GetBarRange(const string symbol, ENUM_TIMEFRAMES tf, int index){ double high = iHigh(symbol, tf, index); double low = iLow (symbol, tf, index); if(high == 0.0 || low == 0.0){ return 0.0; } return NormalizeDouble(high - low, Digits()); }
Diese Funktion gibt die Differenz zwischen dem Höchst- und Tiefstwert einer ausgewählten Bar zurück. In unserem Fall verwenden wir den Barindex 1, um den gestrigen Bereich zu messen. Diese beiden Funktionen unterstützen zwei unterschiedliche Volatilitätsphilosophien. Eine erfasst die jüngste Ausweitung des Swings. Der andere fängt die tägliche Bewegung ein.
Projektion der Einstiegshöhen
Sobald eine Working-Range existiert, prognostizieren wir die Einstiegspreise. Für bullische Ausbrüche:
//+--------------------------------------------------------------------------------+ //| Calculates the bullish breakout entry price using today's open and swing range | //+--------------------------------------------------------------------------------+ double CalculateBuyEntryPrice(double todayOpen, double range, double buyMultiplier){ return todayOpen + (range * buyMultiplier); }Wir fügen einen Bruchteil der Spanne zur heutigen Eröffnung hinzu. Dadurch wird eine Ausbruchsschwelle geschaffen, die der Kurs überschreiten muss, um die Aufwärtsdynamik zu bestätigen.
Für bärische Ausbrüche:
//+--------------------------------------------------------------------------------+ //| Calculates the bearish breakout entry price using today's open and swing range | //+--------------------------------------------------------------------------------+ double CalculateSellEntryPrice(double todayOpen, double range, double sellMultiplier){ return todayOpen - (range * sellMultiplier); }Wir subtrahieren einen Bruchteil der Spanne vom heutigen Eröffnungswert. Diese beiden Funktionen übersetzen die Volatilität in umsetzbare Ausbruchsniveaus.
Planen von Stop-Loss- und Take-Profit-Levels
Der Stop-Loss kann nach zwei Modellen platziert werden.
Das Modell der Range:
//+--------------------------------------------------------------------------------------------------+ //| Calculates the stop-loss price for a bullish position based on entry price and yesterday's range | //+--------------------------------------------------------------------------------------------------+ double CalculateBullishStopLoss(double entryPrice, double range, double stopMultiplier){ return entryPrice - (range * stopMultiplier); } //+--------------------------------------------------------------------------------------------------+ //| Calculates the stop-loss price for a bearish position based on entry price and yesterday's range | //+--------------------------------------------------------------------------------------------------+ double CalculateBearishStopLoss(double entryPrice, double range, double stopMultiplier){ return entryPrice + (range * stopMultiplier); }
Diese Funktionen setzen den Stop-Loss in einem Abstand, der einem Bruchteil der Working-Range entspricht, vom Einstiegskurs entfernt. Das Swing-Extrem-Modell nutzt das Tief oder Hoch des Balkenindex 2 als Schutz-Stopp.
Take-Profit-Levels werden nur projiziert, wenn der Risiko-Ertrags-Modus aktiv ist.
//+--------------------------------------------------------------------------+ //| Calculates take-profit level for a bullish trade using risk-reward logic | //+--------------------------------------------------------------------------+ double CalculateBullishTakeProfit(double entryPrice, double stopLossPrice, double rewardValue){ double stopDistance = entryPrice - stopLossPrice; double rewardDistance = stopDistance * rewardValue; return NormalizeDouble(entryPrice + rewardDistance, Digits()); } //+--------------------------------------------------------------------------+ //| Calculates take-profit level for a bearish trade using risk-reward logic | //+--------------------------------------------------------------------------+ double CalculateBearishTakeProfit(double entryPrice, double stopLossPrice, double rewardValue){ double stopDistance = stopLossPrice - entryPrice; double rewardDistance = stopDistance * rewardValue; return NormalizeDouble(entryPrice - rewardDistance, Digits()); }Diese Funktionen multiplizieren die Stoppdistanz mit dem konfigurierten Belohnungsfaktor und projizieren das Ziel entsprechend.
Zentralisierung der Volatilitätsberechnungen
Die gesamte Volatilitätslogik ist in einer einzigen Funktion zusammengefasst.
//+--------------------------------------------------------------------------------------------------------------+ //| Calculates and updates all volatility-based entry, stop, and take-profit levels based on the selected models | //+--------------------------------------------------------------------------------------------------------------+ void UpdateVolatilityEntryLevels(){ if(volatilityEntryMode == VOL_SWING_BASED){ lwVolatilityLevels.range = CalculateLwSwingVolatilityRange(_Symbol, timeframe); lwVolatilityLevels.buyEntryPrice = CalculateBuyEntryPrice (askPrice, lwVolatilityLevels.range, inpBuyRangeMultiplier ); lwVolatilityLevels.sellEntryPrice = CalculateSellEntryPrice(bidPrice, lwVolatilityLevels.range, inpSellRangeMultiplier); if(stopLossMode == SL_BY_RANGE_PERCENT){ lwVolatilityLevels.bullishStopLoss = CalculateBullishStopLoss(lwVolatilityLevels.buyEntryPrice, lwVolatilityLevels.range, inpStopRangeMultiplier); lwVolatilityLevels.bearishStopLoss = CalculateBearishStopLoss(lwVolatilityLevels.sellEntryPrice, lwVolatilityLevels.range, inpStopRangeMultiplier); } if(stopLossMode == SL_AT_SWING_EXTREME){ lwVolatilityLevels.bullishStopLoss = iLow(_Symbol, timeframe, 2); lwVolatilityLevels.bearishStopLoss = iHigh(_Symbol, timeframe, 2); } lwVolatilityLevels.bullishTakeProfit = CalculateBullishTakeProfit(lwVolatilityLevels.buyEntryPrice, lwVolatilityLevels.bullishStopLoss, riskRewardRatio); lwVolatilityLevels.bearishTakeProfit = CalculateBearishTakeProfit(lwVolatilityLevels.sellEntryPrice, lwVolatilityLevels.bearishStopLoss, riskRewardRatio); lwVolatilityLevels.bullishStopDistance = lwVolatilityLevels.buyEntryPrice - lwVolatilityLevels.bullishStopLoss; lwVolatilityLevels.bearishStopDistance = lwVolatilityLevels.bearishStopLoss - lwVolatilityLevels.sellEntryPrice; } if(volatilityEntryMode == VOL_SIMPLE_PREVIOUS_RANGE){ lwVolatilityLevels.range = GetBarRange(_Symbol, timeframe, 1); lwVolatilityLevels.buyEntryPrice = CalculateBuyEntryPrice (askPrice, lwVolatilityLevels.range, inpBuyRangeMultiplier ); lwVolatilityLevels.sellEntryPrice = CalculateSellEntryPrice(bidPrice, lwVolatilityLevels.range, inpSellRangeMultiplier); if(stopLossMode == SL_BY_RANGE_PERCENT){ lwVolatilityLevels.bullishStopLoss = CalculateBullishStopLoss(lwVolatilityLevels.buyEntryPrice, lwVolatilityLevels.range, inpStopRangeMultiplier); lwVolatilityLevels.bearishStopLoss = CalculateBearishStopLoss(lwVolatilityLevels.sellEntryPrice, lwVolatilityLevels.range, inpStopRangeMultiplier); } if(stopLossMode == SL_AT_SWING_EXTREME){ lwVolatilityLevels.bullishStopLoss = iLow(_Symbol, timeframe, 2); lwVolatilityLevels.bearishStopLoss = iHigh(_Symbol, timeframe, 2); } lwVolatilityLevels.bullishTakeProfit = CalculateBullishTakeProfit(lwVolatilityLevels.buyEntryPrice, lwVolatilityLevels.bullishStopLoss, riskRewardRatio); lwVolatilityLevels.bearishTakeProfit = CalculateBearishTakeProfit(lwVolatilityLevels.sellEntryPrice, lwVolatilityLevels.bearishStopLoss, riskRewardRatio); lwVolatilityLevels.bullishStopDistance = lwVolatilityLevels.buyEntryPrice - lwVolatilityLevels.bullishStopLoss; lwVolatilityLevels.bearishStopDistance = lwVolatilityLevels.bearishStopLoss - lwVolatilityLevels.sellEntryPrice; } }
Diese Funktion prüft, welches Erfassungsmodell der Volatilität ausgewählt ist. Sie berechnet die Working-Range, die Einstiegsniveaus, die Stop-Loss-Niveaus, die Take-Profit-Niveaus und die Stoppabstände. Alle Werte werden innerhalb der gemeinsamen Struktur gespeichert.
Diese Funktion wird einmal pro neuem Bar innerhalb der OnTick-Funktion aufgerufen.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ ... //--- Run this block only when a new bar is detected on the selected timeframe if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){ //--- Recalculate volatility entry levels on new bar based on the selected entry and stop models UpdateVolatilityEntryLevels(); } }
Verfolgung von Intraday-Ausbrüchen anhand von Ein-Minuten-Daten
Wir verlassen uns nicht auf die Schlusskurse, um Ausbrüche zu bestätigen. Wir verfolgen die Kursentwicklung in Echtzeit anhand von Ein-Minuten-Daten. Um dies zu unterstützen, definieren wir zwei Hilfsfunktionen.
//+------------------------------------------------------------------+ //| 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 vergleichen die letzten beiden einminütigen Schlusskurse, um festzustellen, ob der Kurs ein projiziertes Niveau über- oder unterschritten hat. Um diese Logik zu unterstützen, definieren wir ein globales Array.
//--- To store minutes data double closePriceMinutesData [];
Wir behandeln sie wie eine Zeitreihe und aktualisieren sie bei jedem Tick mit CopyClose.
Filterung nach Handelstag und Tageszeit
Um den Filter nach dem Wochentag für den Handel zu implementieren, definieren wir:
//+------------------------------------------------------------------------------------+ //| 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; } //+-----------------------------------------------------------------------------------------------------+ //| 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 erste Funktion extrahiert den Wochentag aus einem Wert vom Typ datetime. Im zweiten Schritt wird geprüft, ob der Handel auf der Grundlage des gewählten Modus und der Nutzereinstellungen zulässig ist.
Um den Tageszeitfilter zu implementieren, definieren wir:
//+------------------------------------------------------------------+ //| To parse time | //+------------------------------------------------------------------+ bool ParseTime(double hhmm, int &hours, int &minutes){ // Validate input range (0.00 to 23.59) if(hhmm < 0.0 || hhmm >= 24.00){ return false; } hours = (int)hhmm; double fractional = hhmm - hours; // Handle floating-point precision by rounding minutes = (int)MathRound(fractional * 100); // Validate minutes (0-59) if(minutes < 0 || minutes > 59){ return false; } // Handle cases like 12.60 becoming 13:00 if(minutes >= 60){ hours += minutes / 60; minutes %= 60; } // Final validation (hours might have incremented) if(hours < 0 || hours > 23){ return false; } return true; } //+------------------------------------------------------------------+ //| Returns true if current time is within allowed trading hours | //+------------------------------------------------------------------+ bool IsTimeWithinTradingHours(){ datetime currentTm = currentTime; MqlDateTime currentTimeStruct; if(!TimeToStruct(currentTm, currentTimeStruct)){ Print("Error while converting datetime to MqlDateTime struct: ", GetLastError()); return false; } int startHour; int startMins; ParseTime(startTime, startHour, startMins); int endHour; int endMins; ParseTime(endTime, endHour, endMins); MqlDateTime startTimeStruct = currentTimeStruct; startTimeStruct.hour = startHour; startTimeStruct.min = startMins; startTimeStruct.sec = 0; MqlDateTime endTimeStruct = currentTimeStruct; endTimeStruct.hour = endHour; endTimeStruct.min = endMins; endTimeStruct.sec = 0; datetime startTm = StructToTime(startTimeStruct); datetime endTm = StructToTime(endTimeStruct); if(currentTm >= startTm && currentTm <= endTm){ return true; } return false; }
Diese Funktionen wandeln die eingegebenen Zeitwerte in strukturierte Datetime-Werte um und bestimmen, ob die aktuelle Zeit innerhalb des zulässigen Fensters liegt.
Erzwingen einer einzelnen offenen Position
Um sicherzustellen, dass es zu jedem Zeitpunkt nur eine Handelsposition gibt, definieren wir:
//+------------------------------------------------------------------+ //| 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; }
Diese Funktionen durchsuchen alle offenen Positionen und geben true zurück, wenn bereits eine Position mit der magischen Zahl des EAs existiert.
Wir brauchen eine Möglichkeit, Trades zu schließen, die kein festes Take-Profit-Niveau haben. Wir wollen diese Positionen schließen, sobald die Gewinnmitnahmekriterien erreicht sind. Aus diesem Grund definieren wir die folgende nutzerdefinierte Funktion:
//+------------------------------------------------------------------+ //| To close all position with a specified magic number | //+------------------------------------------------------------------+ void ClosePositionsByMagic(ulong magic) { for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetInteger(POSITION_MAGIC) == magic) { ulong positionType = PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); if (positionType == POSITION_TYPE_BUY) { Trade.PositionClose(ticket); } else if (positionType == POSITION_TYPE_SELL) { Trade.PositionClose(ticket); } } } } }
Diese Funktion durchläuft alle derzeit offenen Positionen des Handelskontos und schließt nur die von diesem EA eröffneten. Sie stützt sich auf eine magische Zahl, um Positionen zu identifizieren und zu isolieren, die zu dieser spezifischen Expert Advisor-Instanz gehören.
Eröffnen von Trades und Berechnen der Positionsgröße
Die dynamische Positionsgrößenbestimmung ist wie folgt realisiert:
//+----------------------------------------------------------------------------------+ //| 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); }
Diese Funktion berechnet das Volumen auf der Grundlage eines festen Prozentsatzes des Kontosaldos und der aktuellen Stopp-Distanz.
Die Ausführung des Handels erfolgt durch:
//+------------------------------------------------------------------+ //| Function to open a market buy position | //+------------------------------------------------------------------+ bool OpenBuy(double stopLoss, double takeProfit, double lotSize){ if(lotSizeMode == MODE_AUTO){ lotSize = CalculatePositionSizeByRisk(lwVolatilityLevels.bullishStopDistance); } if(takeProfitMode == TP_BY_RISK_REWARD){ if(!Trade.Buy(lotSize, _Symbol, askPrice, lwVolatilityLevels.bullishStopLoss, lwVolatilityLevels.bullishTakeProfit)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } if(!Trade.Buy(lotSize, _Symbol, askPrice, lwVolatilityLevels.bullishStopLoss)){ 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 stopLoss, double takeProfit, double lotSize){ if(lotSizeMode == MODE_AUTO){ lotSize = CalculatePositionSizeByRisk(lwVolatilityLevels.bearishStopDistance); } if(takeProfitMode == TP_BY_RISK_REWARD){ if(!Trade.Sell(lotSize, _Symbol, bidPrice, lwVolatilityLevels.bearishStopLoss, lwVolatilityLevels.bearishTakeProfit)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; } if(!Trade.Sell(lotSize, _Symbol, bidPrice, lwVolatilityLevels.bearishStopLoss)){ Print("Error while executing a market buy order: ", GetLastError()); Print(Trade.ResultRetcode()); Print(Trade.ResultComment()); return false; } return true; }Diese Funktionen unterstützen sowohl die feste als auch die dynamische Losgröße und wenden die Stop-Loss- und Take-Profit-Logik auf der Grundlage des ausgewählten Gewinnmodus an.
Verwaltung von Einfahrsignalen
Die gesamte Eingabelogik wird vereinheitlicht:
//+---------------------------------------------------------------------------------------------------------+ //| Evaluates swing-based entry signals, applies filters, and executes trades when conditions are satisfied | //+---------------------------------------------------------------------------------------------------------+ void EvaluateAndExecuteEntrySignals(){ bool timeAllowed = true; if(useTimeFilter){ timeAllowed = IsTimeWithinTradingHours(); } //--- Handle bullish entry signals if(IsLarryWilliamsShortTermLow(_Symbol, timeframe)){ if(IsCrossOver(lwVolatilityLevels.buyEntryPrice, closePriceMinutesData)){ if(timeAllowed){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ OpenBuy(lwVolatilityLevels.bullishStopLoss, lwVolatilityLevels.bullishTakeProfit, positionSize); if(takeProfitMode == TP_AFTER_N_CANDLES){ barsSinceEntry = 1; } } } } }else{ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(direction == TRADE_BOTH || direction == ONLY_LONG){ OpenBuy(lwVolatilityLevels.bullishStopLoss, lwVolatilityLevels.bullishTakeProfit, positionSize); if(takeProfitMode == TP_AFTER_N_CANDLES){ barsSinceEntry = 1; } } } } } } } //--- Handle bearish entry signals if(IsLarryWilliamsShortTermHigh(_Symbol, timeframe)){ if(IsCrossUnder(lwVolatilityLevels.sellEntryPrice, closePriceMinutesData)){ if(timeAllowed){ if(tradeDayMode == TDW_SELECTED_DAYS){ if(IsTradingDayAllowed(currentTime)){ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ OpenSel(lwVolatilityLevels.bearishStopLoss, lwVolatilityLevels.bearishTakeProfit, positionSize); if(takeProfitMode == TP_AFTER_N_CANDLES){ barsSinceEntry = 1; } } } } }else{ if(!IsThereAnActiveBuyPosition(magicNumber) && !IsThereAnActiveSellPosition(magicNumber)){ if(direction == TRADE_BOTH || direction == ONLY_SHORT){ OpenSel(lwVolatilityLevels.bearishStopLoss, lwVolatilityLevels.bearishTakeProfit, positionSize); if(takeProfitMode == TP_AFTER_N_CANDLES){ barsSinceEntry = 1; } } } } } } } }
Diese Funktion prüft auf strukturelle Swing-Signale, bewertet Volatilitätsausbrüche, wendet zeit- und tagesbasierte Filter an, setzt Regeln für die Handelsrichtung durch und eröffnet Positionen, wenn alle Bedingungen erfüllt sind. Sie initialisiert auch den Zähler der Bars, wenn der Take-Profit-Modus zeitlich begrenzte Ausstiege erfordert.
Verwaltung der Ausstiegslogik
Positionen, die ohne einen festen Take-Profit eröffnet wurden, werden mithilfe von verwaltet:
//+-------------------------------------------------------------------------------------------+ //| Manages exit logic for the currently open position based on the selected take-profit mode | //+-------------------------------------------------------------------------------------------+ void ManageOpenPositionExits(){ if(takeProfitMode == TP_FIRST_PROFITABLE_OPEN){ for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket == 0){ Print("Error while fetching position ticket ", GetLastError()); continue; }else{ if(PositionGetDouble(POSITION_PROFIT) > 0 ){ ClosePositionsByMagic(magicNumber); } } } } if(takeProfitMode == TP_AFTER_N_CANDLES){ if(barsSinceEntry > exitAfterCandles){ ClosePositionsByMagic(magicNumber); barsSinceEntry = 0; } } }Diese Funktion schließt Positionen entweder beim ersten profitablen Moment oder nach einer bestimmten Anzahl von Bars.
Um zeitgesteuerte Ausgänge zu unterstützen, definieren wir (im globalen Bereich):
//--- Tracks the number of completed bars elapsed since the current trade was opened int barsSinceEntry;
Diese Variable erfasst die Anzahl der abgeschlossenen Bars, die seit dem Öffnen der aktuellen Position verstrichen sind.
Alles vereinen
Schließlich vervollständigen wir die Funktion OnTick.
//+------------------------------------------------------------------+ //| 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, 5, closePriceMinutesData) == -1){ Print("Error while copying minutes datas ", GetLastError()); return; } //--- Run this block only when a new bar is detected on the selected timeframe if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){ //--- Recalculate volatility entry levels on new bar based on the selected entry and stop models UpdateVolatilityEntryLevels(); //--- Increment the number of completed bars since the position was opened if(barsSinceEntry > 0){ barsSinceEntry = barsSinceEntry + 1; } //--- Handle exit conditions for the currently active position based on the configured take-profit mode if(takeProfitMode == TP_FIRST_PROFITABLE_OPEN){ ManageOpenPositionExits(); } } //--- Check for valid entry signals and place trades if all rules are met EvaluateAndExecuteEntrySignals(); //--- Handle exit conditions for the currently active position based on the configured take-profit mode if(takeProfitMode == TP_AFTER_N_CANDLES){ ManageOpenPositionExits(); } }
Sie ruft Live-Kurse ab, aktualisiert Ein-Minuten-Daten, berechnet die Volatilitätsniveaus auf neuen Bars neu, wertet Einstiegssignale aus, erhöht die Barszähler und wendet eine Ausstiegslogik an. An diesem Punkt arbeiten alle Komponenten des EA als ein einziges logisches System zusammen.
Der vollständige Quellcode ist in der Datei lwVolatilityStructureTimeFilterExpert.mq5 enthalten. Anstatt den kompletten Code hier einzufügen, verweisen wir auf ihn als endgültige Ausgabe dieses Builds.
Dies markiert das Ende der EA-Entwicklung. Für jede Funktion gibt es einen offensichtlichen Grund. Jeder logische Block unterstützt einen bestimmten Teil der Methodik von Larry Williams: Die Struktur definiert Signale. Die Volatilität bestimmt die Eröffnungen. Die Filter verfeinern die Ausführung. Das Risikomanagement schützt das Kapital. Die Ausstiegslogik sorgt für Disziplin.
Bevor wir zum Testen übergehen, müssen wir sicherstellen, dass die Chart-Umgebung die Kursentwicklung und die Handelsaktivität klar darstellt. Ein übersichtliches und konsistentes Chart-Layout erleichtert die visuelle Inspektion von Ein- und Ausstiegen sowie des Gesamtverhaltens während des Strategietests. Da dieser Expert Advisor für Forschung und Analyse gedacht ist, ist die Verbesserung der Lesbarkeit der Charts ein kleiner, aber wichtiger Schritt.
Um dies zu erreichen, definieren wir eine nutzerdefinierte Hilfsfunktion, die das Erscheinungsbild des Charts konfiguriert, wenn der Expert Advisor definiert ist. Diese Funktion wendet eine Reihe von visuellen Voreinstellungen an, sodass Kerzen, Hintergrund und Kursbewegungen beim Testen und Wiedergeben leicht zu unterscheiden sind. Nachfolgend finden Sie die Funktionsdefinition, die in dem für nutzerdefinierte MQL5-Funktionen reservierten Abschnitt platziert werden sollte.
//+------------------------------------------------------------------+ //| This function configures the chart's appearance. | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){ Print("Error while setting chart background, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){ Print("Error while setting chart grid, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)){ Print("Error while setting chart mode, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){ Print("Error while setting chart foreground, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)){ Print("Error while setting bullish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){ Print("Error while setting bearish candles color, ", GetLastError()); return false; } return true; }
Die Funktion funktioniert durch den Aufruf einer Reihe von ChartSetInteger-Befehlen. Jeder Befehl ändert eine bestimmte visuelle Eigenschaft des derzeit aktiven Charts.
Zunächst wird die Hintergrundfarbe des Charts auf Weiß gesetzt. Ein heller Hintergrund verbessert den Kontrast und lässt die Farben der Kerzen deutlich hervortreten. Als Nächstes wird das Chart-Gitter deaktiviert. Durch das Entfernen des Gitters wird das visuelle Rauschen reduziert und der Fokus bleibt auf der Preisbewegung und nicht auf den Hilfslinien. Der Chart-Modus wird dann auf Kerzen-Ansicht eingestellt. Kerzen liefern mehr Informationen als Liniencharts und eignen sich besser für die Analyse von Volatilität, Struktur und Intraday-Verhalten.
Danach wird die Vordergrundfarbe auf Schwarz gesetzt. Dadurch wird sichergestellt, dass Chart-Text und Preisskalen vor dem weißen Hintergrund gut sichtbar bleiben. Die Funktion definiert dann unterschiedliche Farben für bullische und bärische Kerzen. Aufwärtskerzen sind meergrün gefärbt, um die Aufwärtsbewegung des Kurses hervorzuheben, während Abwärtskerzen schwarz gefärbt sind, um einen neutralen und lesbaren Kontrast zu erhalten. Die gleiche Farblogik wird auf die Umrisse der Bars im Chart für Aufwärts- und Abwärtsbewegungen angewendet. Auf diese Weise bleiben Kerzenkörper und Umrisse visuell konsistent und leicht zu interpretieren.
Jeder ChartSetInteger-Aufruf wird auf Erfolg geprüft. Wenn ein Konfigurationsschritt fehlschlägt, wird eine Fehlermeldung protokolliert, und die Funktion gibt sofort false zurück. Dadurch kann der Expert Advisor Konfigurationsprobleme frühzeitig erkennen und vermeiden, dass er unter unbeabsichtigten Chart-Bedingungen läuft. Wenn alle Chart-Einstellungen erfolgreich angewendet wurden, gibt die Funktion den Wert true zurück und zeigt damit an, dass das Chart zum Testen und Analysieren bereit ist.
Sobald die Funktion definiert ist, wird sie in der Initialisierungsfunktion des Experten aufgerufen. Dadurch wird sichergestellt, dass das Erscheinungsbild des Charts einmalig konfiguriert wird, und zwar direkt beim Start des Expert Advisors.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ ... //--- To configure the chart's appearance if(!ConfigureChartAppearance()){ Print("Error while configuring chart appearance", GetLastError()); return INIT_FAILED; } return(INIT_SUCCEEDED); }
In der Funktion OnInit rufen wir die Chart-Konfigurationsroutine auf und überprüfen ihr Ergebnis. Wenn die Konfiguration fehlschlägt, bricht der Expert Advisor die Initialisierung ab und meldet das Problem. Wenn dies gelingt, wird die Initialisierung normal fortgesetzt.
Dieser Ansatz stellt sicher, dass jeder Test mit einem sauberen, gut lesbaren Chart-Layout beginnt, was es vereinfacht, das Handelsverhalten zu bewerten, die historische Performance zu überprüfen und visuell zu bestätigen, dass sich die Logik der Strategie während des Tests wie vorgesehen verhält.
Backtest der Strategie für Gold
Nachdem die Kernlogik nun abgeschlossen ist, besteht der nächste Schritt darin, das Verhalten des Modells unter realen Marktbedingungen zu validieren. Um den Test bodenständig und leicht wiederholbar zu halten, wurden ein einziger Markt und ein festes Zeitfenster gewählt. Das verwendete Instrument war Gold (XAUUSD) auf dem täglichen Zeitrahmen. Der Testzeitraum erstreckt sich vom ersten Januar 2025 bis zum dreißigsten Dezember 2025, was zum Zeitpunkt der Erstellung dieses Berichts ein ganzes Jahr an Marktdaten darstellt.
Für diesen Lauf wurden nur Long-Trades aktiviert, indem die Handelsrichtung auf ONLY_LONG gesetzt wurde. Auf diese Weise können wir uns auf die Aufwärtsseite der Volatilitätsausbruchslogik konzentrieren, ohne in diesem Stadium eine Abwärtsdynamik im Handel einzuführen. Die Positionsgröße wurde im automatischen Modus konfiguriert, mit einem festen Risiko von zwei Prozent des Kontosaldos pro Handel. Der Stop-Loss wurde als Prozentsatz der Working-Range festgelegt, um das Risiko an die Marktvolatilität anzupassen und nicht an einen festen Abstand.
Um die Ergebnisse vollständig reproduzierbar zu machen, wurden diesem Artikel zwei Dateien beigefügt. Die erste Datei, configurations.ini, enthält die vom Strategietester verwendeten Umgebungseinstellungen. Die zweite Datei, parameters.set, speichert den vollständigen Satz von Eingabeparametern, die bei diesem Test verwendet wurden. Mit diesen Dateien lassen sich die gleichen Bedingungen mit minimalem Aufwand wiederherstellen.
Der Backtest begann mit einem anfänglichen Kontostand von 10.000 $. Am Ende des Testzeitraums erzielte das System einen Gesamtnettogewinn von 3.710,55 $.

Dies entspricht einer Rendite von etwas über fünfunddreißig Prozent für das Jahr. Die verzeichnete Gewinnquote betrug 54,55 %, was nicht außergewöhnlich hoch ist. Dies steht jedoch im Einklang mit der Natur von Volatilitätsausbruchssystemen, die eher auf ein asymmetrisches Verhältnis von Ertrag und Risiko als auf häufige Gewinner setzen.
Einer der ermutigendsten Aspekte dieser Entwicklung ist die Form der Kapitalkurve. Der beigefügte Screenshot zeigt einen gleichmäßigen Verlauf des Kontowachstums ohne starke Rückgänge oder plötzliche Einbrüche.

Dies deutet darauf hin, dass die Kombination aus volatilitätsbasierten Einstiegen, Strukturbestätigungen und diszipliniertem Risiko-Sizing stabil funktioniert. Das System ist nicht von einer hohen Strike-Rate abhängig, um profitabel zu bleiben, und das Kapitalverhalten spiegelt eher ein kontrolliertes Engagementprofil als eine aggressive Aufzinsung wider.
Diese Ergebnisse sollten nicht als endgültiges Urteil über die Strategie gewertet werden. Sie stellen eine einzige Konfiguration, einen einzigen Markt und ein einziges Zeitfenster dar. Der EA wurde absichtlich mit flexiblen Eingabeparametern konzipiert, um weitere Experimente zu ermöglichen. Es können verschiedene Volatilitätsmultiplikatoren, Stop-Loss-Modelle, Take-Profit-Modi, Zeitfilter und Tagesfilter untersucht werden, um zu bewerten, wie empfindlich die Performance auf die einzelnen Komponenten reagiert.
In diesem Stadium ist der wertvollste nächste Schritt eine unabhängige Prüfung. Die Anwendung desselben Modells auf verschiedene Instrumente, Zeitrahmen und Marktregime kann zeigen, ob das beobachtete Verhalten strukturell oder marktspezifisch ist. Geringfügige Konfigurationsänderungen können auch Leistungsverbesserungen oder Stabilitätseinbußen mit sich bringen. Alle Erkenntnisse, Beobachtungen oder Variationen, die zu interessanten Ergebnissen führen, sind es wert, im Kommentarbereich des Artikels mitgeteilt zu werden, sodass die breiteren Forschungsbemühungen über einen einzelnen Testfall hinausgehen können.
Schlussfolgerung
Dieser Artikel ist ein praktischer Schritt über die Theorie hinaus in die Umsetzung. Wir sind von Larry Williams' Kerngedanken ausgegangen, dass Volatilität Chancen schafft, Struktur die Richtung vorgibt und zeitbasierte Filter die Selektivität verbessern. Anstatt diese Konzepte als isolierte Techniken zu behandeln, haben wir sie in einem einzigen, überprüfbaren Handelsmodell zusammengefasst. Wir haben den gesamten Prozess der Umwandlung dieses Modells in einen funktionierenden Expert Advisor dokumentiert.
Was wir erreicht haben, ist nicht nur ein EA, der Trades platziert. Wir haben einen flexiblen Forschungsrahmen geschaffen. Das System kann volatilitätsbasierte Einstiegsniveaus anhand von zwei verschiedenen Modellen projizieren. Es kann das Risiko entweder an der Marktstruktur oder an einem von der Volatilität abgeleiteten Abstand festmachen. Es kann Trades nach einer zeitbasierten Logik, einer gewinnbasierten Logik oder einem definierten Risiko-Ertrags-Verhältnis beenden. Sie kann den Handel nach Wochentag und Tageszeit filtern. Jedes dieser Elemente ist über Eingabeparameter zugänglich, sodass Ideen getestet werden können, ohne den Code zu ändern.
Der Backtest für Gold hat gezeigt, dass selbst eine einfache Konfiguration dieses Rahmens ein stabiles Wachstum mit kontrollierten Drawdowns erzeugen kann. Eine bescheidene Gewinnquote reichte aus, um sinnvolle Renditen zu erwirtschaften, da sich das Risiko eher am Marktverhalten als an festen Abständen orientierte. Noch wichtiger ist, dass die Aktienkurve nicht von einer kleinen Anzahl extremer Gewinner abhängt. Sie spiegelt die konsequente Teilnahme an Volatilitätsexpansionen, gefiltert nach Struktur und Zeit, wider.
Im Kern hat dieser Artikel eine Grundlage für weitere Forschung geschaffen. Der EA wird nicht als fertiges System präsentiert. Es wird als Labor präsentiert. Sie lädt zur Vervielfältigung, Änderung und Erweiterung ein. Der gleiche Rahmen kann auf andere Märkte, andere Zeitrahmen und andere Parameter angewandt werden, um zu untersuchen, wo die zugrunde liegende Logik gilt und wo sie versagt.
Der wahre Wert dieser Arbeit liegt nicht in einem einzigen Backtest-Ergebnis. Das liegt an der Methode. Wir haben jetzt eine strukturierte Methode, um zu untersuchen, wie Volatilität, Struktur und Zeit auf realen Märkten zusammenwirken. Das ist die Art von Instrument, die eher langfristiges Lernen als kurzfristige Optimierung unterstützt.
In der folgenden Tabelle sind alle Zusatzdateien zu diesem Artikel aufgeführt, zusammen mit einer kurzen Beschreibung des Zwecks jeder Datei. Diese Dateien sollen den Lesern helfen, die besprochenen Ergebnisse zu reproduzieren und die Umsetzung genau zu verfolgen.| Dateiname | Beschreibung |
|---|---|
| lwVolatilityStructureTimeFilterExpert.mq5 | Die Hauptquelldatei des Expert Advisors, in der die gesamte Handelslogik implementiert ist, einschließlich der Volatilitätsausbruchsmodelle von Larry Williams, der Erkennung der Swing-Struktur, der Zeit- und TDW-Filter sowie der Handels- und Risikomanagementregeln. |
| configurations.ini | Die für den Backtest verwendete Konfiguration der Umgebung des Strategietesters. |
| parameters.set | Eine Einstellungsdatei für den Strategietester des MetaTrader 5, die einen festen Satz von Eingabeparametern enthält, die zur Reproduktion der in diesem Artikel besprochenen Backtests und Ergebnisse verwendet werden. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21003
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Optimieren der Trendstärke: Handel in Richtung von Trend und Stärke
Python-MetaTrader 5 Strategietester (Teil 04): Tester 101
Einführung in MQL5 (Teil 35): Beherrschen der API- und WebRequest-Funktion in MQL5 (IX)
Einführung in MQL5 (Teil 34): Beherrschung der API- und WebRequest-Funktion in MQL5 (VIII)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.