English 日本語
preview
Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung

Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung

MetaTrader 5Tester |
67 8
Hlomohang John Borotho
Hlomohang John Borotho

Einführung

Eine der größten Herausforderungen im professionellen Handel besteht darin, die Konsistenz des Portfolios und robuste Risikomanagementprotokolle aufrechtzuerhalten. Händler verlassen sich oft zu sehr auf einzelne Vermögenswerte oder Strategien, wodurch sie bei plötzlichen Veränderungen der Marktbedingungen ein erhöhtes Risiko für erhebliche Verluste eingehen. Verstärkt wird dieses Risiko durch die weit verbreitete Tendenz, korrelierte Instrumente übermäßig zu nutzen, was die Wahrscheinlichkeit gleichzeitiger Verluste erhöht und die Stabilität der Erträge untergräbt. Ohne ein streng diversifiziertes und optimiertes Portfolio sind Händler mit erratischen Leistungsergebnissen konfrontiert, die oft zu emotionsgesteuerten Entscheidungen und volatiler Rentabilität führen. Ein systematischer Rahmen, der die risikoangepassten Renditen über ein Spektrum unkorrelierter Anlagen strategisch ausgleicht, ist daher für eine nachhaltige langfristige Performance unerlässlich.

Um diese Herausforderungen zu bewältigen, bietet eine quantitative Methodik, die Portfoliooptimierung, Multi-Asset-Diversifizierung und eine Breakout-Handelsstrategie, die durch eine oszillatorbasierte Bestätigung ergänzt wird, eine robuste Lösung. Durch den Einsatz einer Ausbruchsstrategie über mehrere Währungspaare hinweg können Händler von hochwahrscheinlichen, momentumgetriebenen Kursbewegungen profitieren und gleichzeitig das Risiko durch ein Engagement in nicht korrelierten Märkten streuen. Die Integration eines Oszillator-Indikators dient der Validierung von Einstiegssignalen, der Minimierung von Fehlausbrüchen und der Einschränkung unproduktiver Handelsgeschäfte. Dieser Ansatz erhöht nicht nur das Gewinnpotenzial, sondern stärkt auch die Stabilität des Portfolios, indem er systematisch Chancen in unterschiedlichen Marktphasen nutzt. Die daraus resultierende Strategie weist eine erhöhte Volatilitätsresistenz auf und gewährleistet eine konsistente Anpassung der Performance an sich verändernde makroökonomische und technische Bedingungen.



Expertenlogik

Kaufmodell:

Der Expert Advisor beginnt mit der Berechnung der Preisspanne zwischen 10:00 und 12:00 Uhr UTC+2 und ermittelt das höchste Hoch und das tiefste Tief innerhalb dieses Zeitrahmens. Bricht der Kurs um 12:00 Uhr oder zu einem späteren Zeitpunkt über den zuvor festgelegten Höchststand aus, wird eine potenzielle Kaufgelegenheit ausgelöst. Zur Bestätigung muss der Stochastik Oszillator jedoch bei oder unter 20 liegen, was auf einen überverkauften Marktzustand hinweist. Dadurch wird sichergestellt, dass der Ausbruch nicht überzogen ist und noch Raum für eine Aufwärtsdynamik hat. Sobald beide Bedingungen erfüllt sind, führt der EA ein Kaufgeschäft aus, wobei er versucht, den Ausbruch zu nutzen und gleichzeitig falsche Signale durch oszillatorbasierte Filterung zu minimieren.

Verkaufsmodell:

Für das Verkaufsmodell folgt der Expert Advisor demselben Berechnungsprozess für die Zeitspanne von 10:00 bis 12:00 Uhr UTC+2 und ermittelt das Hoch und Tief der Sitzung. Wenn der Kurs um 12:00 Uhr oder später unter das festgestellte Tief fällt, wird eine Verkaufsmöglichkeit in Betracht gezogen. Der Handel wird jedoch nur ausgeführt, wenn der Stochastik Oszillator bei oder über 80 liegt, was auf einen überkauften Marktzustand hinweist. Dadurch wird sichergestellt, dass der Ausbruch nach unten durch potenziellen Verkaufsdruck und nicht durch eine falsche Bewegung unterstützt wird. Sobald beide Bedingungen zusammentreffen, geht der EA in einen Verkaufshandel über, wobei er das bärische Momentum ausnutzt und einen verfrühten Einstieg vermeidet.

Erste Schritte

//+------------------------------------------------------------------+
//|                                                      Dyna MP.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

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

enum Signal_Breakout{
   Normal_Signal,
   Reversed_Signal,
};

„#include <Trade/Trade.mqh>“ Diese Zeile bindet die MQL5 Trade-Bibliothek ein. Die Header-Datei „Trade.mqh“ definiert Klassen und Funktionen, die Handelsaufgaben wie das Einleiten, Anpassen oder Schließen von Aufträgen rationalisieren. Hier wird ein Objekt namens „trade“ aus der Klasse „CTrade“ instanziiert. Diese Klasse enthält Methoden zur strukturierten Ausführung von Handelsgeschäften (z. B. Übermittlung von Kauf-/Verkaufsaufträgen, Änderung offener Geschäfte oder Schließen von Positionen).

