Automatisieren von Handelsstrategien in MQL5 (Teil 24): London Session Breakout System mit Risikomanagement und Trailing Stops
Einführung
In unserem vorherigen Artikel (Teil 23) haben wir das Zone Recovery System für Envelopes Trend Trading in MetaQuotes Language 5 (MQL5) mit Trailing Stops und Multi-Basket Trading für eine bessere Gewinnsicherung und Signalverarbeitung erweitert. In Teil 24 entwickeln wir ein London Session Breakout System, das Pre-Session-Ranges identifiziert, schwebende Aufträge platziert und Risikomanagement-Tools wie Risk-to-Reward-Ratios, Drawdown-Limits und ein Kontrollpanel für die Echtzeit-Überwachung einschließt. Wir werden die folgenden Themen behandeln:
Am Ende haben Sie ein komplettes MQL5-Ausbruchsprogramm mit fortschrittlichen Risikokontrollen, das Sie testen und verfeinern können – lassen Sie uns eintauchen!
Verstehen der Strategie des London Session Breakout
Die „London Session Breakout Strategy“ zielt auf die erhöhte Volatilität während der Londoner Markteröffnung ab, indem sie die in den Stunden vor der Londoner Eröffnung gebildete Preisspanne identifiziert und schwebende Orders platziert, um Ausbrüche aus dieser Spanne zu erfassen. Diese Strategie ist wichtig, da die Londoner Sitzung oft eine hohe Liquidität und Kursbewegungen aufweist, die zuverlässige Gewinnchancen bieten; sie erfordert jedoch ein sorgfältiges Risikomanagement, um falsche Ausbrüche und Drawdowns zu vermeiden.
Dies erreichen wir, indem wir die Höchst- und Tiefststände von „Vor-London“ berechnen, um Kauf- und Verkaufsstopps mit Offsets zu setzen, das Risiko-Ertrags-Verhältnis für Gewinnmitnahmen zu berücksichtigen, Trailing Stops für die Gewinnsicherung zu setzen und Limits für offene Geschäfte und den täglichen Drawdown zum Schutz des Kapitals festzulegen. Wir planen den Einsatz eines Kontrollpanels für die Echtzeitüberwachung und sitzungsspezifische Prüfungen, um sicherzustellen, dass nur innerhalb definierter Bandbreiten gehandelt wird, sodass das System an unterschiedliche Marktbedingungen angepasst werden kann. Kurz gesagt, hier ist eine Darstellung des Systems, das wir erreichen wollen.

Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen zum Navigator, suchen den Ordner „Indicators“, klicken auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald das Programm erstellt ist, werden wir in der Programmierumgebung damit beginnen, einige Eingaben und Strukturen zu deklarieren, die das Programm dynamischer machen werden.
//+------------------------------------------------------------------+ //| London Breakout 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" #property strict #include <Trade\Trade.mqh> //--- Include Trade library for trading operations //--- Enumerations enum ENUM_TRADE_TYPE { //--- Enumeration for trade types TRADE_ALL, // All Trades (Buy and Sell) TRADE_BUY_ONLY, // Buy Trades Only TRADE_SELL_ONLY // Sell Trades Only }; //--- Input parameters sinput group "General EA Settings" input double inpTradeLotsize = 0.01; // Lotsize input ENUM_TRADE_TYPE TradeType = TRADE_ALL; // Trade Type Selection sinput int MagicNumber = 12345; // Magic Number input double RRRatio = 1.0; // Risk to Reward Ratio input int StopLossPoints = 500; // Stop loss in points input int OrderOffsetPoints = 10; // Points offset for Orders input bool DeleteOppositeOrder = true; // Delete opposite order when one is activated? input bool UseTrailing = false; // Use Trailing Stop? input int TrailingPoints = 50; // Trailing Points (distance) input int MinProfitPoints = 100; // Minimum Profit Points to start trailing sinput group "London Session Settings" input int LondonStartHour = 9; // London Start Hour input int LondonStartMinute = 0; // London Start Minute input int LondonEndHour = 8; // London End Hour input int LondonEndMinute = 0; // London End Minute input int MinRangePoints = 100; // Min Pre-London Range in points input int MaxRangePoints = 300; // Max Pre-London Range in points sinput group "Risk Management" input int MaxOpenTrades = 2; // Maximum simultaneous open trades input double MaxDailyDrawdownPercent = 5.0; // Max daily drawdown % to stop trading //--- Structures struct PositionInfo { //--- Structure for position information ulong ticket; // Position ticket double openPrice; // Entry price double londonRange; // Pre-London range in points for this position datetime sessionID; // Session identifier (day) bool trailingActive; // Trailing active flag };
Wir beginnen mit der Implementierung unseres London Session Breakout Systems, indem wir die Bibliothek „<Trade\Trade.mqh>“ einbinden und wichtige Enumerationen, Eingaben und eine Struktur für die Positionsverfolgung definieren. Wir fügen „<Trade\Trade.mqh>“ ein, um auf die Klasse CTrade zuzugreifen, mit der Handelsoperationen wie das Platzieren von Aufträgen und das Ändern von Positionen ausgeführt werden können. Wir definieren die Enumeration „ENUM_TRADE_TYPE“ mit den Optionen „TRADE_ALL“ für Kauf- und Verkaufsgeschäfte, „TRADE_BUY_ONLY“ nur für Käufe und „TRADE_SELL_ONLY“ nur für Verkäufe, sodass wir die Handelsrichtungen einschränken können.
Dann stellen wir die Eingabeparameter in Gruppen ein: unter „General EA Settings“, „inpTradeLotsize“ auf 0.01 für die Losgröße, „TradeType“ unter Verwendung der Enumeration mit Standardwert „TRADE_ALL“, „MagicNumber“ auf 12345 zur Identifizierung der EA-Trades, „RRRatio“ auf 1.0 für das Risiko-Ertrags-Verhältnis, „StopLossPoints“ auf 500 für den Stop-Loss-Abstand, „OrderOffsetPoints“ auf 10 für Einstiegsoffsets, „DeleteOppositeOrder“ auf true, um entgegengesetzte schwebende Aufträge zu entfernen, „UseTrailing“ auf false, um Trailing-Stops zu aktivieren, „TrailingPoints“ auf 50 für den Trailing-Abstand und „MinProfitPoints“ auf 100 für den Beginn des Trailing.
Unter „London Session Settings“ setzen Sie „LondonStartHour“ auf 9 und „LondonStartMinute“ auf 0 für den Sitzungsbeginn, „LondonEndHour“ auf 8 und „LondonEndMinute“ auf 0 für das Ende, „MinRangePoints“ auf 100 und „MaxRangePoints“ auf 300 für die Validierung des Bereichs vor London. Unter „Risikomanagement“ wird „MaxOpenTrades“ auf 2 gesetzt, um gleichzeitige Positionen zu begrenzen, und „MaxDailyDrawdownPercent“ auf 5,0, um den Handel bei übermäßigem Drawdown zu stoppen. Wir definieren die Struktur „PositionInfo“, um offene Handelsgeschäfte zu verfolgen, mit „ticket“ für das Positionsticket, „openPrice“ für den Einstiegskurs, „londonRange“ für die Pre-London-Range, „sessionID“ für die Tageskennung und „trailingActive“ als Boolean für den Trailing-Status. Nach dem Kompilieren erhalten wir die folgende Ausgabe.

