English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 41): Candle Range Theory (CRT) – Akkumulation, Manipulation, Distribution (AMD)

Automatisieren von Handelsstrategien in MQL5 (Teil 41): Candle Range Theory (CRT) – Akkumulation, Manipulation, Distribution (AMD)

MetaTrader 5Handel |
37 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

In unserem vorherigen Artikel (Teil 40) haben wir ein Retracement-Handelssystem in MetaQuotes Language 5 (MQL5) entwickelt, das Retracement-Levels entweder anhand von Tageskerzenbereichen oder Lookback-Arrays berechnet, Aufwärts- oder Abwärtskonstellationen identifiziert, bei Überschreiten bestimmter Niveaus einen Einstieg auslöst und optional die Schlusskurse der Balken bei neuen Fib-Berechnungen enthält. In Teil 41 entwickeln wir das Handelssystem der Theorie des Kerzenbereichs, der Candle Range Theory (CRT), das die Phasen Akkumulation, Manipulation und Distribution (AMD) umfasst.

Dieses System identifiziert Akkumulationsbereiche auf einem bestimmten Zeitrahmen, erkennt Durchbrüche mit optionaler Filterung der Manipulationstiefe, bestätigt Umkehrungen durch Schlusskurse der Balken für Einstiegsgeschäfte in der Distributionsphase, unterstützt dynamische oder statische Stop-Loss- und Take-Profit-Berechnungen auf der Grundlage von Risiko-Ertrags-Verhältnissen, umfasst optionale Trailing-Stops und Positionslimits pro Richtung für das Risikomanagement und visualisiert Phasen mit farbigen Rechtecken, Niveaus und Textbeschriftungen auf dem Chart. Wir werden die folgenden Themen behandeln:

  1. Das Verständnis des Systems der Theorie des Kerzenbereichs (CRT)
  2. Implementierung in MQL5
  3. Backtesting
  4. Schlussfolgerung

Am Ende haben Sie eine funktionierende MQL5-Strategie für den Handel mit der Theorie des Kerzenbereichs mit AMD Phasen, bereit für die Anpassung – fangen wir an!


Das Verständnis des Systems der Theorie des Kerzenbereichs (CRT)

Die Candle Range Theory (CRT) ist eine Preisaktionsstrategie, die sich auf die Identifizierung von Schlüsselphasen innerhalb der Spanne einer Kerze konzentriert, um Umkehrgeschäfte mit hoher Wahrscheinlichkeit zu erzielen. Sie unterteilt die Marktbewegungen in drei Kernphasen: Akkumulation, bei der sich der Preis innerhalb einer bestimmten Spanne konsolidiert, was oft auf einen institutionellen Aufbau hindeutet; Manipulation, bei der der Preis kurzzeitig die Extreme der Spanne durchbricht, um Händler zu fangen und schwache Positionen auszuschütteln; und Distribution, bei der sich die wahre Richtungsbewegung nach einer bestätigten Umkehrung zurück in die Spanne entfaltet. Dieser Ansatz macht sich die Idee zunutze, dass signifikante Durchbrüche oft Fehlbewegungen sind, die darauf abzielen, Liquidität zu schaffen, gefolgt von einer starken Gegenbewegung in die andere Richtung.

Bei einer positiven (steigenden) Bereichskonstellation suchen wir nach einem Kerzenbereich, der sich nach oben schließt, nehmen einen Durchbruch nach unten als Manipulation vorweg, um die Stopps unterhalb des Tiefs zu reißen, und kaufen in Erwartung einer Aufwärtsdistribution, sobald die Umkehrung wieder über dem Tief liegt und bestätigt wird. Umgekehrt identifizieren wir bei einer negativen (fallende) Bereichskonstellation eine sich nach unten schließende Kerze, achten auf einen Durchbruch nach oben als Manipulation oberhalb des Hochs und verkaufen bei einer Kehrtwende unter das Hoch, die auf eine Verteilung nach unten abzielt. Durch die Einbeziehung von Filtern wie der Mindestmanipulationstiefe und der balken-basierten Umkehrbestätigung können wir minderwertige Konstellationen vermeiden und uns auf diejenigen konzentrieren, die mehr Überzeugungskraft besitzen. Sehen Sie sich unten ein Beispiel für eine CRT-Einrichtung an.

KERZENBEREICHSTHEORIE (CRT) KONSTELLATION

Unser Plan ist es, Kumulierungsbereiche in einem vom Nutzer festgelegten Zeitrahmen zu definieren. Wenn diese Option aktiviert ist, werden wir Verstöße erkennen und die Manipulationstiefe anhand eines prozentualen Schwellenwerts überprüfen. Wir bestätigen auch Umkehrungen durch eine bestimmte Anzahl von Schlussbalken in einem Bestätigungszeitrahmen. Handelsgeschäfte werden mit Positionslimits pro Richtung ausgeführt. Wir setzen dynamische oder statische Stop-Loss- und Take-Profit-Niveaus auf der Grundlage des Risiko-Ertrags-Verhältnisses ein. Nach Erreichen einer Gewinnschwelle können wir optionale Trailing-Stops einbauen. Alle Phasen werden mit Rechtecken, Ebenen und Beschriftungen auf dem Chart visualisiert, um eine intuitive Überwachung zu ermöglichen. Kurz gesagt, hier ist eine visuelle Darstellung unserer Ziele.

CRT SYSTEMBEISPIEL


Implementation in MQL5

Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner Experts, klicken auf die Registerkarte „Neu“ und folgen den Anweisungen, um die Datei zu erstellen. Sobald sie erstellt ist, müssen wir in der Programmierumgebung einige Eingabeparameter und die globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//|                                   CRT Candle Range Theory EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum SLTP_Method {                                                // Define SL/TP method enum
   Dynamic_Method = 0,                                            // Dynamic based on breach extreme
   Static_Method  = 1                                             // Static based on fixed points
};

