English 日本語
preview
Entwicklung eines volatilitätsbasierten Ausbruchssystems

Entwicklung eines volatilitätsbasierten Ausbruchssystems

MetaTrader 5Handel |
135 0
Hlomohang John Borotho
Hlomohang John Borotho

Einführung

Viele traditionelle Ausbruchssysteme sind mit Problemen konfrontiert, wie z. B. häufigen Fehlsignalen, bei denen der Kurs kurzzeitig ein Unterstützungs- oder Widerstandsniveau durchbricht, nur um dann wieder in den Bereich zurückzukehren. Diese falschen Ausbrüche treten häufig in Umgebungen mit geringer Volatilität oder während Marktgeräuschen auf und führen zu Stop-Loss-Treffern, geringerer Rentabilität und Frustration der Händler. Ohne Berücksichtigung der sich ändernden Marktbedingungen können sich statische Ausbruchsstrategien nur schwer anpassen, was sie in unterschiedlichen Marktphasen unzuverlässig macht.

Ein volatilitätsbasiertes Ausbruchssystem löst dieses Problem, indem es Volatilitätsfilter wie die Average True Range (ATR) einbezieht, um die Marktstärke zu messen und die Ausbruchbedingungen dynamisch anzupassen. Da der Kurs sowohl die Bereichsgrenze als auch eine Volatilitätsschwelle überschreiten muss, hilft das System, echte Ausbrüche von Marktstörungen zu unterscheiden. Dadurch wird sichergestellt, dass nur dann gehandelt wird, wenn ein ausreichendes Momentum vorhanden ist, was die Genauigkeit, das Risikomanagement und die allgemeine Konsistenz der Ergebnisse verbessert.



Planung und Logik

Kaufen des Ausbruchs bei unklarer Volatilität:

Kaufen des Ausbruchs bei klarer Volatilität:

Verkaufen des Ausbruchs bei unklarer Volatilität:

Verkaufen des Ausbruchs bei klarer Volatilität:

Entscheidungslogik bei Ausbruch




Die ersten Schritte

//+------------------------------------------------------------------+
//|                                          Volatility Breakout.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

Wie üblich beginnen wir mit der Einbindung der notwendigen MQL5-Handelsbibliotheken: #include <Trade\Trade.mqh> und #include <Trade\PositionInfo.mqh>. Die erste ermöglicht den Zugriff auf die Klasse CTrade, die es dem EA ermöglicht, Handelsaufträge auszuführen, zu ändern und zu schließen, während die zweite die Klasse CPositionInfo bereitstellt, die es dem EA ermöglicht, detaillierte Informationen über aktive Positionen abzurufen, z. B. Auftragstyp, Losgröße, Stop-Loss- und Take-Profit-Level.

//+------------------------------------------------------------------+
//|                         Input Parameters                         |
//+------------------------------------------------------------------+
input group "--------------- Range Settings ---------------"
input int RangeStartTime = 600;          // Range start time (minutes from midnight)
input int RangeDuration = 120;           // Range duration in minutes
input int RangeCloseTime = 1200;         // Range close time (minutes from midnight)

input group "--------------- Trading Settings ---------------"
input double RiskPerTrade = 1.0;         // Risk percentage per trade
input double StopLossMultiplier = 1.5;   // Stop loss multiplier (range-based)
input double TakeProfitMultiplier = 2.0; // Take profit multiplier (range-based)
input bool UseTrailingStop = true;       // Enable trailing stops
input int BreakEvenAtPips = 250;         // Move to breakeven at this profit (pips)
input int TrailStartAtPips = 500;        // Start trailing at this profit (pips)
input int TrailStepPips = 100;           // Trail by this many pips

input group "--------------- Volatility Settings ---------------"
input int ATRPeriod = 14;                // ATR period for volatility calculation
input double ATRMultiplier = 2.0;        // ATR multiplier for volatility stops

Hier definieren wir die Eingabeparameter, die ein Händler bei der Verwendung des Expert Advisors anpassen kann. Die erste Gruppe, Bereichseinstellungen, gibt das Zeitfenster für die Erstellung eines Preisbereichs an: RangeStartTime gibt an, wann der Bereich beginnt (in Minuten ab Mitternacht), RangeDuration bestimmt, wie lange der Bereich dauert, und RangeCloseTime legt fest, wann die Sitzung geschlossen wird. Diese Einstellungen ermöglichen es dem EA, seine Ausbruchslogik um bestimmte Marktstunden herum zu strukturieren, was besonders nützlich ist, um aktive Handelssitzungen anzuvisieren.

