English 日本語
preview
Automatisieren von Handelsstrategien in MQL5 (Teil 5): Die Entwicklung der Strategie „Adaptive Crossover RSI Trading Suite“

Automatisieren von Handelsstrategien in MQL5 (Teil 5): Die Entwicklung der Strategie „Adaptive Crossover RSI Trading Suite“

MetaTrader 5Handel |
204 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Einführung

Im vorigen Artikel (Teil 4 der Serie) haben wir das Multi-Level Zone Recovery System vorgestellt und gezeigt, wie die Prinzipien der Rückgewinnung erweitert werden können, um mehrere unabhängige Handels-Setups gleichzeitig in MetaQuotes Language 5 (MQL5) zu verwalten. In diesem Artikel (Teil 5) schlagen wir mit der Strategie „Adaptive Crossover RSI Trading Suite“ eine neue Richtung ein, ein umfassendes System, das entwickelt wurde, um hochwahrscheinliche Handelsgelegenheiten zu identifizieren und zu nutzen. Diese Strategie kombiniert zwei wichtige technische Analyseinstrumente: das Kreuzen des Adaptive Moving Average (mit Periodenlängen von 14 und 50) als Hauptsignalgeber und einen Relative Strength Indicator (RSI) mit einer Periodenlänge von 14 als Filter zur Bestätigung.

Darüber hinaus wird ein Handelstagsfilter eingesetzt, um Handelssitzungen mit geringer Wahrscheinlichkeit auszuschließen und so eine bessere Genauigkeit und Leistung zu gewährleisten. Um die Nutzerfreundlichkeit zu erhöhen, visualisiert das System bestätigte Handelssignale direkt auf dem Chart, indem es Pfeile einzeichnet und sie mit klaren Signalbeschreibungen versieht. Ein Dashboard bietet zudem eine Echtzeit-Zusammenfassung des Strategiestatus, der wichtigsten Kennzahlen und der Signalaktivität, sodass der Händler auf einen Blick einen vollständigen Überblick erhält. Dieser Artikel führt Sie Schritt für Schritt durch die Entwicklung dieser Strategie, vom Entwurf bis zur Implementierung in MQL5, dem Backtests für die Performance und der Analyse der Ergebnisse. Wir werden dies anhand der folgenden Themen strukturieren:

  1. Blaupause der Strategie
  2. Implementation in MQL5
  3. Backtests
  4. Schlussfolgerung

Am Ende werden Sie ein praktisches Verständnis dafür haben, wie man ein adaptives, filterbasiertes Handelssystem erstellt und es für eine robuste Leistung unter verschiedenen Marktbedingungen verfeinert. Fangen wir an.


Blaupause der Strategie

Die Strategie „Adaptive Crossover RSI Trading Suite“ basiert auf der Grundlage des Kreuzens von gleitenden Durchschnitten und der Bestätigungen durch das Momentum, die einen ausgewogenen Ansatz für den Handel schaffen. Die Kernsignale werden aus der Interaktion zwischen einem 14-Perioden-Durchschnitt mit schneller Bewegung und einem 50-Perioden-Durchschnitt mit langsamer Bewegung abgeleitet. Ein Kaufsignal wird ausgelöst, wenn sich der schnell bewegende Durchschnitt über den langsamen Durchschnitt kreuzt, was auf einen Aufwärtstrend hindeutet, während ein Verkaufssignal generiert wird, wenn der sich schnell bewegende Durchschnitt unter den sich langsam bewegenden Durchschnitt kreuzt, was auf einen Abwärtstrend hindeutet.

Um die Genauigkeit dieser Signale zu erhöhen, wird ein 14-periodischer Relative Strength Index (RSI) als Bestätigungsfilter eingesetzt. Der RSI sorgt dafür, dass sich der Handel an der vorherrschenden Marktdynamik ausrichtet, und verringert so die Wahrscheinlichkeit, bei überkauften oder überverkauften Bedingungen zu handeln. So wird beispielsweise ein Kaufsignal nur dann bestätigt, wenn der RSI über einem Schwellenwert von 50 liegt, während ein Verkaufssignal voraussetzt, dass der RSI unter diesem Schwellenwert liegt. Die Strategie wird auch einen Handelstagsfilter enthalten, um die Performance zu optimieren, indem Handelsgeschäfte an Tagen mit historisch niedriger Volatilität oder schlechter Performance vermieden werden. Dieser Filter stellt sicher, dass sich das System nur auf Handelsmöglichkeiten mit hoher Wahrscheinlichkeit konzentriert. Kurz gesagt, sieht die Strategie folgendermaßen aus.

Blaupause für einen Verkauf:

BLAUPAUSE EINES VERKAUFS

Blaupause für einen Kauf:

BLAUPAUSE EINES KAUFS

Sobald ein Handel bestätigt wird, markiert das System das Chart mit Signalpfeilen und Anmerkungen, die die Einstiegspunkte deutlich kennzeichnen. Ein Dashboard liefert Echtzeit-Updates und bietet eine Momentaufnahme der Signalaktivität, der wichtigsten Messwerte und des Gesamtstatus des Systems. Dieser strukturierte und anpassungsfähige Ansatz wird sicherstellen, dass die Strategie robust und nutzerfreundlich ist. Der endgültige Ausblick wird wie unten dargestellt aussehen.

ENDGÜLTIGE BLAUPAUSE


Implementation in MQL5

Nachdem wir alle Theorien über die Strategie „Adaptive Crossover RSI Trading Suite“ gelernt haben, wollen wir die Theorie automatisieren und einen Expert Advisor (EA) in MetaQuotes Language 5 (MQL5) für den MetaTrader 5 erstellen.

Um einen Expert Advisor (EA) zu erstellen, klicken Sie in Ihrem Terminal des MetaTrader 5 auf die Registerkarte Extras und aktivieren Sie MetaQuotes Language Editor oder drücken Sie einfach F4 auf Ihrer Tastatur. Alternativ können Sie auch auf das IDE-Symbol (Integrated Development Environment) in der Symbolleiste klicken. Dadurch wird die Umgebung des MetaQuotes-Spracheditors geöffnet, die das Schreiben von Handelsrobotern, technischen Indikatoren, Skripten und Funktionsbibliotheken ermöglicht. Sobald der MetaEditor geöffnet ist, navigieren wir in der Symbolleiste zur Registerkarte „Datei“ und wählen „Neue Datei“, oder drücken einfach die Tastenkombination STRG + N, um ein neues Dokument zu erstellen. Alternativ könnten wir auch auf das Symbol Neu auf der Registerkarte Werkzeuge klicken. Daraufhin erscheint ein Popup-Fenster des MQL-Assistenten.

In dem sich öffnenden Assistenten markieren wir die Option Expert Advisor (Template bzw. Vorlage) und klicken auf Weiter (Next). Wir geben in den allgemeinen Eigenschaften des Expert Advisors unter dem Abschnitt Name den Dateinamen Ihres Experten an. Nicht vergessen, den Backslash vor dem Namen des EA verwenden, um einen Ordner anzugeben oder zu erstellen, wenn er nicht existiert. Hier haben wir zum Beispiel standardmäßig „Experts\“. Das bedeutet, dass unser EA im Ordner Experts erstellt wird und wir ihn dort finden können. Die anderen Abschnitte sind ziemlich einfach, aber Sie können dem Link am Ende des Assistenten folgen, um zu erfahren, wie der Prozess genau abläuft.

NEUER EA-NAME

Nachdem Sie den gewünschten Dateinamen des Expert Advisors eingegeben haben, klicken Sie auf Weiter, dann auf Weiter und schließlich auf Fertig stellen. Nachdem wir all dies getan haben, können wir nun unsere Strategie programmieren.

Zunächst definieren wir einige Metadaten über den Expert Advisor (EA). Dazu gehören der Name des EA, die Copyright-Informationen und ein Link zur MetaQuotes-Website. Wir geben auch die Version des EA an, die auf „1.00“ eingestellt ist.

//+------------------------------------------------------------------+
//|                         Adaptive Crossover RSI Trading Suite.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "EA that trades based on MA Crossover, RSI + Day Filter"
#property strict

Dadurch werden beim Laden des Programms die System-Metadaten angezeigt. Anschließend können wir einige globale Variablen hinzufügen, die wir im Programm verwenden werden. Zunächst binden wir eine Handelsinstanz ein, indem wir #include am Anfang des Quellcodes verwenden. Dadurch erhalten wir Zugriff auf die Klasse „CTrade“, die wir zur Erstellung eines Handelsobjekts verwenden werden. Dies ist von entscheidender Bedeutung, da wir sie zur Eröffnung von Handelsgeschäften benötigen.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

Der Präprozessor wird die Zeile #include <Trade/Trade.mqh> durch den Inhalt der Datei Trade.mqh ersetzen. Die spitzen Klammern zeigen an, dass die Datei Trade.mqh aus dem Standardverzeichnis verwendet werden soll (normalerweise ist es das Terminal-Installationsverzeichnis\MQL5\Include). Das aktuelle Verzeichnis wird bei der Suche nicht berücksichtigt. Die Zeile kann an beliebiger Stelle im Programm platziert werden, aber in der Regel werden alle Einbindungen am Anfang des Quellcodes platziert, um den Code besser zu strukturieren und die Referenz zu erleichtern. Die Deklaration des Objekts „obj_Trade“ der Klasse CTrade ermöglicht uns dank der MQL5-Entwickler einen einfachen Zugriff auf die in dieser Klasse enthaltenen Methoden.

DIE KLASSE CTRADE

Danach müssen wir mehrere wichtige Eingabevariablen deklarieren, die es dem Nutzer ermöglichen, die Handelswerte in die gewünschten Werte zu ändern, ohne den Code selbst zu verändern. Um dies zu erreichen, gliedern wir die Eingaben der Übersichtlichkeit halber in Gruppen, d. h. in allgemeine, Indikator- und Filtereinstellungen.

sinput group "GENERAL SETTINGS"
sinput double inpLots = 0.01; // LotSize
input int inpSLPts = 300; // Stoploss Points
input double inpR2R = 1.0; // Risk to Reward Ratio
sinput ulong inpMagicNo = 1234567; // Magic Number
input bool inpisAllowTrailingStop = true; // Apply Trailing Stop?
input int inpTrailPts = 50; // Trailing Stop Points
input int inpMinTrailPts = 50; // Minimum Trailing Stop Points

sinput group "INDICATOR SETTINGS"
input int inpMA_Fast_Period = 14; // Fast MA Period
input ENUM_MA_METHOD inpMA_Fast_Method = MODE_EMA; // Fast MA Method
input int inpMA_Slow_Period = 50; // Slow MA Period
input ENUM_MA_METHOD inpMA_Slow_Method = MODE_EMA; // Slow MA Method