enum TrailingTypeEnum {                                           // Define trailing type enum
   Trailing_None   = 0,                                           // None
   Trailing_Points = 1                                            // By Points
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_TIMEFRAMES RangeTF = PERIOD_H4;                        // Timeframe for Range Definition
input double TradeVolume = 0.01;                                  // Trade Volume Size
input double RR_Ratio = 1.3;                                      // Risk to Reward Ratio
input SLTP_Method SLTP_Approach = Static_Method;                  // SL/TP Calculation Method
input int SL_Points = 100;                                        // SL Points (for Static Method)
input TrailingTypeEnum TrailingType = Trailing_None;              // Trailing Stop Type
input double Trailing_Stop_Points = 30.0;                         // Trailing Stop in Points
input double Min_Profit_To_Trail_Points = 50.0;                   // Min Profit to Start Trailing in Points
input int UniqueID = 123456789;                                   // Unique Trade Identifier
input int MaxPositionsDir = 1;                                    // Max Positions per Direction
input ENUM_TIMEFRAMES ConfirmTF = PERIOD_CURRENT;                 // Confirmation Timeframe (for bar closures)
input int ConfirmBars = 1;                                        // Bars to Confirm Reversal on Close (0 to disable)
input bool UseManipFilter = true;                                 // Use Manipulation Depth Filter
input double MinManipPct = 5.0;                                   // Min Manipulation % of Range (if filter enabled)
input double DistribProjPct = 50.0;                               // Distribution Projection % of Range Duration

Wir beginnen die Implementierung, indem wir die Handelsbibliothek mit „#include <Trade\Trade.mqh>“ einbinden. Diese Bibliothek bietet wichtige Klassen und Funktionen für Handelsoperationen. Als Nächstes definieren wir Enumerationen, um die vom Nutzer konfigurierbaren Optionen zu kategorisieren. Wir erstellen die Enumeration „SLTP_Method“ mit den Werten „Dynamic_Method“ für die dynamische Berechnung von Stop-Loss und Take-Profit auf der Grundlage des Extremwerts des Ausbruchs und „Static_Method“ für feste Punkte. Wir definieren auch die Enumeration „TrailingTypeEnum“. „Trailing_None“ deaktiviert Trailing-Stopps, und „Trailing_Points“ aktiviert Trailing um eine bestimmte Anzahl von Punkten. Diese werden flexible Konfigurationen des Risikomanagements ermöglichen.

Anschließend deklarieren wir eine Reihe von Eingabeparametern, die der Nutzer über das Dialogfeld Eigenschaften des Expert Advisors anpassen kann. Dazu gehören „RangeTF“ zur Angabe des Zeitrahmens für die Definition des Akkumulationsbereichs, „TradeVolume“ zur Festlegung der Losgröße für jeden Handel, „RR_Ratio“ zur Bestimmung des Risiko-Ertrags-Verhältnisses, „SLTP_Approach“ zur Auswahl der Stop-Loss- und Take-Profit-Methode unter Verwendung der zuvor definierten Enumeration, der Rest ist selbsterklärend. Durch diese Eingaben kann das System an unterschiedliche Marktbedingungen und Nutzerpräferenzen angepasst werden. Beim Kompilieren sollten wir die folgenden Eingabesätze erhalten.

EINGABE-SETS

Nun können wir einige globale Variablen definieren, die wir im gesamten Programm verwenden werden.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object
datetime prevRangeTime = 0;                                       //--- Previous range time
double rangeMax = 0.0;                                            //--- Range maximum
double rangeMin = 0.0;                                            //--- Range minimum
bool positiveDirection = false;                                   //--- Positive direction flag
bool rangeBreached = false;                                       //--- Range breached flag
double breachPoint = 0.0;                                         //--- Breach point
string maxLevelObj = "RangeMaxLevel";                             //--- Max level object name
string minLevelObj = "RangeMinLevel";                             //--- Min level object name
string maxTextObj = "CRT_High_Text";                              //--- CRT high text object
string minTextObj = "CRT_Low_Text";                               //--- CRT low text object
bool tradedSetup = false;                                         //--- Traded setup flag
datetime breachTime = 0;                                          //--- Breach time
datetime lastConfirmTime = 0;                                     //--- Last confirm time

Wir fahren fort, indem wir globale Variablen deklarieren, die im gesamten Programm verwendet werden, um den Zustand zu erhalten und die CRT-Logik zu verwalten. Wir instanziieren das Objekt „obj_Trade“ aus der Klasse CTrade, um alle handelsbezogenen Operationen wie das Eröffnen und Ändern von Positionen zu bearbeiten. Dann definieren wir Variablen für die Verfolgung des Akkumulationsbereichs: „prevRangeTime“, um den Zeitstempel der vorherigen Bereichskerze zu speichern, „rangeMax“ und „rangeMin“, um den Höchst- und Tiefstpreis des aktuellen Bereichs zu speichern, und „positiveDirection“ als boolesches Flag, um anzuzeigen, ob die Bereichskerze positiv (steigend) oder negativ (fallend) geschlossen hat. Zu den zusätzlichen Flags und Werten gehören rangeBreached“, um zu signalisieren, dass ein Durchbruch stattgefunden hat, breachPoint“, um das extreme Preisniveau während der Manipulation aufzuzeichnen, und tradedSetup“, um zu verhindern, dass mehrere Geschäfte mit derselben Konstellation getätigt werden.

Wir richten auch String-Variablen für die Namen von Chart-Objekten ein, z. B. „maxLevelObj“ und „minLevelObj“ für horizontale Linien, die die Extremwerte des Bereichs markieren, und „maxTextObj“ und „minTextObj“ für Textbeschriftungen, die die Höchst- und Tiefstwerte der CRTs kennzeichnen. Schließlich fügen wir „breachTime“ ein, um den Beginn der Manipulationsphase mit einem Zeitstempel zu versehen, und „lastConfirmTime“, um die Zeit des letzten Bestätigungsbalkens zu verfolgen, um sicherzustellen, dass das System zeitabhängige Ereignisse effektiv überwachen kann. Wir sind alle bereit. Wir beginnen damit, die magische Zahl in OnInit für Handelsgeschäfte zu setzen.

//+------------------------------------------------------------------+
//| EA Start Function                                                |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(UniqueID);                      //--- Set magic number
   return(INIT_SUCCEEDED);                                        //--- Return success
}

In OnInit, das ausgeführt wird, wenn der Expert Advisor startet oder an einen Chart angehängt wird, konfigurieren wir das Handelsobjekt durch den Aufruf von „obj_Trade.SetExpertMagicNumber“ mit der Eingabe „UniqueID“, die allen vom Programm geöffneten Handelsgeschäfte eine eindeutige Kennung zuweist, um die Filterung und Verwaltung zu erleichtern. Schließlich geben wir INIT_SUCCEEDED zurück, um zu bestätigen, dass die Initialisierung erfolgreich war und das Programm einsatzbereit ist. Wir werden nun einige Hilfsfunktionen definieren, die wir für die Darstellung der Beschriftungen und Ebenen im Chart benötigen. Hier ist die Logik, mit der wir das erreicht haben.

//+------------------------------------------------------------------+
//| Render Horizontal Level                                          |
//+------------------------------------------------------------------+
void RenderLevel(string objName, double levelVal, color levelClr, string levelDesc) {
   ObjectDelete(ChartID(), objName);                                //--- Delete object
   ObjectCreate(ChartID(), objName, OBJ_HLINE, 0, 0, levelVal);     //--- Create hline
   ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, levelClr);   //--- Set color
   ObjectSetInteger(ChartID(), objName, OBJPROP_STYLE, STYLE_DOT);  //--- Set style
   ObjectSetString(ChartID(), objName, OBJPROP_TOOLTIP, levelDesc); //--- Set tooltip
   ChartRedraw(ChartID());                                          //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Render Text Label                                                |
//+------------------------------------------------------------------+
void RenderText(string objName, datetime timeVal, double priceVal, string textStr, color textClr, int anchorVal) {
   ObjectDelete(ChartID(), objName);                                 //--- Delete object
   ObjectCreate(ChartID(), objName, OBJ_TEXT, 0, timeVal, priceVal); //--- Create text
   ObjectSetString(ChartID(), objName, OBJPROP_TEXT, textStr);       //--- Set text
   ObjectSetInteger(ChartID(), objName, OBJPROP_COLOR, textClr);     //--- Set color
   ObjectSetInteger(ChartID(), objName, OBJPROP_ANCHOR, anchorVal);  //--- Set anchor
   ObjectSetInteger(ChartID(), objName, OBJPROP_FONTSIZE, 10);       //--- Set fontsize
   ChartRedraw(ChartID());                                           //--- Redraw chart
}