input group "--------------General Inputs--------------"
input string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY";
input Signal_Breakout BreakOutMode = Reversed_Signal;
input double  In_Lot = 0.01;
input int TakeProfit = 500;
 double StopLoss = 500;
input bool TrailYourStop = false;
input int TrailingStop = 50;

// Stochastic
input int KPeriod = 21;
input int upprer_level = 80;

„Symbol“ gibt die zu handelnden Vermögenswerte an (z. B. „XAUUSD, GBPUSD“), „In_Lot“ legt die feste Handelsgröße (0,01 Lots) fest, „Take Profit“ (500 Punkte) und „Stop Loss“ (1000 Punkte) definieren Gewinnmitnahmen und Risikolimits, während „TrailYourStop“ und „TrailingStop“ (70 Punkte) die dynamische Nachführung des Stop-Loss aktivieren und anpassen. Für den Stochastik Oszillator bestimmt „KPeriod“ (20) den Berechnungszeitraum für die Prozentlinie, und „upper_level“ (80) markiert die Überkauft-Schwelle. Diese Inputs sorgen für ein Gleichgewicht zwischen den Regeln für die Handelsausführung, dem Risikomanagement und der Erzeugung technischer Signale.

//+------------------------------------------------------------------+
//|                           Global vars                            |
//+------------------------------------------------------------------+
int handles[];
double bufferM[];


int RangeStart = 600; 
int RangeDuration = 120;
int RangeClose = 1200;

int Num_symbs = 0;
string symb_List[];
string Formatted_Symbs[];

In diesem Abschnitt werden globale Variablen definiert. Das Array „handles“ speichert Handles für den Stochastischen Oszillator, die es dem EA ermöglichen, mehrere Assets effizient zu verwalten. Das Array „Puffer“ speichert berechnete Werte, wie z. B. Indikatorwerte oder historische Preisniveaus. Die Variable „RangeStart“ wird auf 600 Minuten (10:00 AM UTC+2) gesetzt, was den Beginn der Bereichsmessung markiert, während „RangeDuration“ auf 120 Minuten gesetzt wird, was einen 2-Stunden-Zeitraum für die Erfassung des Höchst- und Tiefstwertes definiert. Sobald die „RangeClose“-Zeit von 1200 Minuten (12:00 PM UTC+2) erreicht ist, hört der EA auf, den Bereich zu berechnen und beginnt mit der Überwachung auf Ausbruchsbedingungen.

Für die Symbolverwaltung enthält das Array „Num_symbs“ die Rohliste der zu verarbeitenden Symbole. Zusätzlich enthält das Array „Formmated_Symbs“ die Gesamtzahl der Symbole, die nach dem Parsen der „Symbols“-Eingabe verwendet werden. Zusätzlich enthält das Array „Formatted_Symbs“ die Gesamtzahl der Symbole, die nach dem Parsen der „Symbols“-Eingabe verwendet werden. Diese Variablen ermöglichen dem EA die dynamische Ausführung von Handelsgeschäften mit mehreren Vermögenswerten und gewährleisten gleichzeitig eine präzise Berechnung der Handelsspanne zur Erkennung von Ausbrüchen.

//+------------------------------------------------------------------+
//|                    Ranger Global Vars                            |
//+------------------------------------------------------------------+
struct RANGER{
   datetime start_time;
   datetime end_time;
   datetime close_time;
   double high;
   double low;
   bool b_entry;
   bool b_high_breakout;
   bool b_low_breakout;
   
   RANGER() : start_time(0), end_time(0), close_time(0), high(0), low(999999), b_entry(false), b_high_breakout(false), b_low_breakout(false) {};
};

Die Struktur „Ranger“ speichert und verwaltet die wichtigsten Datenpunkte für die Bereichsausbruchstrategie. Sie definiert einen strukturierten Weg, um die Zeitgrenzen, Kursniveaus und Ausbruchsbedingungen der Spanne zu verfolgen. Die Variablen „start_time“, „end_time“ und „close_time“ stellen den Start-, End- und Schlusszeitpunkt der Bereichsberechnung dar und stellen sicher, dass der EA das Ausbruchsfenster korrekt identifiziert. Die Variablen „high“ und „low“ speichern die höchsten und niedrigsten Preise, die innerhalb des Bereichs aufgezeichnet wurden, wobei „low“ auf einen sehr hohen Wert (999999) initialisiert wird, um genaue Preisaktualisierungen zu gewährleisten. „low (999999)“ ist ein Standardmuster zur Ermittlung von Mindestwerten in Preisreihen.

Zusätzlich gibt es drei boolesche Flags. Der Wert „b_entry“ zeigt an, ob ein Handelseinstieg durchgeführt wurde, und verhindert, dass mehrere Handelsgeschäfte innerhalb desselben Ausbruchsereignisses platziert werden. b_high_breakout“ signalisiert, ob der Kurs über den Höchststand des Bereichs ausgebrochen ist, was ein potenzielles Kauf-Setup bestätigt, und „b_low_breakout“ zeigt an, ob der Kurs unter den Tiefststand des Bereichs gebrochen ist, was ein Verkaufs-Setup bestätigt. Der Konstruktor „Ranger()“ initialisiert alle Werte auf Standardwerte, um sicherzustellen, dass die Struktur mit sauberen Daten beginnt, bevor sie während des Live-Handels dynamisch aktualisiert wird.