In der zweiten und dritten Gruppe werden die Handels- und Volatilitätsregeln des Systems konfiguriert. Die Handelseinstellungen steuern das Risiko- und Handelsmanagement, z. B. RiskPerTrade für die Positionsgröße, Multiplikatoren für Stop Loss und Take Profit auf der Grundlage der Bereichsgröße und die Trailing-Stop-Logik (BreakEvenAtPips, TrailStartAtPips und TrailStepPips). In der Zwischenzeit verwenden die Volatilitätseinstellungen den ATR-Indikator (Average True Range) mit einer nutzerdefinierten ATRPeriode und einem ATRMultiplikator, um volatilitätsbasierte Stopp-Levels zu berechnen. Dadurch wird sichergestellt, dass sich Handelsgeschäfte eine Ausbruchs dynamisch an die sich ändernden Marktbedingungen anpassen und die Wahrscheinlichkeit von Fehlsignalen verringert wird.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
long ExpertMagicNumber = 20250908;
CTrade TradeManager;
CPositionInfo PositionInfo;
MqlTick CurrentTick;

// Range structure
struct TradingSession {
   datetime SessionStart;
   datetime SessionEnd;
   datetime SessionClose;
   double SessionHigh;
   double SessionLow;
   bool IsActive;
   bool HighBreakoutTriggered;
   bool LowBreakoutTriggered;
   datetime LastCalculationDate;
   
   TradingSession() : SessionStart(0), SessionEnd(0), SessionClose(0), SessionHigh(0), 
                     SessionLow(DBL_MAX), IsActive(false), HighBreakoutTriggered(false), 
                     LowBreakoutTriggered(false), LastCalculationDate(0) {};
};

TradingSession CurrentSession;

// Volatility stops
double UpperVolatilityStop = 0.0;
double LowerVolatilityStop = 0.0;
int ATRHandle = INVALID_HANDLE;

In diesem Abschnitt des Codes werden die globalen Variablen deklariert, die im gesamten Expert Advisor verwendet werden. Die ExpertMagicNumber identifiziert die von diesem EA eröffneten Handelsgeschäfte eindeutig, sodass sie von manuellen Handelsgeschäften oder anderen EAs unterschieden werden können. Das CTrade TradeManager-Objekt ist für die Ausführung und Verwaltung der Handelsgeschäfte zuständig, während CPositionInfo PositionInfo Einzelheiten über offene Positionen abruft. Die MqlTick CurrentTick-Variable enthält die aktuellste Kursnotierung (Geld, Brief und Zeit) und stellt sicher, dass die Handelslogik immer auf aktuellen Marktdaten basiert.

Die Struktur der Handelssitzung organisiert wichtige sitzungsbezogene Informationen, wie z. B. die Start- und Endzeiten, die höchsten und niedrigsten Kurse während der Sitzung sowie Flaggen, die anzeigen, ob ein Ausbruch bereits stattgefunden hat. Eine Instanz dieser Struktur, CurrentSession, wird deklariert, um Sitzungsdaten für den aktuellen Handelstag zu speichern. Darüber hinaus werden Variablen für das Volatilitätsmanagement eingerichtet: UpperVolatilityStop und LowerVolatilityStop markieren dynamische Schwellenwerte auf der Grundlage der ATR, während ATRHandle den Indikator-Handle speichert, der für den Zugriff auf die ATR-Werte benötigt wird. Zusammen bilden diese Variablen die Grundlage für die Erkennung von Ausbrüchen und die Ausführung von Transaktionen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Validate input parameters
   if(RiskPerTrade <= 0 || RiskPerTrade > 5) {
      Alert("Risk per trade must be between 0.1 and 5.0");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   // Initialize ATR indicator
   ATRHandle = iATR(_Symbol, _Period, ATRPeriod);
   if(ATRHandle == INVALID_HANDLE) {
      Alert("Failed to create ATR indicator");
      return INIT_FAILED;
   }
   
   TradeManager.SetExpertMagicNumber(ExpertMagicNumber);
   
   // Calculate initial session
   CalculateTradingSession();
   
   return INIT_SUCCEEDED;
}

Die Funktion OnInit() wird beim Start des Expert Advisors ausgeführt und bereitet das System auf den Handel vor. Zunächst werden die Eingabeparameter validiert, um sicherzustellen, dass die Risikoeinstellung innerhalb eines akzeptablen Bereichs liegt; andernfalls wird der EA mit einem Fehler beendet. Anschließend wird der ATR-Indikator initialisiert, indem ein Handle für die Volatilitätsberechnungen erstellt wird, und wenn dies fehlschlägt, wird der EA beendet. Als Nächstes wird dem TradeManager die eindeutige ExpertMagicNumber zugewiesen, damit die Handelsgeschäfte korrekt verfolgt werden können, und schließlich ruft der EA CalculateTradingSession() auf, um die erste Handelssitzung einzurichten, bevor er einen Erfolgsstatus zurückgibt.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Clean up indicators
   if(ATRHandle != INVALID_HANDLE) {
      IndicatorRelease(ATRHandle);
   }
   
   // Delete all graphical objects
   ObjectsDeleteAll(0, -1, -1);
}