sinput group "FILTER SETTINGS"
input ENUM_TIMEFRAMES inpRSI_Tf = PERIOD_CURRENT; // RSI Timeframe
input int inpRSI_Period = 14; // RSI Period
input ENUM_APPLIED_PRICE inpRSI_Applied_Price = PRICE_CLOSE; // RSI Application Price
input double inpRsiBUYThreshold = 50; // BUY Signal Threshold
input double inpRsiSELLThreshold = 50; // SELL Signal Threshold

input bool Sunday = false; // Trade on Sunday?
input bool Monday = false; // Trade on Monday?
input bool Tuesday = true; // Trade on Tuesday?
input bool Wednesday = true; // Trade on Wednesday?
input bool Thursday = true; // Trade on Thursday?
input bool Friday = false; // Trade on Friday?
input bool Saturday = false; // Trade on Saturday?

Hier definieren wir die wichtigsten Parameter und Konfigurationen für das Programm Adaptive Crossover RSI Trading Suite, die eine genaue Kontrolle über sein Verhalten ermöglichen. Wir unterteilen diese Einstellungen in drei Hauptgruppen: „GENERAL SETTINGS“, „INDICATOR SETTINGS“ und „FILTER SETTINGS“ sowie spezifische Steuerungen für Handelstage. Die Verwendung von Variablentypen und Enumerationen erhöht die Flexibilität und Klarheit des Systementwurfs.

In der Gruppe „GENERAL SETTINGS“ werden die Parameter für die Handelsverwaltung festgelegt. Wir verwenden das Schlüsselwort input für optimierbare Parameter und sinput für String- oder nicht-optimierbare Parameter. Die Variable „inpLots“ gibt die Lotgröße des Handels an, während „inpSLPts“ das Stop-Loss-Niveau in Punkten festlegt, sodass das Risiko für jeden Handel kontrolliert wird. Die Variable „inpR2R“ legt das gewünschte Risiko-Ertrags-Verhältnis fest, das ein günstiges Verhältnis zwischen Risiko und potenziellem Ertrag gewährleistet. Mit „inpMagicNo“ wird eine eindeutige Handelskennung vergeben, die das Programm zur Unterscheidung seiner Aufträge verwendet. Die Funktionsweise eines Trailing-Stop wird mit „inpisAllowTrailingStop“ verwaltet, sodass der Nutzer sie aktivieren oder deaktivieren kann. Die Variablen „inpTrailPts“ und „inpMinTrailPts“ geben den Abstand des Trailing-Stop bzw. die Mindestaktivierungsschwelle an, um sicherzustellen, dass Trailing-Stops an die Marktbedingungen angepasst werden.

In der Gruppe „INDICATOR SETTINGS“ konfigurieren wir die Parameter für die gleitenden Durchschnitte, die das Rückgrat der Signalerzeugung bilden. Die Periode des gleitenden Durchschnitts wird durch „inpMA_Fast_Period“ definiert, und seine Berechnungsmethode wird mit Hilfe der Enumeration ENUM_MA_METHOD mit der Variablen „inpMA_Fast_Method“ ausgewählt, die Optionen wie MODE_SMA, MODE_EMA, MODE_SMMA und MODE_LWMA unterstützt. In ähnlicher Weise wird der langsame Durchschnitt mit „inpMA_Slow_Period“ festgelegt, während seine Methode mit „inpMA_Slow_Method“ bestimmt wird. Diese Enumerationen stellen sicher, dass die Nutzer die Strategie mit ihren bevorzugten gleitenden Durchschnittstypen für unterschiedliche Marktbedingungen anpassen können.

Die Gruppe „FILTER SETTINGS“ konzentriert sich auf den RSI-Indikator, der als Momentum-Filter dient. Die Variable „inpRSI_Tf“, die mit der Enumeration ENUM_TIMEFRAMES definiert wird, ermöglicht es dem Nutzer, den Zeitrahmen des RSI auszuwählen, z. B. „PERIOD_M1“, „PERIOD_H1“ oder „PERIOD_D1“. Die RSI-Periode wird mit „inpRSI_Period“ angegeben, während „inpRSI_Applied_Price“, eine Enumeration ENUM_APPLIED_PRICE, die für die Berechnungen verwendeten Preisdaten bestimmt (z. B. „PRICE_CLOSE“, „PRICE_OPEN“ oder „PRICE_MEDIAN“). Die Schwellenwerte für die Validierung von Kauf- und Verkaufssignalen werden mit „inpRsiBUYThreshold“ und „inpRsiSELLThreshold“ festgelegt, um sicherzustellen, dass der RSI mit dem Marktmomentum übereinstimmt, bevor die Handelsgeschäfte ausgeführt werden.

Schließlich implementieren wir einen Filter für den Handelstag, der boolesche Variablen wie „Sunday“, „Monday“ usw. verwendet und die Kontrolle über die Aktivität des EA an bestimmten Tagen ermöglicht. Durch die Deaktivierung des Handels an ungünstigen Tagen vermeidet das System unnötige Engagements zu potenziell unrentablen Bedingungen. Anschließend müssen wir die zu verwendenden Handels der Indikatoren definieren.

int handleMAFast = INVALID_HANDLE;
int handleMASlow = INVALID_HANDLE;
int handleRSIFilter = INVALID_HANDLE;

Wir initialisieren drei wichtige Variablen - „handleMAFast“, „handleMASlow“ und „handleRSIFilter“ - und setzen sie auf INVALID_HANDLE. Auf diese Weise stellen wir sicher, dass unser EA mit einem sauberen und kontrollierten Zustand startet und mögliche Probleme durch nicht initialisierte oder ungültige Indikator-Handles vermieden werden. Wir verwenden „handleMAFast“, um den Indikator für den sich schnell bewegenden Durchschnitt zu verwalten, den wir so konfigurieren, dass er kurzfristige Preistrends auf der Grundlage der von uns definierten Parameter erfasst.

In ähnlicher Weise ist „handleMASlow“ für den Indikator des sich langsam bewegenden Durchschnitts bestimmt, der es uns ermöglicht, längerfristige Preistrends zu verfolgen. Diese Handles sind für den dynamischen Abruf und die Verarbeitung der für unsere Strategie benötigten gleitenden Durchschnittswerte unerlässlich. Mit „handleRSIFilter“ bereiten wir die Verbindung mit dem RSI-Indikator vor, den wir als Momentum-Filter zur Bestätigung unserer Signale verwenden. Als Nächstes müssen wir die Speicherfelder definieren, in denen wir die von den Indikatoren abgerufenen Daten speichern werden. Auch hierfür sind drei Arrays erforderlich.

double bufferMAFast[];
double bufferMASlow[];
double bufferRSIFilter[];

Hier werden drei dynamische Arrays deklariert: „bufferMAFast[]“, „bufferMASlow[]“ und „bufferRSIFilter[]“. Diese Arrays dienen als Speicherbehälter, in denen wir die berechneten Werte der in unserer Strategie verwendeten Indikatoren sammeln und verwalten werden. Durch diese Art der Datenorganisation stellen wir sicher, dass unser EA während seiner Arbeit direkten und effizienten Zugriff auf die Indikatorergebnisse hat. Von hier aus müssen wir nun zur Initialisierungsfunktion gehen und die Indikator-Handles erstellen. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE);
   handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE);
   
   handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price);

//----

}

Hier initialisieren wir die Handles für die Indikatoren, die wir in der Strategie verwenden werden: den sich schnell bewegenden Durchschnitt, den sich langsam bewegenden Durchschnitt und den RSI-Filter. Wir beginnen mit der Initialisierung des sich schnell bewegenden Durchschnitts mit der Funktion iMA. Diese Funktion erfordert mehrere Parameter. Die erste, _Symbol, weist die Funktion an, den gleitenden Durchschnitt für das aktuelle Handelsinstrument zu berechnen. Die zweite, _Period, gibt den Zeitrahmen des Charts an (z. B. 1 Minute, 1 Stunde).

Wir übergeben auch die Periode des gleitenden Durchschnitts („inpMA_Fast_Period“), die bestimmt, wie viele Balken zur Berechnung des gleitenden Durchschnitts verwendet werden. Der Parameter „0“ steht für die Verschiebung (shift) des gleitenden Durchschnitts, wobei „0“ keine Verschiebung bedeutet. Die Methode des gleitenden Durchschnitts („inpMA_Fast_Method“) gibt an, ob es sich um einen exponentiellen oder einfachen gleitenden Durchschnitt handelt, und „PRICE_CLOSE“ gibt an, dass wir die Schlusskurse jedes Balkens zur Berechnung des Durchschnitts verwenden.

Das Ergebnis dieser Funktion wird „handleMAFast“ zugewiesen, sodass wir für künftige Berechnungen auf den schnelleren Durchschnittswert zugreifen können.

Als Nächstes initialisieren wir den langsamen Durchschnitt auf die gleiche Weise, indem wir die Funktion iMA aufrufen. Hier verwenden wir dasselbe _Symbol, _Periode und die Periodenlänge des langsamen Durchschnitts („inpMA_Slow_Period“). Auch hier geben wir die Methode und den Preis („PRICE_CLOSE“) an, die zur Berechnung dieses gleitenden Durchschnitts verwendet werden. Dieser Wert wird zur späteren Verwendung in „handleMASlow“ gespeichert. Schließlich initialisieren wir den RSI-Filter mit der Funktion iRSI. Wir stellen das _Symbol zur Verfügung, um das Instrument, den RSI-Zeitrahmen („inpRSI_Tf“), den RSI-Zeitraum („inpRSI_Period“) und den angewandten Preis („inpRSI_Applied_Price“) anzugeben. Das Ergebnis der Funktion wird in „handleRSIFilter“ gespeichert, was es uns ermöglicht, den RSI-Wert zur Bestätigung von Handelssignalen in der Strategie zu verwenden.

Da diese Handles das Rückgrat unserer Strategie sind, müssen wir sicherstellen, dass sie ordnungsgemäß initialisiert sind, und wenn nicht, dann hat es keinen Sinn, das Programm weiter auszuführen.

if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){
   Print("ERROR! Unable to create the indicator handles. Reveting Now!");
   return (INIT_FAILED);
}

Hier wird geprüft, ob die Initialisierung der Indikator-Handles erfolgreich war. Es wird geprüft, ob einer der Handles („handleMAFast“, „handleMASlow“ oder „handleRSIFilter“) gleich INVALID_HANDLE ist, was auf einen Fehler bei der Erstellung der entsprechenden Indikatoren hinweisen würde. Wenn eines der Handles fehlschlägt, verwenden wir die Funktion „Print“, um eine Fehlermeldung im Terminal anzuzeigen, die uns auf das Problem aufmerksam macht. Schließlich geben wir INIT_FAILED zurück, was die Ausführung des EAs stoppt, wenn einer der Indikator-Handles ungültig ist, um sicherzustellen, dass der EA nicht unter fehlerhaften Bedingungen weiterläuft.