Nachdem die Eingaben auf diese Weise strukturiert wurden, können wir nun einige zusätzliche globale Variablen definieren, die wir im gesamten Programm verwenden werden.
//--- Global variables CTrade obj_Trade; //--- Trade object double PreLondonHigh = 0.0; //--- Pre-London session high double PreLondonLow = 0.0; //--- Pre-London session low datetime PreLondonHighTime = 0; //--- Time of Pre-London high datetime PreLondonLowTime = 0; //--- Time of Pre-London low ulong buyOrderTicket = 0; //--- Buy stop order ticket ulong sellOrderTicket = 0; //--- Sell stop order ticket bool panelVisible = true; //--- Panel visibility flag double LondonRangePoints = 0.0; //--- Current session's Pre-London range PositionInfo positionList[]; //--- Array to store position info datetime lastCheckedDay = 0; //--- Last checked day bool noTradeToday = false; //--- Flag to prevent trading today bool sessionChecksDone = false; //--- Flag for session checks completion datetime analysisTime = 0; //--- Time for London analysis double dailyDrawdown = 0.0; //--- Current daily drawdown bool isTrailing = false; //--- Global flag for any trailing active const int PreLondonStartHour = 3; //--- Fixed Pre-London Start Hour const int PreLondonStartMinute = 0; //--- Fixed Pre-London Start Minute
Hier definieren wir globale Variablen für unser Programm: „obj_Trade“ als CTrade für den Handel, „PreLondonHigh“ und „PreLondonLow“ als „doubles“ für Bereiche, „PreLondonHighTime“ und „PreLondonLowTime“ als datetimes für die Zeitangaben, „buyOrderTicket“ und „sellOrderTicket“ als „ulongs“ für Aufträge, „panelVisible“ als true für das Panel, „LondonRangePoints“ als 0.0 für den aktuellen Bereich, „positionList“ als „PositionInfo“-Array für Positionen, „lastCheckedDay“ als 0 für die tägliche Nachverfolgung, „noTradeToday“ und „sessionChecksDone“ als false für Handelsflags, „analysisTime“ als 0 für die Sitzungszeit, „dailyDrawdown“ als 0.0 für das Risiko, „isTrailing“ als false für Trailing, und die Konstanten „PreLondonStartHour“ als 3 und „PreLondonStartMinute“ als 0.
Danach erstellen wir das Panel, was der einfachste Schritt ist, und gehen dann zu der komplexeren Handelslogik über. Beginnen wir mit den notwendigen Erstellungsfunktionen.
//+------------------------------------------------------------------+ //| Create a rectangle label for the panel background | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID) { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set x-size ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set y-size ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success } //+------------------------------------------------------------------+ //| Create a text label for panel elements | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 10, string font = "Arial") { ResetLastError(); //--- Reset last error if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", _LastError); //--- Log creation failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x-distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y-distance ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Set state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectable ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selected ChartRedraw(0); //--- Redraw chart return true; //--- Return success }
Hier implementieren wir Hilfsfunktion, um die Elemente der Nutzeroberfläche (UI) mit Rechtecken für Hintergründe und Textbeschriftungen für die Anzeige zu erstellen. Wir beginnen mit der Funktion „createRecLabel“, um rechteckige Beschriftungen für Panel-Hintergründe zu erzeugen, wobei wir einige Parameter benötigen. Wir setzen Fehler mit der Funktion ResetLastError zurück und erstellen das Objekt mit ObjectCreate als OBJ_RECTANGLE_LABEL, protokollieren Fehler mit Print und geben false zurück, wenn sie nicht erfolgreich waren. Wir setzen die Eigenschaften mit ObjectSetInteger für OBJPROP_XDISTANCE und in gleicher Weise für alle anderen erforderlichen Integer-Eigenschaften, zeichnen dann das Chart mit ChartRedraw neu und geben true zurück.
Als Nächstes erstellen wir die Funktion „createLabel“ für Textbeschriftungen im Panel, wobei wir „objName“, „xD“, „yD“, „txt“, „clrTxt“, „fontSize“ und „font“ als Parameter verwenden. Wir setzen Fehler mit „ResetLastError“ zurück und erstellen das Objekt mit „ObjectCreate“ als OBJ_LABEL, protokollieren Fehler mit „Print“ und geben false zurück, wenn sie nicht erfolgreich waren. Wir setzen die Eigenschaften mit der Funktion „ObjectSetInteger“, genau wie bei der Funktion „Rechteckbeschriftung“, aber hier verwenden wir die zusätzliche Funktion ObjectSetString für die Eigenschaften „OBJPROP_TEXT“ und OBJPROP_FONT, zeichnen dann neu und geben true zurück. Diese Funktionen ermöglichen es uns, ein dynamisches Kontrollpanel zur Überwachung der Sitzungsdaten und des Programmstatus zu erstellen. Wir können nun die Funktionen zum Erstellen und Aktualisieren des Panels verwenden.
string panelPrefix = "LondonPanel_"; //--- Prefix for panel objects //+------------------------------------------------------------------+ //| Create the information panel | //+------------------------------------------------------------------+ void CreatePanel() { createRecLabel(panelPrefix + "Background", 10, 10, 270, 200, clrMidnightBlue, 1, clrSilver); //--- Create background createLabel(panelPrefix + "Title", 20, 15, "London Breakout Control Center", clrGold, 12); //--- Create title createLabel(panelPrefix + "RangePoints", 20, 40, "Range (points): ", clrWhite, 10); //--- Create range label createLabel(panelPrefix + "HighPrice", 20, 60, "High Price: ", clrWhite); //--- Create high price label createLabel(panelPrefix + "LowPrice", 20, 80, "Low Price: ", clrWhite); //--- Create low price label createLabel(panelPrefix + "BuyLevel", 20, 100, "Buy Level: ", clrWhite); //--- Create buy level label createLabel(panelPrefix + "SellLevel", 20, 120, "Sell Level: ", clrWhite); //--- Create sell level label createLabel(panelPrefix + "AccountBalance", 20, 140, "Balance: ", clrWhite); //--- Create balance label createLabel(panelPrefix + "AccountEquity", 20, 160, "Equity: ", clrWhite); //--- Create equity label createLabel(panelPrefix + "CurrentDrawdown", 20, 180, "Drawdown (%): ", clrWhite); //--- Create drawdown label createRecLabel(panelPrefix + "Hide", 250, 10, 30, 22, clrCrimson, 1, clrNONE); //--- Create hide button createLabel(panelPrefix + "HideText", 258, 12, CharToString(251), clrWhite, 13, "Wingdings"); //--- Create hide text ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_SELECTABLE, true); //--- Make hide selectable ObjectSetInteger(0, panelPrefix + "Hide", OBJPROP_STATE, true); //--- Set hide state } //+------------------------------------------------------------------+ //| Update panel with current data | //+------------------------------------------------------------------+ void UpdatePanel() { string rangeText = "Range (points): " + (LondonRangePoints > 0 ? DoubleToString(LondonRangePoints, 0) : "Calculating..."); //--- Format range text ObjectSetString(0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); //--- Update range text string highText = "High Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh, _Digits) : "N/A"); //--- Format high text ObjectSetString(0, panelPrefix + "HighPrice", OBJPROP_TEXT, highText); //--- Update high text string lowText = "Low Price: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow, _Digits) : "N/A"); //--- Format low text ObjectSetString(0, panelPrefix + "LowPrice", OBJPROP_TEXT, lowText); //--- Update low text string buyText = "Buy Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonHigh + OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format buy text ObjectSetString(0, panelPrefix + "BuyLevel", OBJPROP_TEXT, buyText); //--- Update buy text string sellText = "Sell Level: " + (LondonRangePoints > 0 ? DoubleToString(PreLondonLow - OrderOffsetPoints * _Point, _Digits) : "N/A"); //--- Format sell text ObjectSetString(0, panelPrefix + "SellLevel", OBJPROP_TEXT, sellText); //--- Update sell text string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Format balance text ObjectSetString(0, panelPrefix + "AccountBalance", OBJPROP_TEXT, balanceText); //--- Update balance text string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Format equity text ObjectSetString(0, panelPrefix + "AccountEquity", OBJPROP_TEXT, equityText); //--- Update equity text string ddText = "Drawdown (%): " + DoubleToString(dailyDrawdown, 2); //--- Format drawdown text ObjectSetString(0, panelPrefix + "CurrentDrawdown", OBJPROP_TEXT, ddText); //--- Update drawdown text ObjectSetInteger(0, panelPrefix + "CurrentDrawdown", OBJPROP_COLOR, dailyDrawdown > MaxDailyDrawdownPercent / 2 ? clrYellow : clrWhite); //--- Set drawdown color }
Hier definieren wir die Zeichenkette „panelPrefix“ als „LondonPanel_“, um allen Objektnamen des Panels ein Präfix voranzustellen und so eine geordnete Identifizierung des Bedienfelds zu gewährleisten. Wir erstellen die Funktion „CreatePanel“, um die Nutzeroberfläche des Informationspanels zu erstellen. Wir rufen „createRecLabel“ für „panelPrefix + „Background“ auf, um den Panel-Hintergrund an Position 10,10 mit der Größe 270x200, „clrMidnightBlue“ Hintergrund, Breite 1 und einem silbernen Rand zu erstellen. Wir verwenden „createLabel“, um den Titel „London Breakout Control Center“ bei 20,15 mit goldener Farbe und Größe 12 hinzuzufügen, sowie Beschriftungen für Range, High Price, Low Price, Buy Level, Sell Level, Balance, Equity und Drawdown bei den jeweiligen Positionen mit weißer Farbe und Größe 10.
Für die Schaltfläche zum Ausblenden rufen wir „createRecLabel“ für „panelPrefix + "Hide" an 250,10 mit der Größe 30x22 und dem Hintergrund „clrCrimson“ auf, und „createLabel“ für „panelPrefix + "HideText" mit CharToString(251) aus Wingdings bei 258,12 mit „clrWhite“ Farbe und Größe 13. Wir setzen OBJPROP_SELECTABLE und „OBJPROP_STATE“ mit ObjectSetInteger auf true für die Schaltfläche zum Ausblenden, um sie interaktiv zu machen. Die Wahl des zu verwendenden Wingdings-Codes hängt von Ihrem ästhetischen Empfinden ab. Hier finden Sie eine Liste von Codes, aus der Sie wählen können.