Die Funktion OnDeinit() wird ausgeführt, wenn der EA entfernt oder gestoppt wird. Der ATR-Indikator wird freigegeben, um Ressourcen freizugeben, und alle grafischen Objekte werden aus dem Chart gelöscht, um sicherzustellen, dass keine Zeichnungsreste übrig bleiben.

//+------------------------------------------------------------------+
//| Check if we need to calculate a new session for the day          |
//+------------------------------------------------------------------+
void CheckForNewSession()
{
   MqlDateTime currentTime;
   TimeToStruct(CurrentTick.time, currentTime);
   
   MqlDateTime lastCalcTime;
   TimeToStruct(CurrentSession.LastCalculationDate, lastCalcTime);
   
   // Check if we're in a new day or if session hasn't been calculated yet
   if (currentTime.day != lastCalcTime.day || 
       currentTime.mon != lastCalcTime.mon || 
       currentTime.year != lastCalcTime.year ||
       CurrentSession.LastCalculationDate == 0) {
      CalculateTradingSession();
   }
}

Die Funktion CheckForNewSession() sorgt dafür, dass zu Beginn eines jeden neuen Tages eine neue Handelssitzung berechnet wird. Es konvertiert die aktuelle Tick-Zeit und die Berechnungszeit der letzten Sitzung in Datumsstrukturen und vergleicht dann Tag, Monat und Jahr. Wenn das aktuelle Datum vom Datum der letzten aufgezeichneten Sitzung abweicht oder wenn noch keine Sitzung festgelegt wurde, wird CalculateTradingSession() aufgerufen, um eine neue Sitzung vorzubereiten.

//+------------------------------------------------------------------+
//| Calculate trading session for the day                            |
//+------------------------------------------------------------------+
void CalculateTradingSession()
{
   // Reset session variables
   CurrentSession.SessionStart = 0;
   CurrentSession.SessionEnd = 0;
   CurrentSession.SessionClose = 0;
   CurrentSession.SessionHigh = 0;
   CurrentSession.SessionLow = DBL_MAX;
   CurrentSession.IsActive = false;
   CurrentSession.HighBreakoutTriggered = false;
   CurrentSession.LowBreakoutTriggered = false;
   
   // Calculate session times
   int timeCycle = 86400; // Seconds in a day
   CurrentSession.SessionStart = (CurrentTick.time - (CurrentTick.time % timeCycle)) + RangeStartTime * 60;
   
   // Adjust for weekends
   for(int i = 0; i < 8; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionStart, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(CurrentTick.time >= CurrentSession.SessionStart || dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionStart += timeCycle;
      }
   }

   // Calculate session end time
   CurrentSession.SessionEnd = CurrentSession.SessionStart + (RangeDuration * 60);
   for(int i = 0; i < 2; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionEnd, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionEnd += timeCycle;
      }
   }
   
   // Calculate session close time
   CurrentSession.SessionClose = (CurrentSession.SessionEnd - (CurrentSession.SessionEnd % timeCycle)) + RangeCloseTime * 60;
   for(int i = 0; i < 3; i++) {
      MqlDateTime tmp;
      TimeToStruct(CurrentSession.SessionClose, tmp);
      int dayOfWeek = tmp.day_of_week;
      if(CurrentSession.SessionClose <= CurrentSession.SessionEnd || dayOfWeek == 0 || dayOfWeek == 6) {
         CurrentSession.SessionClose += timeCycle;
      }
   }
   
   // Set last calculation date
   CurrentSession.LastCalculationDate = CurrentTick.time;
   
   // Draw session objects
   DrawSessionObjects();
}

Die Funktion CalculateTradingSession() richtet die tägliche Handelssitzung ein, indem sie alle sitzungsbezogenen Variablen zurücksetzt und die korrekten Start-, End- und Schlusszeiten auf der Grundlage der Nutzereingaben berechnet. Es stellt sicher, dass die Sitzungen auf die täglichen Zyklen abgestimmt sind und die Wochenenden ausgelassen werden, sodass nur an gültigen Markttagen gehandelt wird. Die Funktion setzt außerdem die Breakout-Flags zurück, aktualisiert das Berechnungsdatum der Sitzung und ruft DrawSessionObjects() auf, um die Grenzen und Niveaus der Sitzung auf dem Chart visuell darzustellen.