Ein weiterer Fehler liegt vor, wenn der Nutzer unrealistische Zeiträume angibt, die technisch gesehen kleiner oder gleich Null sind. Daher müssen wir die nutzerdefinierten Eingabewerte für die Perioden des sich schnell bewegenden Durchschnitts, des sich langsam bewegenden Durchschnitts und des RSI überprüfen, um sicherzustellen, dass die Perioden („inpMA_Fast_Period“, „inpMA_Slow_Period“, „inpRSI_Period“) größer als Null sind.

if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){
   Print("ERROR! Periods cannot be <= 0. Reverting Now!");
   return (INIT_PARAMETERS_INCORRECT);
}

Wenn die Nutzereingabewerte hier nicht größer als Null sind, wird das Programm durch die Rückgabe von INIT_PARAMETERS_INCORRECT beendet. Wenn wir das hier erfolgreich erledigt haben, dann haben wir die Indikator-Handles fertig und können die Speicher-Arrays als Zeitreihen einstellen. 

ArraySetAsSeries(bufferMAFast,true);
ArraySetAsSeries(bufferMASlow,true);
ArraySetAsSeries(bufferRSIFilter,true);

obj_Trade.SetExpertMagicNumber(inpMagicNo);

Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode());

Schließlich führen wir noch einige wichtige Aktionen durch, um den Initialisierungsprozess abzuschließen. Zunächst verwenden wir die Funktion ArraySetAsSeries, um die Arrays („bufferMAFast“, „bufferMASlow“ und „bufferRSIFilter“) als Zeitreihen zu setzen. Dies ist wichtig, weil dadurch sichergestellt wird, dass die Daten in diesen Arrays so gespeichert werden, dass sie mit der Art und Weise kompatibel sind, wie der MetaTrader Zeitreihendaten behandelt, indem er die neuesten Daten bei Index 0 speichert. Indem wir jedes dieser Arrays als eine Serie festlegen, stellen wir sicher, dass die Indikatoren während des Handels in der richtigen Reihenfolge aufgerufen werden.

Als Nächstes rufen wir die Methode „SetExpertMagicNumber“ auf dem Objekt „obj_Trade“ auf und übergeben den Wert „inpMagicNo“ als magische Zahl. Die magische Zahl ist ein eindeutiger Identifikator für die Handelsgeschäfte des EAs, der sicherstellt, dass sie von anderen Handelsgeschäfte, die manuell oder von anderen EAs platziert wurden, unterschieden werden können. Schließlich verwenden wir die Funktion Print, um eine Erfolgsmeldung im Terminal auszugeben, die bestätigt, dass der Initialisierungsprozess abgeschlossen ist. Die Nachricht enthält den Kontotyp, der mit der Funktion „trading_Account_Mode“ abgefragt wird und angibt, ob es sich um ein Demokonto oder ein Live-Konto handelt. Die dafür zuständige Funktion lautet wie folgt.

string trading_Account_Mode(){
   string account_mode;
   switch ((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)){
      case ACCOUNT_TRADE_MODE_DEMO:
         account_mode = "DEMO";
         break;
      case ACCOUNT_TRADE_MODE_CONTEST:
         account_mode = "COMPETITION";
         break;
      case ACCOUNT_TRADE_MODE_REAL:
         account_mode = "REAL";
         break;
   }
   return account_mode;
}

Hier definieren wir eine String-Funktion „trading_Account_Mode“, um den Typ des Handelskontos zu bestimmen (ob es sich um ein Demokonto, ein Wettbewerbskonto oder ein echtes Konto handelt), basierend auf dem Wert des Parameters ACCOUNT_TRADE_MODE. Zunächst deklarieren wir eine Variable „account_mode“, um den Kontotyp als String zu speichern. Dann verwenden wir eine „switch“-Anweisung, um den Handelsmodus des Kontos auszuwerten, den wir durch den Aufruf der Funktion „AccountInfoInteger“ mit dem Parameter ACCOUNT_TRADE_MODE erhalten. Diese Funktion gibt den Handelsmodus des Kontos als Ganzzahlwert zurück. Die switch-Anweisung prüft den Wert dieser Ganzzahl und vergleicht ihn mit den möglichen Kontomodi:

  1. Wenn der Kontomodus ACCOUNT_TRADE_MODE_DEMO ist, setzen wir „account_mode“ auf „DEMO“.
  2. Wenn der Kontomodus ACCOUNT_TRADE_MODE_CONTEST ist, setzen wir „account_mode“ auf „COMPETITION“.
  3. Wenn der Kontomodus ACCOUNT_TRADE_MODE_REAL ist, setzen wir „account_mode“ auf „REAL“.

Schließlich gibt die Funktion den „account_mode“ als String zurück, der die Art des Kontos angibt, mit dem der EA verbunden ist. Daher lautet die endgültige Initialisierungsfunktion wie folgt:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   handleMAFast = iMA(_Symbol,_Period,inpMA_Fast_Period,0,inpMA_Fast_Method,PRICE_CLOSE);
   handleMASlow = iMA(_Symbol,_Period,inpMA_Slow_Period,0,inpMA_Slow_Method,PRICE_CLOSE);
   
   handleRSIFilter = iRSI(_Symbol,inpRSI_Tf,inpRSI_Period,inpRSI_Applied_Price);
   
   if (handleMAFast == INVALID_HANDLE || handleMASlow == INVALID_HANDLE || handleRSIFilter == INVALID_HANDLE){
      Print("ERROR! Unable to create the indicator handles. Reveting Now!");
      return (INIT_FAILED);
   }
   
   if (inpMA_Fast_Period <= 0 || inpMA_Slow_Period <= 0 || inpRSI_Period <= 0){
      Print("ERROR! Periods cannot be <= 0. Reverting Now!");
      return (INIT_PARAMETERS_INCORRECT);
   }
   
   ArraySetAsSeries(bufferMAFast,true);
   ArraySetAsSeries(bufferMASlow,true);
   ArraySetAsSeries(bufferRSIFilter,true);
   
   obj_Trade.SetExpertMagicNumber(inpMagicNo);
   
   Print("SUCCESS INITIALIZATION. ACCOUNT TYPE = ",trading_Account_Mode());
   
//---
   return(INIT_SUCCEEDED);
}

Nun gehen wir zur Ereignishandlung von OnDeinit, wo wir die Indikator-Handles freigeben müssen, da wir sie nicht mehr benötigen.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   IndicatorRelease(handleMAFast);
   IndicatorRelease(handleMASlow);
   IndicatorRelease(handleRSIFilter);
}