RANGER rangeArray[];
MqlTick prevTick[], currTick[];

Diese Variablen spielen eine entscheidende Rolle bei der Verfolgung der Kursentwicklung und der Verwaltung der Bereichsausbruchslogik im Expert Advisor. „rangeArray“ ist ein Array der Strukturen „RANGER“, in dem mehrere Instanzen der definierten Bereichsausbruchsparameter gespeichert werden. Dadurch kann der EA mehrere Symbole gleichzeitig verfolgen und sicherstellen, dass jedes Symbol über seine eigenen Bereichsdaten verfügt, einschließlich Start- und Endzeiten, Höchst- und Tiefstpreisen sowie Ausbruchsbedingungen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   string separator = ",";
   ushort usprtr;
   usprtr = StringGetCharacter(separator, 0);
   StringSplit(Symbols, usprtr, symb_List);
   Num_symbs = ArraySize(symb_List);
   ArrayResize(Formatted_Symbs, Num_symbs);
   
   for(int i = 0; i < Num_symbs; i++){
      Formatted_Symbs[i] = symb_List[i];
   }
   
   ArrayResize(rangeArray, Num_symbs);
   ArrayResize(prevTick, Num_symbs);
   ArrayResize(currTick, Num_symbs);
   ArrayResize(handles, Num_symbs);   
   ArraySetAsSeries(bufferM, true);
   
   // Calculate initial ranges for each symbol
   for (int i = 0; i < ArraySize(Formatted_Symbs); i++) {
      CalculateRange(i, Formatted_Symbs[i]);  // Pass the symbol index
      handles[i] = iStochastic(Formatted_Symbs[i], PERIOD_CURRENT, KPeriod, 1, 3, MODE_SMA, STO_LOWHIGH);
      
      if(handles[i] == INVALID_HANDLE){
         Alert("Failed to create indicator handle");
         return INIT_FAILED;
      }
      
      StopLoss = SymbolInfoDouble(Formatted_Symbs[i], SYMBOL_POINT)*TrailingStop;
   }
   return(INIT_SUCCEEDED);
}

Die Funktion „OnInit“ ist für die Initialisierung und Einrichtung von Schlüsselvariablen, die Behandlung von Symbolen und die Vorbereitung der erforderlichen Datenstrukturen zuständig. Wir verwenden weiterhin das Komma „ , “ als Trennzeichen, um die Symbolliste in einzelne handelbare Werte aufzuteilen, die in „symb_List“ gespeichert werden. Wir bestimmen die Gesamtzahl der Symbole „Num_symbs“ und passen dann die Größe des Arrays „Formatted_Symbs“ entsprechend an, um sicherzustellen, dass jedes Symbol richtig formatiert und für den Handel zugänglich ist.

Als Nächstes passt der EA die Größe mehrerer Arrays dynamisch an, darunter „rangeArray“ zum Speichern von Range-Breakout-Daten, „prevTick“ und „currTick“ zum Verfolgen von Preisaktualisierungen, „handles“ für den Stochastik-Oszillator und „bufferM“ für Indikatorwerte, um sicherzustellen, dass alle erforderlichen Datenstrukturen ordnungsgemäß zugewiesen werden, bevor der EA mit der Ausführung beginnt.

Sobald die Arrays eingerichtet sind, führt der EA eine Schleife durch jedes Symbol in „Formatted_Symbs“ durch, um den anfänglichen Bereich mit der Funktion „CalculateRange()“ zu berechnen. Außerdem wird für jedes Symbol das Handle des Stochastik-Oszillators (istochastic) erstellt, den wir später zur Bestätigung von Ausbrüchen verwenden werden, indem wir überkaufte und überverkaufte Bedingungen überprüfen. Wenn das Handle des Indikators nicht initialisiert werden kann (INVALID_HANDLE), löst der EA eine Warnung aus und beendet sich mit dem Status INIT_FAILED. Zusätzlich berechnen wir den Stop Loss für jedes Symbol auf der Grundlage seines Punktwertes und eines Trailing-Stop-Parameters. Wenn alles erfolgreich initialisiert wurde, gibt die Funktion INIT_SUCCEEDED zurück, sodass der EA mit der Überwachung der Kursbewegungen und der Ausführung von Handelsgeschäften fortfahren kann.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      if(handles[i] != INVALID_HANDLE){
         IndicatorRelease(handles[i]);
      }   
   }
}

Die Funktion OnDeinit() ist die Deinitialisierungsfunktion des Expert Advisors (EA), die für die ordnungsgemäße Freigabe von Ressourcen verantwortlich ist, wenn der EA aus dem Chart entfernt wird oder nicht mehr läuft.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
 
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      prevTick[i] = currTick[i];
      SymbolInfoTick(symbol, currTick[i]);
      
      // Range Cal
      if(currTick[i].time > rangeArray[i].start_time && currTick[i].time < rangeArray[i].end_time){
         // flag
         rangeArray[i].b_entry = true;
         
         // high
         if(currTick[i].ask > rangeArray[i].high){
            rangeArray[i].high = currTick[i].ask;
         }
         
         // low
         if(currTick[i].bid < rangeArray[i].low){
            rangeArray[i].low = currTick[i].bid;
         }
      }
            
      // now calculate range
      if(((RangeClose >= 0 && currTick[i].time >= rangeArray[i].close_time)
         || (rangeArray[i].b_high_breakout && rangeArray[i].b_low_breakout)
         || (rangeArray[i].end_time == 0)
         || (rangeArray[i].end_time != 0 && currTick[i].time > rangeArray[i].end_time && !rangeArray[i].b_entry))){
         CalculateRange(i, Formatted_Symbs[i]);
      }
      checkBreak(i, Formatted_Symbs[i]);
   }
   
}