//+------------------------------------------------------------------+
//| Update trading session with current price data                   |
//+------------------------------------------------------------------+
void UpdateTradingSession()
{
   if(CurrentTick.time >= CurrentSession.SessionStart && CurrentTick.time < CurrentSession.SessionEnd) {
      CurrentSession.IsActive = true;
      
      // Update session high
      if(CurrentTick.ask > CurrentSession.SessionHigh) {
         CurrentSession.SessionHigh = CurrentTick.ask;
      }
      
      // Update session low
      if(CurrentTick.bid < CurrentSession.SessionLow) {
         CurrentSession.SessionLow = CurrentTick.bid;
      }
      
      // Draw session on chart
      DrawSessionObjects();
   }
}

Die Funktion UpdateTradingSession() aktualisiert kontinuierlich die Daten der aktuellen Sitzung, sobald neue Kurs-Ticks eintreffen. Es aktiviert die Sitzung, wenn die aktuelle Uhrzeit innerhalb des Sitzungszeitraums liegt, und verfolgt dann den höchsten Brief- und den niedrigsten Geldkurs, um den Höchst- und Tiefstkurs der Sitzung zu aktualisieren. Schließlich wird DrawSessionObjects() aufgerufen, um diese aktualisierten Niveaus visuell auf dem Chart darzustellen.

//+------------------------------------------------------------------+
//| Calculate volatility stops using ATR                             |
//+------------------------------------------------------------------+
void CalculateVolatilityStops()
{
   double atrValue[1];
   if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) {
      Print("Failed to get ATR value");
      return;
   }
   
   // Validate ATR value to prevent "inf" errors
   if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0]) || atrValue[0] > 1000) {
      Print("Invalid ATR value: ", atrValue[0], ", using default");
      atrValue[0] = 10 * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   }
   
   // Calculate volatility stops
   UpperVolatilityStop = CurrentSession.SessionHigh + (atrValue[0] * ATRMultiplier);
   LowerVolatilityStop = CurrentSession.SessionLow - (atrValue[0] * ATRMultiplier);
   
   // Draw volatility stops on chart
   DrawVolatilityStops();
}

Die Funktion CalculateVolatilityStops() berechnet dynamische obere und untere Stop-Levels auf der Grundlage der Marktvolatilität unter Verwendung des ATR-Indikators. Es ruft zunächst den letzten ATR-Wert ab, validiert ihn, um Fehler zu vermeiden, und setzt ihn gegebenenfalls auf einen sicheren Wert zurück. Die Funktion berechnet dann die oberen und unteren Volatilitätsstopps, indem sie die ATR (skaliert mit dem ATRMultiplier) vom Höchst- und Tiefstwert der Sitzung addiert bzw. subtrahiert, und ruft DrawVolatilityStops() auf, um diese Werte im Chart anzuzeigen.

//+------------------------------------------------------------------+
//| Check for breakouts and execute trades                           |
//+------------------------------------------------------------------+
void CheckForBreakouts()
{
   // Only check after session formation period
   if(CurrentTick.time < CurrentSession.SessionEnd || !CurrentSession.IsActive) {
      return;
   }
   
   // Check for high breakout
   if(!CurrentSession.HighBreakoutTriggered && CurrentTick.ask >= CurrentSession.SessionHigh) {
      CurrentSession.HighBreakoutTriggered = true;
      
      // Check if price has cleared volatility stop
      if(CurrentTick.ask >= UpperVolatilityStop) {
         // Regular breakout - execute buy
         ExecuteTrade(ORDER_TYPE_BUY, "Session High Breakout");
      } else {
         // False breakout - execute sell (fade)
         ExecuteTrade(ORDER_TYPE_SELL, "False Breakout - Fade High");
      }
   }
   
   // Check for low breakout
   if(!CurrentSession.LowBreakoutTriggered && CurrentTick.bid <= CurrentSession.SessionLow) {
      CurrentSession.LowBreakoutTriggered = true;
      
      // Check if price has cleared volatility stop
      if(CurrentTick.bid <= LowerVolatilityStop) {
         // Regular breakout - execute sell
         ExecuteTrade(ORDER_TYPE_SELL, "Session Low Breakout");
      } else {
         // False breakout - execute buy (fade)
         ExecuteTrade(ORDER_TYPE_BUY, "False Breakout - Fade Low");
      }
   }
}