Um die den Indikator-Handles zugewiesenen Ressourcen freizugeben, rufen wir zunächst die Funktion IndicatorRelease für jeden der Indikator-Handles auf: „handleMAFast“, „handleMASlow“ und „handleRSIFilter“. Der Zweck der Funktion ist es, den Speicher und die Ressourcen freizugeben, die mit den Indikator-Handles verbunden sind, die während der Ausführung des EA initialisiert wurden. Dadurch wird sichergestellt, dass die Ressourcen der Plattform nicht unnötig durch nicht mehr benötigte Indikatoren belegt werden. Als Nächstes gehen wir zur Ereignishandlung durch OnTick über, in dem der größte Teil unserer Handelslogik abgewickelt wird. Zunächst müssen wir die Indikatordaten aus den Handles abrufen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//--- Check if data can be retrieved for the fast moving average (MA)
   if (CopyBuffer(handleMAFast,0,0,3,bufferMAFast) < 3){
   //--- Print error message for fast MA data retrieval failure
      Print("ERROR! Failed to retrieve the requested FAST MA data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }
//--- Check if data can be retrieved for the slow moving average (MA)
   if (CopyBuffer(handleMASlow,0,0,3,bufferMASlow) < 3){
   //--- Print error message for slow MA data retrieval failure
      Print("ERROR! Failed to retrieve the requested SLOW MA data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }
//--- Check if data can be retrieved for the RSI filter
   if (CopyBuffer(handleRSIFilter,0,0,3,bufferRSIFilter) < 3){
   //--- Print error message for RSI data retrieval failure
      Print("ERROR! Failed to retrieve the requested RSI data. Reverting.");
   //--- Exit the function if data retrieval fails
      return;
   }

   //---   

}

Hier konzentrieren wir uns darauf, die neuesten Indikatordaten des schnellen Durchschnitts, des langsamen Durchschnitts und des RSI-Filters abzurufen, um sicherzustellen, dass der EA über die notwendigen Informationen verfügt, um Handelsentscheidungen zu treffen. Zunächst verwenden wir die Funktion CopyBuffer für den schnellen Durchschnitt mit dessen Handles („handleMAFast“). Die Funktion extrahiert die Indikatorwerte zur Verarbeitung in den entsprechenden Puffer („bufferMAFast“). Konkret fordern wir 3 Datenpunkte an, beginnend mit Index 0, der die jüngsten Daten im Chart darstellt. Wenn die Anzahl der abgerufenen Werte weniger als 3 beträgt, bedeutet dies, dass der Zugriff auf die erforderlichen Daten fehlgeschlagen ist. In diesem Fall geben wir mit der Funktion Print eine Fehlermeldung aus und beenden die Funktion vorzeitig mit dem Return-Operator.

Als Nächstes wiederholen wir einen ähnlichen Prozess für den langsam Durchschnitt mit seinem Handle („handleMASlow“) und seinen Puffer („bufferMASlow“). Auch hier gilt: Wenn die Funktion CopyBuffer nicht mindestens 3 Datenpunkte abrufen kann, wird eine Fehlermeldung ausgegeben und die Funktion beendet, um eine weitere Ausführung zu verhindern. Schließlich verwenden wir dieselbe Funktion für den RSI-Filter-Handle („handleRSIFilter“) und seinen Puffer („bufferRSIFilter“). Wie zuvor wird sichergestellt, dass die angeforderten Datenpunkte erfolgreich abgerufen werden; andernfalls wird eine Fehlermeldung ausgegeben und die Funktion beendet. Wenn wir bis zu diesem Punkt nicht zurückkehren, haben wir die notwendigen Daten und können weiterhin Signale erzeugen. Wir möchten jedoch bei jedem Balken und nicht bei jedem Tick Signale erzeugen. Daher benötigen wir eine Funktion zur Erkennung der Erzeugung neuer Balken.

//+------------------------------------------------------------------+
//|     Function to detect if a new bar is formed                    |
//+------------------------------------------------------------------+
bool isNewBar(){
//--- Static variable to store the last bar count
   static int lastBarCount = 0;
//--- Get the current bar count
   int currentBarCount = iBars(_Symbol,_Period);
//--- Check if the bar count has increased
   if (currentBarCount > lastBarCount){
   //--- Update the last bar count
      lastBarCount = currentBarCount;
   //--- Return true if a new bar is detected
      return true;
   }
//--- Return false if no new bar is detected
   return false;
}

Hier definieren wir die Funktion „isNewBar“, die das Erscheinen eines neuen Balkens im Chart erkennen soll. Diese Funktion ist wichtig, um sicherzustellen, dass unsere Operationen nur einmal pro Balken und nicht bei jedem Tick wiederholt ausgeführt werden. Zunächst deklarieren wir eine statische Variable „lastBarCount“ und initialisieren sie mit 0. Eine statische Variable behält ihren Wert zwischen Funktionsaufrufen bei, sodass wir den aktuellen Zustand mit dem vorherigen Zustand vergleichen können. Anschließend wird die Gesamtzahl der Balken im Chart mit der Funktion iBars ermittelt, wobei _Symbol (das aktuelle Handelsinstrument) und _Period (der aktuelle Zeitrahmen) übergeben werden. Das Ergebnis wird in „currentBarCount“ gespeichert.

Als Nächstes vergleichen wir „currentBarCount“ mit „lastBarCount“. Wenn „currentBarCount“ größer ist, bedeutet dies, dass ein neuer Balken auf dem Chart gebildet wurde. In diesem Fall wird „lastBarCount“ aktualisiert, um mit „currentBarCount“ übereinzustimmen, und „true“ zurückgegeben, was das Vorhandensein eines neuen Balkens signalisiert. Wird kein neuer Balken erkannt, gibt die Funktion false zurück. Jetzt können wir diese Funktion für die Ereignisbehandlung der Ticks verwenden.

//--- Check if a new bar has formed
if (isNewBar()){
//--- Print debug message for a new tick
   //Print("THIS IS A NEW TICK");
   
//--- Identify if a buy crossover has occurred
   bool isMACrossOverBuy = bufferMAFast[1] > bufferMASlow[1] && bufferMAFast[2] <= bufferMASlow[2];
//--- Identify if a sell crossover has occurred
   bool isMACrossOverSell = bufferMAFast[1] < bufferMASlow[1] && bufferMAFast[2] >= bufferMASlow[2];
   
//--- Check if the RSI confirms a buy signal
   bool isRSIConfirmBuy = bufferRSIFilter[1] >= inpRsiBUYThreshold;
//--- Check if the RSI confirms a sell signal
   bool isRSIConfirmSell = bufferRSIFilter[1] <= inpRsiSELLThreshold;

   //---
}

Hier implementieren wir die Kernlogik zur Erkennung spezifischer Handelssignale auf der Grundlage der Beziehung zwischen gleitenden Durchschnitten und RSI-Bestätigungen. Der Prozess beginnt mit der Überprüfung, ob sich ein neuer Balken gebildet hat, indem die Funktion „isNewBar“ verwendet wird. Dadurch wird sichergestellt, dass die nachfolgende Logik nur einmal pro Takt ausgeführt wird, um wiederholte Auswertungen innerhalb desselben Balkens zu vermeiden.

Wenn ein neuer Balken entdeckt wird, bereiten wir uns zunächst darauf vor, das Kreuz für einen Kauf zu identifizieren, indem wir das Verhältnis zwischen den sich schnell und langsam bewegenden Durchschnitten bewerten. Konkret wird geprüft, ob der schnell gleitende Durchschnittswert für den vorhergehenden Balken („bufferMAFast[1]“) größer ist als der langsam gleitende Durchschnittswert für denselben Balken („bufferMASlow[1]“), während gleichzeitig der schnell gleitende Durchschnittswert zwei Balken zuvor („bufferMAFast[2]“) kleiner oder gleich dem langsam gleitenden Durchschnittswert für diesen Balken („bufferMASlow[2]“) war. Wenn beide Bedingungen erfüllt sind, setzen wir die boolesche Variable „isMACrossOverBuy“ auf „true“, was auf ein Kreuzen für einen Kauf hinweist.

In ähnlicher Weise identifizieren wir das Kreuz für einen Verkauf, indem wir prüfen, ob der schnelle, gleitende Durchschnitt des vorangegangenen Balkens („bufferMAFast[1]“) kleiner ist als der langsame, gleitende Durchschnitt desselben Balkens („bufferMASlow[1]“), während der schnelle, gleitende Durchschnitt zwei Balken zuvor („bufferMAFast[2]“) größer oder gleich dem langsamen gleitenden Durchschnitt für diesen Balken („bufferMASlow[2]“) war. Wenn diese Bedingungen erfüllt sind, setzen wir die boolesche Variable „isMACrossOverSell“ auf true, was auf ein Kreuzen für einen Verkauf hinweist.

Als Nächstes integrieren wir den RSI als Bestätigungsfilter für das erkannte Kreuzen. Für eine Kaufbestätigung wird überprüft, ob der RSI-Wert für den vorherigen Balken („bufferRSIFilter[1]“) größer oder gleich dem Kaufschwellenwert („inpRsiBUYThreshold“) ist. Wenn ja, setzen wir die boolesche Variable „isRSIConfirmBuy“ auf true. Ebenso wird bei einer Verkaufsbestätigung geprüft, ob der RSI-Wert des vorherigen Balkens („bufferRSIFilter[1]“) kleiner oder gleich dem Verkaufsschwellenwert („inpRsiSELLThreshold“) ist. Wenn ja, wird die boolesche Variable „isRSIConfirmSell“ auf „true“ gesetzt. Wir können diese Variablen nun nutzen, um Handelsentscheidungen zu treffen.

//--- Handle buy signal conditions
if (isMACrossOverBuy){
   if (isRSIConfirmBuy){
   //--- Print buy signal message
      Print("BUY SIGNAL");

   //---
}

Hier prüfen wir, ob sich die gleitenden Durchschnitte kreuzen und der RSI das Signal bestätigt, und wenn alle Bedingungen erfüllt sind, drucken wir ein Kaufsignal aus. Bevor wir jedoch eine Kaufposition eröffnen, müssen wir prüfen, ob der Filter der Handelstage eingehalten wird. Daher brauchen wir eine Funktion, die alles modularisiert.

//+------------------------------------------------------------------+
//|     Function to check trading days filter                        |
//+------------------------------------------------------------------+
bool isCheckTradingDaysFilter(){
//--- Structure to store the current date and time
   MqlDateTime dateTIME;
//--- Convert the current time into structured format
   TimeToStruct(TimeCurrent(),dateTIME);
//--- Variable to store the day of the week
   string today = "DAY OF WEEK";
   
//--- Assign the day of the week based on the numeric value
   if (dateTIME.day_of_week == 0){today = "SUNDAY";}
   if (dateTIME.day_of_week == 1){today = "MONDAY";}
   if (dateTIME.day_of_week == 2){today = "TUESDAY";}
   if (dateTIME.day_of_week == 3){today = "WEDNESDAY";}
   if (dateTIME.day_of_week == 4){today = "THURSDAY";}
   if (dateTIME.day_of_week == 5){today = "FRIDAY";}
   if (dateTIME.day_of_week == 6){today = "SATURDAY";}
   
//--- Check if trading is allowed based on the input parameters
   if (
      (dateTIME.day_of_week == 0 && Sunday == true) ||
      (dateTIME.day_of_week == 1 && Monday == true) ||
      (dateTIME.day_of_week == 2 && Tuesday == true) ||
      (dateTIME.day_of_week == 3 && Wednesday == true) ||
      (dateTIME.day_of_week == 4 && Thursday == true) ||
      (dateTIME.day_of_week == 5 && Friday == true) ||
      (dateTIME.day_of_week == 6 && Saturday == true)
   ){
   //--- Print acceptance message for trading
      Print("Today is on ",today,". Trade ACCEPTED.");
   //--- Return true if trading is allowed
      return true;
   }
   else {
   //--- Print rejection message for trading
      Print("Today is on ",today,". Trade REJECTED.");
   //--- Return false if trading is not allowed
      return false;
   }
}

Hier erstellen wir eine Funktion „isCheckTradingDaysFilter“, um anhand der Eingabeeinstellungen des Nutzers festzustellen, ob der Handel am aktuellen Tag erlaubt ist. Dadurch wird sichergestellt, dass die Handelsgeschäfte nur an den zulässigen Handelstagen ausgeführt werden, was die Präzision erhöht und unbeabsichtigte Vorgänge an Tagen mit eingeschränktem Zugang vermeidet. Zunächst definieren wir das Struktur-Objekt „MqlDateTime dateTIME“, das das aktuelle Datum und die Uhrzeit enthält. Mit der Funktion TimeToStruct wandeln wir die aktuelle Serverzeit (TimeCurrent) in die Struktur „dateTIME“ um, sodass wir leicht auf Komponenten wie den Wochentag zugreifen können.

Als Nächstes definieren wir eine Variable „today“ und weisen ihr einen Platzhalter-String „DAY OF WEEK“ zu. Darin wird später der Name des aktuellen Tages in menschenlesbarem Format gespeichert. Mithilfe einer Reihe von if-Bedingungen wird der numerische „day_of_week“-Wert (von 0 für Sonntag bis 6 für Samstag) dem entsprechenden Tagesnamen zugeordnet und die Variable „today“ mit dem richtigen Tag aktualisiert.

Anschließend wird geprüft, ob der Handel am aktuellen Tag erlaubt ist, indem „dateTIME.day_of_week“ mit den entsprechenden booleschen Eingabevariablen („Sonntag“, „Montag“ usw.) verglichen wird. Wenn der aktuelle Tag mit einem der freigegebenen Handelstage übereinstimmt, wird mit „Print“ eine Nachricht gedruckt, die anzeigt, dass der Handel erlaubt ist, einschließlich des Tagesnamens, und die Funktion gibt „true“ zurück. Umgekehrt wird, wenn der Handel nicht erlaubt ist, eine Nachricht gedruckt, um anzuzeigen, dass der Handel abgelehnt wird, und die Funktion gibt false zurück. Technisch gesehen dient diese Funktion als Torwächter, der sicherstellt, dass die Handelsoperationen mit den tagesabhängigen Präferenzen des Nutzers übereinstimmen. Wir können ihn verwenden, um den Handelstag zu filtern.

//--- Verify trading days filter before placing a trade
if (isCheckTradingDaysFilter()){
//--- Retrieve the current ask price
   double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
//--- Retrieve the current bid price
   double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   
//--- Set the open price to the ask price
   double openPrice = Ask;
//--- Calculate the stop-loss price
   double stoploss = Bid - inpSLPts*_Point;
//--- Calculate the take-profit price
   double takeprofit = Bid + (inpSLPts*inpR2R)*_Point;
//--- Define the trade comment
   string comment = "BUY TRADE";
   
//--- Execute a buy trade
   obj_Trade.Buy(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment);
//--- Initialize the ticket variable
   ulong ticket = 0;
//--- Retrieve the order result ticket
   ticket = obj_Trade.ResultOrder();
//--- Print success message if the trade is opened
   if (ticket > 0){
      Print("SUCCESS. Opened the BUY position with ticket # ",ticket);
   }
//--- Print error message if the trade fails to open
   else {Print("ERROR! Failed to open the BUY position.");}
}

Hier wird durch den Aufruf der Funktion „isCheckTradingDaysFilter“ geprüft, ob der Handel am aktuellen Tag erlaubt ist. Wenn die Funktion den Wert „true“ zurückgibt, werden Marktdaten gesammelt und ein Handel platziert, wobei sichergestellt wird, dass der Handel mit den nutzerdefinierten Tagesfiltern übereinstimmt. Zunächst werden die aktuellen Marktpreise mit der Funktion SymbolInfoDouble abgefragt. Die Parameter SYMBOL_ASK und SYMBOL_BID werden verwendet, um die aktuellen Geld- und Briefkurse für das aktive Handelssymbol (_Symbol) zu ermitteln. Diese Werte werden in den Variablen „Ask“ bzw. „Bid“ gespeichert und bilden die Grundlage für weitere Berechnungen.

Anschließend berechnen wir die für den Handel erforderlichen Preisniveaus. Der „Ask“-Kurs wird als „openPrice“ festgelegt und stellt den Einstiegskurs für eine Kaufposition dar. Wir berechnen den Stop-Loss-Kurs, indem wir „inpSLPts“ (die eingegebenen Stop-Loss-Punkte) multipliziert mit _Point vom „Bid“-Kurs abziehen. Analog dazu wird der Take-Profit-Kurs bestimmt, indem das Produkt aus „inpSLPts“, dem Risiko-Ertrags-Verhältnis („inpR2R“) und _Point zum „Bid“-Kurs addiert wird. Diese Berechnungen legen die Risiko- und Ertragsgrenzen für den Handel fest.

Wir definieren dann einen Handelskommentar („BUY TRADE“), um den Handel für zukünftige Referenzen zu kennzeichnen. Anschließend führen wir den Kaufhandel mit der Methode „obj_Trade.Buy“ aus, wobei wir die Lotgröße („inpLots“), das Handelssymbol, den Einstiegskurs, den Stop-Loss-Kurs, den Take-Profit-Kurs und den Kommentar als Parameter übergeben. Diese Funktion sendet den Handelsauftrag an den Markt. Nach der Handelsausführung initialisieren wir die Variable „ticket“ auf 0 und weisen ihr das von der Methode „obj_Trade.ResultOrder“ zurückgegebene Orderticket zu. Wenn das Ticket größer als 0 ist, bedeutet dies, dass der Handel erfolgreich eröffnet wurde, und es wird eine Erfolgsmeldung mit der Ticketnummer gedruckt. Bleibt das Ticket bei 0, bedeutet dies, dass der Handel fehlgeschlagen ist, und es wird eine Fehlermeldung angezeigt. Bei einer Verkaufsposition gehen wir nach demselben Verfahren vor, allerdings mit umgekehrten Bedingungen. Das Codeschnipsel lautet wie folgt:

//--- Handle sell signal conditions
else if (isMACrossOverSell){
   if (isRSIConfirmSell){
   //--- Print sell signal message
      Print("SELL SIGNAL");
   //--- Verify trading days filter before placing a trade
      if (isCheckTradingDaysFilter()){
      //--- Retrieve the current ask price
         double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      //--- Retrieve the current bid price
         double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
         
      //--- Set the open price to the bid price
         double openPrice = Bid;
      //--- Calculate the stop-loss price
         double stoploss = Ask + inpSLPts*_Point;
      //--- Calculate the take-profit price
         double takeprofit = Ask - (inpSLPts*inpR2R)*_Point;
      //--- Define the trade comment
         string comment = "SELL TRADE";
         
      //--- Execute a sell trade
         obj_Trade.Sell(inpLots,_Symbol,openPrice,stoploss,takeprofit,comment);
      //--- Initialize the ticket variable
         ulong ticket = 0;
      //--- Retrieve the order result ticket
         ticket = obj_Trade.ResultOrder();
      //--- Print success message if the trade is opened
         if (ticket > 0){
            Print("SUCCESS. Opened the SELL position with ticket # ",ticket);
         }
      //--- Print error message if the trade fails to open
         else {Print("ERROR! Failed to open the SELL position.");}
      }
   }
}

Nach Ausführung des Programms erhalten wir folgendes Ergebnis.

SIGNAL- UND HANDELSBESTÄTIGUNGEN

Aus dem Bild können wir ersehen, dass wir den Handel bestätigen. Es wäre jedoch eine gute Idee, die Signale im Chart zu visualisieren, um Klarheit zu schaffen. Daher benötigen wir eine Funktion zum Zeichnen von Pfeilen mit Kommentaren.

//+------------------------------------------------------------------+
//|    Create signal text function                                   |
//+------------------------------------------------------------------+
void createSignalText(datetime time,double price,int arrowcode,
            int direction,color clr,double angle,string txt
){
//--- Generate a unique name for the signal object
   string objName = " ";
   StringConcatenate(objName, "Signal @ ",time," at Price ",DoubleToString(price,_Digits));
//--- Create the arrow object at the specified time and price
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)){
   //--- Set arrow properties
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   
//--- Generate a unique name for the description text object
   string objNameDesc = objName+txt;
//--- Create the text object at the specified time and price
   if (ObjectCreate(0,objNameDesc,OBJ_TEXT,0,time,price)){
   //--- Set text properties
      ObjectSetInteger(0,objNameDesc,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDesc,OBJPROP_ANGLE,angle);
      if (direction > 0){
         ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDesc,OBJPROP_TEXT,"    "+txt);
      }
      if (direction < 0){
         ObjectSetInteger(0,objNameDesc,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDesc,OBJPROP_TEXT,"    "+txt);
      }
      
   }
   
}

Hier definieren wir die Funktion „createSignalText“, um Handelssignale auf dem Chart mit Pfeilen und beschreibendem Text visuell darzustellen. Diese Funktion erhöht die Übersichtlichkeit des Charts, indem sie wichtige Ereignisse wie Kauf- oder Verkaufssignale markiert. Zunächst wird mit der Funktion StringConcatenate ein eindeutiger Name für das Pfeilobjekt erzeugt. Der Name enthält das Wort „Signal“, den angegebenen Zeitpunkt und den Preis des Signals. Durch diese eindeutige Benennung wird sichergestellt, dass es keine Überschneidungen mit anderen Objekten im Chart gibt.

Als Nächstes erstellen wir mit der Funktion ObjectCreate ein Pfeilobjekt im Chart zum angegebenen Zeitpunkt und Preis. Wenn die Erstellung erfolgreich war, können wir die Eigenschaften der Datei anpassen. Der Parameter „arrowcode“ bestimmt die Art des anzuzeigenden Pfeils, während der Parameter „clr“ die Farbe des Pfeils angibt. Je nach Signalrichtung wird der Ankerpunkt des Pfeils bei Aufwärtssignalen nach oben (ANCHOR_TOP) und bei Abwärtssignalen nach unten (ANCHOR_BOTTOM) gesetzt. Dadurch wird sichergestellt, dass die Position des Pfeils korrekt mit dem Kontext des Signals übereinstimmt.

Anschließend erstellen wir ein Objekt für den Beschreibungstext, der den Pfeil begleitet. Ein eindeutiger Name für das Textobjekt wird durch Anhängen der Beschreibung „txt“ an den Namen des Pfeils erzeugt. Das Textobjekt wird an denselben Zeit- und Preiskoordinaten wie der Pfeil platziert. Die Eigenschaften des Textobjekts werden festgelegt, um sein Aussehen und seine Ausrichtung zu verbessern. Der Parameter „clr“ legt die Textfarbe fest, und der Parameter „angle“ bestimmt die Drehung des Textes. Bei Aufwärtssignalen wird der Anker links ausgerichtet (ANCHOR_LEFT), und dem txt werden Leerzeichen vorangestellt, um den Abstand anzupassen. Bei nach unten gerichteten Signalen wird der Anker mit der gleichen Abstandseinstellung nach unten ausgerichtet (ANCHOR_BOTTOM).

Nun können wir diese Funktion verwenden, um die Pfeile mit den entsprechenden Anmerkungen zu erstellen.

//--- FOR A BUY SIGNAL
//--- Retrieve the time of the signal
datetime textTime = iTime(_Symbol,_Period,1);
//--- Retrieve the price of the signal
double textPrice = iLow(_Symbol,_Period,1);
//--- Create a visual signal on the chart for a buy
createSignalText(textTime,textPrice,221,1,clrBlue,-90,"Buy Signal");

//...

//--- FOR A SELL SIGNAL
//--- Retrieve the time of the signal
datetime textTime = iTime(_Symbol,_Period,1);
//--- Retrieve the price of the signal
double textPrice = iHigh(_Symbol,_Period,1);
//--- Create a visual signal on the chart for a sell
createSignalText(textTime,textPrice,222,-1,clrRed,90,"Sell Signal");

Hier erstellen wir visuelle Markierungen auf dem Chart, um Kauf- und Verkaufssignale darzustellen. Diese Markierungen bestehen aus Pfeilen und begleitendem beschreibendem Text, um die Übersichtlichkeit der Karte zu erhöhen und die Entscheidungsfindung zu erleichtern.

Für ein Kaufsignal:

  • Abrufen des Zeitpunktes des Signals:

Mit der Funktion iTime erhalten wir den Zeitpunkt des vorletzten abgeschlossenen Balkens (Index 1) im Chart für das aktuelle Symbol und den aktuellen Zeitrahmen (_Symbol und _Period). Dadurch wird sichergestellt, dass das Signal einem bestätigten Balken entspricht.

  • Rufen wir den Preis des Signals ab:

Wir verwenden die Funktion iLow, um den niedrigsten Preis desselben Balkens (1) zu ermitteln. Dies ist die Position, an der wir den Marker platzieren wollen.

  • Schaffen wir ein visuelles Signal:

Die Funktion „createSignalText“ wird mit den abgerufenen Werten „textTime“ und „textPrice“ sowie weiteren Parametern aufgerufen:

  1. „221“: Der Pfeilcode für einen bestimmten Pfeiltyp, der ein Kaufsignal darstellt.
  2. „1“: Richtung des Signals, das eine Aufwärtsbewegung anzeigt.
  3. „clrBlue“: Farbe des Pfeils und des Textes, die ein positives Signal darstellen.
  4. „-90“: Textwinkel für korrekte Ausrichtung.
  5. „Buy Signal“: Der beschreibende Text wird neben dem Pfeil angezeigt. Damit wird das Kaufsignal auf dem Chart visuell markiert.

Für ein Verkaufssignal:

  • Abrufen des Zeitpunktes des Signals:

Wie beim Kaufsignal verwenden wir iTime, um die Datumszeit des Balkens bei Index 1 zu ermitteln.

  • Rufen wir den Preis des Signals ab:

Die Funktion iHigh wird verwendet, um den höchsten Preis desselben Balkens zu ermitteln. Dies ist die Platzierungsposition für die Verkaufssignalmarkierung.

  • Schaffen wir ein visuelles Signal:

Die Funktion „createSignalText“ wird mit aufgerufen:

  1. „222“: Der Pfeilcode steht für ein Verkaufssignal.
  2. „-1“: Richtung des Signals, das eine Abwärtsbewegung anzeigt.
  3. „clrRed“: Farbe des Pfeils und des Textes, der ein negatives Signal anzeigt.
  4. „90“: Textwinkel für die Ausrichtung.
  5. „Sell Signal“: Der beschreibende Text, der neben dem Pfeil angezeigt wird. Damit wird das Verkaufssignal auf dem Chart deutlich markiert.

Nach der Ausführung des Programms erhalten wir die folgende Ausgabe.

PFEIL MIT VERMERK

Auf dem Bild können wir sehen, dass wir, sobald wir ein bestätigtes Signal sehen können, den Pfeil und die entsprechende Anmerkung auf dem Chart haben, um Klarheit zu schaffen. Dies verleiht dem Chart einen professionellen Touch und erleichtert die Interpretation von Handelssignalen durch klare visuelle Anhaltspunkte. Wir können nun dazu übergehen, eine Trailing-Stop-Funktion in den Code einzubauen, um Gewinne zu sichern, sobald wir bestimmte vordefinierte Niveaus erreichen. Der Einfachheit halber werden wir eine Funktion verwenden.

//+------------------------------------------------------------------+
//|         Trailing stop function                                   |
//+------------------------------------------------------------------+
void applyTrailingStop(int slpoints, CTrade &trade_object,ulong magicno=0,int minProfitPts=0){
//--- Calculate the stop loss price for buy positions
   double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID) - slpoints*_Point,_Digits);
//--- Calculate the stop loss price for sell positions
   double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK) + slpoints*_Point,_Digits);
   