Die Funktion „OnTick“ ist die zentrale Ausführungsschleife des Expert Advisors (EA), die bekanntlich bei jedem neuen Tick-Update ausgeführt wird. Es verwaltet die Live-Kursanalyse, passt Range-Parameter an und bewertet Ausbruchsmöglichkeiten für alle konfigurierten Handelsinstrumente. Der Prozess beginnt mit der sequentiellen Auswertung jedes Symbols in der Liste „Formatted_Symbs“. Für jedes Symbol werden die aktuellen Tickdaten in einem historischen Puffer (prevTick) archiviert und der jüngste Marktstatus über „SymbolInfoTick“ abgefragt.

Während des vordefinierten aktiven Handelsfensters (Start_Zeit bis End_Zeit) verfolgt das System dynamisch Kursextreme. Es erhöht den Höchstwert der Sitzung anhand des Hochs mit dem Ask und verringert das Tief der Sitzung anhand des Tiefs von Bid. Wenn die aktuelle Zeit in diesen Bereich fällt, wird das b_entry-Flag aktiviert, wodurch der Bereich als gültig für den Handel gekennzeichnet wird.

Nach dem Handelsfenster wertet die Logik drei Erneuerungsauslöser aus:

  1. Geplantes Sitzungsende „close_time“.
  2. Gleichzeitiges Auftreten von hohen und tiefen Ausbrüchen.
  3. Ungültige oder abgelaufene „end_time“-Zeitstempel.
Wenn eine der Bedingungen erfüllt ist, werden die Parameter für den Bereich „CalculateRange()“ neu berechnet. Der Zyklus endet mit dem Aufruf von „checkBreak()“, der nach Kursdurchbrüchen jenseits der Range-Grenzen sucht und entsprechende Handelsgeschäfte einleitet. Dieser Rahmen ermöglicht eine kontinuierliche Marktüberwachung und eine strategische Auftragsplatzierung, die auf die Ausbruchsdynamik abgestimmt ist und die Reaktion auf Preisbewegungen in Echtzeit gewährleistet.
//+------------------------------------------------------------------+
//|                  Range Calculation function                      |
//+------------------------------------------------------------------+
void CalculateRange(int index, string symbol) {
   for(index = 0; index < ArraySize(Formatted_Symbs); index++){
      symbol = Formatted_Symbs[index];
      
      // Reset all the range variables
      rangeArray[index].start_time = 0;
      rangeArray[index].end_time = 0;
      rangeArray[index].close_time = 0;
      rangeArray[index].high = 0.0;
      rangeArray[index].low = 999999;
      rangeArray[index].b_entry = false;
      rangeArray[index].b_high_breakout = false;
      rangeArray[index].b_low_breakout = false;
      
      // Calculate range start time
      int time_cycle = 86400;
      rangeArray[index].start_time = (currTick[index].time - (currTick[index].time % time_cycle)) + RangeStart * 60;
      for(int i = 0; i < 8; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].start_time, tmp);
         int dotw = tmp.day_of_week;
         if(currTick[index].time >= rangeArray[index].start_time || dotw == 6 || dotw == 0){
            rangeArray[index].start_time += time_cycle;
         }
      }
   
      // Calculate range end time
      rangeArray[index].end_time = rangeArray[index].start_time + RangeDuration * 60;
      for(int i = 0 ; i < 2; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].end_time, tmp);
         int dotw = tmp.day_of_week;
         if(dotw == 6 || dotw == 0){
            rangeArray[index].end_time += time_cycle;
         }
      }
      
      // Calculate range close
      rangeArray[index].close_time = (rangeArray[index].end_time - (rangeArray[index].end_time % time_cycle)) + RangeClose * 60;
      for(int i = 0; i < 3; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].close_time, tmp);
         int dotw = tmp.day_of_week;
         if(rangeArray[index].close_time <= rangeArray[index].end_time || dotw == 6 || dotw == 0){
            rangeArray[index].close_time += time_cycle;
         }
      } 
      
   }
}

Die Funktion „CalculateRange“ initialisiert und konfiguriert zeitbasierte Handelsbereiche für mehrere Symbole. Für jedes Symbol im Array „Formatted_Symbs“ werden zunächst kritische Bereichsparameter - Start-/End-/Schlusszeiten, Höchst-/Tiefstpreisschwellen und Ausbruchsflags - auf Standardwerte zurückgesetzt. Die „Startzeit“ wird berechnet, indem die aktuelle Tickzeit an einer Tagesgrenze ausgerichtet wird (unter Verwendung eines 24-Stunden-Zeitzyklus) und dann um einen nutzerdefinierten „RangeStart“ (in Minuten) verschoben wird. Eine Schleife stellt sicher, dass die Startzeit die Wochenenden (Samstag/Sonntag) vermeidet und chronologisch gültig bleibt, indem der Zeitstempel bei Konflikten um ganze Tage erhöht wird. Dadurch wird eine Basislinie für das Handelsfenster geschaffen, wobei die Marktschließungszeiten eingehalten werden.