Zunächst definieren wir die Funktion „RenderLevel“, um eine horizontale Linie auf dem Chart zu zeichnen oder zu aktualisieren, die wichtige Kursniveaus, wie Maxima oder Minima, darstellt. Sie benötigt Parameter für den Objektnamen, den Preisstufenwert, die Farbe und die Beschreibung. Innerhalb der Funktion löschen wir zunächst alle vorhandenen Objekte mit demselben Namen mit ObjectDelete, um Duplikate zu vermeiden, und erstellen dann ein neues horizontales Linienobjekt mit ObjectCreate, wobei wir OBJ_HLINE als Typ angeben und es auf der angegebenen Preisebene positionieren. Wir stellen die Farbe mit ObjectSetInteger und OBJPROP_COLOR ein, wenden einen gepunkteten Stil über „OBJPROP_STYLE“ und STYLE_DOT an, fügen eine Tooltip-Beschreibung über „OBJPROP_TOOLTIP“ hinzu und zeichnen das Chart schließlich mit ChartRedraw neu, um die Änderungen sofort anzuzeigen.

In ähnlicher Weise erstellen wir die Funktion „RenderText“, um Textbeschriftungen auf dem Chart zu platzieren oder zu aktualisieren, z. B. für Phasenbezeichner. Diese Funktion akzeptiert Parameter für den Objektnamen, die Zeit- und Preiskoordinaten, die Textzeichenfolge, die Farbe und den Ankerpunkt. Zunächst wird eine frühere Instanz des Objekts mit „ObjectDelete“ gelöscht, dann wird mit „ObjectCreate“ ein neues Textobjekt mit dem Typ „OBJ_TEXT“ zum angegebenen Zeitpunkt und Preis erstellt. Wir konfigurieren den Textinhalt mit ObjectSetString und OBJPROP_TEXT, setzen die Farbe mit „ObjectSetInteger“ und „OBJPROP_COLOR“, definieren die Ankerposition mit „OBJPROP_ANCHOR“ die Ankerposition, stellen die Schriftgröße mit „OBJPROP_FONTSIZE“ auf 10 ein und zeichnen abschließend das Chart mit „ChartRedraw“ neu, um sicherzustellen, dass das Kennzeichen korrekt angezeigt wird. Mit diesen Funktionen können wir den Bereich definieren und ihn in der Ereignisbehandlung durch OnTick im Chart visualisieren.

//+------------------------------------------------------------------+
//| Tick Processing Function                                         |
//+------------------------------------------------------------------+
void OnTick() {
   double currBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);        //--- Get current bid
   double currAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);        //--- Get current ask
   datetime currRangeTime = iTime(_Symbol, RangeTF, 0);           //--- Get current range time
   if (currRangeTime != prevRangeTime) {                          //--- Check new range
      prevRangeTime = currRangeTime;                              //--- Update prev time
      double prevMax = iHigh(_Symbol, RangeTF, 1);                //--- Get prev high
      double prevMin = iLow(_Symbol, RangeTF, 1);                 //--- Get prev low
      double prevStart = iOpen(_Symbol, RangeTF, 1);              //--- Get prev open
      double prevEnd = iClose(_Symbol, RangeTF, 1);               //--- Get prev close
      rangeMax = prevMax;                                         //--- Set range max
      rangeMin = prevMin;                                         //--- Set range min
      positiveDirection = (prevEnd > prevStart);                  //--- Set direction
      rangeBreached = false;                                      //--- Reset breached
      breachPoint = positiveDirection ? rangeMin : rangeMax;      //--- Set breach point
      tradedSetup = false;                                        //--- Reset traded
      breachTime = 0;                                             //--- Reset breach time
      lastConfirmTime = 0;                                        //--- Reset confirm time
      RenderLevel(maxLevelObj, rangeMax, clrOrange, "Range Max"); //--- Render max level
      RenderLevel(minLevelObj, rangeMin, clrPurple, "Range Min"); //--- Render min level
      // Add text labels for current CRT High and Low
      datetime labelTime = currRangeTime;                         //--- Set label time
      RenderText(maxTextObj, labelTime, rangeMax, "CRT High", clrOrange, ANCHOR_RIGHT_LOWER); //--- Render high text
      RenderText(minTextObj, labelTime, rangeMin, "CRT Low", clrPurple, ANCHOR_RIGHT_UPPER);  //--- Render low text
      // Draw background rectangle for the accumulation phase (range candle) with fill true
      string rangeRectObj = "RangeRectangle_" + IntegerToString(currRangeTime);               //--- Range rect name
      datetime rangeStartTime = iTime(_Symbol, RangeTF, 1);                                   //--- Get start time
      datetime rangeEndTime = currRangeTime;                                                  //--- Set end time
      ObjectCreate(ChartID(), rangeRectObj, OBJ_RECTANGLE, 0, rangeStartTime, rangeMax, rangeEndTime, rangeMin); //--- Create rect
      color rectClr = positiveDirection ? clrLightGreen : clrLightPink;                       //--- Set rect color
      ObjectSetInteger(ChartID(), rangeRectObj, OBJPROP_COLOR, rectClr);                      //--- Set color
      ObjectSetInteger(ChartID(), rangeRectObj, OBJPROP_FILL, true);                          //--- Set fill
      ObjectSetInteger(ChartID(), rangeRectObj, OBJPROP_BACK, true);                          //--- Set back
      ObjectSetInteger(ChartID(), rangeRectObj, OBJPROP_STYLE, STYLE_SOLID);                  //--- Set style
      ChartRedraw(ChartID());                                                                 //--- Redraw chart
   }
}

In OnTick wird zunächst der aktuelle Geldkurs mit SymbolInfoDouble unter Verwendung von SYMBOL_BID abgefragt und „currBid“ zugewiesen, ebenso wie der Briefkurs mit SYMBOL_ASK in „currAsk“. Dann holen wir den Zeitstempel des jüngsten Balkens im Zeitrahmen des Bereichs über iTime bei Shift 0 und speichern ihn in „currRangeTime“. Wenn dieser Zeitstempel von „prevRangeTime“ abweicht, was auf einen neuen Bereichsbalken hinweist, aktualisieren wir „prevRangeTime“ und erfassen das Hoch des vorherigen Balkens mit iHigh im Shift 1 in „prevMax“, Low mit „iLow“ in „prevMin“, Open mit „iOpen“ in „prevStart“ und Close mit iClose in „prevEnd“. Wir legen die Bereichsgrenzen fest, indem wir „rangeMax“ dem Hoch und „rangeMin“ dem Tief zuweisen, „positiveDirection“ auf true setzen, wenn der Schlusskurs den Eröffnungskurs für ein Aufwärtskonstellation übersteigt, „rangeBreached“ auf false zurücksetzt, „breachPoint“ auf das Minimum für positive oder das Maximum für negative Richtungen initialisiert, „tradedSetup“ löscht und „breachTime“ und „lastConfirmTime“ auf null setzt.