//--- Loop through all positions in the account
   for (int i=PositionsTotal()-1; i>=0; i--){
   //--- Get the ticket of the position
      ulong ticket = PositionGetTicket(i);
   //--- Ensure the ticket is valid
      if (ticket > 0){
      //--- Select the position by ticket
         if (PositionSelectByTicket(ticket)){
         //--- Check if the position matches the symbol and magic number (if provided)
            if (PositionGetSymbol(POSITION_SYMBOL) == _Symbol &&
               (magicno == 0 || PositionGetInteger(POSITION_MAGIC) == magicno)
            ){
            //--- Retrieve the open price and current stop loss of the position
               double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
               double positionSl = PositionGetDouble(POSITION_SL);
               
            //--- Handle trailing stop for buy positions
               if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
               //--- Calculate the minimum profit price for the trailing stop
                  double minProfitPrice = NormalizeDouble((positionOpenPrice+minProfitPts*_Point),_Digits);
               //--- Apply trailing stop only if conditions are met
                  if (buySl > minProfitPrice &&
                     buySl > positionOpenPrice &&
                     (positionSl == 0 || buySl > positionSl)
                  ){
                  //--- Modify the position's stop loss
                     trade_object.PositionModify(ticket,buySl,PositionGetDouble(POSITION_TP));
                  }
               }
               //--- Handle trailing stop for sell positions
               else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
               //--- Calculate the minimum profit price for the trailing stop
                  double minProfitPrice = NormalizeDouble((positionOpenPrice-minProfitPts*_Point),_Digits);
               //--- Apply trailing stop only if conditions are met
                  if (sellSl < minProfitPrice &&
                     sellSl < positionOpenPrice &&
                     (positionSl == 0 || sellSl < positionSl)
                  ){
                  //--- Modify the position's stop loss
                     trade_object.PositionModify(ticket,sellSl,PositionGetDouble(POSITION_TP));
                  }
               }
               
            }
         }
      }
   }
   
}

