English 日本語
preview
Dynamic Swing Architecture: Marktstrukturerkennung von Umkehrpunkten (Swings) bis zur automatisierten Ausführung

Dynamic Swing Architecture: Marktstrukturerkennung von Umkehrpunkten (Swings) bis zur automatisierten Ausführung

MetaTrader 5Beispiele |
17 4
Hlomohang John Borotho
Hlomohang John Borotho

Inhaltsverzeichnis

  1. Einführung
  2. System-Übersicht
  3. Die ersten Schritte
  4. Backtest-Ergebnisse
  5. Schlussfolgerung


Einführung

Im sich ständig ändernden Rhythmus der Märkte erzählt jede Kursbewegung eine Geschichte – eine Reihe von Hochs und Tiefs, die zeigen, wo Käufer und Verkäufer die Kontrolle übernehmen. Für die meisten Händler bildet die Identifizierung dieser Umkehrpunkte die Grundlage für das Verständnis der Marktstruktur. Doch wenn man dies manuell tut, Balken für Balken, kann dies inkonsistent und anfällig für Verzerrungen sein.

Das System der Dynamic Swing Architecture wurde entwickelt, um den Markt mit Präzision zu lesen. Es passt sich dynamisch an die Preisentwicklung an, erkennt neue Schwankungen in Echtzeit und führt automatisch Handelsgeschäfte aus, wenn es zu strukturellen Verschiebungen kommt. Wenn sich ein tiefer Umkehrpunkt bildet, signalisiert es eine potenzielle Aufwärtsbewegung und löst einen Kauf aus, wenn ein hoher Umkehrpunkt erscheint, signalisiert es einen Abwärtsdruck und eröffnet einen Verkauf.

Erkennen eines hohen Umkehrpunkts

Erkennen eines tiefen Umkehrpunkts

Dieser Ansatz macht emotionale Entscheidungen überflüssig und sorgt dafür, dass Ihre Strategie mit der natürlichen Entwicklung des Marktes übereinstimmt. Unabhängig davon, ob Sie mit Gold, Devisen oder Indizes handeln, gilt für das System dasselbe Grundprinzip: Die Marktstruktur bestimmt die Chancen. Mit dieser Architektur können Händler eine Brücke zwischen menschlicher Marktintuition und algorithmischer Ausführung schlagen, was den strukturbasierten Handel konsistenter, adaptiver und leistungsfähiger macht.


System-Übersicht

Das System der Dynamic Swing Architecture basiert auf einem einfachen, aber wirkungsvollen Prinzip: Der Markt wechselt immer zwischen den hohen und tiefen Umkehrpunkten (Swings). Diese Umkehrpunkte stellen den laufenden Kampf zwischen Käufern und Verkäufern dar – und indem das System sie in Echtzeit erfasst, passt es jeden Handel an den natürlichen Rhythmus der Preisbewegung an.

Im Kern scannt das System kontinuierlich die jüngsten Kursbewegungen, um zu erkennen, wann eine Kerze ein neues strukturelles Hoch oder Tief im Verhältnis zu ihren benachbarten Kerzen bildet. Wenn sich ein tiefer Umkehrpunkt bildet – ein lokaler Punkt, an dem der Kurs aufhört, tiefere Tiefs zu bilden – erkennt das System dies als potenziellen Beginn eines Aufwärtsmomentums und platziert einen Kauf. Bildet sich dagegen ein hoher Umkehrpunkt – ein lokaler Höchststand, der keine höheren Werte mehr erreicht – so wird eine Verkaufsposition ausgelöst.

Was dieses System dynamisch macht, ist seine Anpassungsfähigkeit. Anstatt sich auf eine feste Rückblicksperiode oder statische Regeln der Umkehrpunkte zu verlassen, wertet es jeden neuen Balken im Kontext aus und kann so auf die sich verändernde Volatilität und Struktur reagieren. Dadurch wird sichergestellt, dass der EA nicht dem Markt hinterherhinkt oder auf veraltete Daten zurückgreift – er handelt mit dem, was gerade passiert. Um die Klarheit und Transparenz zu erhöhen, visualisiert das System Schwankungen direkt auf dem Chart und markiert jeden erkannten Schwungpunkt und den entsprechenden Handel mit klaren Kennzeichnungen und Pfeilen. Dadurch erhalten die Händler ein unmittelbares Feedback und können beobachten, wie der Algorithmus die Struktur interpretiert.


Die ersten Schritte

//+------------------------------------------------------------------+
//|                                      Dynamic Swing Detection.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>

CTrade TradeManager;
MqlTick currentTick;

input group "General Trading Parameters"
input int     MinSwingBars = 3;          // Minimum bars for swing formation
input double  MinSwingDistance = 100.0;  // Minimum distance for swing (in points)
input bool    UseWickForTrade = false;   // Use wick or body for trade levels

input group "Trailing Stop Parameters"
input bool UseTrailingStop = true;              // Enable trailing stops
input int BreakEvenAtPips = 500;                // Move to breakeven at this profit (pips) 
input int TrailStartAtPips = 600;               // Start trailing at this profit (pips)
input int TrailStepPips = 100;                  // Trail by this many pips 