Nachdem die Startzeit des Bereichs festgelegt wurde, berechnet die Funktion die Endzeit des Bereichs, indem sie „RangeDuration“ zu „start_time“ addiert. Ähnlich wie bei der Berechnung der Startzeit wird auch hier sichergestellt, dass die Endzeit nicht auf ein Wochenende fällt, indem eine Validierungsschleife durchlaufen wird. Schließlich bestimmt die Funktion den Zeitpunkt des Bereichsschlusses, der den Punkt markiert, an dem der EA die Überwachung auf Ausbrüche beendet. Diese Zeit wird von „RangeClose“ abgeleitet und angepasst, um Wochenenden zu vermeiden. Durch die Beibehaltung dieser dynamischen Berechnungen stellt die Funktion sicher, dass der EA präzise Range-Trading-Bedingungen für verschiedene Symbole einrichtet und den Handel während der handelsfreien Zeiten und an Wochenenden vermeidet, während er gleichzeitig eine präzise Ausbruchserkennung gewährleistet.

bool CLots(double sl, double &lots){
   lots = In_Lot;

   if(!CHLots(lots)){return false;}
   return true;
}

Die Funktion „Lots()“ legt die Losgröße fest und validiert sie, bevor ein Handel ausgeführt wird. Sie benötigt zwei Parameter, nämlich Ihren Stop-Loss (sl) und Lots (eine Referenzvariable, die die endgültige Losgröße speichert).

bool CHLots(double &lots){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
      double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
      double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
      if(lots < min){
         lots = min;
         return true;
      }
      if(lots > max){
         return false;
      }
      
      lots = (int)MathFloor(lots / step)  * step;
      
   }
   return true;

}

Die Funktion „CHLots()“ überprüft die Losgrößen, um die maklerspezifischen Handelsregeln für jedes Instrument einzuhalten. Es durchläuft die Liste der Symbole in „Formatted_Symbs“ und extrahiert die vom Makler definierten Einschränkungen - minimale Losgröße, maximale Losgröße und zulässiger Inkrementschritt. Diese Parameter legen die operativen Grenzen für das Auftragsvolumen fest. Wenn der vorgeschlagene Wert der Losgröße unter den Mindestschwellenwert fällt, korrigiert die Funktion ihn automatisch auf das zulässige Mindestvolumen und bestätigt die Gültigkeit durch Rückgabe von true. Überschreitet die angeforderte Losgröße dagegen die Höchstgrenze des Brokers, bricht die Funktion die Anfrage ab, indem sie false zurückgibt, und blockiert so nicht konforme Handelsgeschäfte.

