Automatisieren von Handelsstrategien in MQL5 (Teil 41): Candle Range Theory (CRT) – Akkumulation, Manipulation, Distribution (AMD)
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:
- Das Verständnis des Systems der Theorie des Kerzenbereichs (CRT)
- Implementierung in MQL5
- Backtesting
- 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.

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.

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.

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.

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.

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:

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.

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.

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:

Bericht des Backtest:

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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Die Grenzen des maschinellen Lernens überwinden (Teil 7): Automatische Strategieauswahl
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Entwicklung einer Handelsstrategie: Der Flower-Volatilitäts-Index als Trendfolgemethode
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Danke, das sieht interessant aus.
Sie haben nicht erklärt, wie der Beginn einer jeden neuen Akkumulationsphase erkannt wird.
Das ist die Kerze Bereiche wie visualisiert.
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.