Wir beginnen damit, dass wir die Handelsinstrumente einbinden und die Objekte vorbereiten, die der EA zur Laufzeit verwenden wird: Mit #include <Trade/Trade.mqh> erhalten wir die Klasse CTrade, und CTrade TradeManager erstellt eine Instanz, die das Senden, Ändern und Schließen von Aufträgen auf sichere, übergeordnete Weise abwickelt, sodass wir keine rohen Ticketaufrufe verwalten müssen. MqlTick currentTick wird deklariert, um den letzten Markttick (Bid/Ask/Time) zu speichern, der bei Ausführungsentscheidungen verwendet wird. Als Nächstes legen wir die allgemeinen Handelsparameter als Eingaben offen, damit der Händler das Verhalten anpassen kann, ohne den Code zu ändern: MinSwingBars steuert, wie viele benachbarte Balken erforderlich sind, bevor wir einen Balken als Umkehrpunkt bezeichnen (wodurch falsche Umkehrpunkte reduziert werden, wenn Sie diesen Wert erhöhen), MinSwingDistance erzwingt eine Mindestgröße für einen Umkehrpunkt (gemessen in Punkten), sodass ein bisschen Rauschen nicht gehandelt werden, und UseWickForTrade lässt Sie wählen, ob als Handelsstufen Kerzendochte (aggressiver, fängt Extreme ein) oder Körper (konservativer) verwenden.

Dann definieren wir die Risiko- und Trailing-Inputs, damit das Handelsmanagement konfigurierbar und transparent ist. UseTrailingStop schaltet die Trailing-Logik ein oder aus, BreakEvenAtPips gibt an, bei welchem Gewinn (in Pips) wir den Stop zum Breakeven verschieben, um das Abwärtsrisiko zu beseitigen, TrailStartAtPips ist die Gewinnschwelle, bei der das Trailing tatsächlich beginnt, und TrailStepPips ist die Granularität, mit der der Stop dem Preis folgt (verschiebt den Stop alle X Pips). Mit diesen Parametern können Sie steuern, wann Gewinne geschützt werden und wie eng der Stopp dem Kurs folgt.

//+------------------------------------------------------------------+
//| Swing detection structure                                        |
//+------------------------------------------------------------------+
struct SwingPoint
{
    int       barIndex;
    double    price;
    datetime  time;
    bool      isHigh;
    double    bodyHigh;
    double    bodyLow;
};

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Clean up all objects on deinit
    ObjectsDeleteAll(0, -1, -1);
}

Anschließend definieren wir eine nutzerdefinierte Struktur namens SwingPoint, die als Grundlage dafür dient, wie das System jeden erkannten Umkehrpunkt speichert und verfolgt. Diese Struktur enthält alle wichtigen Informationen über einen Umkehrpunkt an einem Ort: barIndex zeichnet den genauen Balken auf, an dem der Umkehrpunkt stattgefunden hat, der Preis enthält das Schlüsselniveau des Umkehrpunkts (entweder Hoch oder Tief), und die Zeit markiert den Zeitpunkt, an dem er entstanden ist. Das isHigh-Flag unterscheidet zwischen hohem und tiefem Umkehrpunkt, sodass der Algorithmus entscheiden kann, ob er sich auf einen Verkauf oder Kauf vorbereitet. Zusätzlich erfassen bodyHigh und bodyLow den Bereich des Kerzenkörpers – nützlich, wenn das System zwischen der Verwendung von Dochten oder Kerzenkörpern für die Handelsausführung unterscheiden muss, basierend auf den gewählten Eingabeeinstellungen des Händlers.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if(!isNewBar())
        return;
    
    // Detect swings and manage trading logic
    ManageTradingLogic();
    
    if(UseTrailingStop) ManageOpenTrades();
}

Die Funktion OnTick() ist das Herzstück eines jeden MQL5 Expert Advisors und wird bei jedem Markttick aufgerufen. Darin überprüfen wir zunächst isNewBar(), um sicherzustellen, dass die Logik nur einmal pro neuer Kerze ausgeführt wird, um doppelte Signale innerhalb desselben Balkens zu vermeiden. Sobald dies bestätigt ist, ruft das System ManageTradingLogic() auf, um neue Umkehrpunkte zu erkennen und Handelsgeschäfte auszuführen. Wenn Trailing Stops aktiviert sind, wird auch ManageOpenTrades() ausgelöst, um die Stop-Levels zu aktualisieren und Gewinne dynamisch zu sichern.