Um die Genauigkeit zu gewährleisten, erzwingt die Funktion die Schrittausrichtung durch Abrunden der Losgröße mit der Formel „MathFloor(MathFloor(lots/step) * step“. Dadurch werden gebrochene oder unregelmäßige Teilbeträge vermieden, die zu einer Ablehnung durch den Makler führen könnten. Wenn die Losgröße alle Bedingungen erfüllt, ohne dass Anpassungen erforderlich sind, gibt die Funktion den Wert „true“ zurück und bestätigt damit ihre Zulässigkeit. Durch die rigorose Durchführung dieser Prüfungen fungiert „CHLots()“ als kritische Sicherheitsmaßnahme, die eine Ablehnung von Aufträgen aufgrund von Volumenüberschreitungen verhindert und die operative Zuverlässigkeit des EA in Live-Handelsumgebungen stärkt.

//+------------------------------------------------------------------+
//|                      Check for Breakout                          |
//+------------------------------------------------------------------+
void checkBreak(int i, string symbol) {
   for (i = 0; i < ArraySize(Formatted_Symbs); i++) {
      symbol = Formatted_Symbs[i];
      
      //get indicator vals
      if(CopyBuffer(handles[i], 0, 1, 2, bufferM) != 2){
         Print("Failed to get indicator values");
         return;
      }

      int stopLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
      int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
      double Bid = SymbolInfoDouble(symbol, SYMBOL_BID);
      double Ask = SymbolInfoDouble(symbol, SYMBOL_ASK);

      if (currTick[i].time >= rangeArray[i].end_time && rangeArray[i].end_time > 0 && rangeArray[i].b_entry) {
         double rangeSize = rangeArray[i].high - rangeArray[i].low;

         // High Breakout (BUY/SELL)
         bool upperBreak = bufferM[0] >= upprer_level && bufferM[1] < upprer_level;
         bool lowerBreak = bufferM[0] <= (100 - upprer_level) && bufferM[1] > (100 - upprer_level);
         bool HighSigType,LowSigType;
         
         if(BreakOutMode == Normal_Signal){
            HighSigType = upperBreak;
         }else{HighSigType = lowerBreak;}
         if (!rangeArray[i].b_high_breakout && currTick[i].ask >= rangeArray[i].high && HighSigType) {
            rangeArray[i].b_high_breakout = true;

            double entry = NormalizeDouble(Ask + 100 * _Point, _Digits);
            double sl = rangeArray[i].low;
            //sl = NormalizeDouble(sl, true);
            double tp = entry + TakeProfit * _Point;

            double lots;
            if (!CLots(entry - sl, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, currTick[i].ask, sl, tp, "High Breakout"))
               Print("Buy Order Failed: ", GetLastError());
         }                                                                      
                                                                                
         if(BreakOutMode == Normal_Signal){
            LowSigType = upperBreak;
         }else{LowSigType = lowerBreak; }                                                                       
         // Low Breakout (SELL)
         if (!rangeArray[i].b_low_breakout && currTick[i].bid <= rangeArray[i].low  && LowSigType) {
            rangeArray[i].b_low_breakout = true;

            double entry = NormalizeDouble(Bid - 100 * _Point, _Digits);
            double sl = rangeArray[i].high;
            //sl = NormalizeDouble(sl,true);
            double tp = entry - TakeProfit * _Point;

            double lots;
            if (!CLots(sl - entry, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, currTick[i].bid, sl, tp, "Low Breakout"))
               Print("Sell Order Failed: ", GetLastError());
         }
      }
   }
}

Diese Funktion überwacht Kursausbrüche und steuert die Handelsausführung durch die Kombination von Kursbewegungen mit Signalen des Stochastik-Oszillators. Er durchläuft jedes Symbol in „Formatted_Symbs“ und versucht zunächst, die Werte des Oszillators über „CopyBuffer()“ abzurufen. Wenn diese Datenabfrage fehlschlägt, protokolliert die Funktion einen Fehler und bricht ab, um Fehlentscheidungen zu vermeiden. Für jedes Symbol werden wichtige Parameter wie Stop-Levels, Spreads und aktuelle Geld-/Briefkurse erfasst. Ausbruchsauswertungen erfolgen erst nach dem Ende der Range-Periode. Dies wird überprüft, indem geprüft wird, ob die aktuelle Zeit die „End_time“ der Zeitspanne überschreitet und das Flag „b_entry“ aktiv ist, um sicherzustellen, dass die Analyse auf gültige Handelsfenster beschränkt ist.

Bei Ausbrüchen nach oben (Kaufsignal) bestätigt die Funktion zwei Kriterien, nämlich dass der Briefkurs den Höchststand der Sitzung übersteigt und der Oszillator überverkaufte Bedingungen widerspiegelt (unter 100 - upper_level). Nach der Validierung wird ein Kaufauftrag ausgelöst, wobei der Einstiegspreis, der Stop-Loss (basierend auf der Volatilität der Spanne) und die Gewinnmitnahme berechnet und die Einhaltung der Losgröße mit Hilfe von „CLots()“ überprüft wird.

Umgekehrt muss für einen Ausbruch nach unten (Verkaufssignal) der Geldkurs unter das Tief der Sitzung fallen, während der Oszillator einen überkauften Zustand anzeigt (über dem oberen Niveau). Ist dies der Fall, wird ein Verkaufsauftrag mit analogen Risikoparametern generiert. Beide Szenarien beinhalten eine Fehlerprotokollierung für fehlgeschlagene Aufträge und sorgen so für Transparenz. Durch die Synchronisierung von Kursschwellen mit oszillatorbasierten Bestätigungen erzwingt die Funktion eine disziplinierte, kriteriengesteuerte Handelsausführung.

//+------------------------------------------------------------------+
//|                      Trailing Stoploss                           |
//+------------------------------------------------------------------+
void Trailler(){
   if(!TrailYourStop) return;
   
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      // Get position details
      string symbol = PositionGetString(POSITION_SYMBOL);
      long magic;
      if(!PositionGetInteger(POSITION_MAGIC, magic)) continue;
      if(magic != MagicNumber) continue;

      // Get current prices
      MqlTick latestTick;
      if(!SymbolInfoTick(symbol, latestTick)) continue;
      
      long type;
      double openPrice, currentSl, currentTp;
      PositionGetInteger(POSITION_TYPE, type);
      PositionGetDouble(POSITION_PRICE_OPEN, openPrice);
      PositionGetDouble(POSITION_SL, currentSl);
      PositionGetDouble(POSITION_TP, currentTp);
      
      // Calculate pip values
      double pipSize = 10 * SymbolInfoDouble(symbol, SYMBOL_POINT);
      double currentPrice = type == POSITION_TYPE_BUY ? latestTick.bid : latestTick.ask;
      double priceMove = MathAbs(currentPrice - openPrice);
      
      // Calculate required moves
      double requiredMove = 70 * pipSize; // 20 pips
      double trailAmount = 10 * pipSize;  // 10 pips
      
      // Calculate new stop loss
      double newSl = currentSl;
      bool inProfit = type == POSITION_TYPE_BUY ? 
                     (currentPrice > openPrice) : 
                     (currentPrice < openPrice);
      
      if(inProfit && priceMove >= requiredMove){
         int steps = int(priceMove / requiredMove);
         if(type == POSITION_TYPE_BUY){
             newSl = openPrice + (steps * trailAmount);
             newSl = MathMax(newSl, currentSl + trailAmount);
         }
         else{
             newSl = openPrice - (steps * trailAmount);
             newSl = MathMin(newSl, currentSl - trailAmount);
         }
      }
      
      // Validate and modify SL
      if(newSl != currentSl){
         // Check stop levels
         double minDist = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
         newSl = NormalizeDouble(newSl, _Digits);
         
         if(type == POSITION_TYPE_BUY && (currentPrice - newSl) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Buy Trailing Failed: ", GetLastError());
         }
         else if(type == POSITION_TYPE_SELL && (newSl - currentPrice) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Sell Trailing Failed: ", GetLastError());
         }
      }
   }
}