Hier überwacht die Funktion CheckForBreakouts() das Kursgeschehen, um potenzielle Ausbruchsgelegenheiten zu erkennen, sobald sich die Sitzung gebildet hat und aktiv ist. Er prüft, ob der Kurs den Höchst- oder Tiefstwert der Sitzung überschreitet und stellt sicher, dass jeder Ausbruch nur einmal pro Sitzung ausgelöst wird. Handelsgeschäfte werden ausgeführt, je nachdem, ob der Kurs den entsprechenden Volatilitätsstopp überschreitet: Ein bestätigter Ausbruch löst einen Trade in der Ausbruchsrichtung aus, während ein falscher Ausbruch – bei dem der Kurs die Volatilitätsschwelle nicht überschreitet – einen Fade-Trade in der entgegengesetzten Richtung auslöst.

//+------------------------------------------------------------------+
//| Execute trade with proper risk management                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
{
   // Calculate position size based on risk
   double lotSize = CalculatePositionSize();
   if(lotSize <= 0) {
      Print("Failed to calculate position size");
      return;
   }
   
   // Calculate stop loss and take profit
   double stopLoss = 0.0;
   double takeProfit = 0.0;
   CalculateStopLevels(orderType, stopLoss, takeProfit);
   
   // Validate stop levels
   if(!ValidateStopLevels(orderType, stopLoss, takeProfit)) {
      Print("Invalid stop levels - trade not executed");
      return;
   }
   
   // Execute trade
   if(orderType == ORDER_TYPE_BUY) {
      TradeManager.Buy(lotSize, _Symbol, CurrentTick.ask, stopLoss, takeProfit, comment);
   } else {
      TradeManager.Sell(lotSize, _Symbol, CurrentTick.bid, stopLoss, takeProfit, comment);
   }
   
   // Check result
   if(TradeManager.ResultRetcode() != TRADE_RETCODE_DONE) {
      Print("Trade execution failed: ", TradeManager.ResultRetcodeDescription());
   }
}

Die Funktion ExecuteTrade() dient der Eröffnung eines Handels mit angemessenem Risikomanagement. Zunächst wird die Positionsgröße mit CalculatePositionSize() berechnet, dann werden die entsprechenden Stop-Loss- und Take-Profit-Levels mit CalculateStopLevels() bestimmt. Nach der Validierung dieser Levels mit ValidateStopLevels() führt die Funktion einen Kauf- oder Verkaufsauftrag unter Verwendung des TradeManager-Objekts aus und protokolliert eine Fehlermeldung, wenn der Handel fehlschlägt.

//+------------------------------------------------------------------+
//| Calculate position size based on risk percentage                 |
//+------------------------------------------------------------------+
double CalculatePositionSize()
{
   double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(accountBalance <= 0 || tickSize <= 0 || tickValue <= 0 || point <= 0) {
      return 0.0;
   }
   
   // Get ATR value for stop loss distance
   double atrValue[1];
   if(CopyBuffer(ATRHandle, 0, 0, 1, atrValue) <= 0) {
      return 0.0;
   }
   
   // Validate ATR value
   if(atrValue[0] <= 0 || !MathIsValidNumber(atrValue[0])) {
      return 0.0;
   }
   
   // Calculate stop distance in points
   double stopDistance = atrValue[0] * StopLossMultiplier / point;
   
   // Calculate risk amount
   double riskAmount = accountBalance * (RiskPerTrade / 100.0);
   
   // Calculate position size
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double lotSize = (riskAmount / (stopDistance * tickValue)) * tickSize;
   
   // Normalize lot size
   lotSize = MathFloor(lotSize / lotStep) * lotStep;
   
   // Apply min/max limits
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
   
   return lotSize;
}

Diese Funktion bestimmt das angemessene Handelsvolumen auf der Grundlage des Kontostands, des nutzerdefinierten Risikoanteils und der aktuellen Marktbedingungen. Es ruft symbol-spezifische Details wie Tick-Größe, Tick-Wert und Punktgröße ab und berechnet dann die Stop-Distanz unter Verwendung des ATR-Indikators, der mit dem StopLossMultiplier skaliert wird. Anhand des berechneten Risikobetrags berechnet es eine Losgröße, normalisiert sie auf die zulässige Stufe des Brokers und stellt sicher, dass sie innerhalb der Mindest- und Höchstvolumengrenzen liegt, bevor es die endgültige Positionsgröße zurückgibt.

//+------------------------------------------------------------------+
//| Calculate stop loss and take profit levels                       |
//+------------------------------------------------------------------+
void CalculateStopLevels(ENUM_ORDER_TYPE orderType, double &stopLoss, double &takeProfit)
{
   // Use range-based stops
   double rangeSize = CurrentSession.SessionHigh - CurrentSession.SessionLow;
   
   if(orderType == ORDER_TYPE_BUY) {
      stopLoss = CurrentTick.bid - (rangeSize * StopLossMultiplier / 100.0);
      takeProfit = CurrentTick.ask + (rangeSize * TakeProfitMultiplier / 100.0);
   } else {
      stopLoss = CurrentTick.ask + (rangeSize * StopLossMultiplier / 100.0);
      takeProfit = CurrentTick.bid - (rangeSize * TakeProfitMultiplier / 100.0);
   }
   
   // Normalize prices
   stopLoss = NormalizeDouble(stopLoss, _Digits);
   takeProfit = NormalizeDouble(takeProfit, _Digits);
}