Zur Visualisierung rufen wir „RenderLevel“ auf, um den maximalen Pegel mit oranger Farbe und der Beschreibung „Range Max“ und den minimalen Pegel mit lila und „Range Min“ zu zeichnen. Wir fügen Kennzeichnungen hinzu, indem wir „labelTime“ auf die aktuelle Bereichszeit setzen und dann „RenderText“ für „CRT High“ in orange, verankert rechts unten am Maximum, und „CRT Low“ in lila, verankert rechts oben am Minimum, aufrufen. Um die Akkumulationsphase hervorzuheben, erstellen wir einen eindeutigen Rechtecknamen, indem wir „RangeRectangle_“ mit der umgewandelten aktuellen Bereichszeitzeichenfolge kombinieren. Wir holen uns die Startzeit des vorherigen Balkens über „iTime“ bei Shift 1 in „rangeStartTime“, setzen „rangeEndTime“ auf die aktuelle Zeit und erstellen das Rechteck mit ObjectCreate unter Verwendung von OBJ_RECTANGLE, das den Zeit- und Preisbereich umfasst. Wir wählen hellgrün für positive oder hellrosa für negative Richtungen, wenden die Farbe an, aktivieren die Füllung, legen sie als Hintergrund fest, verwenden einen Volltonstil und zeichnen das Chart neu, um die Aktualisierungen wiederzugeben. Es ist immer eine gute Programmierpraxis, Ihr Programm an jedem Meilenstein zu kompilieren, um die Erreichung der Ziele zu sehen. In unserem Fall erhalten wir das folgende Ergebnis.

CRT-BEREICHSEINSTELLUNG

Wir sehen, dass wir die Bereiche erfolgreich festgelegt haben. Wir können dann die Durchbrüche ermitteln und die Konstellationen handeln.

if (rangeMax == 0.0 || rangeMin == 0.0) return;                //--- Return if no range
bool justBreached = false;                                     //--- Init just breached
if (positiveDirection && currBid <= rangeMin) {                //--- Check positive breach
   if (!rangeBreached) {                                       //--- Check not breached
      rangeBreached = true;                                    //--- Set breached
      justBreached = true;                                     //--- Set just breached
      breachTime = TimeCurrent();                              //--- Set breach time
   }
   breachPoint = MathMin(breachPoint, currBid);                //--- Update breach point
} else if (!positiveDirection && currBid >= rangeMax) {        //--- Check negative breach
   if (!rangeBreached) {                                       //--- Check not breached
      rangeBreached = true;                                    //--- Set breached
      justBreached = true;                                     //--- Set just breached
      breachTime = TimeCurrent();                              //--- Set breach time
   }
   breachPoint = MathMax(breachPoint, currBid);                //--- Update breach point
}
if (rangeBreached && !tradedSetup) {                           //--- Check breached and not traded
   // Check for confirmed reversal on bar closures
   bool reversalConfirmed = false;                              //--- Init confirmed
   if (ConfirmBars == 0) {                                      //--- Check no confirm
      reversalConfirmed = true;                                 //--- Set confirmed
   } else {                                                     //--- Else
      datetime currConfirmTime = iTime(_Symbol, ConfirmTF, 0);  //--- Get confirm time
      if (currConfirmTime != lastConfirmTime) {                 //--- Check new confirm
         lastConfirmTime = currConfirmTime;                     //--- Update last confirm
         int confirmedCount = 0;                                //--- Init count
         for (int i = 1; i <= ConfirmBars; i++) {               //--- Iterate bars
            double confirmClose = iClose(_Symbol, ConfirmTF, i); //--- Get close
            if (positiveDirection && confirmClose > rangeMin) { //--- Check positive
               confirmedCount++;                                //--- Increment count
            } else if (!positiveDirection && confirmClose < rangeMax) { //--- Check negative
               confirmedCount++;                                //--- Increment count
            }
         }
         if (confirmedCount >= ConfirmBars) {                   //--- Check confirmed
            reversalConfirmed = true;                           //--- Set confirmed
         }
      }
   }
   // Calculate manipulation depth for filter
   bool manipSufficient = true;                                 //--- Init sufficient
   double rangeSize = rangeMax - rangeMin;                      //--- Calc range size
   double manipDepth = positiveDirection ? (rangeMin - breachPoint) : (breachPoint - rangeMax); //--- Calc depth
   double manipPct = (manipDepth / rangeSize) * 100.0;          //--- Calc percent
   if (UseManipFilter) {                                        //--- Check filter
      if (manipPct < MinManipPct) {                             //--- Check insufficient
         manipSufficient = false;                               //--- Set insufficient
      }
   }
   bool justEntered = false;                                    //--- Init entered
   datetime entryTime = 0;                                      //--- Init entry time
   double entryPrice = 0.0;                                     //--- Init entry price
   double gainTarget = 0.0;                                     //--- Init target
   if (reversalConfirmed && manipSufficient) {                  //--- Check confirmed and sufficient
      if (positiveDirection && currBid > rangeMin && ActivePositions(POSITION_TYPE_BUY) < MaxPositionsDir) { //--- Check buy entry
         double lossStop;                                       //--- Init SL
         if (SLTP_Approach == Dynamic_Method) {                 //--- Check dynamic
            lossStop = NormalizeDouble(breachPoint, _Digits);   //--- Set SL
            double riskDistance = currAsk - breachPoint;        //--- Calc risk
            gainTarget = NormalizeDouble(currAsk + riskDistance * RR_Ratio, _Digits); //--- Set TP
         } else {                                               //--- Static
            lossStop = NormalizeDouble(currAsk - SL_Points * _Point, _Digits); //--- Set SL
            gainTarget = NormalizeDouble(currAsk + SL_Points * RR_Ratio * _Point, _Digits); //--- Set TP
         }
         if (obj_Trade.Buy(TradeVolume, _Symbol, currAsk, lossStop, gainTarget, "CRT Positive Entry")) { //--- Open buy
            if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success
               Print("Positive Signal: Range raided below min, reversed back in (confirmed). Entry at ", DoubleToString(currAsk, _Digits), 
                     " SL at ", DoubleToString(lossStop, _Digits), " TP at ", DoubleToString(gainTarget, _Digits)); //--- Log entry
               Print("Debug: Accumulation Range: ", DoubleToString(rangeSize / _Point, 0), " points. Manipulation Depth: ", DoubleToString(manipDepth / _Point, 0), " points (", DoubleToString(manipPct, 2), "% of range)"); //--- Log debug
               string markerName = "EntryMarker_" + IntegerToString(TimeCurrent()); //--- Marker name
               ObjectCreate(ChartID(), markerName, OBJ_ARROW, 0, TimeCurrent(), currBid); //--- Create marker
               ObjectSetInteger(ChartID(), markerName, OBJPROP_ARROWCODE, 233); //--- Set code
               ObjectSetInteger(ChartID(), markerName, OBJPROP_COLOR, clrBlue); //--- Set color
               ObjectSetInteger(ChartID(), markerName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); //--- Set anchor
               tradedSetup = true;                                 //--- Set traded
               justEntered = true;                                 //--- Set entered
               entryTime = TimeCurrent();                          //--- Set entry time
               entryPrice = currAsk;                               //--- Set entry price
            }
         }
      } else if (!positiveDirection && currBid < rangeMax && ActivePositions(POSITION_TYPE_SELL) < MaxPositionsDir) { //--- Check sell entry
         double lossStop;                                       //--- Init SL
         if (SLTP_Approach == Dynamic_Method) {                 //--- Check dynamic
            lossStop = NormalizeDouble(breachPoint, _Digits);   //--- Set SL
            double riskDistance = breachPoint - currBid;        //--- Calc risk
            gainTarget = NormalizeDouble(currBid - riskDistance * RR_Ratio, _Digits); //--- Set TP
         } else {                                               //--- Static
            lossStop = NormalizeDouble(currBid + SL_Points * _Point, _Digits); //--- Set SL
            gainTarget = NormalizeDouble(currBid - SL_Points * RR_Ratio * _Point, _Digits); //--- Set TP
         }
         if (obj_Trade.Sell(TradeVolume, _Symbol, currBid, lossStop, gainTarget, "CRT Negative Entry")) { //--- Open sell
            if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) { //--- Check success
               Print("Negative Signal: Range raided above max, reversed back in (confirmed). Entry at ", DoubleToString(currBid, _Digits), 
                     " SL at ", DoubleToString(lossStop, _Digits), " TP at ", DoubleToString(gainTarget, _Digits)); //--- Log entry
               Print("Debug: Accumulation Range: ", DoubleToString(rangeSize / _Point, 0), " points. Manipulation Depth: ", DoubleToString(manipDepth / _Point, 0), " points (", DoubleToString(manipPct, 2), "% of range)"); //--- Log debug
               string markerName = "EntryMarker_" + IntegerToString(TimeCurrent()); //--- Marker name
               ObjectCreate(ChartID(), markerName, OBJ_ARROW, 0, TimeCurrent(), currAsk); //--- Create marker
               ObjectSetInteger(ChartID(), markerName, OBJPROP_ARROWCODE, 234); //--- Set code
               ObjectSetInteger(ChartID(), markerName, OBJPROP_COLOR, clrRed); //--- Set color
               ObjectSetInteger(ChartID(), markerName, OBJPROP_ANCHOR, ANCHOR_TOP); //--- Set anchor
               tradedSetup = true;                                 //--- Set traded
               justEntered = true;                                 //--- Set entered
               entryTime = TimeCurrent();                          //--- Set entry time
               entryPrice = currBid;                               //--- Set entry price
            }
         }
      }
   }
}

