
Automatisieren von Handelsstrategien in MQL5 (Teil 10): Entwicklung der Strategie Trend Flat Momentum
Einführung
Im vorigen Artikel (Teil 9) haben wir einen Expert Advisor entwickelt, um die asiatische Breakout-Strategie unter Verwendung von Key Session Levels und dynamischem Risikomanagement in MetaQuotes Language 5 (MQL5) zu automatisieren. In Teil 10 konzentrieren wir uns nun auf die System Trend Flat Momentum eine Methode, die zwei gleitende Durchschnitte mit dem Momentumfilter durch den Relative Strength Index (RSI) und den Commodity Channel Index (CCI) kombiniert, um Trendbewegungen präzise zu erfassen. Wir werden den Artikel anhand der folgenden Themen strukturieren:
Am Ende werden wir einen voll funktionsfähigen Expert Advisor haben, der die Trend Flat Momentum-Strategie automatisiert. Lasst uns beginnen!
Blaupause der Strategie
Die Strategie Trend Flat Momentum wurde entwickelt, um Markttrends zu erfassen, indem ein einfaches Kreuzen von gleitenden Durchschnitten mit einem robusten Momentumfilter kombiniert wird. Die Kernidee besteht darin, Kaufsignale zu generieren, wenn ein sich schnell bewegender Durchschnitt einen sich langsamer bewegenden Durchschnitt übersteigt - was auf einen Aufwärtstrend hindeutet - und gleichzeitig das Signal mit Momentum-Indikatoren zu bestätigen, bei denen es sich um einen RSI und zwei verschiedene CCI Werte handelt. Umgekehrt wird ein Verkauf signalisiert, wenn der sich schnell bewegende Durchschnitt unter den sich langsam bewegenden Durchschnitt fällt und die Momentum-Indikatoren Abwärts-Bedingungen bestätigen. Die Indikatoreinstellungen sind:
- Commodity Channel Index (CCI) (36 Periods, Close)
- Commodity Channel Index (CCI) (55 Periods, Close)
- Langsamer Relative Strength Index (RSI) (27 Periods, Close)
- Schneller gleitenden Durchschnitt (11 Perioden, Smoothed, Median Price)
- Langsamer gleitenden Durchschnitt (25 Perioden, Smoothed, Median Price)
Was die Ausstiegsstrategie betrifft, so setzen wir den Stop-Loss bei einem Kauf auf den vorherigen tiefen Umkehrpunkt und bei einem Verkauf auf den vorherigen hohen Umkehrpunkt. Die Gewinnmitnahme erfolgt auf einem vorher festgelegten Niveau, 300 Punkte vom Einstiegskurs entfernt. Dieser vielschichtige Ansatz hilft dabei, falsche Signale herauszufiltern und die Qualität der Handelseinträge zu verbessern, indem sichergestellt wird, dass Trendrichtung und Momentum übereinstimmen. Die nachstehende Visualisierung zeigt den vereinfachten Strategieplan in aller Kürze.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen Sie den MetaEditor, gehen Sie zum Navigator, suchen Sie den Ordner Indikatoren, klicken Sie auf die Registerkarte „Neu“ und folgen Sie den Anweisungen, um die Datei zu erstellen. Sobald das erledigt ist, müssen wir in der Programmierumgebung einige globale Variablen deklarieren, die wir im gesamten Programm verwenden werden.
//+------------------------------------------------------------------+ //| Copyright 2025, Forex Algo-Trader, Allan. | //| "https://t.me/Forex_Algo_Trader" | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property description "This EA trades based on Trend Flat Momentum Strategy" #property strict #include <Trade\Trade.mqh> //--- Include the Trade library for order management. CTrade obj_Trade; //--- Create an instance of the CTrade class to handle trading operations. // Input parameters input int InpCCI36Period = 36; //--- CCI period 1 input int InpCCI55Period = 55; //--- CCI period 2 input int InpRSIPeriod = 27; //--- RSI period input int InpMAFastPeriod = 11; //--- Fast MA period input int InpMASlowPeriod = 25; //--- Slow MA period input double InpRSIThreshold = 58.0; //--- RSI threshold for Buy signal (Sell uses 100 - Threshold) input int InpTakeProfitPoints = 300; //--- Take profit in points input double InpLotSize = 0.1; //--- Trade lot size // Pivot parameters for detecting swing highs/lows input int PivotLeft = 2; //--- Number of bars to the left for pivot detection input int PivotRight = 2; //--- Number of bars to the right for pivot detection // Global indicator handles int handleCCI36; //--- Handle for the CCI indicator with period InpCCI36Period int handleCCI55; //--- Handle for the CCI indicator with period InpCCI55Period int handleRSI; //--- Handle for the RSI indicator with period InpRSIPeriod int handleMA11; //--- Handle for the fast moving average (MA) with period InpMAFastPeriod int handleMA25; //--- Handle for the slow moving average (MA) with period InpMASlowPeriod // Global dynamic storage buffers double ma11_buffer[]; //--- Dynamic array to store fast MA values double ma25_buffer[]; //--- Dynamic array to store slow MA values double rsi_buffer[]; //--- Dynamic array to store RSI values double cci36_buffer[]; //--- Dynamic array to store CCI values (period 36) double cci55_buffer[]; //--- Dynamic array to store CCI values (period 55) // To detect a new bar datetime lastBarTime = 0; //--- Variable to store the time of the last processed bar
Wir beginnen mit dem Einbinden der Datei „Trade\Trade.mqh“ unter Verwendung von #include, um auf die integrierten Handelsfunktionen zuzugreifen und „obj_Trade“, eine Instanz der Klasse „CTrade“, für die Ausführung von Kauf- und Verkaufsaufträgen zu erstellen. Wir definieren wichtige Eingabeparameter für die Strategiekonfiguration, darunter „InpCCI36Period“ und „InpCCI55Period“ für „CCI“-Indikatoren, „InpRSIPeriod“ für „RSI“ sowie „InpMAFastPeriod“ und „InpMASlowPeriod“ für zwei gleitende Durchschnitte. „InpRSIThreshold“ legt eine Bedingung für die Handelsfilterung fest, während „InpTakeProfitPoints“ das feste Take-Profit-Niveau bestimmt und „InpLotSize“ die Positionsgröße steuert.
Um die Handelsausführung zu verbessern, führen wir „PivotLeft“ und „PivotRight“ ein, die die Anzahl der Balken definieren, die zur Erkennung von hohen und tiefen Umkehrpunkten für die Stop-Loss-Platzierung verwendet werden. Globale Indikator-Handles wie „handleCCI36“, „handleCCI55“, „handleRSI“, „handleMA11“ und „handleMA25“ ermöglichen es uns, Indikatorwerte effizient abzurufen. Dynamische Arrays speichern diese Werte in „ma11_buffer“, „ma25_buffer“, „rsi_buffer“, „cci36_buffer“ und „cci55_buffer“ und gewährleisten so eine reibungslose Datenverarbeitung. Schließlich verfolgt „lastBarTime“ den zuletzt verarbeiteten Balken, um zu verhindern, dass mehrere Handelsgeschäfte auf derselben Kerze getätigt werden, und um eine genaue Handelsausführung zu gewährleisten. Anschließend können wir die Indikatoren in OnInit initialisieren.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create CCI handle for period InpCCI36Period using the close price. handleCCI36 = iCCI(_Symbol, _Period, InpCCI36Period, PRICE_CLOSE); //--- Create the CCI36 indicator handle. if (handleCCI36 == INVALID_HANDLE) { //--- Check if the CCI36 handle is valid. Print("Error creating CCI36 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create CCI handle for period InpCCI55Period using the close price. handleCCI55 = iCCI(_Symbol, _Period, InpCCI55Period, PRICE_CLOSE); //--- Create the CCI55 indicator handle. if (handleCCI55 == INVALID_HANDLE) { //--- Check if the CCI55 handle is valid. Print("Error creating CCI55 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create RSI handle for period InpRSIPeriod using the close price. handleRSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); //--- Create the RSI indicator handle. if (handleRSI == INVALID_HANDLE) { //--- Check if the RSI handle is valid. Print("Error creating RSI handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create fast MA handle using MODE_SMMA on the median price with period InpMAFastPeriod. handleMA11 = iMA(_Symbol, _Period, InpMAFastPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the fast MA handle. if (handleMA11 == INVALID_HANDLE) { //--- Check if the fast MA handle is valid. Print("Error creating MA11 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Create slow MA handle using MODE_SMMA on the median price with period InpMASlowPeriod. handleMA25 = iMA(_Symbol, _Period, InpMASlowPeriod, 0, MODE_SMMA, PRICE_MEDIAN); //--- Create the slow MA handle. if (handleMA25 == INVALID_HANDLE) { //--- Check if the slow MA handle is valid. Print("Error creating MA25 handle"); //--- Print an error message if invalid. return (INIT_FAILED); //--- Return failure if handle creation failed. } //--- Set the dynamic arrays as time series (index 0 = most recent closed bar). ArraySetAsSeries(ma11_buffer, true); //--- Set ma11_buffer as a time series. ArraySetAsSeries(ma25_buffer, true); //--- Set ma25_buffer as a time series. ArraySetAsSeries(rsi_buffer, true); //--- Set rsi_buffer as a time series. ArraySetAsSeries(cci36_buffer, true); //--- Set cci36_buffer as a time series. ArraySetAsSeries(cci55_buffer, true); //--- Set cci55_buffer as a time series. return (INIT_SUCCEEDED); //--- Return success after initialization. }
In OnInit initialisieren wir den Expert Advisor, indem wir Indikator-Handles erstellen und validieren. Wir verwenden die Funktion iCCI, um CCI-Handles mit den Perioden „InpCCI36Period“ und „InpCCI55Period“ zu erstellen, die Funktion iRSI für den RSI-Handle und die Funktion iMA für schnelle und langsame SMMA mit den Perioden „InpMAFastPeriod“ und „InpMASlowPeriod“. Wenn ein Handle ungültig ist (INVALID_HANDLE), wird eine Fehlermeldung ausgegeben und ein Fehler zurückgegeben (INIT_FAILED). Schließlich verwenden wir die Funktion ArraySetAsSeries, um die Puffer als Zeitreihen zu formatieren und bei erfolgreicher Initialisierung den Erfolg zurückzugeben. Um Ressourcen zu sparen, müssen wir die erstellten Handles freigeben, wenn das Programm wie folgt entfernt wird.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (handleCCI36 != INVALID_HANDLE) { //--- If the CCI36 handle is valid, IndicatorRelease(handleCCI36); //--- release the CCI36 indicator. } if (handleCCI55 != INVALID_HANDLE) { //--- If the CCI55 handle is valid, IndicatorRelease(handleCCI55); //--- release the CCI55 indicator. } if (handleRSI != INVALID_HANDLE) { //--- If the RSI handle is valid, IndicatorRelease(handleRSI); //--- release the RSI indicator. } if (handleMA11 != INVALID_HANDLE) { //--- If the fast MA handle is valid, IndicatorRelease(handleMA11); //--- release the fast MA indicator. } if (handleMA25 != INVALID_HANDLE) { //--- If the slow MA handle is valid, IndicatorRelease(handleMA25); //--- release the slow MA indicator. } }
Hier wird die Deinitialisierung des Expert Advisors durch Freigabe der Indikatorressourcen in OnDeinit durchgeführt. Wir prüfen, ob jeder Indikator - „CCI36“, „CCI55“, „RSI“, schneller und langsamer MA - gültig ist. Ist dies der Fall, verwenden wir die Funktion IndicatorRelease, um die zugewiesenen Ressourcen freizugeben und so eine effiziente Speicherverwaltung zu gewährleisten. Wir können nun zu OnTick übergehen, wo die gesamte Datenverarbeitung und Entscheidungsfindung stattfindet.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get the time of the current bar. datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Retrieve the current bar's time. if (currentBarTime != lastBarTime) { //--- If a new bar has formed, lastBarTime = currentBarTime; //--- update lastBarTime with the current bar's time. OnNewBar(); //--- Process the new bar. } }
Hier verwenden wir die Ereignisbehandlung von OnTick, um Preisaktualisierungen zu überwachen und neue Balken zu erkennen. Mit der Funktion iTime wird die Zeit des aktuellen Balkens ermittelt und mit dem gespeicherten Wert „lastBarTime“ verglichen. Wenn ein neuer Balken erkannt wird, aktualisieren wir „lastBarTime“, um sicherzustellen, dass wir nur einmal pro Balken handeln, und rufen die Funktion „OnNewBar“ auf, um die Daten des neuen Balkens zu verarbeiten. Dies ist die Funktion, mit der wir die gesamte Signalerzeugung abwickeln, und sie lautet wie folgt.
//+------------------------------------------------------------------+ //| Function called on every new bar (bar close) | //+------------------------------------------------------------------+ void OnNewBar() { //--- Resize the dynamic arrays to hold 2 values (last closed bar and the one before). ArrayResize(ma11_buffer, 2); //--- Resize ma11_buffer to 2 elements. ArrayResize(ma25_buffer, 2); //--- Resize ma25_buffer to 2 elements. ArrayResize(rsi_buffer, 2); //--- Resize rsi_buffer to 2 elements. ArrayResize(cci36_buffer, 2); //--- Resize cci36_buffer to 2 elements. ArrayResize(cci55_buffer, 2); //--- Resize cci55_buffer to 2 elements. //--- Copy indicator values into the dynamic arrays. if (CopyBuffer(handleMA11, 0, 1, 2, ma11_buffer) != 2) { //--- Copy 2 values from the fast MA indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleMA25, 0, 1, 2, ma25_buffer) != 2) { //--- Copy 2 values from the slow MA indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleRSI, 0, 1, 2, rsi_buffer) != 2) { //--- Copy 2 values from the RSI indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleCCI36, 0, 1, 2, cci36_buffer) != 2) { //--- Copy 2 values from the CCI36 indicator. return; //--- Exit the function if copying fails. } if (CopyBuffer(handleCCI55, 0, 1, 2, cci55_buffer) != 2) { //--- Copy 2 values from the CCI55 indicator. return; //--- Exit the function if copying fails. } //--- For clarity, assign the values from the arrays. //--- Index 0: last closed bar, Index 1: bar before. double ma11_current = ma11_buffer[0]; //--- Fast MA value for the last closed bar. double ma11_previous = ma11_buffer[1]; //--- Fast MA value for the previous bar. double ma25_current = ma25_buffer[0]; //--- Slow MA value for the last closed bar. double ma25_previous = ma25_buffer[1]; //--- Slow MA value for the previous bar. double rsi_current = rsi_buffer[0]; //--- RSI value for the last closed bar. double cci36_current = cci36_buffer[0]; //--- CCI36 value for the last closed bar. double cci55_current = cci55_buffer[0]; //--- CCI55 value for the last closed bar. }
In der ungültigen Funktion „OnNewBar“, die wir erstellen, stellen wir zunächst sicher, dass die dynamischen Arrays, die die Indikatorwerte enthalten, mit der Funktion ArrayResize in der Größe angepasst werden, um die letzten beiden geschlossenen Balken zu speichern. Anschließend werden mit der Funktion CopyBuffer die Indikatorwerte für den schnell gleitenden Durchschnitt, den langsam gleitenden Durchschnitt, den RSI und zwei CCI-Indikatoren abgerufen. Wenn einer dieser Vorgänge fehlschlägt, wird die Funktion beendet, um Fehler zu vermeiden. Sobald die Werte erfolgreich kopiert wurden, ordnen wir sie Variablen zu, um sie leichter referenzieren zu können, wobei wir zwischen dem letzten geschlossenen Balken und dem Balken davor unterscheiden. Auf diese Weise ist sichergestellt, dass wir für unsere Handelsentscheidungen immer die aktuellsten Marktdaten zur Verfügung haben. Wenn wir die Daten erfolgreich abrufen, können wir fortfahren, Handelsentscheidungen zu treffen. Wir beginnen mit der Logik für einen Kauf.
//--- Check for Buy Conditions: bool maCrossoverBuy = (ma11_previous < ma25_previous) && (ma11_current > ma25_current); //--- True if fast MA crosses above slow MA. bool rsiConditionBuy = (rsi_current > InpRSIThreshold); //--- True if RSI is above the Buy threshold. bool cci36ConditionBuy = (cci36_current > 0); //--- True if CCI36 is positive. bool cci55ConditionBuy = (cci55_current > 0); //--- True if CCI55 is positive. if (maCrossoverBuy) { //--- If crossover for MA Buy is true... bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met. //--- Check RSI condition for Buy. if (!rsiConditionBuy) { //--- If the RSI condition is not met... Print("Buy signal rejected: RSI condition not met. RSI=", rsi_current, " Threshold=", InpRSIThreshold); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI36 condition for Buy. if (!cci36ConditionBuy) { //--- If the CCI36 condition is not met... Print("Buy signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI55 condition for Buy. if (!cci55ConditionBuy) { //--- If the CCI55 condition is not met... Print("Buy signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. }
Hier bewerten wir die Bedingungen für den Einstieg in einen Kauf. Die Variable „maCrossoverBuy“ prüft, ob der sich schnell bewegende Durchschnitt („ma11“) den sich langsam bewegenden Durchschnitt („ma25“) überschritten hat, was ein potenzielles Kaufsignal darstellt. Die „rsiConditionBuy“ stellt sicher, dass der RSI-Wert über dem definierten „InpRSIThreshold“ liegt, was ein starkes Aufwärts-Momentum bestätigt. Die Indikatoren „cci36ConditionBuy“ und „cci55ConditionBuy“ prüfen, ob beide CCI-Indikatoren positiv sind, was bedeutet, dass sich der Markt in einem günstigen Trend befindet. Wenn die Bedingung „maCrossoverBuy“ erfüllt ist, werden die übrigen Bedingungen überprüft. Wenn eine Bedingung fehlschlägt, drucken wir eine Nachricht aus, die angibt, warum das Kaufsignal abgelehnt wurde, und setzen das Flag „conditionsOk“ auf false, um eine weitere Handelsausführung zu verhindern. Diese umfassende Prüfung stellt sicher, dass nur Handelsgeschäfte mit einer starken Aufwärts-Bestätigung getätigt werden. Wenn wir dann eine erkennen, können wir die Position eröffnen.
if (conditionsOk) { //--- If all Buy conditions are met... //--- Get stop loss from previous swing low. double stopLoss = GetPivotLow(PivotLeft, PivotRight); //--- Use pivot low as the stop loss. if (stopLoss <= 0) { //--- If no valid pivot low is found... stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Fallback to current bid price. } double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Determine entry price as current ask price. double tp = entryPrice + InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points. //--- Print the swing point (pivot low) used as stop loss. Print("Buy signal: Swing Low used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot low used. if (obj_Trade.Buy(InpLotSize, NULL, entryPrice, stopLoss, tp, "Buy Order")) { //--- Attempt to open a Buy order. Print("Buy order opened at ", entryPrice); //--- Notify the user if the order is opened successfully. } else { Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails. } return; //--- Exit after processing a valid Buy signal. } else { Print("Buy signal not executed due to failed condition(s)."); //--- Notify the user if Buy conditions failed. }
Nachdem wir uns vergewissert haben, dass alle Kaufbedingungen erfüllt sind, legen wir den Stop-Loss für den Handel fest. Wir verwenden die Funktion „GetPivotLow“, um den vorherige, tiefen Umkehrpunkt zu finden, das als Stop-Loss festgelegt wird. Wenn kein gültiges Pivot-Tief gefunden wird (d.h. der Stop-Loss ist kleiner oder gleich 0), wird der aktuelle Geldkurs als Ausweichlösung verwendet. Der Einstiegskurs wird mit Hilfe der Funktion SymbolInfoDouble mit dem Parameter SYMBOL_ASK aus dem aktuellen Briefkurs übernommen. Wir berechnen den Take-Profit („tp“), indem wir die angegebenen „InpTakeProfitPoints“ zum Einstiegskurs addieren, angepasst um den Punktwert des Marktes(_Point).
Sobald der Einstiegskurs, der Stop-Loss und der Take-Profit festgelegt sind, wird ein Kaufauftrag mit der Methode „obj_Trade.Buy“ versucht. Wenn der Kaufauftrag erfolgreich eröffnet wurde, drucken wir eine Bestätigungsnachricht aus. Wenn der Auftrag fehlschlägt, geben wir die Fehlermeldung und die Fehlerbeschreibung mit „obj_Trade.ResultRetcodeDescription“ an. Wenn die Kaufbedingungen nicht erfüllt sind, wird eine Meldung gedruckt, die besagt, dass das Kaufsignal abgelehnt wurde, und es wird kein Handelsgeschäft eröffnet. Wir haben eine nutzerdefinierte Funktion „GetPivotLow“ verwendet, die wie folgt implementiert ist.
//+------------------------------------------------------------------+ //| Function to find the most recent swing low (pivot low) | //+------------------------------------------------------------------+ double GetPivotLow(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array. if (copied <= (left + right)) { //--- Check if sufficient data was copied. return (0); //--- Return 0 if there are not enough bars. } //--- Loop through the bars to find a pivot low. for (int i = left; i <= copied - right - 1; i++) { bool isPivot = true; //--- Assume the current bar is a pivot low. double currentLow = rates[i].low; //--- Get the low value of the current bar. for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars. if (j == i) { //--- Skip the current bar. continue; } if (rates[j].low <= currentLow) { //--- If any neighbor's low is lower or equal, isPivot = false; //--- then the current bar is not a pivot low. break; } } if (isPivot) { //--- If a pivot low is confirmed, return (currentLow); //--- return the low value of the pivot. } } return (0); //--- Return 0 if no pivot low is found. }
Die Funktion zielt darauf ab, den jüngsten tiefen Umkehrpunkt (Pivot-Tief) zu identifizieren, indem die letzten 100 Balken der Marktdaten mit der Funktion CopyRates gescannt werden. Das Array „rates“ der Struktur MqlRates enthält die Preisdaten, und wir stellen sicher, dass genügend Balken vorhanden sind, um die Berechnung durchzuführen, indem wir prüfen, ob die kopierten Daten größer oder gleich der Summe der Parameter „left“ und „right“ sind. Die Funktion läuft dann in einer Schleife durch die Balken und sucht nach einem tiefen Umkehrpunkt, indem sie das Tief des aktuellen Balkens mit den benachbarten Balken innerhalb des angegebenen „linken“ und „rechten“ Bereichs vergleicht. Wenn das Tief eines benachbarten Balkens niedriger oder gleich dem Tief des aktuellen Balkens ist, ist der aktuelle Balken kein tiefer Umkehrpunkt. Wenn ein tiefer Umkehrpunkt gefunden wird, wird dessen Tiefstwert zurückgegeben. Wenn nach Prüfung aller Balken kein tiefer Umkehrpunkt gefunden wird, gibt die Funktion 0 zurück.
Um den hohen Umkehrpunkt zu bekommen, verwenden wir einen ähnlichen Ansatz, nur mit umgekehrter Logik.
//+------------------------------------------------------------------+ //| Function to find the most recent swing high (pivot high) | //+------------------------------------------------------------------+ double GetPivotHigh(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. int copied = CopyRates(_Symbol, _Period, 0, 100, rates); //--- Copy the last 100 bars into the rates array. if (copied <= (left + right)) { //--- Check if sufficient data was copied. return (0); //--- Return 0 if there are not enough bars. } //--- Loop through the bars to find a pivot high. for (int i = left; i <= copied - right - 1; i++) { bool isPivot = true; //--- Assume the current bar is a pivot high. double currentHigh = rates[i].high; //--- Get the high value of the current bar. for (int j = i - left; j <= i + right; j++) { //--- Loop through neighboring bars. if (j == i) { //--- Skip the current bar. continue; } if (rates[j].high >= currentHigh) { //--- If any neighbor's high is higher or equal, isPivot = false; //--- then the current bar is not a pivot high. break; } } if (isPivot) { //--- If a pivot high is confirmed, return (currentHigh); //--- return the high value of the pivot. } } return (0); //--- Return 0 if no pivot high is found. }
Mit diesen Funktionen können wir die Verkaufssignale mit einem ähnlichen umgekehrten Ansatz wie bei den Kaufpositionen verarbeiten.
//--- Check for Sell Conditions: bool maCrossoverSell = (ma11_previous > ma25_previous) && (ma11_current < ma25_current); //--- True if fast MA crosses below slow MA. bool rsiConditionSell = (rsi_current < (100.0 - InpRSIThreshold)); //--- True if RSI is below the Sell threshold. bool cci36ConditionSell = (cci36_current < 0); //--- True if CCI36 is negative. bool cci55ConditionSell = (cci55_current < 0); //--- True if CCI55 is negative. if (maCrossoverSell) { //--- If crossover for MA Sell is true... bool conditionsOk = true; //--- Initialize a flag to track if all conditions are met. //--- Check RSI condition for Sell. if (!rsiConditionSell) { //--- If the RSI condition is not met... Print("Sell signal rejected: RSI condition not met. RSI=", rsi_current, " Required below=", (100.0 - InpRSIThreshold)); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI36 condition for Sell. if (!cci36ConditionSell) { //--- If the CCI36 condition is not met... Print("Sell signal rejected: CCI36 condition not met. CCI36=", cci36_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } //--- Check CCI55 condition for Sell. if (!cci55ConditionSell) { //--- If the CCI55 condition is not met... Print("Sell signal rejected: CCI55 condition not met. CCI55=", cci55_current); //--- Notify the user. conditionsOk = false; //--- Mark the conditions as not met. } if (conditionsOk) { //--- If all Sell conditions are met... //--- Get stop loss from previous swing high. double stopLoss = GetPivotHigh(PivotLeft, PivotRight); //--- Use pivot high as the stop loss. if (stopLoss <= 0) { //--- If no valid pivot high is found... stopLoss = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Fallback to current ask price. } double entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Determine entry price as current bid price. double tp = entryPrice - InpTakeProfitPoints * _Point; //--- Calculate take profit based on fixed points. //--- Print the swing point (pivot high) used as stop loss. Print("Sell signal: Swing High used as Stop Loss = ", stopLoss); //--- Notify the user of the pivot high used. if (obj_Trade.Sell(InpLotSize, NULL, entryPrice, stopLoss, tp, "Sell Order")) { //--- Attempt to open a Sell order. Print("Sell order opened at ", entryPrice); //--- Notify the user if the order is opened successfully. } else { Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Notify the user if the order fails. } return; //--- Exit after processing a valid Sell signal. } else { Print("Sell signal not executed due to failed condition(s)."); //--- Notify the user if Sell conditions failed. } }
Hier prüfen wir die Bedingungen für ein Verkaufssignal, indem wir die für Kaufsignale verwendete Logik umkehren. Zunächst wird geprüft, ob der sich schnell bewegende Durchschnitt den sich langsam bewegenden Durchschnitt unterschreitet, was durch die Bedingung „maCrossoverSell“ erfasst wird. Als Nächstes überprüfen wir, ob der RSI unter der Verkaufsschwelle liegt und ob die Werte des CCI36 und des CCI55 negativ sind.
Wenn alle Bedingungen erfüllt sind, berechnen wir den Stop-Loss mit der Funktion GetPivotHigh“, um den letzten hohen Umkehrpunkt zu finden und den Take-Profit auf der Grundlage eines festen Punktabstands zu bestimmen. Anschließend versuchen wir, mit der Methode „obj_Trade.Sell“ einen Verkaufsauftrag zu eröffnen. Wenn der Auftrag erfolgreich ist, wird eine Bestätigungsmeldung gedruckt, wenn nicht, wird eine Fehlermeldung angezeigt. Wenn eine Bedingung nicht erfüllt ist, wird der Nutzer benachrichtigt, dass das Verkaufssignal abgelehnt wurde. Nach dem Kompilieren und Ausführen des Programms erhalten wir das folgende Ergebnis.
Aus dem Bild können wir ersehen, dass das Programm alle Eingabebedingungen identifiziert und überprüft und bei Bestätigung die entsprechende Position mit den entsprechenden Eingabeparametern öffnet und somit unser Ziel erreicht. Bleibt nur noch der Backtest des Programms, und das wird im nächsten Abschnitt behandelt.
Backtest
Bei intensiven Backtests des Programms ist uns aufgefallen, dass wir bei der Suche nach Umkehrpunkten zuerst die ältesten Daten zum Vergleich herangezogen haben, was manchmal, wenn auch selten, zu ungültigen Umkehrpunkten und damit zu ungültigen Stopps für den Stop-Loss führte.
Um das Problem zu entschärfen, haben wir einen Ansatz gewählt, bei dem wir die Suchdaten mit der Funktion ArraySetAsSeries als Zeitreihe festlegen, sodass die neuesten Daten an der ersten Position in den Speicherarrays stehen und wir daher die neuesten Daten zuerst für die Analyse verwenden, wie folgt.
//+------------------------------------------------------------------+ //| Function to find the most recent swing low (pivot low) | //+------------------------------------------------------------------+ double GetPivotLow(int left, int right) { MqlRates rates[]; //--- Declare an array to store rate data. ArraySetAsSeries(rates, true); //--- }
Nach weiteren Tests zur Bestätigung, haben wir folgendes Ergebnis.
Aus dem Bild können wir ersehen, dass wir die aktuellen Umkehrpunkte korrekt erhalten und somit den Fehler „ungültige Stopps“ vermeiden konnten. Auf diese Weise wird uns der Handel nicht verwehrt, und nach gründlichen Tests haben wir von 2023 bis 2024 die folgenden Ergebnisse.
Backtest-Grafik:
Backtest-Bericht:
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir erfolgreich einen Expert Advisor in MQL5 entwickelt haben, der eine umfassende Handelsstrategie des Trend Flat Momentum automatisiert, die mehrere Trend- und Momentum-Indikatoren für Kauf- und Verkaufssignale kombiniert. Durch die Einbeziehung von Schlüsselbedingungen wie Indikatorüberkreuzungen und Schwellenwertprüfungen haben wir ein dynamisches System geschaffen, das auf Markttrends mit präzisen Einstiegs- und Ausstiegspunkten reagiert.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Marktbedingungen können sich schnell ändern. Die angebotene Strategie bietet zwar einen strukturierten Ansatz für den Handel, garantiert aber keine Rentabilität. Gründliche Backtests und ein solides Risikomanagement sind von entscheidender Bedeutung, bevor dieses System in einer Live-Umgebung eingesetzt wird.
Durch die Umsetzung dieser Konzepte können Sie Ihre Fähigkeiten im algorithmischen Handel verbessern und Ihren Ansatz der technischen Analyse verfeinern. Wir wünschen Ihnen viel Erfolg bei der Weiterentwicklung und Verbesserung Ihrer Handelsstrategien!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17247
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.





- 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.