Anschließend implementieren wir die Funktion „UpdatePanel“, um das Panel mit den aktuellen Daten zu aktualisieren. Wir formatieren „rangeText“ mit „LondonRangePoints“ unter Verwendung von DoubleToString oder „Calculating...“, falls Null, und aktualisieren den Text mit „panelPrefix“ + "RangePoints" durch die Funktion ObjectSetString. In ähnlicher Weise formatieren und aktualisieren wir Texte für den Höchstkurs, den Tiefstkurs, das Kaufniveau (Addition von „OrderOffsetPoints * _Point“ zu „PreLondonHigh“), das Verkaufsniveau (Subtraktion von „OrderOffsetPoints * _Point“ von „PreLondonLow“), den Kontosaldo mit AccountInfoDouble(ACCOUNT_BALANCE), das Kapital mit „AccountInfoDouble(ACCOUNT_EQUITY)“, und den Drawdown mit „dailyDrawdown“.
Wir setzen die Farbe des Drawdowns mit ObjectSetInteger auf gelb, wenn der tägliche Drawdown „MaxDailyDrawdownPercent / 2“ überschreitet, sonst auf weiß. Um die Funktionen nutzbar zu machen, rufen wir sie in der Initialisierungsfunktion wie folgt auf.
//+------------------------------------------------------------------+ //| Initialize EA | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(MagicNumber); //--- Set magic number ArrayFree(positionList); //--- Free position list CreatePanel(); //--- Create panel panelVisible = true; //--- Set panel visible return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Deinitialize EA | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ArrayFree(positionList); //--- Free position list }
In OnInit setzen wir die magische Zahl mithilfe des Handelsobjekts, verwenden die Funktion ArrayFree, um die Positionsliste freizugeben, rufen die Funktion „CreatePanel“ auf, um das Panel zu erstellen, und setzen das Flag für die Sichtbarkeit des Panels nach seiner Erstellung auf true. Dann, in OnDeinit, verwenden wir die Funktion ObjectsDeleteAll, um alle Objekte mit dem angegebenen Präfix zu löschen und das Positionslisten-Array freizugeben, da wir es nicht mehr benötigen. Wenn wir kompilieren, erhalten wir das folgende Ergebnis.