Im weiteren Verlauf der Tick-Funktion wird zunächst überprüft, ob der Bereich richtig definiert ist, indem geprüft wird, ob „rangeMax“ oder „rangeMin“ Null ist. Wir initialisieren einen booleschen Wert „justBreached“ auf false, um neue Verstöße zu verfolgen. Wenn das aktuelle Gebot bei oder unter dem Minimum der Handelsspanne liegt und die Spanne noch nicht durchbrochen wurde, setzen wir „rangeBreached“ auf true, markieren „justBreached“ als true und zeichnen den Zeitpunkt des Durchbruchs mit der Funktion TimeCurrent auf. Anschließend wird „breachPoint“ mit Hilfe der Funktion MathMin auf den niedrigeren Wert von „breachPoint“ oder „bid“ aktualisiert. Für negative Richtungen gilt das Gleiche.

Wenn ein Durchbruch stattgefunden hat und noch kein Handel für diese Konstellation platziert wurde, fahren wir fort, die Umkehr zu bestätigen. Wir initialisieren „ReversalConfirmed“ auf false; wenn keine Bestätigungsbalken erforderlich sind, weil „ConfirmBars“ Null ist, setzen wir es sofort auf true. Andernfalls holen wir die letzte Barzeit auf dem Bestätigungszeitrahmen mit iTime bei Shift Null in „currConfirmTime“, und wenn sie im Vergleich zu „lastConfirmTime“ neu ist, aktualisieren wir die letzte Zeit und zählen bestätigende Abschlüsse: In einer Schleife von Shift Eins bis „ConfirmBars“ holen wir jeden Schlusskurs mit iClose und erhöhen einen Zähler, wenn Abschlüsse über dem Minimum für positive Konstellationen oder unter dem Maximum für negative liegen. Wenn der Zählerstand „ConfirmBars“ erreicht oder überschreitet, bestätigen wir die Umkehrung. Als Nächstes bewerten wir, ob die Manipulationen ausreichend sind, wobei wir mit „manipSufficient“ als true beginnen. Wir berechnen die Größe des Bereichs als die Differenz zwischen Maximum und Minimum, die Manipulationstiefe als den Abstand zwischen dem Rand des Bereichs und der Bruchstelle (subtrahiert für positiv, addiert für negativ) und den Prozentsatz, indem wir Tiefe durch Größe mal 100 teilen. Wenn der Filter über „UseManipFilter“ aktiviert ist und der Prozentsatz unter „MinManipPct“ fällt, setzen wir „sufficiency“ auf false.

Als Nächstes bereiten wir die Eingangsvariablen vor und initialisieren sie. Wenn sowohl die Umkehrung bestätigt wird als auch die Manipulation ausreichend ist, prüfen wir die Einstiegsbedingungen für positive Richtungen – wenn das Gebot das Minimum überschreitet und die Kaufpositionen aus „ActivePositions“ mit POSITION_TYPE_BUY unter „MaxPositionsDir“ liegen, berechnen wir die Einstiegslevels. Die Funktion „ActivePositions“ ist eine Hilfsfunktion, die wir zur Modularisierung des Codes definiert haben und die unsere aktiven Positionen zurückgibt. Siehe seine Umsetzung unten.

//+------------------------------------------------------------------+
//| Count Active Positions by Type                                   |
//+------------------------------------------------------------------+
int ActivePositions(ENUM_POSITION_TYPE posType) {
   int total = 0;                                                 //--- Init total
   for (int pos = PositionsTotal() - 1; pos >= 0; pos--) {        //--- Iterate positions
      if (PositionGetSymbol(pos) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID && PositionGetInteger(POSITION_TYPE) == posType) { //--- Check position
         total++;                                                 //--- Increment total
      }
   }
   return total;                                                  //--- Return total
}

Die Funktion ist ganz einfach. Wir haben der Klarheit halber Kommentare hinzugefügt. Zurück zur Logik: Wir versuchen, mit „obj_Trade.Buy“ eine Kauforder mit Volumen, Symbol, Ask, Stop-Loss, Take-Profit und einem Kommentar zu eröffnen. Wenn „ResultRetcode“ mit TRADE_RETCODE_DONE ein Erfolg meldet, drucken wir Log-Meldungen für das Signal und Debug-Informationen über Bereich und Tiefe, erstellen einen Eröffnungspfeil mit „ObjectCreate“ mit OBJ_ARROW auf aktuelle Zeit und Geld, verwenden den Pfeil-Code 233, die blaue Farbe, und den Anker unten, dann weisen wir den Flags „tradedSetup“ und „justEntered“ true zu und speichern Eröffnungs-Zeit und -Preis. MQL5 bietet die Pfeil-Codes aus der Schriftart Wingdings an, die wir, wie unten beschrieben, auswählen können.