Die Funktion „Trailing Stop Loss“ (TSL) stellt sicher, dass der Stop-Loss automatisch angepasst wird, sobald sich ein Handel günstig entwickelt, um Gewinne zu sichern und gleichzeitig Risiken zu minimieren. Die Funktion „Trailer()“ berechnet und aktualisiert das Stop-Loss-Niveau auf der Grundlage der Kursbewegung und eines Prozentsatzes der Hoch-Tief-Spanne, wodurch ein vorzeitiger Ausstieg verhindert und gleichzeitig Gewinne gesichert werden.



Schlussfolgerung

Zusammenfassend lässt sich sagen, dass Portfolio-Optimierung und Diversifizierung wesentliche Strategien im Handel sind, die darauf abzielen, die Erträge zu maximieren und gleichzeitig das Risiko zu minimieren, indem die Anlagen auf mehrere Vermögenswerte verteilt werden. Herkömmliche Handelsmethoden konzentrieren sich oft auf Single-Pair-Strategien und setzen die Händler einer höheren Volatilität und marktspezifischen Risiken aus. Diversifizierung stärkt die Widerstandsfähigkeit des Portfolios, während Optimierungsmethoden das Kapital strategisch einsetzen, indem sie historische Performancemuster, Volatilitätsmetriken von Vermögenswerten und Beziehungen zwischen den Märkten analysieren, um ein effizientes Risiko-Ertrags-Verhältnis zu erreichen.

Zusammenfassend lässt sich sagen, dass die Einbeziehung von Portfolio-Optimierung und Diversifizierung in eine Handelsstrategie einen widerstandsfähigeren und anpassungsfähigeren Ansatz für Marktschwankungen bietet. Durch die Kombination einer Breakout-Handelsstrategie mit einem Oszillator-Indikator können Händler hochwahrscheinliche Setups identifizieren und gleichzeitig das Risiko dynamisch steuern. Dieser Ansatz erhöht nicht nur die Chancen auf eine konstante Rentabilität, sondern verbessert auch die langfristige Nachhaltigkeit, indem er die Inanspruchnahme der Mittel abmildert.

Um die folgenden Ergebnisse zu erzielen, wurde der Expert Advisor unter Verwendung des EURUSD als Basissymbol mit Null-Latenz-Verzögerungen, idealer Ausführung und Modellierung jedes Ticks für maximale Genauigkeit getestet. Der Testzeitraum reichte vom 01.02.2022 bis zum 22.03.2022. In den Eingabeeinstellungen wurde der BreakoutMode auf Reversed_signal gesetzt, und TrailYourStop wurde aktiviert (auf true gesetzt), um dynamische Stop-Loss-Anpassungen zu ermöglichen. Alle anderen Eingabeparameter wurden auf ihren Standardwerten belassen.

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