Nachdem wir das Panel erstellt haben, wollen wir ihm nun etwas Leben einhauchen, indem wir die Abbrechen-Schaltfläche reaktionsfähig machen, mit der wir das Panel zerstören, sobald es angeklickt wird. Wir werden dies in der Ereignisbehandlung durch OnChartEvent erreichen.
//+------------------------------------------------------------------+ //| Handle chart events (e.g., panel close) | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK && sparam == panelPrefix + "Hide") { //--- Check hide click panelVisible = false; //--- Set panel hidden ObjectsDeleteAll(0, "LondonPanel_"); //--- Delete panel objects ChartRedraw(0); //--- Redraw chart } }
In OnChartEvent wird geprüft, ob es sich bei der Ereignis-ID um einen Objektklick handelt und ob es sich bei dem Objekt um die Schaltfläche „Ausblenden“ oder „Abbrechen“ handelt, und dann das Flag für die Sichtbarkeit auf false gesetzt. Anschließend werden die Panel-Objekte gelöscht und das Chart neu gezeichnet, damit die Änderungen wirksam werden. Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Anhand der Visualisierung können wir sehen, dass das Panel nun vollständig eingerichtet ist. Aktualisieren wir sie so, dass sie alle Teile initialisiert. Wir benötigen einige Funktionen zum Einstellen der täglichen Bereiche und zur Berechnung der Drawdown-Merkmale.
//+------------------------------------------------------------------+ //| Check if it's a new trading day | //+------------------------------------------------------------------+ bool IsNewDay(datetime currentBarTime) { MqlDateTime barTime; //--- Bar time structure TimeToStruct(currentBarTime, barTime); //--- Convert time datetime currentDay = StringToTime(StringFormat("%04d.%02d.%02d", barTime.year, barTime.mon, barTime.day)); //--- Get current day if (currentDay != lastCheckedDay) { //--- Check new day lastCheckedDay = currentDay; //--- Update last day sessionChecksDone = false; //--- Reset checks noTradeToday = false; //--- Reset no trade buyOrderTicket = 0; //--- Reset buy ticket sellOrderTicket = 0; //--- Reset sell ticket LondonRangePoints = 0.0; //--- Reset range return true; //--- Return new day } return false; //--- Return not new day } //+------------------------------------------------------------------+ //| Update daily drawdown | //+------------------------------------------------------------------+ void UpdateDailyDrawdown() { static double maxEquity = 0.0; //--- Max equity tracker double equity = AccountInfoDouble(ACCOUNT_EQUITY); //--- Get equity if (equity > maxEquity) maxEquity = equity; //--- Update max equity dailyDrawdown = (maxEquity - equity) / maxEquity * 100; //--- Calculate drawdown if (dailyDrawdown >= MaxDailyDrawdownPercent) noTradeToday = true; //--- Set no trade if exceeded }
Hier implementieren wir die Funktion „IsNewDay“, um zu prüfen, ob ein neuer Handelstag vorliegt. Wir erstellen die Struktur „barTime“ vom Typ MqlDateTime und wandeln „currentBarTime“ mit der Funktion TimeToStruct in diese Struktur um. Wir berechnen „currentDay“ mit StringToTime und StringFormat aus „barTime.year“, „barTime.mon“ und „barTime.day“. Wenn „currentDay“ von „lastCheckedDay“ abweicht, aktualisieren wir „lastCheckedDay“, setzen „sessionChecksDone“ und „noTradeToday“ auf false, löschen „buyOrderTicket“ und „sellOrderTicket“ auf 0, setzen „LondonRangePoints“ auf 0.0, und geben true zurück, andernfalls geben false. Diese Funktion sorgt für tägliche Rücksetzungen für Sitzungsanalysen und Handels-Flags.
Als Nächstes implementieren wir die Funktion „UpdateDailyDrawdown“, um das tägliche Risiko zu überwachen. Wir verwenden eine statische „maxEquity“, die auf 0,0 initialisiert ist, um den Höchstwert des Eigenkapitals zu verfolgen. Wir ermitteln „equity“ mit AccountInfoDouble unter Verwendung von „ACCOUNT_EQUITY“, aktualisieren „maxEquity“, wenn „equity“ höher ist, und berechnen „dailyDrawdown“ als den prozentualen Rückgang von „maxEquity“. Wenn „dailyDrawdown“ den Wert von „MaxDailyDrawdownPercent“ erreicht oder überschreitet, setzen wir „noTradeToday“ auf true, um den Handel zu stoppen und einen wesentlichen Schutz vor Drawdowns zu bieten. Um die Funktionen nutzbar zu machen, rufen wir sie in OnTick auf, um das Panel mit aktualisierten Daten aufzufüllen.
//+------------------------------------------------------------------+ //| Main tick handler | //+------------------------------------------------------------------+ void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current bar time IsNewDay(currentBarTime); //--- Check new day UpdatePanel(); //--- Update panel UpdateDailyDrawdown(); //--- Update drawdown }
Hier rufen wir einfach die Funktion für den neuen Tag auf, um die Bereichsparameter zu setzen, das Panel zu aktualisieren und den täglichen Drawdown zu erhöhen. Wenn wir das Programm jetzt ausführen, erhalten wir das folgende Ergebnis.