MQL5 WINGDINGS

Bei negativen Richtungen wenden wir die Logik spiegelbildlich an: Wir prüfen, ob der Geldkurs unter dem Höchstkurs liegt, und verkaufen unterhalb des Limitkurses; wir berechnen den Stop-Loss dynamisch als normalisierten Durchbruchspunkt unter Berücksichtigung des Risikos vom Durchbruchspunkt bis zum Geldkurs und den Take-Profit durch Abzug des Risikos multipliziert mit dem Verhältnis; alternativ addieren wir statisch „SL_Points“ multipliziert mit dem Punkt zum Geldkurs für den Stop-Loss und ziehen diesen Wert für den Take-Profit ab. Wir verkaufen mit „obj_Trade.Sell“ unter Verwendung von Bid und protokollieren bei Erfolg vergleichsweise, zeichnen eine Markierung mit Code 234, in roter Farbe und dem Anker oben und aktualisieren die Flags und Eröffnungsdetails entsprechend. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

ERÖFFNUNGSBESTÄTIGUNGEN

Aus dem Bild können wir ersehen, dass wir Handelsgeschäfte bestätigen können, wenn es Bestätigungen gibt. Was wir tun müssen, ist, die Niveaus zu visualisieren, um beim Handel Klarheit zu schaffen, damit wir visuell verfolgen können, was wirklich passiert.

// If just entered trade, draw manipulation rectangle, distribution, and labels (including accumulation)
if (justEntered) {                                             //--- Check entered
   string setupSuffix = IntegerToString(prevRangeTime);        //--- Setup suffix
   // Label the range as Accumulation phase (now only for complete setups)
   string accumTextUnique = "AccumText_" + setupSuffix;        //--- Accum text name
   double accumPrice = (rangeMax + rangeMin) / 2;              //--- Accum price
   datetime labelTime = prevRangeTime;                         //--- Label time
   RenderText(accumTextUnique, labelTime, accumPrice, "Accumulation", clrBlue, ANCHOR_RIGHT); //--- Render accum text
   // Calculate the manipulation extreme using candle highs/lows between currRangeTime and entryTime
   int startBar = iBarShift(_Symbol, PERIOD_CURRENT, prevRangeTime); //--- Start bar
   int endBar = iBarShift(_Symbol, PERIOD_CURRENT, entryTime); //--- End bar
   if (startBar < 0 || endBar < 0) return;                     //--- Return invalid
   if (startBar < endBar) { int temp = startBar; startBar = endBar; endBar = temp; } //--- Swap if needed
   int barCount = startBar - endBar + 1;                       //--- Calc bar count
   double manipExtreme;                                        //--- Init manip extreme
   double manipStartPrice = positiveDirection ? rangeMin : rangeMax; //--- Manip start
   if (positiveDirection) {                                    //--- Check positive
      int lowestBar = iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, barCount, endBar); //--- Get lowest
      manipExtreme = iLow(_Symbol, PERIOD_CURRENT, lowestBar); //--- Set extreme
   } else {                                                    //--- Negative
      int highestBar = iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, barCount, endBar); //--- Get highest
      manipExtreme = iHigh(_Symbol, PERIOD_CURRENT, highestBar); //--- Set extreme
   }
   // Draw manipulation rectangle (border only) from CRT end to signal time
   string manipRectObj = "ManipRectangle_" + setupSuffix;      //--- Manip rect name
   double topPrice = MathMax(manipStartPrice, manipExtreme);   //--- Top price
   double bottomPrice = MathMin(manipStartPrice, manipExtreme); //--- Bottom price
   ObjectCreate(ChartID(), manipRectObj, OBJ_RECTANGLE, 0, prevRangeTime, topPrice, entryTime, bottomPrice); //--- Create rect
   ObjectSetInteger(ChartID(), manipRectObj, OBJPROP_COLOR, clrBlue); //--- Set color
   ObjectSetInteger(ChartID(), manipRectObj, OBJPROP_FILL, false); //--- Set no fill
   ObjectSetInteger(ChartID(), manipRectObj, OBJPROP_BACK, true); //--- Set back
   ObjectSetInteger(ChartID(), manipRectObj, OBJPROP_STYLE, STYLE_DOT); //--- Set style
   ObjectSetInteger(ChartID(), manipRectObj, OBJPROP_WIDTH, 2); //--- Set width
   ChartRedraw(ChartID());                                     //--- Redraw chart
   // Add manipulation text label at breach time
   string manipTextUnique = "ManipText_" + setupSuffix;        //--- Manip text name
   int anchorManip = positiveDirection ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER; //--- Manip anchor
   RenderText(manipTextUnique, breachTime, manipExtreme, "Manipulation", clrBlue, anchorManip); //--- Render manip text
   // Label and draw distribution
   string distribTextUnique = "DistribText_" + setupSuffix;    //--- Distrib text name
   color distribClr = positiveDirection ? clrGreen : clrRed;   //--- Distrib color
   int anchor = positiveDirection ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER; //--- Distrib anchor
   RenderText(distribTextUnique, entryTime, entryPrice, "Distribution", distribClr, anchor); //--- Render distrib text
   // Draw border rectangle (fill false) for distribution phase (% of range duration)
   string distribRectObj = "DistribRectangle_" + setupSuffix;  //--- Distrib rect name
   datetime rangeStartTime = iTime(_Symbol, RangeTF, 1);       //--- Range start
   datetime rangeEndTime = prevRangeTime;                      //--- Range end
   long duration = rangeEndTime - rangeStartTime;              //--- Calc duration
   double projFactor = MathMax(DistribProjPct / 100.0, 0.01);  //--- Proj factor
   datetime projEndTime = entryTime + (datetime)(duration * projFactor); //--- Proj end
   double topDistrib = MathMax(entryPrice, gainTarget);        //--- Top distrib
   double bottomDistrib = MathMin(entryPrice, gainTarget);     //--- Bottom distrib
   ObjectCreate(ChartID(), distribRectObj, OBJ_RECTANGLE, 0, entryTime, topDistrib, projEndTime, bottomDistrib); //--- Create rect
   ObjectSetInteger(ChartID(), distribRectObj, OBJPROP_COLOR, distribClr); //--- Set color
   ObjectSetInteger(ChartID(), distribRectObj, OBJPROP_FILL, false); //--- Set no fill
   ObjectSetInteger(ChartID(), distribRectObj, OBJPROP_BACK, true); //--- Set back
   ObjectSetInteger(ChartID(), distribRectObj, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(ChartID(), distribRectObj, OBJPROP_WIDTH, 2); //--- Set width
   ChartRedraw(ChartID());                                     //--- Redraw chart
}