Hier implementieren wir einen Trailing-Stop-Mechanismus in der Funktion „applyTrailingStop“, um die Stop-Loss-Niveaus für aktive Handelspositionen dynamisch anzupassen. Dadurch wird sichergestellt, dass wir bei günstigen Marktentwicklungen Gewinne sichern und gleichzeitig die Risiken minimieren. Die Funktion arbeitet nach der folgenden Logik. Zunächst berechnen wir die Stop-Loss-Niveaus sowohl für Kauf- als auch für Verkaufspositionen. Mit der Funktion SymbolInfoDouble wird der Preis von SYMBOL_BID abgerufen, um das „buySl“-Niveau zu bestimmen, wobei die angegebenen „slpoints“ (Stop-Loss-Abstand in Punkten) abgezogen und mit der Funktion NormalizeDouble auf die richtige Anzahl von Dezimalstellen normalisiert werden. In ähnlicher Weise berechnen wir das „sellSl“-Niveau, indem wir die „slpoints“ zum Preis SYMBOL_ASK addieren und normalisieren.

Als Nächstes werden alle aktiven Positionen des Handelskontos in einer umgekehrten for-Schleife „for (int i=PositionsTotal()-1; i>=0; i--)“ durchlaufen. Für jede Position wird das „Ticket“ mit der Funktion PositionGetTicket abgerufen. Wenn das „Ticket“ gültig ist, wählen wir die entsprechende Position mit der Funktion PositionSelectByTicket aus. Innerhalb der Schleife wird geprüft, ob die Position mit dem aktuellen „Symbol“ und der angegebenen „magicno“ (magische Zahl) übereinstimmt. Wenn „magicno“ gleich 0 ist, werden alle Positionen unabhängig von ihrer magischen Zahl berücksichtigt. Für in Frage kommende Positionen werden deren POSITION_PRICE_OPEN (Eröffnungspreis) und POSITION_SL (aktuelles Stop-Loss-Niveau) abgefragt.

Für Kaufpositionen berechnen wir den „minProfitPrice“, indem wir den „minProfitPts“ (Mindestgewinn in Punkten) zum Eröffnungspreis addieren und normalisieren. Wir wenden den Trailing-Stop nur an, wenn das „buySl“-Niveau alle Bedingungen erfüllt:

  • „buySl“ übersteigt den „minProfitPrice“.
  • „buySl“ ist höher als der Eröffnungskurs.
  • „buySl“ ist entweder größer als der aktuelle Stop-Loss oder es ist kein Stop-Loss gesetzt („positionSl == 0“).

Wenn diese Bedingungen erfüllt sind, ändern wir den Stop-Loss der Position mit der Methode „PositionModify“ des Objekts „CTrade“. Für Verkaufspositionen berechnen wir den „minProfitPrice“, indem wir den „minProfitPts“ vom Eröffnungspreis abziehen und normalisieren. In ähnlicher Weise wird der Trailing-Stop angewendet, wenn das SellSl-Niveau die folgenden Bedingungen erfüllt:

  • „sellSl“ liegt unter dem „minProfitPrice“.
  • „sellSl“ ist niedriger als der Eröffnungskurs.
  • „sellSl“ ist entweder niedriger als der aktuelle Stop-Loss oder es ist kein Stop-Loss gesetzt.

Wenn diese Bedingungen erfüllt sind, ändern wir den Stop-Loss der Position ebenfalls mit der Methode „PositionModify“. Wir können diese Funktionen dann bei jedem Tick aufrufen, um die Trailing-Stop-Logik für die offenen Positionen wie folgt anzuwenden.

//--- Apply trailing stop if allowed in the input parameters
if (inpisAllowTrailingStop){
   applyTrailingStop(inpTrailPts,obj_Trade,inpMagicNo,inpMinTrailPts);
}

Hier rufen wir die Trailing-Stop-Funktion auf und erhalten bei der Ausführung des Programms das folgende Ergebnis.

TRAILING STOP

Aus dem Bild geht hervor, dass das Ziel des Trailing-Stops erfolgreich erreicht wurde. Nun müssen wir die Daten in einem Chart darstellen. Dazu benötigen wir ein Dashboard mit einer Hauptbasis und Beschriftungen. Für die Basis benötigen wir ein rechteckiges Label. Hier ist die Implementierung der Funktion.

//+------------------------------------------------------------------+
//|     Create Rectangle label function                              |
//+------------------------------------------------------------------+
bool createRecLabel(string objNAME,int xD,int yD,int xS,int yS,
                  color clrBg,int widthBorder,color clrBorder = clrNONE,
                  ENUM_BORDER_TYPE borderType = BORDER_FLAT,ENUM_LINE_STYLE borderStyle = STYLE_SOLID
){
//--- Reset the last error code
   ResetLastError();
//--- Attempt to create the rectangle label object
   if (!ObjectCreate(0,objNAME,OBJ_RECTANGLE_LABEL,0,0,0)){
   //--- Log the error if creation fails
      Print(__FUNCTION__,": Failed to create the REC LABEL. Error Code = ",_LastError);
      return (false);
   }
   
//--- Set rectangle label properties
   ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD);
   ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD);
   ObjectSetInteger(0, objNAME,OBJPROP_XSIZE, xS);
   ObjectSetInteger(0, objNAME,OBJPROP_YSIZE, yS);
   ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, objNAME,OBJPROP_BGCOLOR, clrBg);
   ObjectSetInteger(0, objNAME,OBJPROP_BORDER_TYPE, borderType);
   ObjectSetInteger(0, objNAME,OBJPROP_STYLE, borderStyle);
   ObjectSetInteger(0, objNAME,OBJPROP_WIDTH, widthBorder);
   ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrBorder);
   ObjectSetInteger(0, objNAME,OBJPROP_BACK, false);
   ObjectSetInteger(0, objNAME,OBJPROP_STATE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false);
   
//--- Redraw the chart to reflect changes
   ChartRedraw(0);
   
   return (true);
}

Mit der booleschen Funktion „createRecLabel“, die wir definieren, erstellen wir in mehreren Schritten eine anpassbare rechteckige Beschriftung im Chart. Zunächst setzen wir alle vorherigen Fehlercodes mit der Funktion ResetLastError zurück. Dann versuchen wir, das rechteckige Label-Objekt mit der Funktion ObjectCreate zu erstellen. Wenn die Erstellung fehlschlägt, wird eine Fehlermeldung mit dem Grund des Fehlschlags ausgegeben und „false“ zurückgegeben. Wenn die Erstellung erfolgreich war, werden mit der Funktion ObjectSetInteger verschiedene Eigenschaften für das Rechteck-Label festgelegt.

Mit diesen Eigenschaften können wir die Position, die Größe, die Hintergrundfarbe, den Rahmenstil und andere visuelle Aspekte des Rechtecks festlegen. Wir weisen die Parameter „xD“, „yD“, „xS“ und „yS“ zu, um die Position und Größe des Rechtecks mit Hilfe von OBJPROP_XDISTANCE, „OBJPROP_YDISTANCE“, „OBJPROP_XSIZE“ und „OBJPROP_YSIZE“ festzulegen. Außerdem werden die Hintergrundfarbe, der Rahmentyp und der Rahmenstil über „OBJPROP_BGCOLOR“, „OBJPROP_BORDER_TYPE“ und „OBJPROP_STYLE“ festgelegt.