//+------------------------------------------------------------------+
//| Manage trading logic                                             |
//+------------------------------------------------------------------+
void ManageTradingLogic()
{
    static SwingPoint lastSwingLow = {-1, 0, 0, false, 0, 0};
    static SwingPoint lastSwingHigh = {-1, 0, 0, true, 0, 0};
    static bool lookingForHigh = false;
    static bool lookingForLow = false;
    
    // Detect current swings
    SwingPoint currentSwing;
    if(!DetectSwing(currentSwing))
        return;
    
    // Bullish scenario logic
    if(!lookingForHigh && !currentSwing.isHigh) // New swing low detected
    {
        lastSwingLow = currentSwing;
        lookingForHigh = true;
        lookingForLow = false;
        Print("Swing Low detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_BUY, "SwingLow_Buy");
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
    else if(lookingForHigh && currentSwing.isHigh) // Swing high after swing low
    {
        lastSwingHigh = currentSwing;
        lookingForHigh = false;
        Print("Swing High detected after Swing Low.");
        
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    
    // Bearish scenario logic
    if(!lookingForLow && currentSwing.isHigh) // New swing high detected
    {
        lastSwingHigh = currentSwing;
        lookingForLow = true;
        lookingForHigh = false;
        Print("Swing High detected at: ", DoubleToString(currentSwing.price, _Digits), " Time: ", TimeToString(currentSwing.time));
        ExecuteTrade(ORDER_TYPE_SELL, "SwingHigh_Sell");
        // Draw swing high
        string swingName = "SwingHigh_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 218, clrRed, -1);
    }
    else if(lookingForLow && !currentSwing.isHigh) // Swing low after swing high
    {
        lastSwingLow = currentSwing;
        lookingForLow = false;
        Print("Swing Low detected after Swing High.");
        
        // Draw swing low
        string swingName = "SwingLow_" + IntegerToString(currentSwing.time);
        drawswing(swingName, currentSwing.time, currentSwing.price, 217, clrBlue, 1);
    }
}

Die Funktion ManageTradingLogic() ist das Gehirn des Systems, das alle Entscheidungen auf der Grundlage der neu erkannten Schwankungen trifft. Darin initialisieren wir zwei statische Variablen, lastSwingLow und lastSwingHigh, um die letzten Schwungpunkte zu speichern, damit das System immer weiß, wo die letzte strukturelle Drehung stattgefunden hat. Flags wie lookingForHigh und lookingForLow helfen dem EA dabei, sich zu merken, welche Art von Schwung er als Nächstes erwartet, um sicherzustellen, dass er logisch und nicht zufällig auf jede Kursbewegung reagiert. Sobald sich ein neuer Balken bildet, ruft das System DetectSwing() auf, um festzustellen, ob ein gültiger Umkehrpunkt stattgefunden hat, und wenn nicht, wartet es einfach bis zur nächsten Balkenaktualisierung.

Wenn sich ein steigendes Szenario herausbildet, d. h. ein neuer tiefer Umkehrpunkt festgestellt wurde, erkennt das System dies als potenziellen Kaufdruck. Er aktualisiert den Datensatz von lastSwingLow, sucht nach dem nächsten hohen Umkehrpunkt und führt mit ExecuteTrade(ORDER_TYPE_BUY, „SwingLow_Buy“) sofort einen Kaufhandel aus. Um dieses Ereignis visuell zu verdeutlichen, zeichnet der EA auch eine blaue Markierung auf dem Chart, wo sich der tiefe Umkehrpunkt gebildet hat, sodass die Händler sehen können, wie der Algorithmus die Preisstruktur in Echtzeit interpretiert. Sobald danach ein hoher Umkehrpunkt auftaucht, wird er einfach rot eingezeichnet, um zu bestätigen, dass der Aufwärtstrend beendet ist.

Im Abwärtsszenario ist die Logik spiegelbildlich. Wenn ein hoher Umkehrpunkt festgestellt wird, bedeutet dies potenziellen Verkaufsdruck. Der EA zeichnet diesen Umkehrpunkt auf, schaltet die Richtungsflags um und führt einen Verkaufshandel mit ExecuteTrade(ORDER_TYPE_SELL, „SwingHigh_Sell“) aus. Auch hier ist der Umkehrpunkt zur Verdeutlichung rot eingezeichnet, gefolgt von einer blauen Swing-Markierung, sobald der Markt sein nächstes Tief erreicht hat. Diese abwechselnde Erkennung von Hochs und Tiefs sorgt dafür, dass sich das System kontinuierlich an die aktuelle Preisentwicklung anpasst und automatisch Handelsgeschäfte ausführt, die sich an der aktuellen Marktstruktur orientieren, ohne dass manuelle Eingriffe erforderlich sind.

//+------------------------------------------------------------------+
//| Detect swing points dynamically                                  |
//+------------------------------------------------------------------+
bool DetectSwing(SwingPoint &swing)
{
    swing.barIndex = -1;
    
    int barsToCheck = MinSwingBars * 2 + 1;
    if(Bars(_Symbol, _Period) < barsToCheck) 
        return false;
    
    // Check multiple recent bars for swings
    for(int i = MinSwingBars; i <= MinSwingBars + 5; i++)
    {
        if(i >= Bars(_Symbol, _Period)) break;
        
        // Check for swing high
        if(IsSwingHigh(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iHigh(_Symbol, _Period, i) : 
                          MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = true;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
        // Check for swing low
        else if(IsSwingLow(i))
        {
            swing.barIndex = i;
            swing.price = UseWickForTrade ? iLow(_Symbol, _Period, i) : 
                          MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.time = iTime(_Symbol, _Period, i);
            swing.isHigh = false;
            swing.bodyHigh = MathMax(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            swing.bodyLow = MathMin(iOpen(_Symbol, _Period, i), iClose(_Symbol, _Period, i));
            return true;
        }
    }
    
    return false;
}

Die Funktion DetectSwing() ist für das dynamische Scannen der letzten Kursbalken verantwortlich, um neue hohe oder tiefe Umkehrpunkte zu identifizieren. Er stellt zunächst sicher, dass genügend Kerzen auf dem Chart vorhanden sind, um eine Auswertung vorzunehmen, indem er MinSwingBars prüft, und durchläuft dann einen kleinen Bereich der letzten Balken, um nach potenziellen Wendepunkten zu suchen. Für jeden Balken wird IsSwingHigh() oder IsSwingLow() aufgerufen, um zu überprüfen, ob sich dieser Balken strukturell von seinen Nachbarn unterscheidet. Wenn ein gültiger Umkehrpunkt gefunden wird, zeichnet die Funktion dessen Index, Preis und Zeit auf, bestimmt, ob es sich um ein Hoch oder ein Tief handelt, und speichert sowohl den Docht- als auch den Körperbereich, je nach der vom Händler gewählten Einstellung. Sobald ein Umkehrpunkt bestätigt wird, gibt die Funktion sofort den Wert „true“ zurück und signalisiert der Hauptlogik, dass gerade eine strukturelle Verschiebung erkannt wurde und daraufhin gehandelt werden kann.

//+------------------------------------------------------------------+
//| Check if bar is swing high                                       |
//+------------------------------------------------------------------+
bool IsSwingHigh(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentHigh = iHigh(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftHigh = iHigh(_Symbol, _Period, barIndex + i);
        if(leftHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightHigh = iHigh(_Symbol, _Period, barIndex - i);
        if(rightHigh >= currentHigh - MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

//+------------------------------------------------------------------+
//| Check if bar is swing low                                        |
//+------------------------------------------------------------------+
bool IsSwingLow(int barIndex)
{
    if(barIndex < MinSwingBars || barIndex >= Bars(_Symbol, _Period) - MinSwingBars)
        return false;
        
    double currentLow = iLow(_Symbol, _Period, barIndex);
    
    // Check left side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double leftLow = iLow(_Symbol, _Period, barIndex + i);
        if(leftLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    // Check right side
    for(int i = 1; i <= MinSwingBars; i++)
    {
        double rightLow = iLow(_Symbol, _Period, barIndex - i);
        if(rightLow <= currentLow + MinSwingDistance * _Point)
            return false;
    }
    
    return true;
}

Die Funktionen IsSwingHigh() und IsSwingLow() bilden den analytischen Kern der Erkennung von Umkehrpunkten, indem sie feststellen, ob ein bestimmter Balken als lokales Hoch oder Tief hervorsticht. Bei IsSwingHigh() prüft der Algorithmus zunächst, ob der zu prüfende Balken genügend benachbarte Kerzen auf beiden Seiten hat, mit denen er verglichen werden kann. Anschließend wird der Höchstkurs des Balkens ermittelt und mit den Höchstkursen der umliegenden Kerzen verglichen. Wenn einer dieser nahegelegenen Höchststände innerhalb der definierten MinSwingDistance liegt, wird der Balken als hoher Umkehrpunkt disqualifiziert, da die Kursbewegung um ihn herum nicht deutlich genug ist. Dadurch wird sichergestellt, dass nur klar definierte Höchststände, bei denen der Markt tatsächlich von einem Aufwärts- in einen Abwärtstrend übergegangen ist, als gültige hoher Umkehrpunkt anerkannt werden.

In ähnlicher Weise führt IsSwingLow() die gleiche strukturelle Logik aus, jedoch in umgekehrter Weise für Tiefstwerte. Es wird geprüft, ob der Tiefpunkt des Balkens deutlich unter seinen Nachbarn innerhalb der zulässigen MinSwingDistance liegt. Wenn eine der umgebenden Kerzen Tiefstwerte aufweist, die zu nahe beieinander liegen, bedeutet dies, dass der Markt nicht wirklich nach oben gedreht hat, sodass der Balken ignoriert wird. Durch diese Abstandsüberprüfungen und Nachbarschaftstests filtert das System Rauschen heraus und konzentriert sich auf echte strukturelle Wendepunkte, d. h. auf solche, die eine echte Richtungsabsicht und keine vorübergehenden Kursschwankungen markieren.

//+------------------------------------------------------------------+
//| Draw swing point on chart                                        |
//+------------------------------------------------------------------+
void drawswing(string objName, datetime time, double price, int arrCode, color clr, int direction)
{
   if(ObjectFind(0, objName) < 0)
   {
      // Create arrow object
      if(!ObjectCreate(0, objName, OBJ_ARROW, 0, time, price))
      {
         Print("Error creating swing object: ", GetLastError());
         return;
      }
      
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      if(direction > 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      }
      else if(direction < 0)
      {
         ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      }
      
      // Create text label
      string textName = objName + "_Text";
      string text = DoubleToString(price, _Digits);
      
      if(ObjectCreate(0, textName, OBJ_TEXT, 0, time, price))
      {
         ObjectSetString(0, textName, OBJPROP_TEXT, text);
         ObjectSetInteger(0, textName, OBJPROP_COLOR, clr);
         ObjectSetInteger(0, textName, OBJPROP_FONTSIZE, 8);
         
         // Adjust text position based on direction
         double offset = (direction > 0) ? - (100 * _Point) : (100 * _Point);
         ObjectSetDouble(0, textName, OBJPROP_PRICE, price + offset);
      }
   }
}

Die Funktion drawswing() sorgt für die visuelle Darstellung der Umkehrpunkte direkt auf dem Chart, sodass Händler sehen können, wo der Algorithmus wichtige Wendepunkte identifiziert hat. Zunächst wird geprüft, ob ein Objekt mit dem angegebenen Namen bereits existiert, um eine Duplizierung zu vermeiden. Ist dies nicht der Fall, wird ein Pfeil mit dem Zeitpunkt und dem Preis des erkannten Schwungs erstellt. Das Aussehen des Pfeils – Farbe, Breite und Anker – wird angepasst, je nachdem, ob er ein hoher oder tiefe Umkehrpunkt darstellt, wodurch das Chart visuell intuitiv wird (z. B. rote Pfeile für Hochs und blaue für Tiefs). Zusätzlich erstellt die Funktion eine Textbeschriftung, die die genaue Preisstufe neben dem Pfeil anzeigt, zur besseren Lesbarkeit leicht versetzt. Diese Visualisierung hilft nicht nur dabei, zu bestätigen, dass der EA die Struktur richtig erkennt, sondern gibt Händlern auch einen klaren Echtzeitüberblick über Marktrhythmus und Wendepunkte.

//+------------------------------------------------------------------+
//| Helper functions                                                 |
//+------------------------------------------------------------------+
bool isNewBar()
{
   static datetime lastBar;
   datetime currentBar = iTime(_Symbol, _Period, 0);
   if(lastBar != currentBar)
   {
      lastBar = currentBar;
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| Execute trade with proper risk management                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment)
{
   // Calculate position size based on risk
   double lotSize = 0.03;
   if(lotSize <= 0)
   {
      Print("Failed to calculate position size");
      return;
   }
   
   // Get current tick data
   if(!SymbolInfoTick(_Symbol, currentTick))
   {
      Print("Failed to get current tick data. Error: ", GetLastError());
      return;
   }
   
   // Define stop levels in points (adjust these values as needed)
   int stopLossPoints = 600;
   int takeProfitPoints = 2555;
   
   // Calculate stop loss and take profit prices
   double stopLoss = 0.0, takeProfit = 0.0;
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   
   if(orderType == ORDER_TYPE_BUY)
   {
      stopLoss = NormalizeDouble(currentTick.bid - (stopLossPoints * point), digits);
      takeProfit = NormalizeDouble(currentTick.ask + (takeProfitPoints * point), digits);
   }
   else if(orderType == ORDER_TYPE_SELL)
   {
      stopLoss = NormalizeDouble(currentTick.ask + (stopLossPoints * point), digits);
      takeProfit = NormalizeDouble(currentTick.bid - (takeProfitPoints * point), digits);
   }
   
   // Validate stop levels before sending the trade
   if(!ValidateStopLevels(orderType, currentTick.ask, currentTick.bid, stopLoss, takeProfit))
   {
      Print("Invalid stop levels. Trade not executed.");
      return;
   }
   
   // Execute trade
   bool requestSent;
   if(orderType == ORDER_TYPE_BUY)
   {
      requestSent = TradeManager.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   else
   {
      requestSent = TradeManager.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, comment);
   }
   
   // Check if the request was sent successfully and then check the server's result
   if(requestSent)
   {
      // Check the server's return code from the trade operation
      uint result = TradeManager.ResultRetcode();
      if(result == TRADE_RETCODE_DONE || result == TRADE_RETCODE_DONE_PARTIAL)
      {
         Print("Trade executed successfully. Ticket: ", TradeManager.ResultDeal());
      }
      else if(result == TRADE_RETCODE_REQUOTE || result == TRADE_RETCODE_TIMEOUT || result == TRADE_RETCODE_PRICE_CHANGED)
      {
         Print("Trade failed due to price change. Consider implementing a retry logic. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Here you can add logic to re-check prices and re-send the request
      }
      else
      {
         Print("Trade execution failed. Retcode: ", TradeManager.ResultRetcodeDescription());
         // Handle other specific errors like TRADE_RETCODE_INVALID_STOPS (10016)
      }
   }
   else
   {
      Print("Failed to send trade request. Last Error: ", GetLastError());
   }
}

Die Hilfsfunktionen spielen eine wesentliche Rolle dabei, dass der EA effizient arbeitet und nur bei Bedarf reagiert. Die Funktion isNewBar() verhindert beispielsweise, dass bei jedem Tick eine sich wiederholende Logik abläuft, indem sie bestätigt, dass sich eine neue Kerze gebildet hat, bevor irgendwelche Berechnungen oder Handelsgeschäfte stattfinden. Dazu speichert sie die Zeit des letzten verarbeiteten Balkens und vergleicht sie mit dem aktuellen; wenn sie sich unterscheiden, bedeutet dies, dass ein neuer Balken geöffnet wurde, und die Funktion gibt true zurück. Dieser einfache, aber entscheidende Mechanismus reduziert unnötige Berechnungen und stellt sicher, dass Handelsgeschäfte nur einmal pro Kerze ausgewertet werden – so bleibt das System sauber, effizient und synchron mit neuen Marktdaten.

Die Funktion ExecuteTrade() führt Aufträge mit strukturierter Präzision und eingebauter Risikokontrolle aus. Es beginnt mit der Berechnung der Losgröße (derzeit fest, aber anpassbar an risikobasierte Formeln), ruft die neuesten Tick-Daten ab und definiert Stop-Loss- und Take-Profit-Levels in Punkten. Je nachdem, ob es sich bei dem Signal um ein Kauf- oder Verkaufssignal handelt, berechnet es die entsprechenden Kursniveaus, normalisiert sie und prüft ihren Abstand, bevor es einen Auftrag über den CTrade-Manager sendet. Nach der Übermittlung des Handelsgeschäftes prüft es den Antwortcode des Brokers, um festzustellen, ob die Ausführung erfolgreich war oder ob Probleme wie Requotes oder Preisänderungen aufgetreten sind. Diese Detailgenauigkeit stellt sicher, dass das System automatisch und sicher handelt, indem es jeden Auftrag überprüft und mit Fehlern sorgfältig umgeht, so wie es ein professioneller Handelsalgorithmus tun sollte.

//+------------------------------------------------------------------+
//| Validate stop levels against broker requirements                 |
//+------------------------------------------------------------------+
bool ValidateStopLevels(ENUM_ORDER_TYPE orderType, double ask, double bid, double &sl, double &tp)
{
   double spread = ask - bid;
   // Get the minimum allowed stop distance in points
   int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDist = stopLevel * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   if(orderType == ORDER_TYPE_BUY)
   {
      // Check if Stop Loss is too close to or above the current Bid price
      if(sl >= bid - minDist) 
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or below the current Ask price
      if(tp <= ask + minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Buy Take Profit adjusted to minimum allowed level.");
      }
   }
   else // ORDER_TYPE_SELL
   {
      // Check if Stop Loss is too close to or below the current Ask price
      if(sl <= ask + minDist)
      {
         // Option 1: Adjust SL to the minimum allowed distance
         sl = NormalizeDouble(ask + minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Stop Loss adjusted to minimum allowed level.");
      }
      // Check if Take Profit is too close to or above the current Bid price
      if(tp >= bid - minDist)
      {
         // Option 1: Adjust TP to the minimum allowed distance
         tp = NormalizeDouble(bid - minDist, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
         Print("Sell Take Profit adjusted to minimum allowed level.");
      }
   }
   
   return true;
}

Die Funktion ValidateStopLevels() stellt sicher, dass die Stop-Loss- und Take-Profit-Levels eines jeden Handelsgeschäftes den Mindestabständen des Brokers entsprechen, um eine Ablehnung ungültiger Orders zu verhindern. Es ruft das SYMBOL_TRADE_STOPS_LEVEL des Brokers ab, berechnet den minimal zulässigen Stop-Abstand in Punkten und passt sowohl SL als auch TP an, wenn sie zu nahe am Geld- oder Briefkurs des Marktes gesetzt sind. Diese Schutzmaßnahme garantiert nicht nur die Einhaltung der Regeln des Brokers, sondern stabilisiert auch die Auftragsausführung, indem riskante oder ungültige Kursniveaus automatisch korrigiert werden, bevor ein Handel an den Server gesendet wird.

//+------------------------------------------------------------------+
//| Trailing stop function                                           |
//+------------------------------------------------------------------+
void ManageOpenTrades()
{
   if(!UseTrailingStop) return;

   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
   {
      // get ticket (PositionGetTicket returns ulong; it also selects the position)
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;

      // ensure the position is selected (recommended)
      if(!PositionSelectByTicket(ticket)) continue;

      // Optional: only operate on same symbol or your EA's magic number
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      // if(PositionGetInteger(POSITION_MAGIC) != MyMagicNumber) continue;

      // read position properties AFTER selecting
      double open_price   = PositionGetDouble(POSITION_PRICE_OPEN);
      double current_price= PositionGetDouble(POSITION_PRICE_CURRENT);
      double current_sl   = PositionGetDouble(POSITION_SL);
      double current_tp   = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      // pip size
      double pip_price = PipsToPrice(1);

      // profit in pips (use current_price returned above)
      double profit_price = (pos_type == POSITION_TYPE_BUY) ? (current_price - open_price)
                                                             : (open_price - current_price);
      double profit_pips = profit_price / pip_price;
      if(profit_pips <= 0) continue;

      // get broker min stop distance (in price units)
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      double stop_level_points = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
      double stopLevelPrice = stop_level_points * point;

      // get market Bid/Ask for stop-level checks
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      // -------------------------
      // 1) Move to breakeven
      // -------------------------
      if(profit_pips >= BreakEvenAtPips)
      {
         double breakeven = open_price;
         // small adjustment to help account for spread/commissions (optional)
         if(pos_type == POSITION_TYPE_BUY)  breakeven += point; 
         else                                breakeven -= point;

         // Check stop-level rules: for BUY SL must be >= (bid - stopLevelPrice) distance below bid
         if(pos_type == POSITION_TYPE_BUY)
         {
            if((bid - breakeven) >= stopLevelPrice) // allowed by server
            {
               if(breakeven > current_sl) // only move SL up
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
         else // SELL
         {
            if((breakeven - ask) >= stopLevelPrice)
            {
               if(current_sl == 0.0 || breakeven < current_sl) // move SL down
               {
                  if(!TradeManager.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
      } // end breakeven

      // -------------------------
      // 2) Trailing in steps after TrailStartAtPips
      // -------------------------
      if(profit_pips >= TrailStartAtPips)
      {
         double extra_pips = profit_pips - TrailStartAtPips;
         int step_count = (int)(extra_pips / TrailStepPips);

         // compute desired SL relative to open_price (as per your original request)
         double desiredOffsetPips = (double)(TrailStartAtPips + step_count * TrailStepPips);
         double new_sl_price;

         if(pos_type == POSITION_TYPE_BUY)
         {
            new_sl_price = open_price + PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Bid
            if((bid - new_sl_price) < stopLevelPrice)
               new_sl_price = bid - stopLevelPrice;

            if(new_sl_price > current_sl) // only move SL up
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Buy) ticket %I64u error %d", ticket, GetLastError());
            }
         }
         else // SELL
         {
            new_sl_price = open_price - PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Ask
            if((new_sl_price - ask) < stopLevelPrice)
               new_sl_price = ask + stopLevelPrice;

            if(current_sl == 0.0 || new_sl_price < current_sl) // only move SL down (more profitable)
            {
               if(!TradeManager.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Sell) ticket %I64u error %d", ticket, GetLastError());
            }
         }
      }
   } 
}

//+------------------------------------------------------------------+
//| Helper: convert pips -> price (taking 3/5-digit fractional pips) |
//+------------------------------------------------------------------+
double PipsToPrice(int pips)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits   = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double pip   = (digits == 3 || digits == 5) ? point * 10.0 : point;
   return(pips * pip);
}

Die Funktion ManageOpenTrades() automatisiert den Prozess der Gewinnsicherung und Risikominderung durch dynamische Stop-Loss-Anpassungen. Es überprüft jedes offene Handelsgeschäft, stellt sicher, dass er zum gleichen Symbol oder zur gleichen magischen Zahl gehört, und misst, wie viele Pips die Position derzeit im Gewinn ist. Sobald der Gewinn einen definierten Schwellenwert erreicht, verschiebt die Funktion zunächst den Stop-Loss auf Breakeven, um einen risikofreien Handel zu sichern, und stellt gleichzeitig sicher, dass alle Änderungen die Mindestanforderungen des Brokers für den Stop-Level-Abstand erfüllen.

Nach dem Breakeven geht das System in einen strukturierten Trailing-Stop-Modus über, der den Kurs verfolgt, während er weiter im Gewinn ist. Dies geschieht in kontrollierten Schritten, basierend auf den nutzerdefinierten TrailStepPips, um sicherzustellen, dass der Stop-Loss nur dann verschoben wird, wenn ein sinnvoller Gewinn erzielt wurde. Die Funktion berechnet das neue Stop-Loss-Niveau in Bezug auf den offenen Preis, überprüft, ob es den Serverregeln entspricht, und aktualisiert es nur, wenn das neue Niveau die Rentabilität der Position verbessert.

Der Trailing-Stop-Mechanismus fügt eine adaptive Ebene des Handelsmanagements hinzu und stellt sicher, dass der Handel sowohl geschützt als auch für maximale potenzielle Gewinne optimiert wird. Es ermöglicht ein Gleichgewicht zwischen dem Auslaufenlassen von Gewinnen und der Beibehaltung der Risikodisziplin, indem die Stopp-Levels methodisch erhöht werden, wenn der Handel reift. Ein solches dynamisches Management hilft Händlern, nachhaltige Trends zu erfassen und gleichzeitig emotionale Eingriffe und manuelle Eingriffe zu minimieren – wichtige Merkmale eines robusten, automatisierten Handelssystems.


Backtest-Ergebnisse

Die Backtests wurde für den Zeitrahmen 2H über ein etwa zweimonatiges Testfenster (08. April 2025 bis 29. Juli 2025) mit den Standardeinstellungen bewertet.

Kapitalkurve

BackTest-Ergebnisse


Schlussfolgerung

Abschließend haben wir die Dynamic Swing Architecture entwickelt: „Marktstrukturerkennung von Umkehrpunkten (Swings) bis zur automatisierten Ausführung“, ein System, das rohe Marktbewegungen durch eine umkehrbasierte Logik interpretiert und in intelligente Handelsentscheidungen umsetzt. Die Architektur beginnt mit einem präzisen Erkennen von Umkehrpunkten, die kritische Hochs und Tiefs identifiziert, die das Rückgrat der Marktstruktur bilden. Anschließend wird eine risikogesteuerte Ausführung integriert, die sicherstellt, dass jeder Handel mit der richtigen Validierung eingegeben wird. Um die Handelseffizienz aufrechtzuerhalten, verwendet das System eine automatische Trailing-Logik, die sich an die Preisbewegung anpasst und die Stop-Loss-Werte auf den Breakeven und die Trailing-Gewinne auf intelligente Weise verschiebt, wenn sich Trends entwickeln. Jede Komponente – von der Strukturanalyse über die Ausführung bis hin zur Verwaltung – arbeitet zusammen, um einen sich selbst tragenden, adaptiven Handelsrahmen zu bilden.

Zusammenfassend lässt sich sagen, dass den Händlern von der Dynamic Swing Architecture ein völlig autonomes System an die Hand gibt, das technische Präzision, Verständnis der Marktstruktur und adaptives Handelsmanagement miteinander verbindet. Durch die Umwandlung von Kursschwankungen in verwertbare Erkenntnisse und die automatische Verwaltung von Positionen werden menschliche Fehler und emotionale Voreingenommenheit reduziert und gleichzeitig die Gewinnmöglichkeiten maximiert. Dieser Rahmen verbessert die Handelskonsistenz und dient als Grundlage für fortschrittliche Marktintelligenz, bei der jeder Umkehrpunkt, jedes Retracement und jede Strukturverschiebung zu intelligenteren, marktdatengesteuerten Handelsentscheidungen beiträgt.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
TahianaBE
TahianaBE | 22 Okt. 2025 in 11:20
Vielen Dank für den tollen Artikel.
Hlomohang John Borotho
Hlomohang John Borotho | 23 Okt. 2025 in 22:11
TahianaBE #:
Vielen Dank für den tollen Artikel.
Sie sind willkommen.
Tonij Trisno
Tonij Trisno | 4 Nov. 2025 in 06:47
Hallo. Ich danke Ihnen für diese wunderbare EA und Backtest-Ergebnis. Darf ich wissen, welches Paar/Symbol Sie es auf H2 Zeitrahmen getestet, die das Ergebnis in Ihrem Artikel geben.
Das wird uns helfen, zu bestätigen, wir haben die richtige gleiche/ähnliche Ergebnis zu. Vielen Dank!
Hlomohang John Borotho
Hlomohang John Borotho | 6 Nov. 2025 in 21:51
Tonij Trisno Backtest Ergebnis. Darf ich wissen, welches Paar/Symbol Sie es auf H2 Zeitrahmen getestet, die das Ergebnis in Ihrem Artikel geben.
Das wird uns helfen, zu bestätigen, wir haben die richtige gleiche/ähnliche Ergebnis zu. Vielen Dank!
Hey, um die gleichen Ergebnisse zu erhalten, achten Sie bitte auf das Testfenster "(08. April 2025 bis 29. Juli 2025)".
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 85): Verwendung von Mustern des Stochastik-Oszillators und der FrAMA mit Beta-VAE-Inferenzlernen MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 85): Verwendung von Mustern des Stochastik-Oszillators und der FrAMA mit Beta-VAE-Inferenzlernen
Dieser Beitrag schließt an Teil 84 an, in dem wir die Kombination von Stochastik und Fractal Adaptive Moving Average vorgestellt haben. Wir verlagern nun den Schwerpunkt auf das Inferenzlernen, um zu sehen, ob die im letzten Artikel unterlegenen Muster eine Trendwende erfahren könnten. Der Stochastik und der FrAMA sind eine sich ergänzende Paarung von Momentum und Trend. Für unser Inferenzlernen greifen wir auf den Beta-Algorithmus eines Variational Auto Encoders zurück. Außerdem implementieren wir, wie immer, eine nutzerdefinierte Signalklasse, die für die Integration mit dem MQL5-Assistenten entwickelt wurde.
Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 4): Überwindung mehrzeiliger Eingaben, Sicherstellung der Chat-Persistenz und Generierung von Signalen Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 4): Überwindung mehrzeiliger Eingaben, Sicherstellung der Chat-Persistenz und Generierung von Signalen
In diesem Artikel erweitern wir das in ChatGPT integrierte Programm in MQL5, indem wir die Beschränkungen bei mehrzeiligen Eingaben durch eine verbesserte Textdarstellung überwinden, eine Seitenleiste für die Navigation im persistenten Chatspeicher mit AES256-Verschlüsselung und ZIP-Komprimierung einführen und erste Handelssignale durch die Integration von Chart-Daten erzeugen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 84): Verwendung von Mustern des Stochastik-Oszillators und des FrAMA – Schlussfolgerung MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 84): Verwendung von Mustern des Stochastik-Oszillators und des FrAMA – Schlussfolgerung
Der Stochastik-Oszillator und der Fractal Adaptive Moving Average sind ein Indikatorpaar, das aufgrund seiner Fähigkeit, sich gegenseitig zu ergänzen, in einem MQL5 Expert Advisor verwendet werden kann. Wir haben diese Paarung im letzten Artikel vorgestellt und wollen nun abschließend ihre 5 letzten Signalmuster betrachten. Dabei verwenden wir wie immer den MQL5-Assistenten, um deren Potenzial zu erkunden und zu testen.