Wenn ein Handelsgeschäft gerade eröffnet wurde, was durch den Wert „justEntered“ angezeigt wird, werden die restlichen Phasen angezeigt. Wir erstellen ein eindeutiges Suffix für Objektnamen mit IntegerToString auf „prevRangeTime“. Zur Kennzeichnung der Akkumulation erzeugen wir einen eindeutigen Textnamen, indem wir das Suffix an „AccumText_“ anhängen, den Mittelwertpreis als Durchschnitt des Bereichsmaximums und -minimums berechnen, die Kennzeichenzeit auf „prevRangeTime“ setzen und „RenderText“ aufrufen, um „Accumulation“ in blauer Farbe am Anker rechts zu platzieren. Um das Manipulationsextrem zu bestimmen, konvertieren wir die Zeiten in Balken-Indizes mit „iBarShift“ für den Start bei „prevRangeTime“ und das Ende bei „entryTime“ und kehren bei Ungültigkeit vorzeitig zurück. Wir stellen sicher, dass der Startpreis größer als der Endpreis ist, indem wir ihn gegebenenfalls vertauschen, die Anzahl der Balken berechnen und „manipStartPrice“ auf das Minimum des Bereichs für positive oder das Maximum für negative Richtungen setzen. Im positiven Fall suchen wir den niedrigsten Balken mit „iLowest“ unter Verwendung von „MODE_LOW“ über die Zählung des letzten Balkens und erhalten das Tief über iLow; im negativen Fall verwenden wir iHighest mit MODE_HIGH und iHigh für den Extremwert.

Dann zeichnen wir das Manipulationsrechteck, indem wir einen eindeutigen Namen mit dem Suffix „ManipRectangle_“ erstellen, den oberen und unteren Preis als Maximal- und Minimalwert von Start und Extremwert mit MathMax und „MathMin“ bestimmen und es mit ObjectCreate als OBJ_RECTANGLE erstellen, das sich von der vorherigen Bereichszeit am oberen Rand bis zur Eröffnungszeit am unteren Rand erstreckt. Wir setzen seine Farbe auf Blau, deaktivieren das Füllen, platzieren es im Hintergrund, wenden einen gepunkteten Stil mit einer Breite von 2 an und zeichnen das Chart neu. Als Nächstes fügen wir eine Manipulationsbeschriftung mit einem eindeutigen Namen mit dem Suffix „ManipText_“ hinzu, wobei wir einen Anker oben rechts für positive oder unten rechts für negative Werte wählen und „Manipulation“ in blauer Farbe für den Zeitpunkt des Bruchs und den Höchstpreis darstellen. Für die Verteilung erstellen wir einen Kennzeichennamen mit dem Suffix „DistribText_“, wählen grün für aufwärts oder rot für abwärts, setzen den Anker links unten für aufwärts oder links oben für abwärts und geben „Distribution“ zum Eröffnungszeitpunkt und -Preis wieder. Schließlich zeichnen wir das Verteilungsrechteck nach einer ähnlichen Logik und zeichnen das Chart neu. Hier ist das Ergebnis.

PHASEN DER AKKUMULATION, MANIPULATION UND DISTRIBUTION

Aus dem Bild geht hervor, dass wir die Phasen der Bearbeitung und der Verteilung der Übersichtlichkeit halber hinzugefügt haben. Nun gilt es, die Positionen, die sich zu unseren Gunsten entwickeln, mit einer Trailing-Stop-Logik zu steuern. Auch das werden wir in einer Funktion unterbringen.

//+------------------------------------------------------------------+
//| Apply Points Trailing Stop                                       |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                         //--- Get point
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate positions
      if (PositionGetTicket(i) > 0) {                             //--- Check ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == UniqueID) { //--- Check symbol magic
            double sl = PositionGetDouble(POSITION_SL);              //--- Get SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Get TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Get ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Points * point, _Digits); //--- Calc new SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Points * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Points * point, _Digits); //--- Calc new SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Points * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            }
         }
      }
   }
}

//--- Call the function per tick in the "OnTick" event handler

if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing
   ApplyPointsTrailing();                                      //--- Apply trailing
}

Wir definieren die Funktion „ApplyPointsTrailing“, um Trailing-Stop-Loss-Anpassungen auf der Basis von Punkten zu verwalten, wenn diese Funktion aktiviert ist. Wir beginnen damit, dass wir den Punktwert des Symbols „point“ mit _Point zuweisen. Anschließend durchlaufen wir mit PositionsTotal eine Rückwärtsschleife durch alle offenen Positionen, um Indexprobleme bei Änderungen zu vermeiden, und überprüfen die Gültigkeit der einzelnen Tickets mit der Funktion PositionGetTicket. Für Positionen, die mit unserem Symbol über PositionGetString mit POSITION_SYMBOL und der magischen Zahl über „PositionGetInteger“ mit „POSITION_MAGIC“ übereinstimmen, rufen wir den aktuellen Stop-Loss mit „PositionGetDouble“ und „POSITION_SL“, den Take-Profit mit „POSITION_TP“, den Eröffnungskurs über „POSITION_PRICE_OPEN“ und die Ticketnummer mit „POSITION_TICKET“. Für Kaufpositionen, die durch „POSITION_TYPE_BUY“ identifiziert werden, berechnen wir einen neuen Stop-Loss, indem wir „Trailing_Stop_Points“ mal Punkt vom aktuellen Gebot subtrahieren, das wir über SymbolInfoDouble mit SYMBOL_BID erhalten haben, und es auf die Ziffern des Symbols normalisieren. Wenn dieser neue Wert den bestehenden Stop-Loss übersteigt und der Gewinn (Bid minus Eröffnungskurs) den „Min_Profit_To_Trail_Points“-Zeitpunkt übersteigt, modifizieren wir die Position mit „obj_Trade.PositionModify“ mit dem neuen Stop-Loss und unverändertem Take-Profit.

Ähnlich verhält es sich bei Verkaufspositionen mit POSITION_TYPE_SELL: Wir berechnen den neuen Stop-Loss, und wenn dieser unter dem aktuellen Stop-Loss liegt und der Gewinn (Eröffnungskurs minus Ask) die Mindestschwelle erreicht, aktualisieren wir die Position entsprechend. Schließlich wird in der Funktion „OnTick“, wenn „TrailingType“ gleich „Trailing_Points“ ist und es offene Positionen in „PositionsTotal“ gibt, die Funktion „ApplyPointsTrailing“ aufgerufen, um diese Anpassungen bei jedem Tick anzuwenden. Wir müssen uns nun um die von uns erstellten Objekte kümmern, indem wir sie bei der Deinitialisierung löschen.

//+------------------------------------------------------------------+
//| EA Stop Function                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int code) {
   ObjectDelete(ChartID(), maxLevelObj);                          //--- Delete max level
   ObjectDelete(ChartID(), minLevelObj);                          //--- Delete min level
   ObjectDelete(ChartID(), maxTextObj);                           //--- Delete max text
   ObjectDelete(ChartID(), minTextObj);                           //--- Delete min text
   // Clean dynamic rects and texts
   ObjectsDeleteAll(ChartID(), "RangeRectangle_", OBJ_RECTANGLE); //--- Delete range rects
   ObjectsDeleteAll(ChartID(), "ManipRectangle_", OBJ_RECTANGLE); //--- Delete manip rects
   ObjectsDeleteAll(ChartID(), "DistribRectangle_", OBJ_RECTANGLE); //--- Delete distrib rects
   ObjectsDeleteAll(ChartID(), "AccumText_", OBJ_TEXT);           //--- Delete accum texts
   ObjectsDeleteAll(ChartID(), "ManipText_", OBJ_TEXT);           //--- Delete manip texts
   ObjectsDeleteAll(ChartID(), "DistribText_", OBJ_TEXT);         //--- Delete distrib texts
}