Um sicherzustellen, dass die visuelle Darstellung des Etiketts aktualisiert wird, rufen wir schließlich die Funktion ChartRedraw auf, um das Chart zu aktualisieren. Wenn das rechteckige Label erfolgreich erstellt wurde und alle Eigenschaften korrekt eingestellt sind, gibt die Funktion „true“ zurück. Auf diese Weise können wir das Chart mit nutzerdefinierten Rechteckbeschriftungen, die auf den angegebenen Parametern basieren, visuell annotieren. Das Gleiche gilt für eine Beschriftungsfunktion.

//+------------------------------------------------------------------+
//|    Create label function                                         |
//+------------------------------------------------------------------+
bool createLabel(string objNAME,int xD,int yD,string txt,
                  color clrTxt = clrBlack,int fontSize = 12,
                  string font = "Arial Rounded MT Bold"
){
//--- Reset the last error code
   ResetLastError();
//--- Attempt to create the label object
   if (!ObjectCreate(0,objNAME,OBJ_LABEL,0,0,0)){
   //--- Log the error if creation fails
      Print(__FUNCTION__,": Failed to create the LABEL. Error Code = ",_LastError);
      return (false);
   }
   
//--- Set label properties
   ObjectSetInteger(0, objNAME,OBJPROP_XDISTANCE, xD);
   ObjectSetInteger(0, objNAME,OBJPROP_YDISTANCE, yD);
   ObjectSetInteger(0, objNAME,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, objNAME,OBJPROP_TEXT, txt);
   ObjectSetInteger(0, objNAME,OBJPROP_COLOR, clrTxt);
   ObjectSetString(0, objNAME,OBJPROP_FONT, font);
   ObjectSetInteger(0, objNAME,OBJPROP_FONTSIZE, fontSize);
   ObjectSetInteger(0, objNAME,OBJPROP_BACK, false);
   ObjectSetInteger(0, objNAME,OBJPROP_STATE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objNAME,OBJPROP_SELECTED, false);
   
//--- Redraw the chart to reflect changes
   ChartRedraw(0);
   
   return (true);
}

Mit diesen Funktionen können wir nun eine Funktion erstellen, die bei Bedarf die Erstellung des Dashboards übernimmt.

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){

   //---
}

Hier erstellen wir eine ungültige Funktion mit dem Namen „createDashboard“, in der wir die Logik für die Erstellung des Dashboards unterbringen können. Um die Änderungen effektiv zu verfolgen, können wir die Funktion in der Ereignisbehandlung von OnInit zuerst aufrufen, bevor wir den Textkörper definieren (siehe unten).

//---

createDashboard();

//---

Nach dem Aufruf der Funktion können wir den Körper der Funktion definieren. Als erstes definieren wir das Dashboard und müssen seinen Namen als globale Konstante festlegen.

//+------------------------------------------------------------------+
//|    Global constants for dashboard object names                   |
//+------------------------------------------------------------------+
const string DASH_MAIN = "MAIN";

Hier definieren wir eine konstante Zeichenkette, const, was bedeutet, dass sie während des Programms nicht verändert wird. Wir verwenden nun die Konstante für die Erstellung des Labels wie folgt.

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){
//--- Create the main dashboard rectangle
   createRecLabel(DASH_MAIN,10,50+30,200,120,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID);
   
   //---

}

Mit der Funktion „createDashboard“ wird der Prozess der Erstellung eines visuellen Dashboards auf dem Chart eingeleitet. Zu diesem Zweck rufen wir die Funktion „createRecLabel“ auf, die ein Rechteck auf dem Chart zeichnet, das als Basis für das Dashboard dient. Die Funktion ist mit spezifischen Parametern ausgestattet, um das Aussehen und die Positionierung dieses Rechtecks zu definieren. Zunächst geben wir den Namen des Rechtecks als „DASH_MAIN“ an, damit wir dieses Objekt später identifizieren können. Anschließend wird die Position des Rechtecks festgelegt, indem seine linke obere Ecke mit den Parametern „xD“ und „yD“ auf die Koordinaten (10, 50+30) im Chart gesetzt wird. Die Breite und Höhe des Rechtecks werden mit den Parametern „xS“ und „yS“ auf 200 bzw. 120 Pixel festgelegt, können aber nachträglich angepasst werden.

Als Nächstes wird das Erscheinungsbild des Rechtecks festgelegt. Die Hintergrundfarbe des Rechtecks wird auf „clrBlack“ gesetzt, und wir wählen eine blaue Farbe, „clrBlue“, für den Rand. Der Rahmen hat eine Breite von 2 Pixeln und einen durchgehenden Linienstil (STYLE_SOLID), und der Rahmentyp ist auf flach (BORDER_FLAT) eingestellt. Diese Einstellungen sorgen dafür, dass das Rechteck ein klares und deutliches Aussehen hat. Dieses Rechteck dient als Basiselement des Dashboards, dem in weiteren Schritten zusätzliche Elemente wie Text oder interaktive Komponenten hinzugefügt werden können. Doch lassen Sie uns den aktuellen Meilenstein ausführen und das Ergebnis ablesen.

DASHBOARD BASIS

Auf dem Bild können wir sehen, dass die Basis des Dashboards so ist, wie wir es erwartet haben. Anschließend können wir die anderen Elemente für das Dashboard mithilfe der Funktion label und nach demselben Verfahren erstellen. Wir definieren also die restlichen Objekte wie folgt.

//+------------------------------------------------------------------+
//|    Global constants for dashboard object names                   |
//+------------------------------------------------------------------+
const string DASH_MAIN = "MAIN";
const string DASH_HEAD = "HEAD";
const string DASH_ICON1 = "ICON 1";
const string DASH_ICON2 = "ICON 2";
const string DASH_NAME = "NAME";
const string DASH_OS = "OS";
const string DASH_COMPANY = "COMPANY";
const string DASH_PERIOD = "PERIOD";
const string DASH_POSITIONS = "POSITIONS";
const string DASH_PROFIT = "PROFIT";

Hier definieren wir nur den Rest der Objekte. Auch hier verwenden wir die Funktion label, um die Kopfzeile wie unten dargestellt zu erstellen.

//+------------------------------------------------------------------+
//|    Create dashboard function                                     |
//+------------------------------------------------------------------+
void createDashboard(){
//--- Create the main dashboard rectangle
   createRecLabel(DASH_MAIN,10,50+30,200,120+30,clrBlack,2,clrBlue,BORDER_FLAT,STYLE_SOLID);
   
//--- Add icons and text labels to the dashboard
   createLabel(DASH_ICON1,13,53+30,CharToString(40),clrRed,17,"Wingdings");
   createLabel(DASH_ICON2,180,53+30,"@",clrWhite,17,"Webdings");
   createLabel(DASH_HEAD,65,53+30,"Dashboard",clrWhite,14,"Impact");
}

Hier wird das Dashboard durch Hinzufügen von Symbolen und einer Überschrift mit der Funktion „createLabel“ erweitert. Diese Funktion wird mehrfach aufgerufen, um textbasierte Elemente an bestimmten Positionen im Chart zu platzieren, wodurch wir eine visuell ansprechende und informative Schnittstelle erstellen können. Zunächst erstellen wir ein Symbol mit der Bezeichnung „DASH_ICON1“, das an den Koordinaten (13, 53+30) relativ zum Chart positioniert wird. Das Symbol wird durch den Zeichencode 40 dargestellt, der mit der Funktion „CharToString(40)“ in eine Zeichenkette umgewandelt wird. Dieses Symbol wird in Rot, „clrRed“, mit einer Schriftgröße von 17 angezeigt, und der Schriftstil ist auf „Wingdings“ eingestellt, um das Zeichen als grafisches Symbol darzustellen.

Als Nächstes fügen wir ein weiteres Symbol mit der Bezeichnung „DASH_ICON2“ hinzu, das sich an den Koordinaten (180, 53+30) befindet. Dieses Symbol verwendet das Zeichen „@“, das in weißer Schrift („clrWhite“) mit einer Schriftgröße von 17 dargestellt wird. Die Schriftart ist „Webdings“, sodass das „@“-Zeichen in einer dekorativen und stilisierten Weise erscheint. Hier ist die Darstellung.

WEBDINGS FONT

Schließlich fügen wir eine Textüberschrift mit der Bezeichnung „DASH_HEAD“ an der Position (65, 53+30) ein. Die Überschrift zeigt den Text „Dashboard“ in weiß („clrWhite“) mit einer Schriftgröße von 14 an. Die Schriftart ist auf „Impact“ eingestellt, was der Überschrift ein fettes und markantes Aussehen verleiht. Dann können wir die restlichen Etiketten definieren.

createLabel(DASH_NAME,20,90+30,"EA Name: Crossover RSI Suite",clrWhite,10,"Calibri");
createLabel(DASH_COMPANY,20,90+30+15,"LTD: "+AccountInfoString(ACCOUNT_COMPANY),clrWhite,10,"Calibri");
createLabel(DASH_OS,20,90+30+15+15,"OS: "+TerminalInfoString(TERMINAL_OS_VERSION),clrWhite,10,"Calibri");
createLabel(DASH_PERIOD,20,90+30+15+15+15,"Period: "+EnumToString(Period()),clrWhite,10,"Calibri");

createLabel(DASH_POSITIONS,20,90+30+15+15+15+30,"Positions: "+IntegerToString(PositionsTotal()),clrWhite,10,"Calibri");
createLabel(DASH_PROFIT,20,90+30+15+15+15+30+15,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY),clrWhite,10,"Calibri");

Hier füllen wir das Dashboard mit wichtigen Informationsbeschriftungen, indem wir die Funktion „createLabel“ verwenden. Zunächst erstellen wir ein Etikett „DASH_NAME“, das bei (20, 90+30) positioniert wird. Dieses Etikett zeigt den Text „EA Name: Crossover RSI Suite“ in weiß („clrWhite“) mit einer Schriftgröße von 10 und dem Schriftstil „Calibri“. Diese Bezeichnung dient als Name des Expert Advisors und ermöglicht dem Nutzer eine eindeutige Identifizierung.

Als Nächstes fügen wir das Label „DASH_COMPANY“ bei (20, 90+30+15) ein. Es wird der Text „LTD: “ angezeigt, gefolgt von den Unternehmensdaten des Kontos, die mit der Funktion AccountInfoString mit dem Parameter ACCOUNT_COMPANY abgerufen werden. Das Etikett ist in Weiß mit einer Schriftgröße von 10 gestaltet und verwendet die Schriftart „Calibri“. Anschließend wird das Etikett „DASH_OS“ bei (20, 90+30+15+15) platziert. Es zeigt die Version des Betriebssystems mit dem Text „OS: “, kombiniert mit dem Ergebnis der Funktion TerminalInfoString mit dem Parameter TERMINAL_OS_VERSION. Dieses Etikett hilft dem Nutzer, das Betriebssystem des Terminals zu erkennen. Es ist ebenfalls in Weiß mit einer Schriftgröße von 10 und der Schriftart „Calibri“ gestaltet.