Beigefügte Dateien |
Dyna_MP.mq5 (37.35 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (8)
Alberto Tortella
Alberto Tortella | 19 Apr. 2025 in 13:48

Ok, ich habe EURUSD in Input/Symbols eingegeben und jetzt funktioniert es.

Dankeschön

CapeCoddah
CapeCoddah | 19 Apr. 2025 in 20:10

Toller Artikel! Ich werde es morgen ausprobieren. Mich interessiert, warum Sie einen so seltsamen Zeitraum für den Strategietester verwendet haben. Ich hätte volle Monate im Jahr 2024 erwartet. Mir gefällt Ihr Konzept des Trailing-Stop-Loss, ich verwende die gleiche Technik. Eine Neuerung, die ich eingeführt habe, besteht darin, dass ich auch versuche, den Verlust zu minimieren, wenn der Handel negativ wird, nachdem ich fast den Break-Even erreicht habe.


Prost und weiter so mit den Artikeln, sie sind großartig.

CapeCoddah

Brian Pereira
Brian Pereira | 23 Apr. 2025 in 08:07
Alberto Tortella stochastischen Oszillator funktioniert gut auf Grafik.


Können Sie mir helfen? Dankeschön

Jede Eingabewährung sollte nur durch ein Komma getrennt werden. Setzen Sie kein Leerzeichen zwischen die Währungen

CapeCoddah
CapeCoddah | 30 Apr. 2025 in 11:33

Hallo nochmal,


Ich habe versucht, Ihr System auf einem aktiven Chart zu verwenden und habe ein paar Verbesserungen gefunden


Albertos Problem war wahrscheinlich, dass er nicht alle Paare in der Symbolliste seines Marktbeobachtungsfensters hatte, ctlM. Ich hatte diesen Fehler auch mit XAUUSD

Anstelle von ArraySize(... für For-Anweisungen sollten Sie Num_symbls verwenden, das ist etwas schneller. Außerdem habe ich festgestellt, dass die Schreibweise von vollständigen Namen anderen hilft, Ihren Code besser zu verstehen und auch viele Syntaxfehler zu vermeiden, z.B. ist Number_Symbols meiner Meinung nach besser als Num_symbls.

DisplayObjects war nicht im Code enthalten, ich habe es hinzugefügt.

In DisplayObjects habe ich eine Bedingung hinzugefügt, um nur das Diagrammsymbol auszuwählen. Die Aufzählung der anderen ist nicht erforderlich und würde den Bildschirm überladen. Aber vielleicht übersehe ich etwas.

Schließlich gibt es ein Problem mit der Bereichsberechnung. Auf einem aktiven Chart, nicht im Strategietester, erzeugt das Starten des EA einen Strahl, der über das aktuelle Datum hinaus in der Zukunft liegt. Zum Beispiel erzeugt das Starten am 30.4. einen Strahl, der am 30.4. um 10 Uhr beginnt und am 1.5. endet. Dies führt zu einem nicht sichtbaren Strahl, der nicht auf dem Chart, aber in der Objektliste angezeigt wird. Ich lasse Sie dieses Problem lösen.

Ich hänge meinen Code an, damit Sie ihn verwenden können.


Vielen Dank, CapeCoddah

CapeCoddah
CapeCoddah | 30 Apr. 2025 in 13:02
Ich glaube, da ist etwas schief gelaufen, denn Teil 1 und Teil 2 sind identisch. Es sieht so aus, als wäre Teil 1 identisch mit Teil 2.
Vom Neuling zum Experten: Programmieren von Kerzen Vom Neuling zum Experten: Programmieren von Kerzen
In diesem Artikel machen wir den ersten Schritt in die MQL5-Programmierung, auch für absolute Anfänger. Wir zeigen Ihnen, wie Sie bekannte Kerzenmuster in einen voll funktionsfähigen nutzerdefinierten Indikator verwandeln können. Kerzenmuster sind wertvoll, da sie reale Kursbewegungen widerspiegeln und Marktverschiebungen signalisieren. Anstatt die Charts manuell zu scannen - ein Ansatz, der fehleranfällig und ineffizient ist - werden wir besprechen, wie Sie den Prozess mit einem Indikator automatisieren können, der Muster für Sie identifiziert und kennzeichnet. Auf dem Weg dorthin werden wir uns mit Schlüsselkonzepten wie Indexierung, Zeitreihen, Average True Range (für Genauigkeit bei schwankender Marktvolatilität) und der Entwicklung einer nutzerdefinierten, wiederverwendbaren Bibliothek von Kerzen-Mustern für den Einsatz in zukünftigen Projekten beschäftigen.
Entwicklung des Price Action Analysis Toolkit (Teil 21): Das Tool Market Structure Flip Detector Entwicklung des Price Action Analysis Toolkit (Teil 21): Das Tool Market Structure Flip Detector
Der Market Structure Flip Detector Expert Advisor (EA) agiert als Ihr aufmerksamer Partner, der ständig die Veränderungen der Marktstimmung beobachtet. Durch die Verwendung von Average True Range (ATR)-basierten Schwellenwerten erkennt es effektiv Strukturumkehrungen und kennzeichnet jedes höhere Tief und niedrigere Hoch mit klaren Indikatoren. Dank der schnellen Ausführung und der flexiblen API von MQL5 bietet dieses Tool eine Echtzeitanalyse, die die Anzeige für eine optimale Lesbarkeit anpasst und ein Live-Dashboard zur Überwachung der Anzahl und des Timings von Flips bereitstellt. Darüber hinaus sorgen anpassbare Ton- und Push-Benachrichtigungen dafür, dass Sie über kritische Signale informiert bleiben, sodass Sie sehen können, wie einfache Eingaben und Hilfsroutinen Kursbewegungen in umsetzbare Strategien verwandeln können.
Automatisieren von Handelsstrategien in MQL5 (Teil 15): Price Action Harmonic Cypher Pattern mit Visualisierung Automatisieren von Handelsstrategien in MQL5 (Teil 15): Price Action Harmonic Cypher Pattern mit Visualisierung
In diesem Artikel befassen wir uns mit der Automatisierung des harmonischen Cypher-Musters in MQL5 und erläutern seine Erkennung und Visualisierung auf MetaTrader 5-Charts. Wir implementieren einen Expert Advisor, der Umkehrpunkte identifiziert, Fibonacci-basierte Muster validiert und Handelsgeschäfte mit klaren grafischen Kommentaren ausführt. Der Artikel schließt mit einer Anleitung zu den Backtests und zur Optimierung des Programms für einen effektiven Handel.
Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups
Hochwahrscheinliche Setups sind in unserer Trading-Community gut bekannt, aber leider sind sie nicht gut definiert. In diesem Artikel wollen wir einen empirischen und algorithmischen Weg finden, um genau zu definieren, was ein Hochwahrscheinlichkeits-Setup ist, und um diese zu identifizieren und auszunutzen. Durch die Verwendung von Gradient Boosting Trees haben wir gezeigt, wie der Leser die Leistung einer beliebigen Handelsstrategie verbessern und unserem Computer die genaue Aufgabe auf sinnvollere und explizitere Weise mitteilen kann.