//+------------------------------------------------------------------+
//| Validate stop loss and take profit levels                        |
//+------------------------------------------------------------------+
bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit)
{
   // Check if stop levels are valid
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double minStopDistance = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
   
   if(orderType == ORDER_TYPE_BUY) {
      if(CurrentTick.ask - stopLoss < minStopDistance) {
         Print("Stop loss too close for buy order");
         return false;
      }
      if(takeProfit - CurrentTick.ask < minStopDistance) {
         Print("Take profit too close for buy order");
         return false;
      }
   } else {
      if(stopLoss - CurrentTick.bid < minStopDistance) {
         Print("Stop loss too close for sell order");
         return false;
      }
      if(CurrentTick.bid - takeProfit < minStopDistance) {
         Print("Take profit too close for sell order");
         return false;
      }
   }
   
   return true;
}

Die Funktion CalculateStopLevels() bestimmt die Stop-Loss- und Take-Profit-Levels für einen Handel auf der Grundlage der aktuellen Handelsspanne der Sitzung. Bei Kaufaufträgen wird der Stop-Loss unter dem aktuellen Geldkurs und der Take-Profit über dem aktuellen Briefkurs gesetzt, skaliert mit nutzerdefinierten Multiplikatoren; bei Verkaufsaufträgen ist die Logik umgekehrt. Beide Werte werden auf die Dezimalpräzision des Geräts normalisiert, um die Genauigkeit der Ausführung zu gewährleisten.

Die Funktion ValidateStopLevels() stellt sicher, dass die berechneten Stop-Loss- und Take-Profit-Levels mit den Mindestabstandsanforderungen des Brokers übereinstimmen. Er prüft, ob der Abstand zwischen dem Einstiegskurs und den Stops größer ist als das SYMBOL_TRADE_STOPS_LEVEL multipliziert mit der Punktgröße, um ungültige oder zu enge Aufträge zu verhindern. Wenn die Stopps zu nahe beieinander liegen, gibt die Funktion den Wert false zurück und gibt eine Fehlermeldung aus, um sicherzustellen, dass nur gültige Handelsgeschäfte ausgeführt werden.

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void ManageTrailingStops()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      
      if(PositionSelectByTicket(ticket) && 
         PositionGetString(POSITION_SYMBOL) == _Symbol && 
         PositionGetInteger(POSITION_MAGIC) == ExpertMagicNumber) {
         
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double currentStop = PositionGetDouble(POSITION_SL);
         double currentProfit = PositionGetDouble(POSITION_PROFIT);
         double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
         
         // Calculate profit in pips
         double profitInPips = 0;
         if(positionType == POSITION_TYPE_BUY) {
            profitInPips = (CurrentTick.bid - openPrice) / point;
         } else {
            profitInPips = (openPrice - CurrentTick.ask) / point;
         }
         
         // Check if we should move to breakeven
         if(profitInPips >= BreakEvenAtPips && currentStop != openPrice) {
            if(positionType == POSITION_TYPE_BUY) {
               TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP));
            } else {
               TradeManager.PositionModify(ticket, openPrice, PositionGetDouble(POSITION_TP));
            }
         }
         
         // Check if we should start trailing
         if(profitInPips >= TrailStartAtPips) {
            double newStop = 0;
            
            if(positionType == POSITION_TYPE_BUY) {
               newStop = CurrentTick.bid - (TrailStepPips * point);
               
               // Only move stop if it's higher than current
               if(newStop > currentStop || currentStop == 0) {
                  TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP));
               }
            } else {
               newStop = CurrentTick.ask + (TrailStepPips * point);
               
               // Only move stop if it's lower than current
               if(newStop < currentStop || currentStop == 0) {
                  TradeManager.PositionModify(ticket, newStop, PositionGetDouble(POSITION_TP));
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Draw session objects on chart                                    |
//+------------------------------------------------------------------+
void DrawSessionObjects()
{
   // Draw session high
   ObjectDelete(0, "SessionHigh");
   ObjectCreate(0, "SessionHigh", OBJ_HLINE, 0, 0, CurrentSession.SessionHigh);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, "SessionHigh", OBJPROP_WIDTH, 2);
   
   // Draw session low
   ObjectDelete(0, "SessionLow");
   ObjectCreate(0, "SessionLow", OBJ_HLINE, 0, 0, CurrentSession.SessionLow);
   ObjectSetInteger(0, "SessionLow", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionLow", OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, "SessionLow", OBJPROP_WIDTH, 2);
   
   // Draw session start time
   ObjectDelete(0, "SessionStart");
   ObjectCreate(0, "SessionStart", OBJ_VLINE, 0, CurrentSession.SessionStart, 0);
   ObjectSetInteger(0, "SessionStart", OBJPROP_COLOR, clrBlue);
   ObjectSetInteger(0, "SessionStart", OBJPROP_WIDTH, 2);
   
   // Draw session end time
   ObjectDelete(0, "SessionEnd");
   ObjectCreate(0, "SessionEnd", OBJ_VLINE, 0, CurrentSession.SessionEnd, 0);
   ObjectSetInteger(0, "SessionEnd", OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, "SessionEnd", OBJPROP_WIDTH, 2);
   
   // Draw session close time
   ObjectDelete(0, "SessionClose");
   ObjectCreate(0, "SessionClose", OBJ_VLINE, 0, CurrentSession.SessionClose, 0);
   ObjectSetInteger(0, "SessionClose", OBJPROP_COLOR, clrDarkRed);
   ObjectSetInteger(0, "SessionClose", OBJPROP_WIDTH, 2);
}

//+------------------------------------------------------------------+
//| Draw volatility stops on chart                                   |
//+------------------------------------------------------------------+
void DrawVolatilityStops()
{
   // Draw upper volatility stop
   ObjectDelete(0, "VolatilityStopUpper");
   ObjectCreate(0, "VolatilityStopUpper", OBJ_HLINE, 0, 0, UpperVolatilityStop);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_COLOR, clrGreen);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_STYLE, STYLE_DOT);
   ObjectSetInteger(0, "VolatilityStopUpper", OBJPROP_WIDTH, 1);
   
   // Draw lower volatility stop
   ObjectDelete(0, "VolatilityStopLower");
   ObjectCreate(0, "VolatilityStopLower", OBJ_HLINE, 0, 0, LowerVolatilityStop);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_STYLE, STYLE_DOT);
   ObjectSetInteger(0, "VolatilityStopLower", OBJPROP_WIDTH, 1);
}