Dann fügen wir das Label „DASH_PERIOD“ bei (20, 90+30+15+15+15) ein. Dieses Etikett zeigt den aktuellen Zeitrahmen des Charts mit dem Text „Period:“ an, der durch das Ergebnis der Funktion EnumToString mit der Periode ergänzt wird. Der weiße Text, die kleine Schriftgröße und die Schriftart „Calibri“ stehen im Einklang mit dem Gesamtdesign des Armaturenbretts. Zusätzlich fügen wir das Label „DASH_POSITIONS“ bei (20, 90+30+15+15+15+30) hinzu. Dieses Etikett zeigt die Gesamtzahl der derzeit offenen Positionen auf dem Konto an, mit dem Text „Positionen: “, gefolgt von der Gesamtzahl der Positionen. Diese Informationen sind für die Kontrolle aktiver Handelsgeschäfte von entscheidender Bedeutung.

Schließlich wird das Label „DASH_PROFIT“ bei (20, 90+30+15+15+15+30+15) platziert. Es zeigt den aktuellen Gewinn des Kontos mit dem Text „Profit: “ an, gefolgt vom Ergebnis der Gewinnfunktion des Kontos, das den Gewinn mit zwei Dezimalstellen darstellt, zusammen mit der Kontowährung, die über die Funktion AccountInfoString abgerufen wurde.

Schließlich müssen wir das Dashboard am Ende löschen, sobald das Programm entfernt ist. Wir brauchen also eine Funktion zum Löschen des Dashboards.

//+------------------------------------------------------------------+
//|     Delete dashboard function                                    |
//+------------------------------------------------------------------+
void deleteDashboard(){
//--- Delete all objects related to the dashboard
   ObjectDelete(0,DASH_MAIN);
   ObjectDelete(0,DASH_ICON1);
   ObjectDelete(0,DASH_ICON2);
   ObjectDelete(0,DASH_HEAD);
   ObjectDelete(0,DASH_NAME);
   ObjectDelete(0,DASH_COMPANY);
   ObjectDelete(0,DASH_OS);
   ObjectDelete(0,DASH_PERIOD);
   ObjectDelete(0,DASH_POSITIONS);
   ObjectDelete(0,DASH_PROFIT);
   
//--- Redraw the chart to reflect changes
   ChartRedraw();
} 

Hier erstellen wir eine ungültige Funktion „deleteDashboard“, rufen die Funktion ObjectDelete mit allen Objektnamen auf und zeichnen schließlich das Chart mit der Funktion ChartRedraw neu, damit die Änderungen wirksam werden. Wir rufen diese Funktion dann in der Deinitialisierungsfunktion auf. Auch hier müssen wir das Dashboard jedes Mal aktualisieren, wenn wir Positionen haben, um die richtigen Positionen und Gewinne anzuzeigen. Hier ist die Logik, die wir anwenden.

if (PositionsTotal() > 0){
   ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal()));
   ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
}

Hier wird geprüft, ob die Positionen über 0 liegen, d. h., wir haben eine Position und können ihre Eigenschaften aktualisieren. Hier ist das Ergebnis.

NICHT AKTUALISIERTE GEWINNE

Aus der Visualisierung geht hervor, dass das Dashboard nicht mehr aktualisiert wird, sobald die Positionen geschlossen sind. Daher müssen wir verfolgen, wann die Positionen geschlossen sind, und wenn es keine Positionen gibt, geben wir die Werte des Dashboards vor. Dazu benötigen wir die Ereignisbehandlung von OnTradeTransaction.

//+------------------------------------------------------------------+
//|    OnTradeTransaction function                                   |
//+------------------------------------------------------------------+
void  OnTradeTransaction(
   const MqlTradeTransaction&    trans,     // trade transaction structure 
   const MqlTradeRequest&        request,   // request structure 
   const MqlTradeResult&         result     // response structure 
){
   if (trans.type == TRADE_TRANSACTION_DEAL_ADD){
      Print("A deal was added. Make updates.");
      if (PositionsTotal() <= 0){
         ObjectSetString(0,DASH_POSITIONS,OBJPROP_TEXT,"Positions: "+IntegerToString(PositionsTotal()));
         ObjectSetString(0,DASH_PROFIT,OBJPROP_TEXT,"Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
      }
   }
}

Hier richten wir die Funktion OnTradeTransaction ein, die jedes Mal ausgelöst wird, wenn eine handelsbezogene Transaktion stattfindet. Diese Funktion verarbeitet Handelsereignisse und aktualisiert die relevanten Informationen auf dem Dashboard als Reaktion auf bestimmte Aktionen. Zunächst wird geprüft, ob der „Typ“ der Handelstransaktion, der durch den Parameter „trans“ vom Typ MqlTradeTransaction angegeben wird, gleich TRADE_TRANSACTION_DEAL_ADD ist. Diese Bedingung bestimmt, ob dem Konto ein neues Geschäft hinzugefügt wurde. Wenn eine solche Transaktion entdeckt wird, drucken wir die Meldung „A deal was added. Make updates.“ in das Protokoll zu Debugging- oder Informationszwecken.

Als Nächstes wird geprüft, ob die Gesamtzahl der offenen Positionen, die mit der Funktion PositionsTotal ermittelt wurde, kleiner oder gleich 0 ist. Dadurch wird sichergestellt, dass das Dashboard nur dann aktualisiert wird, wenn keine aktiven Positionen mehr im Konto vorhanden sind. Wenn die Bedingung erfüllt ist, verwenden wir die Funktion ObjectSetString, um zwei Beschriftungen auf dem Dashboard zu aktualisieren. Hier ist das Ergebnis.

STANDARDWERTE DES GEWINNS

Aus dem Bild ist ersichtlich, dass die Aktualisierungen bei jedem getätigten Geschäft wirksam werden. Damit ist unser Ziel erreicht, und es bleibt nur noch, das Programm einem Backtest zu unterziehen und seine Leistung zu analysieren. Dies wird im nächsten Abschnitt behandelt.


Backtests

Nach einem gründlichen Backtest haben wir folgende Ergebnisse.

Backtest-Grafik:

GRAPH

Backtest-Bericht:

BERICHT

Hier ist auch ein Videoformat, das den gesamten Backtest der Strategie über einen Zeitraum von 1 Jahr, 2024, zeigt.


Schlussfolgerung

Abschließend haben wir gezeigt, wie man einen robusten MQL5 Expert Advisor (EA) entwickelt, der technische Indikatoren, automatisches Handelsmanagement und ein interaktives Dashboard integriert. Durch die Kombination von Instrumenten wie das Kreuzen von Moving Average, Relative Strength Index (RSI) und Trailing Stops mit Funktionen wie dynamischen Handelsaktualisierungen, Trailing Stops und einer nutzerfreundlichen Oberfläche haben wir einen EA geschaffen, der in der Lage ist, Signale zu generieren, Handelsgeschäfte zu verwalten und Echtzeit-Einsichten für eine effektive Entscheidungsfindung zu liefern.

Haftungsausschluss: Dieser Artikel ist nur für Bildungszwecke gedacht. Der Handel ist mit erheblichen finanziellen Risiken verbunden, und die Marktbedingungen können unvorhersehbar sein. Die erörterten Strategien bieten zwar einen strukturierten Rahmen, doch sind die Ergebnisse der Vergangenheit keine Garantie für zukünftige Ergebnisse. Gründliche Tests und ein angemessenes Risikomanagement sind vor dem Echtbetrieb unerlässlich.

Durch die Anwendung dieser Konzepte können Sie anpassungsfähigere Handelssysteme aufbauen und Ihre algorithmischen Handelsstrategien verbessern. Viel Spaß beim Kodieren und ein erfolgreicher Handel!

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17040

Letzte Kommentare | Zur Diskussion im Händlerforum (2)
1149190
1149190 | 17 Feb. 2025 in 21:33
Ich kann die rückgetesteten Ergebnisse für AUDUSD über den Zeitraum von 2024 nicht replizieren. Die Ergebnisse, die ich bekomme, ist viel schlechter. Ich überprüfte und meine Input-Parameter scheint identisch zu sein, was in dem Tutorial-Video verwendet wurde. Irgendwelche Ideen, warum meine Ergebnisse nicht bis zu binden, was Sie in dem Artikel haben?
Allan Munene Mutiiria
Allan Munene Mutiiria | 18 Feb. 2025 in 13:01
1149190 rückgetesteten Ergebnisse für AUDUSD über den Zeitraum von 2024 nicht replizieren. Die Ergebnisse, die ich bekomme, ist viel schlechter. Ich überprüfte und meine Input-Parameter scheint identisch zu sein, was in dem Tutorial-Video verwendet wurde. Irgendwelche Ideen, warum meine Ergebnisse nicht mit denen im Artikel übereinstimmen?

Hallo. Das Video zeigt alles, von der Kompilierung über den Testzeitraum bis hin zu den verwendeten Eingabeparametern.

JSON beherrschen: Erstellen Sie Ihren eigenen JSON-Reader in MQL5 von Grund auf JSON beherrschen: Erstellen Sie Ihren eigenen JSON-Reader in MQL5 von Grund auf
Erleben Sie eine Schritt-für-Schritt-Anleitung zur Erstellung eines nutzerdefinierten JSON-Parsers in MQL5, komplett mit Objekt- und Array-Handling, Fehlerprüfung und Serialisierung. Gewinnen Sie praktische Einblicke in die Verknüpfung Ihrer Handelslogik mit strukturierten Daten mit dieser flexiblen Lösung für den Umgang mit JSON in MetaTrader 5.
Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 10): External Flow (II) VWAP Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 10): External Flow (II) VWAP
Meistern Sie die Macht des VWAP mit unserem umfassenden Leitfaden! Lernen Sie, wie Sie mit MQL5 und Python die VWAP-Analyse in Ihre Handelsstrategie integrieren können. Optimieren Sie Ihre Markteinblicke und verbessern Sie Ihre Handelsentscheidungen noch heute.
Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 11): Heikin Ashi Signal EA Entwicklung eines Toolkit zur Analyse von Preisaktionen (Teil 11): Heikin Ashi Signal EA
MQL5 bietet unendlich viele Möglichkeiten, automatisierte Handelssysteme zu entwickeln, die auf Ihre Wünsche zugeschnitten sind. Wussten Sie, dass er sogar komplexe mathematische Berechnungen durchführen kann? In diesem Artikel stellen wir die japanische Heikin Ashi Technik als automatisierte Handelsstrategie vor.
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 5): Selbstanpassende Handelsregeln Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 5): Selbstanpassende Handelsregeln
Die besten Praktiken, die festlegen, wie ein Indikator sicher zu verwenden ist, sind nicht immer leicht zu befolgen. Bei ruhigen Marktbedingungen kann der Indikator überraschenderweise Werte anzeigen, die nicht als Handelssignal gelten, was dazu führt, dass algorithmischen Händlern Chancen entgehen. In diesem Artikel wird eine mögliche Lösung für dieses Problem vorgeschlagen, da wir erörtern, wie Handelsanwendungen entwickelt werden können, die ihre Handelsregeln an die verfügbaren Marktdaten anpassen.