
Automatisieren von Handelsstrategien in MQL5 (Teil 14): Stapelstrategie für den Handel mit statistischen MACD-RSI-Methoden
Einführung
In unserem letzten Artikel (Teil 13) haben wir den Handelsalgorithmus von Kopf und Schultern in MetaQuotes Language 5 (MQL5) implementiert, um ein klassisches Umkehrmuster zur Erfassung von Marktumschwüngen zu automatisieren. In Teil 14 widmen wir uns nun der Entwicklung einer Stapelstrategie für den Handel mit dem Moving Average Convergence Divergence (MACD) und dem Relative Strength Indicator (RSI), die durch statistische Methoden ergänzt wird, um Positionen in Trendmärkten dynamisch zu skalieren. Wir werden die folgenden Themen behandeln:
Am Ende dieses Artikels werden Sie über einen robusten Expert Advisor verfügen, mit dem Sie Handelsgeschäfte präzise stapeln können - legen Sie los!
Strategische Architektur
Die in diesem Artikel vorgestellte Strategie der Handelsstapel (Trading-Layering) zielt darauf ab, aus anhaltenden Markttrends Kapital zu schlagen, indem schrittweise Positionen hinzugefügt werden, wenn sich der Preis in eine günstige Richtung bewegt - eine Methode, die oft als Kaskadierung bezeichnet wird. Im Gegensatz zu traditionellen Strategien der Einzeleröffnungen, die auf ein festes Ziel abzielen, wird bei diesem Ansatz das Momentum genutzt, indem jedes Mal, wenn eine Gewinnschwelle erreicht wird, zusätzliche Handelsgeschäfte getätigt werden, wodurch sich die potenziellen Gewinne bei kontrolliertem Risiko effektiv erhöhen. Im Kern kombiniert die Strategie zwei weithin bekannte technische Indikatoren - MACD und RSI - mit einem statistischen Overlay, um sicherzustellen, dass Einstiege sowohl zeitnah als auch robust erfolgen, was sie für Märkte mit klaren Richtungsbewegungen geeignet macht.
Wir werden die Stärken von MACD und RSI nutzen, um eine solide Grundlage für Handelssignale zu schaffen und klare Regeln festzulegen, wann der Prozess des Stapelns eingeleitet werden soll. Unser Plan sieht vor, dass wir MACD nutzen, um die Richtung und Stärke des Trends zu bestätigen und sicherzustellen, dass wir nur dann in den Handel einsteigen, wenn der Markt einen konsistenten Trend aufweist, während der RSI die optimalen Einstiegszeitpunkte ermittelt, indem es Verschiebungen von extremen Kursniveaus erkennt. Durch die Integration dieser Indikatoren wollen wir einen zuverlässigen Trigger-Mechanismus schaffen, der den ersten Handel auslöst, der dann als Ausgangspunkt für unsere kaskadierende Sequenz dient, die es uns ermöglicht, Positionen im weiteren Verlauf des Trends aufzubauen. Hier ist eine Visualisierung der Strategie.
Als Nächstes werden wir diesen Aufbau durch die Einbeziehung statistischer Methoden verbessern, um unsere Eingabegenauigkeit zu erhöhen und den Stapelprozess zu steuern. Wir werden untersuchen, wie man statistische Filter - z. B. die Analyse des historischen RSI-Verhaltens - anwendet, um Signale zu validieren und sicherzustellen, dass nur unter statistisch signifikanten Bedingungen gehandelt wird. Der Plan erstreckt sich dann auf die Definition der Stapelregeln, in denen wir darlegen, wie jedes neue Handelsgeschäft hinzugefügt wird, wenn die Gewinnziele erreicht sind, sowie auf die Anpassung der Risikoniveaus, um die Gewinne zu schützen, was in einer dynamischen Strategie gipfelt, die sich der Marktdynamik anpasst und gleichzeitig diszipliniert ausgeführt wird. Fangen wir an.
Implementation in MQL5
Um das Programm in MQL5 zu erstellen, öffnen wir den MetaEditor, gehen zum Navigator, suchen den Ordner der Indikatoren, klicken 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.
//+------------------------------------------------------------------+ //| MACD-RSI LAYERING STRATEGY.mq5 | //| Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. | //| https://youtube.com/@ForexAlgo-Trader? | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader" #property link "https://youtube.com/@ForexAlgo-Trader?" #property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels" #property version "1.0" #include <Trade\Trade.mqh>//---- Includes the Trade.mqh library for trading operations CTrade obj_Trade;//---- Declares a CTrade object for executing trade operations int rsiHandle = INVALID_HANDLE;//---- Initializes RSI indicator handle as invalid double rsiValues[];//---- Declares an array to store RSI values int handleMACD = INVALID_HANDLE;//---- Initializes MACD indicator handle as invalid double macdMAIN[];//---- Declares an array to store MACD main line values double macdSIGNAL[];//---- Declares an array to store MACD signal line values double takeProfitLevel = 0;//---- Initializes the take profit level variable double stopLossLevel = 0;//---- Initializes the stop loss level variable bool buySequenceActive = false;//---- Flag to track if a buy sequence is active bool sellSequenceActive = false;//---- Flag to track if a sell sequence is active // Inputs with clear names input int stopLossPoints = 300; // Initial Stop Loss (points) input double tradeVolume = 0.01; // Trade Volume (lots) input int minStopLossPoints = 100; // Minimum SL for cascading orders (points) input int rsiLookbackPeriod = 14; // RSI Lookback Period input double rsiOverboughtLevel = 70.0; // RSI Overbought Threshold input double rsiOversoldLevel = 30.0; // RSI Oversold Threshold input bool useStatisticalFilter = true; // Enable Statistical Filter input int statAnalysisPeriod = 20; // Statistical Analysis Period (bars) input double statDeviationFactor = 1.0; // Statistical Deviation Factor input double riskRewardRatio = 1.0; // Risk:Reward Ratio // Object names for visualization string takeProfitLineName = "TakeProfitLine";//---- Name of the take profit line object for chart visualization string takeProfitTextName = "TakeProfitText";//---- Name of the take profit text object for chart visualization
Wir beginnen mit der Einrichtung des wesentlichen Rahmens für unsere Handelsstapelstrategie, beginnend mit der Einbeziehung der Bibliothek „Trade.mqh“ und der Deklaration eines „CTrade“-Objekts mit dem Namen „obj_Trade“, um alle Handelsoperationen abzuwickeln. Wir initialisieren die Handles der Schlüsselindikatoren - „rsiHandle“ für RSI und "handleMACD" for MACD - beide werden auf INVALID_HANDLE gesetzt, zusammen mit den Arrays wie „rsiValues“, „macdMAIN“ und „macdSIGNAL“, um ihre jeweiligen Daten zu speichern. Um den Status der Strategie zu verfolgen, definieren wir Variablen wie „takeProfitLevel“ und „stopLossLevel“ für die Handelsniveaus und boolesche Flags „buySequenceActive“ und „sellSequenceActive“, um zu überwachen, ob eine Kauf- oder Verkaufsstapel im Gange ist, um sicherzustellen, dass das System weiß, wann eine Kaskadierung von Geschäften erforderlich ist.
Als Nächstes legen wir durch den Nutzer konfigurierbare Eingaben fest, um die Strategie anpassungsfähig zu machen, darunter „stopLossPoints“ für den anfänglichen Stop-Loss-Abstand, „tradeVolume“ für die Losgröße und „minStopLossPoints“ für engere Stops bei kaskadierenden Handelsgeschäfte. Für die Indikatoren setzen wir „rsiLookbackPeriod“, um das RSI-Berechnungsfenster zu definieren, „rsiOverboughtLevel“ und „rsiOversoldLevel“ als Einstiegsschwellen und „riskRewardRatio“, um die Gewinnziele im Verhältnis zum Risiko zu steuern. Um statistische Methoden einzubeziehen, führen wir „useStatisticalFilter“ als Toggle ein, gepaart mit „statAnalysisPeriod“ und „statDeviationFactor“, die es uns ermöglichen, Signale basierend auf dem statistischen Verhalten des RSI zu verfeinern, um sicherzustellen, dass Handelsgeschäfte mit signifikanten Marktabweichungen übereinstimmen.
Schließlich bereiten wir uns auf ein visuelles Feedback vor, indem wir „takeProfitLineName“ und „takeProfitTextName“ als Objektnamen für chartbasierte Take-Profit-Linien und -Beschriftungen definieren, um die Fähigkeit des Händlers zu verbessern, den Stapel in Echtzeit zu überwachen. Sobald wir das Programm kompiliert haben, sehen wir die folgende Ausgabe.
Als Nächstes gehen wir zur Ereignisbehandlung für die Initialisierung (OnInit) über, wo wir die Initialisierungseigenschaften behandeln.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){//---- Expert advisor initialization function rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);//---- Creates RSI indicator handle with specified parameters handleMACD = iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);//---- Creates MACD indicator handle with standard 12,26,9 settings if(rsiHandle == INVALID_HANDLE){//---- Checks if RSI handle creation failed Print("UNABLE TO LOAD RSI, REVERTING NOW");//---- Prints error message if RSI failed to load return(INIT_FAILED);//---- Returns initialization failure code } if(handleMACD == INVALID_HANDLE){//---- Checks if MACD handle creation failed Print("UNABLE TO LOAD MACD, REVERTING NOW");//---- Prints error message if MACD failed to load return(INIT_FAILED);//---- Returns initialization failure code } ArraySetAsSeries(rsiValues, true);//---- Sets RSI values array as a time series (latest data at index 0) ArraySetAsSeries(macdMAIN,true);//---- Sets MACD main line array as a time series ArraySetAsSeries(macdSIGNAL,true);//---- Sets MACD signal line array as a time series return(INIT_SUCCEEDED);//---- Returns successful initialization code }
Hier beginnen wir mit der Implementierung unserer Strategie der Handelsstapel in der Funktion OnInit, dem Ausgangspunkt, an dem wir die wichtigsten Komponenten initialisieren, bevor der EA mit Marktdaten arbeitet. Wir richten den RSI-Indikator ein, indem wir die Funktion iRSI verwenden, um „rsiHandle“ ein Handle zuzuweisen, wobei wir _Symbol für den aktuellen Chart, _Period für den Zeitrahmen, „rsiLookbackPeriod“ für das Rückblickfenster und „PRICE_CLOSE“, um die Schlusskurse zu verwenden, gefolgt von der Einstellungen des MACD mit der Funktion iMACD, die ihr Handle in „handleMACD“ speichert und die Standardperioden 12, 26, 9 auf „PRICE_CLOSE“ für die Trendanalyse verwendet.
Um Robustheit zu gewährleisten, prüfen wir, ob „rsiHandle“ gleich INVALID_HANDLE ist, und wenn ja, verwenden wir die Print, um „UNABLE TO LOAD RSI, REVERTING NOW“ zu protokollieren und INIT_FAILED zurückzugeben, und wiederholen dies für „handleMACD“ mit „UNABLE TO LOAD MACD, REVERTING NOW“, um im Fehlerfall anzuhalten. Sobald dies bestätigt ist, konfigurieren wir „rsiValues“, „macdMAIN“ und „macdSIGNAL“ als Zeitserien-Arrays, indem wir die Funktion ArraySetAsSeries mit true verwenden und die neuesten Daten am Index Null ausrichten, und geben dann INIT_SUCCEEDED zurück, um ein erfolgreiches Setup für den Handel zu signalisieren. Als Nächstes können wir zu OnTick gehen und unsere eigentliche Handelslogik definieren.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){//---- Function called on each price tick double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);//---- Gets and normalizes current ask price double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);//---- Gets and normalizes current bid price if(CopyBuffer(rsiHandle, 0, 1, 3, rsiValues) < 3){//---- Copies 3 RSI values into array, checks if successful Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");//---- Prints error if insufficient RSI data return;//---- Exits function if data copy fails } if (!CopyBuffer(handleMACD,MAIN_LINE,0,3,macdMAIN))return;//---- Copies 3 MACD main line values, exits if fails if (!CopyBuffer(handleMACD,SIGNAL_LINE,0,3,macdSIGNAL))return;//---- Copies 3 MACD signal line values, exits if fails }
Hier entwickeln wir unsere Strategie der Handelsstapel weiter, indem wir die Funktion OnTick implementieren, die bei jeder Preisaktualisierung aktiviert wird, um die Entscheidungsfindung im Expert Advisor in Echtzeit zu unterstützen. Wir beginnen mit der Erfassung der aktuellen Marktpreise und verwenden die Funktion NormalizeDouble, um „askPrice“ mit SymbolInfoDouble für _Symbol und „SYMBOL_ASK“, angepasst an die Genauigkeit von „_Digits“, und „bidPrice“ mit „SYMBOL_BID“ zu setzen, um genaue Preisdaten für die Handelsberechnungen sicherzustellen. Dieser Schritt schafft die Grundlage für die Beobachtung von Preisbewegungen, die unsere Stapellogik auf der Grundlage der aktuellen Marktbedingungen auslösen.
Als Nächstes sammeln wir Indikatordaten, um Signale zu analysieren, beginnend mit dem RSI, indem wir die Funktion CopyBuffer verwenden, um drei Werte in „rsiValues“ von „rsiHandle“ zu laden, beginnend bei Index 1 mit Puffer 0, und prüfen, ob weniger als 3 Werte kopiert wurden - ist dies der Fall, verwenden wir die Funktion „Print“, um „INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK“ zu protokollieren, und kehren zurück, um die Tickverarbeitung zu verlassen und Entscheidungen mit unvollständigen Daten zu verhindern. Dann wenden wir den gleichen Ansatz für den MACD an, indem wir „CopyBuffer“ verwenden, um „macdMAIN“ mit drei Werten der Hauptlinie von „handleMACD“ an MAIN_LINE und „macdSIGNAL“ mit Signalline-Werten an SIGNAL_LINE zu füllen, wobei wir sofort zurückkehren, wenn einer der beiden Versuche fehlschlägt, um sicherzustellen, dass wir nur mit vollständigen RSI- und MACD-Datensätzen fortfahren. Wir müssen jedoch statistische Daten abrufen und sie auch hier einbeziehen. Nehmen wir also die Funktionen.
//+------------------------------------------------------------------+ //| Calculate RSI Average | //+------------------------------------------------------------------+ double CalculateRSIAverage(int bars){//---- Function to calculate RSI average double sum = 0;//---- Initializes sum variable double buffer[];//---- Declares buffer array for RSI values ArraySetAsSeries(buffer, true);//---- Sets buffer as time series if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails for(int i = 0; i < bars; i++){//---- Loops through specified number of bars sum += buffer[i];//---- Adds each RSI value to sum } return sum / bars;//---- Returns average RSI value } //+------------------------------------------------------------------+ //| Calculate RSI STDDev | //+------------------------------------------------------------------+ double CalculateRSIStandardDeviation(int bars){//---- Function to calculate RSI standard deviation double average = CalculateRSIAverage(bars);//---- Calculates RSI average double sumSquaredDiff = 0;//---- Initializes sum of squared differences double buffer[];//---- Declares buffer array for RSI values ArraySetAsSeries(buffer, true);//---- Sets buffer as time series if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails for(int i = 0; i < bars; i++){//---- Loops through specified number of bars double diff = buffer[i] - average;//---- Calculates difference from average sumSquaredDiff += diff * diff;//---- Adds squared difference to sum } return MathSqrt(sumSquaredDiff / bars);//---- Returns standard deviation }
Wir implementieren zwei Schlüsselfunktionen - „CalculateRSIAverage“ und „CalculateRSIStandardDeviation“ - um die statistische Analyse von RSI-Daten einzuführen und die Signalgenauigkeit zu verbessern. In „CalculateRSIAverage“ definieren wir eine Funktion, die „bars“ als Input nimmt, „sum“ auf 0 initialisiert und ein „buffer“-Array deklariert, das wir als Zeitreihe konfigurieren, indem wir die Funktion ArraySetAsSeries mit true verwenden, um sicherzustellen, dass die letzten RSI-Werte bei Index Null ausgerichtet sind. Anschließend wird die Funktion CopyBuffer verwendet, um die Anzahl der RSI-Werte „bars“ aus „rsiHandle“ in „buffer“ zu laden, wobei 0 zurückgegeben wird, wenn weniger als „bars“ kopiert wurden, und das Array wird in einer Schleife durchlaufen, um jeden „buffer[i]“ in „sum“ zu addieren und schließlich „sum/bars“ als RSI-Durchschnitt für die Verwendung bei der statistischen Filterung zurückzugeben.
In „CalculateRSIStandardDeviation“ wird darauf aufbauend die Standardabweichung des RSI über denselben Zeitraum „bars“ berechnet, wobei zunächst „CalculateRSIAverage“ aufgerufen wird, um das Ergebnis in „average“ zu speichern, und „sumSquaredDiff“ auf 0 für quadrierte Differenzen gesetzt wird. Wir deklarieren wieder ein Array „buffer“, definieren es mit ArraySetAsSeries als Zeitreihe und verwenden die Funktion „CopyBuffer“, um RSI-Werte aus „rsiHandle“ zu holen, wobei 0 zurückgegeben wird, wenn das Kopieren fehlschlägt, um die Datenintegrität sicherzustellen. Wir durchlaufen eine Schleife durch „buffer“, berechnen jeden „diff“ als „buffer[i] - average“, addieren „diff * diff“ zu „sumSquaredDiff“ und geben die Standardabweichung mit der Funktion MathSqrt auf „sumSquaredDiff/bars“ zurück, um ein statistisches Maß zur Verfeinerung unserer Entscheidungen zur Handelsstapel zu erhalten. Wir können diese Daten nun für die statistische Analyse verwenden, aber wir müssen sie einmal pro Balken durchführen, um Mehrdeutigkeit zu vermeiden. Definieren wir also eine Funktion für diesen Zweck.
//+------------------------------------------------------------------+ //| Is New Bar | //+------------------------------------------------------------------+ bool IsNewBar(){//---- Function to detect a new bar static int previousBarCount = 0;//---- Stores the previous bar count int currentBarCount = iBars(_Symbol, _Period);//---- Gets current number of bars if(previousBarCount == currentBarCount) return false;//---- Returns false if no new bar previousBarCount = currentBarCount;//---- Updates previous bar count return true;//---- Returns true if new bar detected }
Hier fügen wir die Funktion „IsNewBar“ zu unserer Strategie hinzu, um neue Balken zu erkennen. Dabei verwenden wir ein statisches „previousBarCount“, das auf 0 gesetzt ist, um die letzte Balkenanzahl zu verfolgen, und die Funktion iBars, um „currentBarCount“ für _Symbol und _Period zu erhalten, die false zurückgibt, wenn sie unverändert ist, oder true, nachdem „previousBarCount“ aktualisiert wurde, wenn sich ein neuer Balken bildet. Mit dieser Funktion ausgestattet, können wir nun die Handelsregeln definieren.
// Calculate statistical measures if enabled double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : 0;//---- Calculates RSI average if filter enabled double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : 0;//---- Calculates RSI std dev if filter enabled
Wir implementieren statistische Erweiterungen, indem wir „rsiAverage“ mit der Funktion „CalculateRSIAverage“ mit „statAnalysisPeriod“ berechnen, wenn „useStatisticalFilter“ wahr ist, andernfalls wird er auf 0 gesetzt, und in ähnlicher Weise wird „rsiStdDeviation“ mit der Funktion „CalculateRSIStandardDeviation“ berechnet oder standardmäßig auf 0 gesetzt, was eine verfeinerte Signalfilterung ermöglicht, wenn sie aktiviert ist. Anhand der Ergebnisse können wir dann die Handelsbedingungen festlegen. Wir werden mit den Kaufbedingungen beginnen.
if(PositionsTotal() == 0 && IsNewBar()){//---- Checks for no positions and new bar // Buy Signal bool buyCondition = rsiValues[1] <= rsiOversoldLevel && rsiValues[0] > rsiOversoldLevel;//---- Checks RSI crossing above oversold if(useStatisticalFilter){//---- Applies statistical filter if enabled buyCondition = buyCondition && (rsiValues[0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition } buyCondition = macdMAIN[0] < 0 && macdSIGNAL[0] < 0;//---- Confirms MACD below zero for buy signal }
Wir definieren die Logik des Kaufsignals, indem wir prüfen, ob PositionsTotal 0 ist, und die Funktion IsNewBar“ verwenden, um einen neuen Balken zu bestätigen, um sicherzustellen, dass Handelsgeschäfte nur bei Balkeneröffnungen ohne offene Positionen ausgelöst werden. Wir setzen „buyCondition“ auf true, wenn „rsiValues[1]“ unter „rsiOversoldLevel“ liegt und „rsiValues[0]“ darüber steigt, und wenn „useStatisticalFilter“ aktiviert ist, verfeinern wir die Bedingung weiter, indem wir verlangen, dass „rsiValues[0]“ kleiner ist als „rsiAverage minus „statDevaultFactor“ mal „rsiStdDay“, aktiviert ist, wird sie weiter verfeinert, indem verlangt wird, dass „rsiValues[0]“ kleiner ist als „rsiAverage“ minus „statDeviationFactor“ mal „rsiStdDeviation“, was die statistische Präzision erhöht. Schließlich bestätigen wir das Signal mit „macdMAIN[0]“ und „macdSIGNAL[0]“, die beide unter Null liegen, was den Kauf mit einer fallenden MACD-Zone zur Trendbestätigung in Einklang bringt. Wenn die Bedingungen erfüllt sind, eröffnen wir die Positionen, initialisieren die Track-Variablen und zeichnen die bestätigten Levels auf dem Chart, insbesondere das Take-Profit-Level. Wir brauchen also eine nutzerdefinierte Funktion, um die Ebenen zu zeichnen.
//+------------------------------------------------------------------+ //| Draw TrendLine | //+------------------------------------------------------------------+ void DrawTradeLevelLine(double price, bool isBuy){//---- Function to draw take profit line on chart // Delete existing objects first DeleteTradeLevelObjects();//---- Removes existing trade level objects // Create horizontal line ObjectCreate(0, takeProfitLineName, OBJ_HLINE, 0, 0, price);//---- Creates a horizontal line at specified price ObjectSetInteger(0, takeProfitLineName, OBJPROP_COLOR, clrBlue);//---- Sets line color to blue ObjectSetInteger(0, takeProfitLineName, OBJPROP_WIDTH, 2);//---- Sets line width to 2 ObjectSetInteger(0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);//---- Sets line style to solid // Create text, above for buy, below for sell with increased spacing datetime currentTime = TimeCurrent();//---- Gets current time double textOffset = 30.0 * _Point;//---- Sets text offset distance from line double textPrice = isBuy ? price + textOffset : price - textOffset;//---- Calculates text position based on buy/sell ObjectCreate(0, takeProfitTextName, OBJ_TEXT, 0, currentTime + PeriodSeconds(_Period) * 5, textPrice);//---- Creates text object ObjectSetString(0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));//---- Sets text to price value ObjectSetInteger(0, takeProfitTextName, OBJPROP_COLOR, clrBlue);//---- Sets text color to blue ObjectSetInteger(0, takeProfitTextName, OBJPROP_FONTSIZE, 10);//---- Sets text font size to 10 ObjectSetInteger(0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);//---- Sets text anchor based on buy/sell }
Hier implementieren wir die Funktion „DrawTradeLevelLine“, um Take-Profit-Levels zu visualisieren, indem wir zunächst „DeleteTradeLevelObjects“ aufrufen, um die vorhandenen Objekte zu löschen, und dann die Funktion ObjectCreate verwenden, um eine horizontale Linie bei „Preis“ mit „takeProfitLineName“ als OBJ_HLINE, gestylt mit ObjectSetInteger für Blau „clrBlue“, Breite 2 und „STYLE_SOLID“. Wir fügen eine Textbeschriftung hinzu, indem wir „currentTime“ mit „TimeCurrent“ abrufen, „textOffset“ auf „30.0 * _Point“ und die Berechnung von „textPrice“ über oder unter „price“ auf der Grundlage von „isBuy“, die Erstellung mit „ObjectCreate“ als „takeProfitTextName“ bei „OBJ_TEXT“. Wir konfigurieren den Text mit ObjectSetString, um „price“ über DoubleToString mit „_Digits“ anzuzeigen, und „ObjectSetInteger“ für blau „clrBlue“, Größe 10, und „ANCHOR_BOTTOM“ oder „ANCHOR_TOP“ je nach „isBuy“, was die Lesbarkeit des Charts verbessert. Wir können nun die Funktion verwenden, um die Zielwerte zu visualisieren.
if(buyCondition){//---- Executes if buy conditions are met Print("BUY SIGNAL - RSI: ", rsiValues[0],//---- Prints buy signal details useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : ""); stopLossLevel = askPrice - stopLossPoints * _Point;//---- Calculates stop loss level for buy takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for buy obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Signal Position");//---- Places buy order buySequenceActive = true;//---- Activates buy sequence flag DrawTradeLevelLine(takeProfitLevel, true);//---- Draws take profit line for buy }
Hier behandeln wir die Ausführung des Kaufs, wenn „buyCondition“ wahr ist, indem wir die Funktion Print verwenden, um „BUY SIGNAL - RSI: „ mit „rsiValues[0]“ zu protokollieren und „rsiAverage“ und „rsiStdDeviation“ über die Funktion DoubleToString anzuhängen, wenn „useStatisticalFilter“ aktiviert ist, um ein detailliertes Feedback zu erhalten. Wir berechnen „stopLossLevel“ als „askPrice“ minus „stopLossPoints * _Point“ und „takeProfitLevel“ als „askPrice“ plus „stopLossPoints * riskRewardRatio * _Point“ und verwenden dann „obj_Trade.Buy“-Methode einen Kaufauftrag mit „tradeVolume“, _Symbol, „askPrice“, „stopLossLevel“ und „takeProfitLevel“ mit der Bezeichnung „Signal Position“. Schließlich setzen wir „buySequenceActive“ auf true und rufen „DrawTradeLevelLine“ mit „takeProfitLevel“ und true auf, um die Take-Profit-Linie des Kaufs zu visualisieren. Beim Ausführen des Programms sehen wir Folgendes.
Aus dem Bild geht hervor, dass alle Bedingungen für ein Kaufsignal erfüllt sind, und wir markieren automatisch die nächste Stufe im Chart. So können wir auch bei einem Verkaufssignal verfahren.
// Sell Signal bool sellCondition = rsiValues[1] >= rsiOverboughtLevel && rsiValues[0] < rsiOverboughtLevel;//---- Checks RSI crossing below overbought if(useStatisticalFilter){//---- Applies statistical filter if enabled sellCondition = sellCondition && (rsiValues[0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition } sellCondition = macdMAIN[0] > 0 && macdSIGNAL[0] > 0;//---- Confirms MACD above zero for sell signal if(sellCondition){//---- Executes if sell conditions are met Print("SELL SIGNAL - RSI: ", rsiValues[0],//---- Prints sell signal details useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : ""); stopLossLevel = bidPrice + stopLossPoints * _Point;//---- Calculates stop loss level for sell takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for sell obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Signal Position");//---- Places sell order sellSequenceActive = true;//---- Activates sell sequence flag DrawTradeLevelLine(takeProfitLevel, false);//---- Draws take profit line for sell }
Wir gehen hier nach der umgekehrten Logik des Kauf vor, indem wir „sellCondition“ setzen, wenn „rsiValues[1]“ über „rsiOverboughtLevel“ liegt und „rsiValues[0]“ darunter fällt, indem wir „useStatisticalFilter“, um zu prüfen, ob „rsiValues[0]“ über „rsiAverage + statDeviationFactor * rsiStdDeviation“ liegt, und Bestätigung mit „macdMAIN[0]“ und „macdSIGNAL[0]“ über Null. Wenn true, verwenden wir „Print“ für „SELL SIGNAL - RSI:“ mit „rsiValues[0]“ und stats via DoubleToString, setzen wir „stopLossLevel“ als „bidPrice + stopLossPoints * _Point“ und „takeProfitLevel“ als „bidPrice - (stopLossPoints * riskRewardRatio) * _Point“, rufen dann „obj_Trade.Sell“ und „DrawTradeLevelLine“ mit false auf, wodurch „sellSequenceActive“ aktiviert wird. Nun, da die Positionen eröffnet sind, müssen wir die Gewinnpositionen kaskadieren, indem wir dem Trend folgen und die Positionen ändern. Hier ist eine Funktion zum Ändern der Handelsgeschäfte.
//+------------------------------------------------------------------+ //| Modify Trades | //+------------------------------------------------------------------+ void ModifyTrades(ENUM_POSITION_TYPE positionType, double newStopLoss){//---- Function to modify open trades for(int i = 0; i < PositionsTotal(); i++){//---- Loops through all open positions ulong ticket = PositionGetTicket(i);//---- Gets ticket number of position if(ticket > 0 && PositionSelectByTicket(ticket)){//---- Checks if ticket is valid and selectable ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);//---- Gets position type if(type == positionType){//---- Checks if position matches specified type obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));//---- Modifies position with new stop loss } } } }
Hier implementieren wir die Funktion „ModifyTrades“, um die offenen Handelsgeschäfte zu aktualisieren, indem wir „positionType“ und „newStopLoss“ als Eingaben verwenden und dann die Funktion „PositionsTotal“ nutzen, um alle Positionen in einer for-Schleife von 0 bis „i“ zu durchlaufen. Für jede dieser Funktionen verwenden wir die Funktion „PositionGetTicket“, um die „Ticket“-Nummer zu erhalten, prüfen, ob sie gültig und mit „PositionSelectByTicket“ auswählbar ist, und verwenden „PositionGetInteger“, um den „Typ“ als ENUM_POSITION_TYPE zu erhalten und vergleichen ihn mit „positionType“. Bei Übereinstimmung verwenden wir „obj_Trade.PositionModify“, um den Stop-Loss des Handelsgeschäfts auf „newStopLoss“ anzupassen, während der Take-Profit aus PositionGetDouble mit „POSITION_TP“ beibehalten wird, um ein präzises Handelsmanagement zu gewährleisten. Wir können dann die Funktion verwenden, um die Handelsgeschäfte zu kaskadieren.
else {//---- Handles cascading logic when positions exist // Cascading Buy Logic if(buySequenceActive && askPrice >= takeProfitLevel){//---- Checks if buy sequence active and price hit take profit double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level stopLossLevel = askPrice - minStopLossPoints * _Point;//---- Sets new stop loss level obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Cascade Position");//---- Places new buy order ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);//---- Modifies existing buy trades with new stop loss Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading buy details DrawTradeLevelLine(takeProfitLevel, true);//---- Updates take profit line for buy } // Cascading Sell Logic else if(sellSequenceActive && bidPrice <= takeProfitLevel){//---- Checks if sell sequence active and price hit take profit double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level stopLossLevel = bidPrice + minStopLossPoints * _Point;//---- Sets new stop loss level obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Cascade Position");//---- Places new sell order ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);//---- Modifies existing sell trades with new stop loss Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading sell details DrawTradeLevelLine(takeProfitLevel, false);//---- Updates take profit line for sell } }
Hier handhaben wir die Kaskadenlogik, wenn Positionen vorhanden sind, indem wir prüfen, ob „buySequenceActive“ wahr ist und „askPrice“ das „takeProfitLevel“ erreicht, dann speichern wir „previousTakeProfit“, Setzen eines neuen „takeProfitLevel“ mit „stopLossPoints * riskRewardRatio * _Point“ und „stopLossLevel“ als „askPrice - minStopLossPoints * _Point“, Verwendung von „obj_Trade.Buy“ für eine neue Order und „ModifyTrades“, um die Stopps von POSITION_TYPE_BUY zu aktualisieren, wobei „Print“ die „CASCADING BUY“-Details protokolliert und „DrawTradeLevelLine“ die Kauflinie aktualisiert.
Bei Verkäufen, wenn „sellSequenceActive“ wahr ist und „bidPrice“ das „takeProfitLevel“ erreicht, spiegeln wir dies durch Subtraktion von „previousTakeProfit“ für das neue „takeProfitLevel“ subtrahieren, „stopLossLevel“ als „bidPrice + minStopLossPoints * _Point“ setzen, „obj_Trade.Sell“ und „ModifyTrades“ für POSITION_TYPE_SELL aufrufen, mit „Print“ protokollieren und die Verkaufslinie mit „DrawTradeLevelLine“ aktualisieren. Wenn wir das System starten, erhalten wir folgendes Ergebnis.
Anhand des Bildes können wir die Kaskadierung der Positionen bestätigen und den Stop-Loss für alle Positionen ändern. Jetzt müssen wir sicherstellen, dass wir die Objekte, die wir hinzufügen, wenn wir das System nicht mehr benötigen, bereinigen. Wir können dies über OnDeinit erreichen, aber zuerst brauchen wir eine Aufräumfunktion.
//+------------------------------------------------------------------+ //| Delete Level Objects | //+------------------------------------------------------------------+ void DeleteTradeLevelObjects(){//---- Function to delete trade level objects ObjectDelete(0, takeProfitLineName);//---- Deletes take profit line object ObjectDelete(0, takeProfitTextName);//---- Deletes take profit text object }
Hier implementieren wir die Funktion „DeleteTradeLevelObjects“, um die Darstellungen auf den Chart zu bereinigen. Dabei verwenden wir die Funktion ObjectDelete, um das Linienobjekt „takeProfitLineName“ und das Textobjekt „takeProfitTextName“ zu entfernen und sicherzustellen, dass alte Take-Profit-Levels gelöscht werden, bevor neue gezeichnet werden. Wir rufen diese Funktion nun in OnDeinit auf.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){//---- Expert advisor deinitialization function DeleteTradeLevelObjects();//---- Removes trade level visualization objects from chart }
Hier rufen wir nur die Funktion zum Löschen der Objekte aus dem Chart auf und stellen so sicher, dass wir das Chart bereinigen, sobald wir das Programm entfernt haben, und somit unser Ziel erreichen. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.
Backtests
Nach einem gründlichen Backtest haben wir folgende Ergebnisse.
Backtest-Grafik:
Bericht des Backtest:
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir in MQL5 erfolgreich eine Stapelstrategie für den Handel entwickelt haben, die den MACD und den RSI mit statistischen Methoden verbindet, um die dynamische Positionsskalierung in trendigen Märkten zu automatisieren. Das Programm verfügt über eine robuste Signalerkennung, eine kaskadierende Handelslogik und visuelle Take-Profit-Levels, die sich präzise an Momentumverschiebungen anpassen lassen. Sie können diese Grundlage nutzen, um sie durch Optimierungen wie die von „rsiLookbackPeriod“ oder die Anpassung von „riskRewardRatio“ für eine bessere Leistung weiter zu verbessern.
Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel birgt ein erhebliches finanzielles Risiko, und das Marktverhalten kann unbeständig sein. Gründliches Backtests und Risikomanagement sind vor dem Live-Einsatz unerlässlich.
Anhand dieser Illustration können Sie Ihre Automatisierungsfähigkeiten schärfen und die Strategie verfeinern. Experimentieren und optimieren Sie. Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17741
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.