Die Funktion ManageTrailingStops() stellt sicher, dass aktive Handelsgeschäfte dynamisch verwaltet werden, wenn sie in den Gewinn laufen. Es durchläuft alle Positionen, filtert die zum EA gehörenden Positionen nach Symbol und magischer Zahl und berechnet dann den Gewinn in Pips. Je nach den Bedingungen kann der Stop-Loss auf den Break-Even verschoben werden, sobald eine bestimmte Gewinnschwelle erreicht ist, oder es kann ein Trailing-Stop angewendet werden, um die Gewinne zu sichern, wenn sich der Handel weiter zu Gunsten des Händlers entwickelt. Dies trägt dazu bei, Risiken zu verringern und Gewinne zu schützen, ohne dass manuelle Eingriffe erforderlich sind.

Die Funktion DrawSessionObjects() stellt die aktuelle Handelssitzung visuell auf dem Chart dar. Es erstellt horizontale Linien für den Höchst- und Tiefststand der Sitzung und vertikale Linien für den Beginn, das Ende und den Abschluss der Sitzung, jeweils mit unterschiedlichen Farben und Stilen. Dies erleichtert Händlern die visuelle Bestätigung von Sitzungsgrenzen und Schlüsselniveaus direkt in ihrem Chart.

Schließlich stellt die Funktion DrawVolatilityStops() die berechneten volatilitätsbasierten Stop-Levels auf dem Chart dar. Eine grün gepunktete Linie stellt den oberen Volatilitätsstopp dar, während eine rot gepunktete Linie den unteren darstellt. Diese Linien bieten klare visuelle Referenzpunkte für die Validierung von Ausbrüchen und die Erkennung von Fehlausbrüchen und verbessern die Entscheidungsfindung, indem sie die Kursentwicklung mit der Volatilitätsdynamik in Einklang bringen.



Backtest-Ergebnisse

