English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 14): Stapelstrategie für den Handel mit statistischen MACD-RSI-Methoden

Automatisieren von Handelsstrategien in MQL5 (Teil 14): Stapelstrategie für den Handel mit statistischen MACD-RSI-Methoden

MetaTrader 5Handel |
197 0
Allan Munene Mutiiria
Allan Munene Mutiiria

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:

  1. Strategische Architektur
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

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.

STRATEGIE-ENTWURF

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.

NUTZEREINGÄNGE

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.

BESTÄTIGTE KAUFPOSITION

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.

KASKADIERENDE POSITIONEN

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:

GRAFIK

Bericht des Backtest:

BERICHT


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

MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 57): Überwachtes Lernen mit gleitendem Durchschnitt und dem stochastischen Oszillator MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 57): Überwachtes Lernen mit gleitendem Durchschnitt und dem stochastischen Oszillator
Der gleitende Durchschnitt und der Stochastik-Oszillator sind sehr gängige Indikatoren, die von manchen Händlern aufgrund ihres verzögerten Charakters nicht oft verwendet werden. In einer dreiteiligen Miniserie, die sich mit den drei wichtigsten Formen des maschinellen Lernens befasst, gehen wir der Frage nach, ob die Voreingenommenheit gegenüber diesen Indikatoren gerechtfertigt ist, oder ob sie vielleicht einen Vorteil haben. Wir führen unsere Untersuchung mit Hilfe eines Assistenten durch, der Expert Advisors zusammenstellt.
Datenwissenschaft und ML (Teil 35): NumPy in MQL5 - Die Kunst, komplexe Algorithmen mit weniger Code zu erstellen Datenwissenschaft und ML (Teil 35): NumPy in MQL5 - Die Kunst, komplexe Algorithmen mit weniger Code zu erstellen
Die NumPy-Bibliothek treibt fast alle Algorithmen des maschinellen Lernens in der Programmiersprache Python an. In diesem Artikel werden wir ein ähnliches Modul implementieren, das eine Sammlung des gesamten komplexen Codes enthält, um uns bei der Erstellung anspruchsvoller Modelle und Algorithmen jeglicher Art zu unterstützen.
Erforschung fortgeschrittener maschineller Lerntechniken bei der Darvas Box Breakout Strategie Erforschung fortgeschrittener maschineller Lerntechniken bei der Darvas Box Breakout Strategie
Die von Nicolas Darvas entwickelte Darvas-Box-Breakout-Strategie ist ein technischer Handelsansatz, der potenzielle Kaufsignale erkennt, wenn der Kurs einer Aktie über einen festgelegten Bereich der „Box“ ansteigt, was auf eine starke Aufwärtsdynamik hindeutet. In diesem Artikel werden wir dieses Strategiekonzept als Beispiel anwenden, um drei fortgeschrittene Techniken des maschinellen Lernens zu untersuchen. Dazu gehören die Verwendung eines maschinellen Lernmodells zur Generierung von Signalen anstelle von Handelsfiltern, die Verwendung von kontinuierlichen Signalen anstelle von diskreten Signalen und die Verwendung von Modellen, die auf verschiedenen Zeitrahmen trainiert wurden, um Handelsgeschäfte zu bestätigen.
Vom Neuling zum Experten: Support and Resistance Strength Indicator (SRSI) Vom Neuling zum Experten: Support and Resistance Strength Indicator (SRSI)
In diesem Artikel erfahren Sie, wie Sie die MQL5-Programmierung nutzen können, um Marktniveaus zu bestimmen und zwischen schwächeren und stärkeren Kursniveaus zu unterscheiden. Wir werden einen funktionierenden Support and Resistance Strength Indicator (SRSI) entwickeln.