In der Ereignisbehandlung von OnDeinit, die ausgeführt wird, wenn das Programm aus dem Chart entfernt oder heruntergefahren wird, werden zunächst statische Chart-Objekte einzeln mit ObjectDelete und dem aktuellen Chart-Identifikator ChartID gelöscht: die horizontale Linie des maximalen Niveaus über „maxLevelObj“, des minimale Niveau mit „minLevelObj“, die CRT-Beschriftung für den hohen Text über „maxTextObj“ und der CRT-Text für den niedrigen Text über „minTextObj“.

Um dynamisch erstellte Objekte zu behandeln, verwenden wir ObjectsDeleteAll, um alle übereinstimmenden Elemente auf dem Chart zu entfernen: alle Rechtecke mit dem Präfix „RangeRectangle_“ vom Typ OBJ_RECTANGLE für Akkumulationsphasen, ebenso für „ManipRectangle_“, um Manipulationsgrenzen zu löschen, und „DistribRectangle_“ für Verteilungsprojektionen; dann alle Textobjekte beginnend mit „AccumText_“ vom Typ OBJ_TEXT für das Akkumulationskennzeichen, „ManipText_“ für Manipulationsanmerkungen und „DistribText_“ für Verteilungsmarkierungen. Dies gewährleistet eine vollständige Reinigung, ohne sichtbare Rückstände zu hinterlassen. Nach der Kompilierung erhalten wir das folgende Ergebnis, wenn der Trailing-Stop aktiviert ist.

ENDERGEBNIS MIT AKTIVIERTEM TRAILING-STOP

Aus dem Bild geht hervor, dass wir die Positionen verwalten, indem wir bei Bedarf Trailing-Stops einsetzen und so unsere Ziele erreichen. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.



Backtests

Nach einem gründlichen Backtest erhalten wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Bericht des Backtest:

BERICHT


Schlussfolgerung

Zusammenfassend haben wir ein Candle Range Theory (CRT) Handelssystem in MQL5 entwickelt, das Akkumulationsbereiche auf einem bestimmten Zeitrahmen identifiziert, Durchbrüche mit Filterung der Manipulationstiefe erkennt, Umkehrungen durch beim Schließen der Balken bestätigt und Handelsgeschäfte in der Distributionsphase mit dynamischem oder statischem Stop-Loss und Take-Profit auf Basis des Risiko-Ertrags-Verhältnisses ausführt.

Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Volatilität der Märkte kann zu Verlusten führen. Gründliche Backtests und sorgfältiges Risikomanagement sind entscheidend, bevor Sie dieses Programm auf den Live-Märkten einsetzen.

Mit dieser Strategie der Theorie der Balkenbereiche, die die Phasen Accumulation, Manipulation und Distribution (AMD) umfasst, sind Sie für den Handel mit Umkehrgelegenheiten gerüstet und bereit für weitere Optimierungen auf Ihrem Handelsweg. Viel Spaß beim Handeln!

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (6)
Stanislav Korotky
Stanislav Korotky | 21 Nov. 2025 in 14:11
Sie haben nicht erklärt, wie der Beginn jeder neuen Akkumulationsphase festgestellt wird.
Allan Munene Mutiiria
Allan Munene Mutiiria | 22 Nov. 2025 in 06:18
Christian Gomez #:
Danke, das sieht interessant aus.
Danke
Allan Munene Mutiiria
Allan Munene Mutiiria | 22 Nov. 2025 in 06:24
Stanislav Korotky #:
Sie haben nicht erklärt, wie der Beginn einer jeden neuen Akkumulationsphase erkannt wird.

Das ist die Kerze Bereiche wie visualisiert.

      double prevMax = iHigh(_Symbol, RangeTF, 1);                //--- Vorherigen Höchststand abrufen
      double prevMin = iLow(_Symbol, RangeTF, 1);                 //--- Vorherigen Tiefpunkt abrufen
      double prevStart = iOpen(_Symbol, RangeTF, 1);              //--- Vorherige Öffnung abrufen
      double prevEnd = iClose(_Symbol, RangeTF, 1);               //--- Vorherige Schließung abrufen
      rangeMax = prevMax;                                         //--- Bereich max einstellen
Stanislav Korotky
Stanislav Korotky | 22 Nov. 2025 in 18:21
Allan Munene Mutiiria #:

Das sind die Kerzenbereiche, wie sie visualisiert werden.

Das beantwortet nicht die Frage, wie man den Beginn der Akkumulationsphase findet (jede einzelne, denn die Phase tritt immer wieder in verschiedenen Abschnitten des Charts auf). Es geht um die Zeit, nicht um eine Kursspanne. Es geht auch nicht um die Visualisierung.

Juvenille Emperor Limited
Eleni Anna Branou | 24 Nov. 2025 in 08:12
Kommentare, die sich nicht auf dieses Thema beziehen, wurden in "Off-Topic-Beiträge" verschoben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Die Grenzen des maschinellen Lernens überwinden (Teil 7): Automatische Strategieauswahl Die Grenzen des maschinellen Lernens überwinden (Teil 7): Automatische Strategieauswahl
Dieser Artikel zeigt, wie man mit MetaTrader 5 automatisch potenziell profitable Handelsstrategien identifizieren kann. White-Box-Lösungen, die auf unüberwachter Matrixfaktorisierung beruhen, sind schneller zu konfigurieren, leichter zu interpretieren und bieten eine klare Anleitung, welche Strategien beibehalten werden sollen. Black-Box-Lösungen sind zwar zeitaufwändiger, eignen sich aber besser für komplexe Marktbedingungen, die mit White-Box-Ansätzen nicht erfasst werden können. Diskutieren Sie mit uns, wie unsere Handelsstrategien uns helfen können, unter allen Umständen profitable Strategien zu identifizieren.
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.
Entwicklung einer Handelsstrategie: Der Flower-Volatilitäts-Index als Trendfolgemethode Entwicklung einer Handelsstrategie: Der Flower-Volatilitäts-Index als Trendfolgemethode
Das unermüdliche Bestreben, Marktrhythmen zu entschlüsseln, hat Händler und quantitative Analysten dazu veranlasst, unzählige mathematische Modelle zu entwickeln. In diesem Artikel wird der Flower Volatility Index (FVI) vorgestellt, ein neuartiger Ansatz, der die mathematische Eleganz der Rosenkurven in ein funktionales Handelsinstrument verwandelt. Mit dieser Arbeit haben wir gezeigt, wie mathematische Modelle in praktische Handelsmechanismen umgewandelt werden können, die sowohl die Analyse als auch die Entscheidungsfindung unter realen Marktbedingungen unterstützen.