Der Backtest wurde für den 1H-Zeitrahmen über ein etwa zweimonatiges Testfenster (07. Juli 2025 bis 08. September 2025) mit den folgenden Einstellungen ausgewertet:


Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir ein volatilitätsbasiertes Ausbruchssystem entwickelt haben, indem wir die Erkennung von Sitzungsspannen, die Volatilitätsanalyse mittels ATR und ein strukturiertes Handelsmanagement miteinander kombiniert haben. Das System beginnt mit der Identifizierung der täglichen Handelssitzungen, der Verfolgung der Höchst- und Tiefststände der Sitzungen und der Berechnung von Volatilitätsstopps, um zwischen echten und falschen Ausbrüchen zu unterscheiden. Es beinhaltet dann eine Logik zur Erkennung von Ausbrüchen, führt Handelsgeschäfte mit dynamischer Stop-Loss- und Take-Profit-Platzierung aus und wendet ein robustes Risikomanagement durch Positionsgrößenanpassung, Breakeven-Anpassungen und Trailing-Stops an. Visuelle Elemente wie Session-Marker und Volatilitäts-Stopp-Linien wurden ebenfalls integriert, um die Handelsbedingungen direkt auf dem Chart zu verdeutlichen.

Zusammenfassend lässt sich sagen, dass dieses System Händlern hilft, die üblichen Fallstricke traditioneller Ausbruchsstrategien zu vermeiden, wie z. B. das Eingehen von Geschäften bei falschen Bewegungen ohne Berücksichtigung der Marktvolatilität. Durch den Einsatz von ATR-basierten Volatilitätsstopps wird sichergestellt, dass sich die Handelsgeschäfte an der tatsächlichen Marktdynamik orientieren und nicht an willkürlichen Werten. Die zusätzlichen automatischen Risikokontrollen und die Chart-Visualisierung verbessern die Entscheidungsfindung und bieten Händlern einen disziplinierten, transparenten und zuverlässigeren Rahmen für den Breakout-Handel, der sich an unterschiedliche Marktbedingungen anpassen kann.

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

Beigefügte Dateien |
Der MQL5 Standard Library Explorer (Teil 1): Einführung in CTrade, CiMA, und CiATR Der MQL5 Standard Library Explorer (Teil 1): Einführung in CTrade, CiMA, und CiATR
Die MQL5-Standardbibliothek spielt eine wichtige Rolle bei der Entwicklung von Handelsalgorithmen für MetaTrader 5. In dieser Diskussionsreihe wollen wir seine Anwendung beherrschen, um die Erstellung effizienter Handelswerkzeuge für MetaTrader 5 zu vereinfachen. Zu diesen Tools gehören nutzerdefinierte Expert Advisors, Indikatoren und andere Hilfsmittel. Wir beginnen heute mit der Entwicklung eines trendfolgenden Expert Advisors unter Verwendung der Klassen CTrade, CiMA und CiATR. Dies ist ein besonders wichtiges Thema für alle – egal, ob Sie Anfänger oder erfahrener Entwickler sind. Nehmen Sie an dieser Diskussion teil und erfahren Sie mehr.
Der Parafrac V2 Oszillator: Integration von Parabolic SAR mit Average True Range Der Parafrac V2 Oszillator: Integration von Parabolic SAR mit Average True Range
Der Parafrac V2 Oszillator ist ein fortschrittliches technisches Analysewerkzeug, das den Parabolic SAR mit der Average True Range (ATR) integriert, um die Einschränkungen seines Vorgängers zu überwinden, der auf Fraktalen beruhte und anfällig für Signalspitzen war, die vorherige und aktuelle Signale überschatteten. Durch die Nutzung des ATR-Volatilitätsmaßes bietet die Version 2 eine sanftere, zuverlässigere Methode zur Erkennung von Trends, Umkehrungen und Divergenzen und hilft Händlern, Überlastung des Charts und Analyselähmungen zu vermeiden.
Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights Entwicklung von Handelsstrategien mit den Oszillatoren Parafrac und Parafrac V2: Einzeleintritt Performance Insights
In diesem Artikel werden der ParaFrac Oscillator und sein V2-Modell als Handelsinstrumente vorgestellt. Es werden drei Handelsstrategien vorgestellt, die mit Hilfe dieser Indikatoren entwickelt wurden. Jede Strategie wurde getestet und optimiert, um ihre Stärken und Schwächen zu ermitteln. Die vergleichende Analyse zeigte die Leistungsunterschiede zwischen dem Original und dem V2-Modell auf.
Dynamic Mode Decomposition angewandt auf univariate Zeitreihen in MQL5 Dynamic Mode Decomposition angewandt auf univariate Zeitreihen in MQL5
Die Dynamic Mode Decomposition (DMD) ist eine Technik, die in der Regel auf hochdimensionale Datensätze angewendet wird. In diesem Artikel demonstrieren wir die Anwendung der DMD auf univariate Zeitreihen und zeigen, dass sie in der Lage ist, sowohl eine Reihe zu charakterisieren als auch Prognosen zu erstellen. Dabei werden wir die in MQL5 eingebaute Implementierung der Dynamic Mode Decomposition untersuchen und dabei besonderes Augenmerk auf die neue Matrixmethode DynamicModeDecomposition() legen.