Anhand der Visualisierung können wir sehen, dass das Panel nun mit aktuellen Daten gefüllt ist und den Status anzeigt. Gehen wir nun zur komplexeren Logik der Definition der Sitzungsspannen über. Wir prüfen zunächst die Handelsbedingungen und platzieren die Aufträge, wenn die Handelsbedingungen erfüllt sind, und verwalten die Positionen später. Wir benötigen also einige Funktionen, mit denen wir zunächst den Bereich definieren, die Bereiche visualisieren und dann die Aufträge erteilen können. Hier ist die Logik, mit der wir das erreichen.
//+------------------------------------------------------------------+ //| Fixed lot size | //+------------------------------------------------------------------+ double CalculateLotSize(double entryPrice, double stopLossPrice) { return NormalizeDouble(inpTradeLotsize, 2); //--- Normalize lot size } //+------------------------------------------------------------------+ //| Calculate session range (high-low) in points | //+------------------------------------------------------------------+ double GetRange(datetime startTime, datetime endTime, double &highVal, double &lowVal, datetime &highTime, datetime &lowTime) { int startBar = iBarShift(_Symbol, _Period, startTime, true); //--- Get start bar int endBar = iBarShift(_Symbol, _Period, endTime, true); //--- Get end bar if (startBar == -1 || endBar == -1 || startBar < endBar) return -1; //--- Invalid bars int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + 1, endBar); //--- Get highest bar int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + 1, endBar); //--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); //--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); //--- Set low value highTime = iTime(_Symbol, _Period, highestBar); //--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); //--- Set low time return (highVal - lowVal) / _Point; //--- Return range in points } //+------------------------------------------------------------------+ //| Place pending buy/sell stop orders | //+------------------------------------------------------------------+ void PlacePendingOrders(double preLondonHigh, double preLondonLow, datetime sessionID) { double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; //--- Calculate buy price double sellPrice = preLondonLow - OrderOffsetPoints * _Point; //--- Calculate sell price double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { //--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket } if (TradeType == TRADE_ALL || TradeType == TRADE_SELL_ONLY) { //--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } } //+------------------------------------------------------------------+ //| Draw session ranges on the chart | //+------------------------------------------------------------------+ void DrawSessionRanges(datetime preLondonStart, datetime londonEnd) { string sessionID = "Sess_" + IntegerToString(lastCheckedDay); //--- Session ID string preRectName = "PreRect_" + sessionID; //--- Rectangle name ObjectCreate(0, preRectName, OBJ_RECTANGLE, 0, PreLondonHighTime, PreLondonHigh, PreLondonLowTime, PreLondonLow); //--- Create rectangle ObjectSetInteger(0, preRectName, OBJPROP_COLOR, clrTeal); //--- Set color ObjectSetInteger(0, preRectName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, preRectName, OBJPROP_BACK, true); //--- Set background string preTopLineName = "PreTopLine_" + sessionID; //--- Top line name ObjectCreate(0, preTopLineName, OBJ_TREND, 0, preLondonStart, PreLondonHigh, londonEnd, PreLondonHigh); //--- Create top line ObjectSetInteger(0, preTopLineName, OBJPROP_COLOR, clrBlack); //--- Set color ObjectSetInteger(0, preTopLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preTopLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preTopLineName, OBJPROP_BACK, true); //--- Set background string preBotLineName = "PreBottomLine_" + sessionID; //--- Bottom line name ObjectCreate(0, preBotLineName, OBJ_TREND, 0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); //--- Create bottom line ObjectSetInteger(0, preBotLineName, OBJPROP_COLOR, clrRed); //--- Set color ObjectSetInteger(0, preBotLineName, OBJPROP_WIDTH, 1); //--- Set width ObjectSetInteger(0, preBotLineName, OBJPROP_RAY_RIGHT, false); //--- Disable ray ObjectSetInteger(0, preBotLineName, OBJPROP_BACK, true); //--- Set background }
Um die Berechnung der Handelsspanne, die Platzierung von Orders und das Zeichnen von Charts zu ermöglichen, beginnen wir mit der Funktion „CalculateLotSize“, um feste Losgrößen zu berechnen, wobei wir „entryPrice“ und „stopLossPrice“ als Parameter verwenden (die für feste Losgrößen nicht verwendet werden). Wir geben „inpTradeLotsize“ normalisiert auf 2 Dezimalstellen mit NormalizeDouble zurück, um konsistente Losgrößen für alle Handelsgeschäfte zu gewährleisten. Je nach Art Ihres Kontos können Sie eine andere Anzahl an Dezimalstellen verwenden.
Als Nächstes erstellen wir die Funktion „GetRange“, um den Bereich vor der Londoner Sitzung zu berechnen. Wir erhalten „startBar“ und „endBar“ mit der Funktion iBarShift unter Verwendung von „startTime“ und „endTime“, wobei -1 zurückgegeben wird, wenn ungültig oder „startBar“ < „endBar“. Wir finden „highestBar“ mit iHighest auf MODE_HIGH und „lowestBar“ mit „iLowest“ auf MODE_LOW über den Balkenbereich. Wir setzen „highVal“ mit „iHigh“ auf „highestBar“, „lowVal“ mit „iLow“ auf „lowestBar“, „highTime“ mit „iTime“ am „highestBar“ und „lowTime“ mit „iTime“ am „lowestBar“. Wir geben den Bereich als Wert von „(highVal-lowVal)/_Point“ zurück.
Anschließend definieren wir die Funktion „PlacePendingOrders“, um Kauf- und Verkaufsstopps einzurichten. Wir berechnen „buyPrice“ als „preLondonHigh + OrderOffsetPoints * _Point“ und „sellPrice“ als „preLondonLow – OrderOffsetPoints * _Point“. Wir setzen „slPoints“ auf „StopLossPoints“, „buySL“ als „buyPrice – slPoints * _Point“, „sellSL“ als „sellPrice + slPoints * _Point“, „tpPoints“ als „slPoints * RRRatio“, „buyTP“ als „buyPrice + tpPoints * _Point“, und „sellTP“ als „sellPrice – tpPoints * _Point“. Wir berechnen „lotSizeBuy“ und „lotSizeSell“ mit „CalculateLotSize“.
Wenn „TradeType“ „TRADE_ALL“ oder „TRADE_BUY_ONLY“ ist, platzieren wir einen Buy Stop mit „obj_Trade.BuyStop“ mit „lotSizeBuy“, „buyPrice“, „buySL“, „buyTP“ und dem Label „Buy Stop – London“ und speichern das Ticket in „buyOrderTicket“ von „ResultOrder“. Ähnliches gilt für den Verkauf, wenn „TRADE_ALL“ oder „TRADE_SELL_ONLY“.
Schließlich implementieren wir die Funktion „DrawSessionRanges“, um die Sitzung im Chart zu visualisieren. Wir erstellen „sessionID“ als „Sess_“ plus „lastCheckedDay“ mit der Funktion IntegerToString. Für das Rechteck „preRectName“ als „PreRect_“ plus „sessionID“, verwenden wir ObjectCreate als OBJ_RECTANGLE von „PreLondonHighTime“, „PreLondonHigh“ zu „PreLondonLowTime“, „PreLondonLow“ und setzen OBJPROP_COLOR auf „clrTeal“, „OBJPROP_FILL“ auf true und „OBJPROP_BACK“ auf true.
Für die oberste Linie „preTopLineName“ als „PreTopLine_“ plus „sessionID“, erstellen wir OBJ_TREND von „preLondonStart“, „PreLondonHigh“ bis „londonEnd“, „PreLondonHigh“, wobei „OBJPROP_COLOR“ auf „clrBlack“, „OBJPROP_WIDTH“ auf 1, „OBJPROP_RAY_RIGHT“ auf false und „OBJPROP_BACK“ auf true gesetzt wird. Ähnlich für die untere Linie „preBotLineName“ als „PreBottomLine_“ plus „sessionID“ von „preLondonStart“, „PreLondonLow“ bis „londonEnd“, „PreLondonLow“, mit roter Farbe. Wir können nun eine Funktion definieren, die die Handelsbedingungen anhand dieser Funktionen überprüft.
//+------------------------------------------------------------------+ //| Check trading conditions and place orders | //+------------------------------------------------------------------+ void CheckTradingConditions(datetime currentTime) { MqlDateTime timeStruct; //--- Time structure TimeToStruct(currentTime, timeStruct); //--- Convert time datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); //--- Get today datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; //--- Pre-London start datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; //--- London start datetime londonEnd = today + LondonEndHour * 3600 + LondonEndMinute * 60; //--- London end analysisTime = londonStart; //--- Set analysis time if (currentTime < analysisTime) return; //--- Exit if before analysis double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); //--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { //--- Check range limits noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges return; //--- Exit } LondonRangePoints = preLondonRange; //--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); //--- Place orders noTradeToday = true; //--- Set no trade sessionChecksDone = true; //--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); //--- Draw ranges }
Wir implementieren die Funktion „CheckTradingConditions“, um die Sitzungsbedingungen zu bewerten und Aufträge in unserem London Session Breakout System zu platzieren. Wir erstellen „timeStruct“, eine Struktur vom Typ MqlDateTime, und konvertieren die aktuelle Zeit mit der Funktion TimeToStruct in diese Struktur. Wir berechnen „heute“ mit „StringToTime“ und „StringFormat“ aus „timeStruct.year“, „timeStruct.mon“ und „timeStruct.day“. Wir setzen „preLondonStart“ als „heute“ plus „PreLondonStartHour“ und „PreLondonStartMinute“ in Sekunden, „londonStart“ als „heute“ plus „LondonStartHour“ und „LondonStartMinute“, und „londonEnd“ als „heute“ plus „LondonEndHour“ und „LondonEndMinute“. Wir ordnen „analysisTime“ „londonStart“ zu und verlassen das Programm, wenn die aktuelle Zeit vor dieser liegt.
Wir erhalten „preLondonRange“ mit der Funktion „GetRange“ und übergeben „preLondonStart“, „currentTime“ und Referenzen für „PreLondonHigh“, „PreLondonLow“, „PreLondonHighTime“ und „PreLondonLowTime“. Wenn „preLondonRange“ unter „MinRangePoints“ oder über „MaxRangePoints“ liegt, setzen wir „noTradeToday“ und „sessionChecksDone“ auf true, rufen „DrawSessionRanges“ mit „preLondonStart“ und „londonEnd“ auf und beenden den Handel. Ansonsten setzen wir „LondonRangePoints“ auf „preLondonRange“, rufen „PlacePendingOrders“ mit „PreLondonHigh“, „PreLondonLow“ und „today“, setzen „noTradeToday“ und „sessionChecksDone“ auf true und rufen „DrawSessionRanges“ auf, um sicherzustellen, dass nur innerhalb gültiger Session-Ranges gehandelt wird. Wir können die Funktion jetzt in OnTick für die Signale aufrufen.
if (!noTradeToday && !sessionChecksDone) { //--- Check trading conditions CheckTradingConditions(TimeCurrent()); //--- Check conditions }
Wenn für den heutigen Tag noch keine Handelsgeschäfte getätigt wurden und noch keine Sitzungsprüfungen durchgeführt wurden, rufen wir die Funktion auf, um die Bedingungen für den aktuellen Zeitpunkt zu prüfen. Wenn wir das Programm ausführen, erhalten wir das folgende Ergebnis.

Wir können sehen, dass wir die Bereiche festgelegt und die ausstehenden Aufträge platziert haben. Die Spanne beträgt 100 Punkte, was unseren Handelsbedingungen entspricht. Wir können nun zur Verwaltung der Handelsgeschäfte übergehen, aber zunächst müssen wir die ausstehenden Aufträge prüfen und löschen, wenn einer aktiviert wurde, und die Positionen zur Verwaltung in die Positionsliste aufnehmen.
//+------------------------------------------------------------------+ //| Delete opposite pending order when one is filled | //+------------------------------------------------------------------+ void CheckAndDeleteOppositeOrder() { if (!DeleteOppositeOrder || TradeType != TRADE_ALL) return; //--- Exit if not applicable bool buyOrderExists = false; //--- Buy exists flag bool sellOrderExists = false; //--- Sell exists flag for (int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders ulong orderTicket = OrderGetTicket(i); //--- Get ticket if (OrderSelect(orderTicket)) { //--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { //--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; //--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; //--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != 0) { //--- Check delete sell obj_Trade.OrderDelete(sellOrderTicket); //--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != 0) { //--- Check delete buy obj_Trade.OrderDelete(buyOrderTicket); //--- Delete buy order } } //+------------------------------------------------------------------+ //| Add position to tracking list when opened | //+------------------------------------------------------------------+ void AddPositionToList(ulong ticket, double openPrice, double londonRange, datetime sessionID) { if (londonRange <= 0) return; //--- Exit if invalid range int index = ArraySize(positionList); //--- Get current size ArrayResize(positionList, index + 1); //--- Resize array positionList[index].ticket = ticket; //--- Set ticket positionList[index].openPrice = openPrice; //--- Set open price positionList[index].londonRange = londonRange; //--- Set range positionList[index].sessionID = sessionID; //--- Set session ID positionList[index].trailingActive = false; //--- Set trailing inactive }
Wir beginnen mit der Funktion „CheckAndDeleteOppositeOrder“, um den entgegengesetzten schwebenden Auftrag zu entfernen, wenn ein solcher ausgelöst wird. Wir beenden den Vorgang vorzeitig, wenn „DeleteOppositeOrder“ falsch ist oder „TradeType“ nicht „TRADE_ALL“ ist. Wir initialisieren „buyOrderExists“ und „sellOrderExists“ mit false. Mit OrdersTotal und OrderGetTicket durchlaufen wir rückwärts eine Schleife durch die Aufträge und wählen jede mit „OrderSelect“ aus. Wenn die Aufträge mit „_Symbol“ und „MagicNumber“ über „OrderGetString“ und OrderGetInteger übereinstimmt, setzen wir „buyOrderExists“ oder „sellOrderExists“, wenn das Ticket mit „buyOrderTicket“ oder „sellOrderTicket“ übereinstimmt.
Wenn der Kaufauftrag weg ist und der Verkaufsauftrag existiert, löschen wir „sellOrderTicket“ mit „obj_Trade.OrderDelete“; ebenso, wenn der Verkaufsauftrag weg ist und der Kaufauftrag existiert. Diese Funktion gewährleistet, dass nach der Aktivierung nur in eine Richtung gehandelt wird.
Als Nächstes erstellen wir die Funktion „AddPositionToList“, um die geöffneten Positionen zu verfolgen. Wir brechen ab, wenn „londonRange“ <= 0 ist, weil der Bereich dann noch nicht festgelegt wurde. Wir holen uns den aktuellen „Index“ mit ArraySize von „positionList“, ändern seine Größe mit ArrayResize, um einen Slot hinzuzufügen, und setzen „positionList[index]. ticket“, „openPrice“, „londonRange“, „sessionID“ und „trailingActive“ auf false, um eine Liste zur Verwaltung von Trailing-Stops und sitzungsspezifischen Daten zu erhalten. Wir können diese Logik nun in den Tick-Event-Handler implementieren.
CheckAndDeleteOppositeOrder(); //--- Delete opposite order // Add untracked positions for (int i = 0; i < PositionsTotal(); i++) { //--- Iterate through positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check position bool tracked = false; //--- Tracked flag for (int j = 0; j < ArraySize(positionList); j++) { //--- Check list if (positionList[j].ticket == ticket) tracked = true; //--- Set tracked } if (!tracked) { //--- If not tracked double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); //--- Add to list } } }
Hier rufen wir die Funktion „CheckAndDeleteOppositeOrder“ auf, um ausstehende Aufträge zu verwalten und sicherzustellen, dass, wenn der Auftrag einer Richtung ausgeführt wird, der entgegengesetzte Auftrag gemäß der Eingabe „DeleteOppositeOrder“ gelöscht wird, um widersprüchliche Abschlüsse zu verhindern.
Als Nächstes fügen wir der „positionList“ noch nicht verfolgte Positionen hinzu, um sicherzustellen, dass alle relevanten offenen Handelsgeschäfte auf Trailing-Stops überwacht werden. Wir durchlaufen alle Positionen mit PositionsTotal und PositionGetTicket, um jedes „Ticket“ zu erhalten. Wenn PositionSelectByTicket erfolgreich ist und die Position mit „PositionGetString“ und „PositionGetInteger“ mit _Symbol und „MagicNumber“ übereinstimmt, setzen wir das Flag „tracked“ auf false und überprüfen das Array „positionList“ mit „ArraySize“ und einer inneren Schleife, um zu sehen, ob das „ticket“ bereits in „positionList[j].ticket“. Wenn „tracked“ „false“ ist, erhalten wir „openPrice“ mit PositionGetDouble unter Verwendung von POSITION_PRICE_OPEN und rufen „AddPositionToList“ mit „ticket“, „openPrice“, „LondonRangePoints“ und „lastCheckedDay“ auf. Dadurch wird sichergestellt, dass jede übereinstimmende Position ohne Duplikate in die Liste für die Verwaltung aufgenommen wird. Hier ist das Ergebnis.

Da bis zu diesem Punkt alles perfekt ist, können wir nun die Funktion zur Verwaltung dieser Positionen wie folgt definieren.
//+------------------------------------------------------------------+ //| Remove position from tracking list when closed | //+------------------------------------------------------------------+ void RemovePositionFromList(ulong ticket) { for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through list if (positionList[i].ticket == ticket) { //--- Match ticket for (int j = i; j < ArraySize(positionList) - 1; j++) { //--- Shift elements positionList[j] = positionList[j + 1]; //--- Copy next } ArrayResize(positionList, ArraySize(positionList) - 1); //--- Resize array break; //--- Exit loop } } } //+------------------------------------------------------------------+ //| Manage trailing stops | //+------------------------------------------------------------------+ void ManagePositions() { if (PositionsTotal() == 0 || !UseTrailing) return; //--- Exit if no positions or no trailing isTrailing = false; //--- Reset trailing flag double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Get bid double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Get ask double point = _Point; //--- Get point value for (int i = 0; i < ArraySize(positionList); i++) { //--- Iterate through positions ulong ticket = positionList[i].ticket; //--- Get ticket if (!PositionSelectByTicket(ticket)) { //--- Select position RemovePositionFromList(ticket); //--- Remove if not selected continue; //--- Skip } if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; //--- Skip if not matching double openPrice = positionList[i].openPrice; //--- Get open price long positionType = PositionGetInteger(POSITION_TYPE); //--- Get type double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; //--- Get current price double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; //--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { //--- Check for trailing double newSL = 0.0; //--- New SL variable if (positionType == POSITION_TYPE_BUY) { //--- Buy position newSL = currentPrice - TrailingPoints * point; //--- Calculate new SL } else { //--- Sell position newSL = currentPrice + TrailingPoints * point; //--- Calculate new SL } double currentSL = PositionGetDouble(POSITION_SL); //--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { //--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { //--- Modify position positionList[i].trailingActive = true; //--- Set trailing active isTrailing = true; //--- Set global trailing } } } } }
Hier implementieren wir Funktionen, um geschlossene Positionen aus der Tracking-Liste zu entfernen und Trailing-Stops zu verwalten. Wir beginnen mit der Funktion „RemovePositionFromList“, um das Array „positionList“ zu bereinigen, wenn eine Position geschlossen wird, und nehmen „ticket“ als Parameter. Wir durchlaufen „positionList“ mit ArraySize, und wenn „positionList[i].ticket“ mit „ticket“ übereinstimmt, verschieben wir die nachfolgenden Elemente mit einer inneren Schleife, indem wir „positionList[j + 1]“ nach „positionList[j]“ kopieren, dann die Größe des Arrays mit ArrayResize ändern, um seine Größe um eins zu verringern, und die Schleife abbrechen. Diese Funktion stellt sicher, dass die Liste aktuell bleibt, und vermeidet unnötige Überprüfungen von geschlossenen Positionen, insbesondere wenn wir sie nachverfolgen und schließen.
Als Nächstes erstellen wir die Funktion „ManagePositions“, um Trailing Stops für offene Handelsgeschäfte zu verwalten. Wir steigen vorzeitig aus, wenn PositionsTotal 0 ist oder „UseTrailing“ falsch ist. Wir setzen „isTrailing“ auf false zurück, erhalten „currentBid“ und „currentAsk“ mit SymbolInfoDouble unter Verwendung von „SYMBOL_BID“ und „SYMBOL_ASK“ und „point“ als „_Point“. Wir durchlaufen „positionList“ mit „ArraySize“, holen „ticket“ und wählen es mit der Funktion PositionSelectByTicket aus. Wenn die Auswahl fehlschlägt, rufen wir „RemovePositionFromList“ auf und fahren fort. Wenn die Position nicht mit „_Symbol“ oder „MagicNumber“ mit „PositionGetString“ und „PositionGetInteger“ übereinstimmt, wird sie übersprungen. Wir erhalten „openPrice“ aus „positionList[i]“, „positionType“ mit PositionGetInteger, „currentPrice“ basierend auf dem Typ, und „profitPoints“ als Differenz geteilt durch den Punkt.
Wenn „profitPoints“ „MinProfitPoints + TrailingPoints“ erreicht oder überschreitet, berechnen wir „newSL“ als „currentPrice – TrailingPoints * point“ für Käufe oder plus für Verkäufe. Wir erhalten „currentSL“ mit PositionGetDouble, und wenn „newSL“ um mindestens „point“ besser ist als „currentSL“, ändern wir die Position mit „obj_Trade.PositionModify“ unter Verwendung von normalisiertem „newSL“ und aktuellem TP aus „PositionGetDouble“. Bei Erfolg werden „positionList[i].trailingActive“ und „isTrailing“ auf „true“ gesetzt, sodass wir die Stopps dynamisch anpassen können, um Gewinne zu sichern, während wir gewinnbringende Handelsgeschäfte laufen lassen. Rufen wir nun einfach die Funktion auf, um die Positionen bei jedem Tick zu verwalten. Nach der Kompilierung erhalten wir folgendes Ergebnis.

Aus dem Bild können wir ersehen, dass wir die Handelsbedingungen prüfen, Aufträge erteilen und sie entsprechend der Handelsstrategie verwalten und somit 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 London Session Breakout System in MQL5 entwickelt, das Pre-Session-Ranges analysiert, um schwebende Aufträge mit anpassbaren Risiko-Ertrags-Verhältnissen, Trailing Stops und Multi-Trade-Limits zu platzieren, ergänzt durch ein Kontrollpanel zur Echtzeit-Überwachung von Ranges, Levels und Drawdown. Durch modulare Komponenten wie die Struktur „PositionInfo“ bietet dieses Programm einen disziplinierten Ansatz für den Breakout-Handel, den Sie durch die Anpassung von Sitzungszeiten oder Risikoparametern verfeinern können.
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.
Indem Sie die vorgestellten Konzepte und Implementierungen nutzen, können Sie dieses Breakout-System an Ihren Handelsstil anpassen und Ihre algorithmischen Strategien stärken. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18867
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.
Entwicklung des Price Action Analysis Toolkit (Teil 33): Candle-Range Theory Tool
Entwicklung fortschrittlicher ICT-Handelssysteme: Implementierung von Signalen in den Indikator "Order Block"
Datenwissenschaft und ML (Teil 46): Aktienmarktprognosen mit N-BEATS in Python
Time Evolution Travel Algorithm (TETA)
- 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.
Das zweite Problem ist die hohe / niedrige Ebenen und die Kauf / Verkauf Ebenen im Bedienfeld nicht aktualisieren.
Die Hoch-/Tiefstwerte werden eindeutig auf dem Diagramm gefunden, also nehme ich an, dass die Kauf-/Verkaufswerte auch auf dem Diagramm angezeigt und im Kontrollpanel aktualisiert werden sollten, da sie direkt von den Hoch-/Tiefstwerten abgeleitet werden.
Welche Vorschläge haben Sie, um dies richtig zu machen?
Danke im Voraus.
Was Ihr zweites Problem anbelangt, so wird dies in dem Artikel erklärt, aber wenn man davon ausgeht, dass Ihr Problem von schlechten Testdaten ausgeht und einen Hinweis gibt, werden Sie, wenn der Bereich in der Berechnung ist, immer den Status "Berechnen..." sehen, bis genügend Daten vorhanden sind, um die Londoner Bereichssitzung oder die von Ihnen in den Eingaben definierte Sitzung festzulegen. Angenommen, Sie verwenden die Standardeinstellungen, wobei die Stunden vor London 3 sind, und Ihre Zeit aus dem gemeinsam genutzten Screenshot ist der 13. Februar, 2 Balken nach 22:00 Uhr, was 2*15 Minuten = 30 ist, was 22:30 ergibt, liegt also außerhalb der Bereichsberechnungszeit, so dass die Daten auf dem Panel noch sichtbar sein sollten, da der zuvor eingestellte Bereich noch im Spiel ist, es sei denn, die erste Sitzung wurde noch nicht gefunden, und wird gelöscht, wenn die Bereichsberechnung ab Mitternacht erreicht ist. Siehe unten:
Sehen Sie sich die Logik unten an, um den Bereich zu finden
Und wie sie festgelegt wird.
Obwohl wir das Jahr Ihres Tests nicht kennen, nehmen wir das Jahr 2025, wenn es wie in Ihrem Fall 2020 ist, haben wir keine Qualitätsdaten dafür, so dass wir in jedem Fall das Jahr 2025 verwenden und die Bereichsberechnung um Mitternacht beginnen sollte.
Auf dem Bild können Sie sehen, dass die Daten um 23:55 Uhr noch intakt sind. Wenn es jedoch Mitternacht ist, sollten wir zurücksetzen. Siehe unten.
Sie können sehen, dass wir die Daten um Mitternacht für die andere Bereichsberechnung zurücksetzen. Wenn die Bereichsberechnung abgeschlossen ist, kann die Visualisierung Ihnen helfen, zu erkennen, was wirklich passiert ist. In Ihrem Fall, in dem Sie die Standardeinstellungen verwenden, sehen Sie zum Beispiel die Balken von 0300 Uhr bis 0800 Uhr, weil wir das so definiert haben. Siehe unten:
Ich hoffe, das klärt die Dinge wieder. Sie können alles entsprechend Ihrem Handelsstil anpassen. Um die Probleme zu vermeiden, mit denen Sie konfrontiert sind, ist es ratsam, zuverlässige Testdaten zu verwenden. Vielen Dank!
Was Ihr zweites Problem anbelangt, so wird dies im Artikel erklärt, aber wenn man davon ausgeht, dass Ihr Problem von schlechten Testdaten ausgeht und einen Hinweis gibt, werden Sie, wenn der Bereich in der Berechnung ist, immer den Status "Berechnen..." sehen, bis genügend Daten vorhanden sind, um die Londoner Bereichssitzung oder die von Ihnen in den Eingaben definierte Sitzung festzulegen. Angenommen, Sie verwenden die Standardeinstellungen, wobei die Stunden vor London 3 sind, und Ihre Zeit aus dem gemeinsam genutzten Screenshot ist der 13. Februar, 2 Balken nach 22:00 Uhr, was 2*15 Minuten = 30 ist, was 22:30 ergibt, liegt also außerhalb der Bereichsberechnungszeit, so dass die Daten auf dem Panel noch sichtbar sein sollten, da der zuvor eingestellte Bereich noch im Spiel ist, es sei denn, die erste Sitzung wurde noch nicht gefunden, und wird gelöscht, wenn die Bereichsberechnung ab Mitternacht erreicht ist. Siehe unten:
Möglicherweise müssen Sie die folgende Logik zur Ermittlung des Bereichs beachten
Und wie sie festgelegt wird.
In der Abbildung unten sehen Sie, dass wir, obwohl wir das Jahr Ihres Tests nicht kennen, das Jahr 2025 nehmen. Wenn es, wie in Ihrem Fall, 2020 ist, haben wir keine Qualitätsdaten dafür, also verwenden wir so oder so das Jahr 2025 und die Berechnung des Bereichs sollte um Mitternacht beginnen.
Auf dem Bild können Sie sehen, dass die Daten um 23:55 Uhr noch intakt sind. Wenn es jedoch Mitternacht ist, sollten wir zurücksetzen. Siehe unten.
Sie können sehen, dass wir die Daten um Mitternacht für die andere Bereichsberechnung zurückgesetzt haben. Wenn die Bereichsberechnung abgeschlossen ist, kann die Visualisierung Ihnen helfen, zu erkennen, was wirklich passiert ist. In Ihrem Fall, in dem Sie die Standardeinstellungen verwenden, sehen Sie zum Beispiel die Balken von 0300 Uhr bis 0800 Uhr, weil wir das so definiert haben. Siehe unten:
Ich hoffe, das klärt die Dinge wieder. Sie können alles entsprechend Ihrem Handelsstil anpassen. Um die Probleme zu vermeiden, mit denen Sie konfrontiert sind, ist es ratsam, zuverlässige Testdaten zu verwenden. Vielen Dank!
Vielen Dank für die ausführliche Antwort.
Ja, ich habe den Artikel gelesen und meine eigene Kopie kodiert, bis ich auf die von mir beschriebenen Probleme stieß. Was ich gesehen habe, war, dass das Panel nicht aktualisiert wurde, nicht einmal während der Standardzeiten. Mein Screenshot sollte zeigen, dass, obwohl die Box auf dem Diagramm gezeichnet wurde, die Daten gesammelt wurden, aber das Panel nicht aktualisiert wurde. Außerdem gab es in den Protokollen keine Fehlermeldungen über ungültige Preise oder Niveaus.
Ich habe meiner Version Protokollmeldungen hinzugefügt; daraus kann ich ersehen, dass das Panel nicht aktualisiert wird, wenn die Spanne zu groß oder zu klein ist; das könnte also ein Teil des Grundes sein.
Ich werde die Qualität der Testdaten noch einmal überprüfen. Und vielen Dank für den Hinweis, welches Paar Sie getestet haben; ich werde sicherlich Anpassungen für die von mir gewählten Paare vornehmen.
Vielen Dank für Ihre Hilfe.
Vielen Dank für die ausführliche Antwort.
Ja, ich habe den Artikel gelesen, folgte entlang Kodierung meine eigene Kopie, bis ich in die Probleme lief ich skizziert. Was ich gesehen habe, war, dass das Feld nicht aktualisiert wurde, auch nicht zu den Standardzeiten. Mein Screenshot sollte zeigen, dass, obwohl die Box auf dem Diagramm gezeichnet wurde, die Daten gesammelt wurden, aber das Panel nicht aktualisiert wurde. Außerdem gab es in den Protokollen keine Fehlermeldungen über ungültige Preise oder Niveaus.
Ich habe meiner Version Protokollmeldungen hinzugefügt; daraus kann ich ersehen, dass das Panel nicht aktualisiert wird, wenn die Spanne zu groß oder zu klein ist; das könnte also ein Teil des Grundes sein.
Ich werde die Qualität der Testdaten noch einmal überprüfen. Und vielen Dank für den Hinweis, welches Paar Sie getestet haben; ich werde sicherlich Anpassungen für die von mir gewählten Paare vornehmen.
Vielen Dank für Ihre Hilfe.
Sicher, gern geschehen.
Danke, dass Du Deinen Code mit uns teilst.
Da ich selbst schon Session abhängige EA geschrieben habe, kann ich Dir sagen, dass der Code nur funktioniert, wenn Dein Broker immer in der Zeitzone GMT+1 ist und ebenfalls die britische Sommerzeit verwendet.
In allen anderen Fällen passt Deine Startzeit leider nicht. Warum? Weil die Londonsession um 8:00 Uhr UK-Time beginnt. Im Winter ist das 8:00 Uhr GMT und im Sommer 7:00 Uhr GMT.
TimeCurrent() gibt Dir nicht Deine lokale Uhrzeit, sondern immer die Zeit vom Handelsserver zurück.