MTF-Indikatoren als Werkzeuge der technischen Analyse

Alexander Lasygin | 16 Mai, 2019

Einführung

Die meisten Händler sind sich einig, dass eine Zustandsanalyse des aktuellen Marktes mit der Bewertung höherer Zeitrahmen beginnt. Die Analyse wird nach unten auf niedrigere Zeitrahmen bis zu demjenigen durchgeführt, in dem der Handel durchgeführt wird. Diese Analysemethode scheint ein obligatorischer Bestandteil des professionellen Ansatzes für einen erfolgreichen Handel zu sein. Um eine MTF-Analyse durchzuführen, müssen Nutzer oft mehrere Fenster öffnen oder zwischen den Charts wechseln, wenn derselbe Satz von Instrumenten verwendet wird. Somit wird die Voranalyse durchgeführt.

Was ist als Nächstes zu tun? Muss man ältere Zeiträume ignorieren oder immer wieder zwischen Fenstern und Zeitrahmen wechseln? Wenn der Arbeitszeitrahmen Н1 oder höher ist, hat der Benutzer Zeit für eine sorgfältige Bewertung der allgemeinen Situation. Bei der Arbeit an den Zeitfenstern M1-M15 bleibt jedoch keine Zeit für die Überprüfung der erforderlichen Informationen aus höheren Zeitrahmen. Diese Informationen können manchmal sehr wichtig sein. Außerdem sollten die Daten hier und jetzt verfügbar sein, nicht auf einer anderen Registerkarte oder irgendwo anders. Dies ist besonders wichtig für MTF-Strategien, die auf der gleichzeitigen Auswertung verschiedener Zeitrahmen (TF) basieren, wie z.B. die Wolfe-Wellen oder das System Elders Drei Bildschirme.

So befinden sich Händler, die diese Strategien nutzen, oft im Zustand der psychischen Belastung. Wenn man auf einem niedrigeren TFs handelt, kann man profitable Signale der höheren verpassen. Dies kann zu unsauberen Entscheidungen, vorzeitiger Schließung von Positionen oder dem Verpassen von Marktumkehrpunkten führen. Die Folgen können sehr beunruhigend sein. In diesen Situationen kann sich der Benutzer nur auf seine Erfahrung oder seine Zugriffsgeschwindigkeit auf Informationen verlassen.

Die Lösung dieses Problems ist ein Indikator, der Informationen aus verschiedenen Zeiträumen oder mehreren Handelssymbolen erhält und die umfangreichen Daten auf dem Bildschirm anzeigt, so dass der Anwender den Marktzustand effizient beurteilen kann. Neben den Basisinformationen können diese Indikatoren den tatsächlichen Zustand der Marktentwicklung darstellen und weitere Handelsaktivitäten empfehlen.

Merkmale des Algorithmus

Der wesentliche Unterschied zu den klassischen Indikatoren besteht darin, dass MTF-Indikatoren verallgemeinerte Informationen aus allen Zeitintervallen oder Finanzinstrumenten verarbeiten und dann Daten an das aktuelle Chart übergeben können. Jeder Indikatortyp (Oszillator, Trend, Lautstärke, etc.) oder eine Kombination davon kann mit mehrere Zeitrahmen arbeiten. Sie werden nach ihrem Grundalgorithmus berechnet und geben Informationen gemäß dem in den Einstellungen festgelegten Zeitintervall weiter.

Für MTF-Versionen im Vergleich zu klassischen Versionen ist keine spezielle Konfiguration erforderlich, mit Ausnahme des zusätzlichen Parameters (oder der Gruppe von Parametern) zur Angabe von Zeitintervallen oder der Liste von Symbolen zur Verwendung von Daten. Das Ergebnis kann im Hauptdiagrammfenster oder separat ausgegeben und als Signal angezeigt werden, während Gruppen je nach Gerätetyp kombiniert werden.

Klassifizierung von Indikatoren mit mehreren Zeitrahmen

Diese Indikatoren werden in allen Standardklassen dargestellt, während die meisten von ihnen vom komplexen Typ sind, d.h. Berechnungen und grafische Elemente kombinieren. Die Indikatorengruppen sind nachfolgend dargestellt:

1. Informativ: Solche Indikatoren zeigen Daten und Zusatzinformationen ohne Signale oder Grafiken an. Ein klassisches Beispiel ist der Indikator MultiTimeFrame. Es zeigt die Schließzeit der Kerze für jeden Zeitrahmen, Ask und Bid für die ausgewählten Währungspaare, den Status der Kerze (Auf-, Abwärts, DOJI) und das Volumen. Der Chart wird vom Indikator mit einer großen Menge nützlicher Informationen gefüllt, die jedoch im Handel schwer zu verwenden sind und nur angesehen werden können.

Abb. 1. Informativer Indikator

Diese Gruppe verfügt auch über unabhängige Hilfen, mit denen Handelsentscheidungen getroffen werden können. Sie zeigen die Analyseergebnisse mehrerer Standardindikatoren an, ohne dass solche Indikatoren in einem Chart installiert werden müssen (die Berechnung erfolgt automatisch), und generieren Handelsempfehlungen.


Abb. 2. Signale eines informativen Indikators



Abb. 3. Signale eines informativen Indikators

2. Grafische Indikatoren zeigen Grafiken desselben Indikators auf verschiedenen TFs. Hier ist es der standardmäßige Envelope MA(13) aus drei verschiedenen Zeitrahmen.

Abb. 4. Grafische Indikatoren

Eine weitere Art der grafischen Konstruktion ist die Gruppe von Charts mit unterschiedlichem Berechnungszeitraum. Dieser Ansatz basiert auf reiner Mathematik. D.h. der stochastische Indikator (5.3.3) hat auf M5 die Parameter von (15.3.9) aus M15 und (30.3.18) aus M30.

Abb. 5. Grafische Indikatoren mit unterschiedlichen Berechnungszeiträumen

Die obige Version kann unter Vorbehalt auf eine MTF-Klasse verweisen. Es ist nicht immer möglich, diesen Ansatz umzusetzen, während die Nachteile dieser Lösung so groß sein können, dass ihre Anwendung unangemessen wäre. Wir werden auf Fälle eingehen, in denen das Verfahren anwendbar ist, sowie danach auf die Vor- und Nachteile.

Eine separate Gruppe besteht aus den so genannten Signalindikatoren. Um zu viele grafische Elemente auf dem Arbeitsbereich zu vermeiden, bildet der Indikator Signallinien oder Grafikblöcke, die die Trendrichtung oder andere Parameter widerspiegeln. Siehe den standardmäßigen Indikator MTF_Coral mit der oben genannten MTF-Lösung:

Abb. 6. Signalindikatoren

Eine andere Gruppe kann "Fenster in einem Fenster" genannt werden. Charts mit unterschiedlichen Zeitrahmen oder Indikatoren werden im Hauptchartfenster angezeigt.


Abb. 7. "Fenster im Fenster" Indikatoren


Abb. 7.1. Ein "Fenster im Fenster" Indikatoren

Einweiteres Beispiel ist All_Woodies CCI.


Abb. 7.1. "Fenster im Fenster" Indikator All_Woodies CCI


Eine separate Gruppe beinhaltet MTF-Volatilitätsindikatoren. Dazu gehören auch MTF-Kerzen.

Abb. 8. Volatilitätsindikator MTF Kerzen

Implementierungsmethoden

Wir haben die wichtigsten Arten von MTF-Indikatoren betrachtet. Betrachten wir nun einfache Beispiele, die die wichtigsten Methoden für die lineare Implementierung zeigen. Wir werden auch die spezifischen Merkmale jeder Lösung analysieren.

Indikatoren mit mehreren Periodenlängen.

Lassen Sie uns den MA-Indikator betrachten und versuchen, die folgende Aufgabe zu lösen: Erstellen Sie eine Indikatorversion mit einem sich ändernden Berechnungszeitraum, um drei verschiedene TFs anzuzeigen.

Lassen Sie uns die wichtigsten Parameter und unsere Variablen festlegen:

//---- Indikatoreinstellungen
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_type2   DRAW_LINE
#property indicator_type3   DRAW_LINE
#property indicator_color1  Blue
#property indicator_color2  Red
#property indicator_color3  Lime
#property indicator_width1  1
#property indicator_width2  1
#property indicator_width3  1
//---- Eingabeparameter
input ENUM_TIMEFRAMES    tf1             = 1;              // Zeitrahmen (1)
input ENUM_TIMEFRAMES    tf2             = 5;              // Zeitrahmen (2)Z
input ENUM_TIMEFRAMES    tf3             = 15;             // Zeitrahmen (3)
input int                maPeriod        = 13;             // MA Periodenlänge
input int                Shift           = 0;              // Versatz
input ENUM_MA_METHOD     InpMAMethod     = MODE_SMA;       // Glättungsverfahren
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;    // Berechnungspreis
//---- Indikatorpuffer
double ExtBuf1[];
double ExtBuf2[];
double ExtBuf3[];
//---- Handles der gleitenden Durchschnitte
int    ExtHandle1;
int    ExtHandle2;
int    ExtHandle3;
//--- Minimum der Bars für die Berechnung
int    ExtBarsMinimum;
//---
int period1=0;
int period2=0;
int period3=0;

Implementieren Sie nun Array-Daten mit der Bedingung, dass der TF, an den sie angehängt sind, <= ist, als die in Variablen angegebenen.

void OnInit()
  {
   int timeframe;
//---- zum Zeichnen der Indikatorpuffer
   SetIndexBuffer(0,ExtBuf1,INDICATOR_DATA);
   SetIndexBuffer(1,ExtBuf2,INDICATOR_DATA);
   SetIndexBuffer(2,ExtBuf3,INDICATOR_DATA);
//---
   timeframe =_Period;
//---
   if(tf1>=timeframe)
   {
   period1=maPeriod*(int)MathFloor(tf1/timeframe);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,period1-1);             //Setzen der ersten Bar für das Zeichnen des Index
   PlotIndexSetInteger(0,PLOT_SHIFT,Shift);                      //Versatz der Linie beim Zeichnen
   PlotIndexSetString(0,PLOT_LABEL,"MA("+string(period1)+")");   //Name im DatenFenster
   ExtHandle1=iMA(NULL,0,period1,0,InpMAMethod,InpAppliedPrice); //abrufen des Handles des MAs
   }
//---- 
   if(tf2>=timeframe)
   {
   period2=maPeriod*(int)MathFloor(tf2/timeframe);
   PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,period2-1);             //Setzen der ersten Bar für das Zeichnen des Index
   PlotIndexSetInteger(1,PLOT_SHIFT,Shift);                      //Versatz der Linie beim Zeichnen
   PlotIndexSetString(1,PLOT_LABEL,"MA("+string(period2)+")");   //Name im DatenFenster
   ExtHandle2=iMA(NULL,0,period2,0,InpMAMethod,InpAppliedPrice); //abrufen des Handles des MAs
   }
//---- 
   if(tf3>=timeframe)
   {
   period3=maPeriod*(int)MathFloor(tf3/timeframe);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,period3-1);             //Setzen der ersten Bar für das Zeichnen des Indikators
   PlotIndexSetInteger(2,PLOT_SHIFT,Shift);                      //Versatz der Linie beim Zeichnen
   PlotIndexSetString(2,PLOT_LABEL,"MA("+string(period3)+")");   //Name im DatenFenster 
   ExtHandle3=iMA(NULL,0,period3,0,InpMAMethod,InpAppliedPrice); //abrufen des Handles des MAs
   }
//--- Einstellen der Genauigkeit
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- Minimum der Bars für die Berechnung
   int per=MathMax(period3,MathMax(period1,period2));
   ExtBarsMinimum=per+Shift;
//--- Initialisierung beendet
  }

Die Hauptschleife zur Initialisierung und Berechnung:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Prüfen von rates_total
   if(rates_total<ExtBarsMinimum)
      return(0); // not enough bars for calculation
//--- nicht alle Daten können berechnet werden
   int calculated=BarsCalculated(ExtHandle1);
   if(calculated<rates_total&&period1!=0)
     {
      Print("Not all data of ExtHandle1 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
   calculated=BarsCalculated(ExtHandle2);
   if(calculated<rates_total&&period2!=0)
     {
      Print("Not all data of ExtHandle2 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
   calculated=BarsCalculated(ExtHandle3);
   if(calculated<rates_total&&period3!=0)
     {
      Print("Not all data of ExtHandle3 is calculated (",calculated,"bars ). Error",GetLastError());
      return(0);
     }
//--- wir können nicht alle Daten kopieren
   int to_copy;
   if(prev_calculated>rates_total || prev_calculated<0) to_copy=rates_total;
   else
     {
      to_copy=rates_total-prev_calculated;
      if(prev_calculated>0) to_copy++;
     }
//---- Abrufen der MA-Puffer
   if(IsStopped()) return(0); //Prüfen des Stop-Flags
   if(period1!=0)
   if(CopyBuffer(ExtHandle1,0,0,to_copy,ExtBuf1)<=0)
     {
      Print("getting ExtHandle1 is failed! Error",GetLastError());
      return(0);
     }
   if(IsStopped()) return(0); //Prüfen des Stop-Flags
   if(period2!=0)
   if(CopyBuffer(ExtHandle2,0,0,to_copy,ExtBuf2)<=0)
     {
      Print("getting ExtHandle2 is failed! Error",GetLastError());
      return(0);
     }
   if(IsStopped()) return(0); //Prüfen des Stop-Flags
   if(period3!=0)
   if(CopyBuffer(ExtHandle3,0,0,to_copy,ExtBuf3)<=0)
     {
      Print("getting ExtHandle3 is failed! Error",GetLastError());
      return(0);
     }
//--- Rückgabe des Wertes von prev_calculated für den nächsten Aufruf
   return(rates_total);
  }
//+------------------------------------------------------------------+

Schauen wir uns das Ergebnis an.

Abb. 9. Implementierung eines MTF-Indikators

Wir haben das Beispiel der Verwendung desselben Indikators in verschiedenen Zeiträumen unter Verwendung der Erhöhung des Berechnungszeitraums betrachtet. Mit einer leichten Änderung können wir dies nutzen und gleichzeitig die Möglichkeit bieten, individuelle Periodenlängen für jede Linie festzulegen. Diese Methode mag ineffizient erscheinen. Es ist sehr einfach, die Periodenlänge zu berechnen und mehrere Indikatoren gleichzeitig anzuwenden. In einigen Fällen ist diese Methode jedoch trotz aller Nachteile die optimale. Einer davon ist die Notwendigkeit, zwei (drei) nicht normierte Oszillatoren in einem Fenster gleichzeitig zu beobachten. Aufgrund des Amplitudenbereichs werden diese Oszillatoren gegenüber der Mittellinie verschoben und erschweren so ihre Interpretation. Diese Methode eliminiert den oben genannten Nachteil.


Indikatoren für mehrere Zeitrahmen

Statt der gut bekannten MQL4-Funktionen iClose(), iHigh(), iLow(), iOpen(), iTime(), iVolume() bietet MQL5 die Funktionen CopyTime(), CopyClose(), CopyHigh(), CopyLow(), CopyOpen(), CopyTime(), CopyVolume(), während die Funktionen iCustom, iMA, iCCI, iMACD und andere durch die Funktion CopyBuffer() implementiert sind. Jede von ihnen hat ihre Vor- und Nachteile. In unserem Fall werden wir nur MQL5 berücksichtigen. Für unseren Indikator benötigen wir die gesamte Liste der Zeitrahmen von M1 bis MN1, d.h. 26 verschiedene. Wenn Sie mehrere Handelssymbole oder -instrumente verwenden müssen, steigt diese Zahl um ein Vielfaches. In den meisten Fällen ist es nicht notwendig, die gesamte Historie zu kopieren. Bei den meisten Informationsindikatoren ist die Anzahl der Balken durch zwei begrenzt. Um zu verhindern, dass der Code zu lang wird, ist es daher besser, diese Befehle als separate Funktionen zu schreiben und wiederholt aufzurufen.

Für die Zeitreihenfunktion CopyClose() sieht die Funktion wie folgt aus:

//+------------------------------------------------------------------+
double _iClose(string symbol,int tf,int index)
{
   if(index < 0) return(-1);

   double buf[];
   ENUM_TIMEFRAMES timeframe=TFMigrate(tf);
   if(CopyClose(symbol,timeframe, index, 1, buf)>0) 
        return(buf[0]);
   else return(-1);
}
//+------------------------------------------------------------------+

Für den Aufruf von WPR

//+------------------------------------------------------------------+
double _iWPR(string symbol,
                int tf,
                int period,
                int shift)
  {
   ENUM_TIMEFRAMES timeframe=TFMigrate(tf);
   int handle=iWPR(symbol,timeframe,period);
   if(handle<0)
     {
      Print("The iWPR object not created: Error ",GetLastError());
      return(-1);
     }
   else
      return(_CopyBuffer(handle,shift));
  }
//+------------------------------------------------------------------+
double _CopyBuffer(int handle,int shift)
  {
   double buf[];
if(CopyBuffer(handle,0,shift,1,buf)>0)
         return(buf[0]);

   return(EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

Gibt es mehrere Linien, wird _CopyBuffer wie folgt geschrieben:

//+------------------------------------------------------------------+
double _CopyBuffer(int handle,int index,int shift)
  {
   double buf[];
   switch(index)
     {
      case 0: if(CopyBuffer(handle,0,shift,1,buf)>0)
         return(buf[0]); break;
      case 1: if(CopyBuffer(handle,1,shift,1,buf)>0)
         return(buf[0]); break;
      case 2: if(CopyBuffer(handle,2,shift,1,buf)>0)
         return(buf[0]); break;
      case 3: if(CopyBuffer(handle,3,shift,1,buf)>0)
         return(buf[0]); break;
      case 4: if(CopyBuffer(handle,4,shift,1,buf)>0)
         return(buf[0]); break;
default: break;
     }
   return(EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

während die Funktion _iWPR die Zeile

return(_CopyBuffer(handle,shift)

ändert auf

return(_CopyBuffer(handle,0,shift)

in beiden Fällen schaut TFMigrate() wie folgt aus:

//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TFMigrate(int tf)
  {
   switch(tf)
     {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 5: return(PERIOD_M5);
      case 15: return(PERIOD_M15);
      case 30: return(PERIOD_M30);
      case 60: return(PERIOD_H1);
      case 240: return(PERIOD_H4);
      case 1440: return(PERIOD_D1);
      case 10080: return(PERIOD_W1);
      case 43200: return(PERIOD_MN1);
      
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);      
      case 6: return(PERIOD_M6);
      case 10: return(PERIOD_M10);
      case 12: return(PERIOD_M12);
      case 20: return(PERIOD_M20);
      case 16385: return(PERIOD_H1);
      case 16386: return(PERIOD_H2);
      case 16387: return(PERIOD_H3);
      case 16388: return(PERIOD_H4);
      case 16390: return(PERIOD_H6);
      case 16392: return(PERIOD_H8);
      case 16396: return(PERIOD_H12);
      case 16408: return(PERIOD_D1);
      case 32769: return(PERIOD_W1);
      case 49153: return(PERIOD_MN1);      
      default: return(PERIOD_CURRENT);
     }
  }
//+------------------------------------------------------------------+

Wie bereits erwähnt, wird für diese Berechnungsart eine begrenzte Anzahl von Elementen (Balken) benötigt. Aber manchmal ist es ratsam, die gesamte Historie zu berechnen. Man muss hier vorsichtig sein. Vergessen Sie nicht, dass es mehr historische Bars auf niedrigeren TFs als auf höheren geben wird. Diese Tatsache sollte bei der Erstellung eines solchen Tools berücksichtigt werden. Am einfachsten ist es, die kleinste Anzahl von Balken zu bestimmen und diesen Wert für die Berechnung zu verwenden. Eine schwierigere Methode ist es, diesen Wert für jedes TF separat zu bestimmen. Häufig (insbesondere bei Informationsindikatoren) werden Daten erst nach dem Schließen eines Balkens benötigt, so dass es nicht notwendig ist, ältere TFs bei jedem Tick des unteren neu zu berechnen. Wenn wir diesen Aspekt in unserer Entwicklung berücksichtigen, wird sich die Codekomplexität deutlich reduzieren.

Die Entwicklung der Informationsindikatoren (Abb. 1, Abb. 2, Abb. 3) unterscheidet sich nicht von der Entwicklung der klassischen Indikatoren, daher lassen Sie uns sofort zur Klasse der grafischen Indikatoren übergehen, die meiner Meinung nach die interessanteste ist. Informative Indikatoren benötigen nur die aktuellen Daten über den Marktzustand und die verwendeten Symbole, grafische Indikatoren haben zusätzliche Anforderungen an die Grafik. Der Balken des M5-Zeitrahmens besteht aus 5 M1 Balken, M15 aus drei M5 Balken, etc. Das heißt, wenn eine Linie auf M5 gebildet wird, wird während drei Balken eine Linie von M15 gezogen. Die Linienposition ist nicht festgelegt und kann geändert werden, bis die Kerze М15 geschlossen ist. Deshalb müssen wir die Formation an die Öffnungszeit des Kerzenständers binden. Lassen Sie uns dies am Beispiel von MA umsetzen.

//---- Indikatoreinstellungen
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  Blue
#property indicator_width1  1
//---- Eingabeparameter
input ENUM_TIMEFRAMES    tf              = 5;              // Zeitrahmen 
input int                maPeriod        = 13;             // MA Periodenlänge
input int                Shift           = 0;              // Versatz
input ENUM_MA_METHOD     InpMAMethod     = MODE_SMA;       // Glättungsverfahren
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;    // Berechnungspreis
input  int               Bars_Calculated = 500;
//---- Indikatorpuffer
double ExtMA[];
//---- Handles der gleitenden Durchschnitte
int    MA_Handle;
//--- Minimum der Bars für die Berechnung
int    ExtBarsMinimum;
ENUM_TIMEFRAMES _tf;
int pf;
//--- wir sichern die Anzahl der Werte im gleitenden Durchschnitt 
int    bars_calculated=0; 
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des nutzerdefinierten Indikators        |
//+------------------------------------------------------------------+
int OnInit()
  {
   _tf=tf;
   ENUM_TIMEFRAMES timeframe;
   int draw_shift=Shift;// Anfangswert PLOT_SHIFT
   int draw_begin=maPeriod;// Anfangswert PLOT_DRAW_BEGIN
//---
   timeframe=_Period;
   if(_tf<=timeframe)_tf=timeframe;// wenn der TF kleiner oder gleich dem aktuellen ist, setze es auf PERIOD_CURRENT
   pf=(int)MathFloor(_tf/timeframe);// Koeffizientenberechnung für PLOT_DRAW_BEGIN, PLOT_SHIFT und die Anzahl der zu berechnenden Bar.
   draw_begin=maPeriod*pf;// berechnen von PLOT_DRAW_BEGIN
   draw_shift=Shift*pf;// berechnen von PLOT_SHIFT
//---- zum Zeichnen der Indikatorpuffer
   SetIndexBuffer(0,ExtMA,INDICATOR_DATA);
//--- 
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,draw_begin-pf);                      //Setzen der ersten Bar für das Zeichnen des Indikators
   PlotIndexSetInteger(0,PLOT_SHIFT,draw_shift);                              //Versatz der Linie beim Zeichnen
   PlotIndexSetString(0,PLOT_LABEL,"MA("+string(tf)+" "+string(maPeriod)+")");//Name im DatenFenster
//---
   MA_Handle=iMA(NULL,_tf,maPeriod,0,InpMAMethod,InpAppliedPrice);            //abrufen des Handles des MAs
   if(MA_Handle==INVALID_HANDLE)
     {
      Print("getting MA Handle is failed! Error",GetLastError());
      return(INIT_FAILED);
     }
//--- Einstellen der Genauigkeit
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- Minimum der Bars für die Berechnung
   ExtBarsMinimum=draw_begin+draw_shift;// Berechnen der Mindestanzahl der Bars für die Berechnung
//--- Initialisierung beendet
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Funktion des nutzerdefinierten Indikators                        |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Prüfen von rates_total
   if(rates_total<ExtBarsMinimum)
      return(0); // not enough bars for calculation
   int limit;
//--- Anwenden des Index der Zeitreihen auf die Arrayelemente  
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(ExtMA,true);
//--- Ermitteln des Anfangspunktes
//--- der Berechnungen für den benötigten Umfang der Daten, die zu kopieren sind
//--- und dem Anfangsindex 'limit' der Bars für die Schleife der Neuberechnung
   if(prev_calculated>rates_total || prev_calculated<=0|| calculated!=bars_calculated)// Prüfen auf den Erststart der Indikatorberechnung
     {
      limit=rates_total-ExtBarsMinimum-1; // Startindex für die Berechnung aller Bars
     }
   else
     {
      limit=(rates_total-prev_calculated)+pf+1; // Startindex für die Berechnung der neuen Bars
     }
   if(Bars_Calculated!=0)   limit=MathMin(Bars_Calculated,limit);

Wir werden nicht nach dem Index einer Bar suchen (iBarShift()), sondern die Werte direkt zeitorientiert kopieren.

//--- Hauptschleife
   for(int i=limit;i>=0 && !IsStopped();i--)
     {
      ExtMA[i]=_CopyBuffer(MA_Handle,time[i]);
     }
//---
   bars_calculated=calculated;
Verwenden Sie hier die oben erwähnten Funktion.
//+--------- CopyBuffer MA Handle ----------------------------------+
double _CopyBuffer(int handle,datetime start_time)
  {
   double buf[];
   if(CopyBuffer(handle,0,start_time,1,buf)>0)
      return(buf[0]);

   return(EMPTY_VALUE);
  }
//+-------------------- ENDE ----------------------------------------+

Hier ist das Ergebnis:

Abb. 10. MTF-Indikatorlinie

Diese Methode kann für alle Arten von linearen Indikatoren verwendet werden. Der größte Nachteil ist offensichtlich aus dem obigen Screenshot: die Stufenform. Für MA ist ein solches Verhalten kein Problem und kann sogar nützlich sein, da es eine klare Definition von Unterstützungs- und Widerstandsstufen ermöglicht. Aber für Oszillatoren, in denen wir Muster verwenden, behindert dieses Verhalten die Identifizierung und Zeichnung solcher Muster. Darüber hinaus ist die Lösung für WPR, CCI und ähnliche Indikatoren inakzeptabel, da sich das Linienbild bis zur Unkenntlichkeit verändert.

Um dieses Problem zu lösen, sollte der letzte Balken unter Berücksichtigung der Gewichtungsfaktoren berechnet werden. Fügen wir die globale Variable 'Interpolate' hinzu, die die Verwendung beider Lösungen ermöglicht.

input bool               Interpolate     = true;
//--- Hauptschleife
   for(int i=limit;i>=0 && !IsStopped();i--)
     {
      int n;
      datetime t=time[i];
      ExtMA[i]=_CopyBuffer(MA_Handle,t);
      if(!Interpolate) continue;
      //---
      datetime times= _iTime(t);
      for(n = 1; i+n<rates_total && time[i+n]>= times; n++) continue;
      double factor=1.0/n;
      for(int k=1; k<n; k++)
      ExtMA[i+k]=k*factor*ExtMA[i+n]+(1.0-k*factor)*ExtMA[i];
     }
//---

In dieser Variante müssen wir die Funktion _iTime hinzufügen, die die Nummer des Balkens basierend auf der Öffnungszeit für das aktuelle Diagramm bestimmt.

//+------------------------------------------------------------------+
datetime _iTime(datetime start_time)
{
   if(start_time < 0) return(-1);
   datetime Arr[];
   if(CopyTime(NULL,_tf, start_time, 1, Arr)>0)
        return(Arr[0]);
   else return(-1);
}
//+------------------------------------------------------------------+

Das sieht jetzt wie eine normale Linie aus.

Abb. 11. MTF-Indikatorlinie mit _iTime

Auch wenn die Entwicklung solcher komplexen und ressourcenintensiven Systeme unangemessen erscheint, haben sie ihre Nachteile und können sogar unerlässlich sein. Bei Verwendung der klassischen Mittelung (MA, Alligator etc.) kann die Erhöhung des Berechnungszeitraums zu einer Verzögerung gegenüber der MTF-Version führen. Dies macht sich besonders bei kleinen Perioden bemerkbar.

Abb. 12. Verzögerung des Indikators MTF-MAs

Abb. 13. Verzögerung des MTF Stochastic Indikators

Bei einfachen Indikatoren wie den MAs und dem Alligator ist dies möglicherweise nicht so signifikant. Aber für komplexe Systeme, die aus zwei oder mehr MAs bestehen, wie MACD, AO, etc. kann dies von Bedeutung sein. Außerdem besteht keine Möglichkeit, die den Glättungszeitraum für AO oder AC zu ändern. Bei Indikatoren mit einer nicht glättenden Linie (WPR, CCI, etc.) kann eine geringfügige Erhöhung des Berechnungszeitraums kein zufriedenstellendes Ergebnis liefern, da sie sehr verrauscht sind.


Abb. 14. Der MTF-Indikator WPR


Abb. 15. Der MTF-Indikator WPR

Die Abbildungen 14-15 zeigen, dass sie erfolgreich zur Glättung eingesetzt werden können, wenn diese Möglichkeit im Algorithmus nicht vorgesehen ist.

Zusätzlich zu seiner direkten Funktion kann dieser Typ die Mängel des visuellen Testmodus im MetaTrader 5 Strategy Tester ausgleichen. Bei der Erstellung von MTF Expert Advisors für den Handel oder der Analyse der Wirksamkeit dieser Art von Strategie können wir nicht gleichzeitig die Position von Indikatoren aus verschiedenen TFs auf dem Bildschirm beobachten. Nach dem Testen erhalten wir eine Reihe von Registerkarten, abhängig von der Anzahl der verwendeten Perioden. Betrachten wir das Beispiel der Strategie "Elders Drei Bildschirme" aus dem Artikel Das MQL5-Kochbuch: Entwickeln eines Grundgerüsts für ein Handelssystem auf Basis der Drei-Bildschirme-Strategie von Anatoli Kazharski. Die Idee der Strategie ist wie folgt: Der erste Zeitrahmen ist der größte, wie wöchentlich, täglich oder 4 Stunden. Es wird verwendet, um den Haupttrend zu bestimmen. Der zweite Zeitrahmen unterscheidet sich von dem ersten um 1 oder 2 Grade. Sie wird zur Bestimmung der Korrektur verwendet. Der dritte Zeitrahmen unterscheidet sich um einen weiteren Grad. Es wird verwendet, um den optimalen Einstiegspunkt zu bestimmen.

Starten Sie im ersten Bildschirm, der normalerweise M30-W1 ist, den MACD (12,26,1) und den EMA mit einer Periodenlänge von 13. Im zweiten Bildschirm, М5-D1, starten Sie den stochastischen Oszillator (5,3,3,3). Der dritte Bildschirm von M1 bis H4 dient zur Platzierung von Stop-Orders in der Haupttrendrichtung.

Abb. 16. Elders Drei Bildschirme

Der Autor des Artikels hielt sich nicht an diese Variante, aber das Konzept der "Drei Bildschirme" blieb erhalten. Was wir während des Testprozesses und danach sehen:


Abb. 17. Der Test der Strategie von Elders Drei Bildschirme

Diese Variante erlaubt es uns nicht, die Funktionsweise des Experten Beraters (Strategie) richtig zu analysieren.
Lassen Sie uns mit unseren Tools eine eigene Version des EAs erstellen. Es wird der klassischen Strategieversion sehr ähnlich sein. Auch die Erstellung von Expert Advisors auf der Grundlage der oben genannten Indikatoren ist den klassischen EAs sehr ähnlich. Hier ist der Hauptteil des Codes.

#define EXPERT_MAGIC 1234502
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\OrderInfo.mqh>
//+------------------------------------------------------------------+
enum compTF
  {
   A, //m1-m5-m15
   B, //m5-m15-h1
   C, //m15-h1-h4
   E  //h1-h4-d1
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input string s0="//--- Eingabeparameter Losgröße+Trailing ---//";
input double InpLots          =0.1; // Losgröße
input int    InpTakeProfit    =150; // Take-Profit (in Pips)
input int    InpStopLoss      =60;  // Stop-Loss (in Pips)
input int    InpLevel_S       =20;  // Signallevel
input compTF InpTFreg         =2;
input string s1="//--- Eingabeparameter MA ---//";
input int                Signal_MA_PeriodMA      =21;                             // Glättungslänge
input string s2="//--- Eingabeparameter MACD ---//";
input int                Signal_MACD_PeriodFast  =12;                             // Periodenlänge des schnellen MA
input int                Signal_MACD_PeriodSlow  =26;                             // Periodenlänge des langsamen MA
input string s3="//--- Eingabeparameter der Stochastic ---//";
input int                Signal_Stoch_PeriodK    =5;                              //  K-Periode
input int                Signal_Stoch_PeriodD    =3;                              //  D-Periode
input string s4="//--- Eingabeparameter Trailing ---//";
//--- Eingabeparameter für das Trailing
input int    InpTrailingStop  =25;  // Trailing Stop-Level (in Pips)
input int    InpOffset        =5;   // Abstand vom Preis (in Pips)
//---
int ExtTimeOut=10; // Zeitspanne zwischen den Handelsoperationen in Sekunden
int barsCalculated=3;
datetime t=0;
datetime time[];
//+------------------------------------------------------------------+
//| AC Beispielklasse des Experten                                   |
//+------------------------------------------------------------------+
class CSampleExpert
  {
protected:
   double            m_adjusted_point;             // 3- oder 5-stellige Werte
   CTrade            m_trade;                      // Handelsobjekt
   CSymbolInfo       m_symbol;                     // Symbolinformation
   CPositionInfo     m_position;                   // Handelsposition
   CAccountInfo      m_account;                    // Kontoinformation
   COrderInfo        m_order;                      // Auftragsinformation
   //--- Indikatoren
   ENUM_TIMEFRAMES   mas_tf[3];                   // Array der Zeitrahmen
   int               handle_MACD;                 // Handle des MACD
   int               handle_MA;                   // Handle des MA
   int               handle_Stochastic;           // Handle der Stochastik
   //--- Indikatorpuffer
   double            macd_buff[];           // Hauptpuffer des MACD
   double            ma_buff[];             // Hauptpuffer des MA
   double            stoch_buff[];          // Hauptpuffer der Stochastic
   //---
   double            close[];
   double            open[];
   double            low[];
   double            high[];
   //--- zu bearbeitenden Indikatordaten
   double            macd_ind_0;
   double            macd_ind_1;
   double            ma_ind;
   double            stoch_ind_0;
   double            stoch_ind_1;
   int               level_stoch;
   //--- zu verarbeitenden Daten des Trailing-Stop
   double            m_traling_stop;
   double            m_take_profit;
   double            m_stop_losse;
public:
                     CSampleExpert(void);
                    ~CSampleExpert(void);
   bool              Init(void);
   void              Deinit(void);
   bool              Processing(void);

protected:
   bool              InitCheckParameters(const int digits_adjust);
   bool              Copy(void);              // 
   bool              InitIndicators(void);
   bool              LongModified(void);
   bool              ShortModified(void);
   bool              LongOpened(void);          // Prüfen der Kaufbedingungen
   bool              ShortOpened(void);         // Prüfen der Verkaufsbedingungen
   bool              OpenSellStop(void);        // platzieren einer SELLSTOP-Order
   bool              OpenBuyStop(void);         // platzieren einer BUYSTOP-Order
   bool              OrderModifySellStop(void); // Ändern einer SELLSTOP-Order
   bool              OrderModifyBuyStop(void);  // Ändern einer BUYSTOP-Order
   bool              DellSellStop(void);        // Löschen einer SELLSTOP-Order
   bool              DellBuyStop(void);         // Löschen einer BUYSTOP-Order
  };
//--- globaler Experte
CSampleExpert ExtExpert;
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CSampleExpert::CSampleExpert(void) : m_adjusted_point(0),
                                     handle_MACD(INVALID_HANDLE),
                                     handle_Stochastic(INVALID_HANDLE),
                                     handle_MA(INVALID_HANDLE),
                                     macd_ind_0(0),
                                     macd_ind_1(0),
                                     stoch_ind_0(0),
                                     stoch_ind_1(0),
                                     ma_ind(0),
                                     m_traling_stop(0),
                                     m_take_profit(0)
  {
   ArraySetAsSeries(macd_buff,true);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSampleExpert::~CSampleExpert(void)
  {
  }
//+------------------------------------------------------------------+
//| Initialisierung und Verifikation der Eingabeparameter            |
//+------------------------------------------------------------------+
bool CSampleExpert::Init(void)
  {
//--- Initialisierung der allgemeinen Informationen
   m_symbol.Name(Symbol());                  // Symbol
   m_trade.SetExpertMagicNumber(EXPERT_MAGIC); // Magicnummer
   m_trade.SetMarginMode();
   m_trade.SetTypeFillingBySymbol(Symbol());
//--- Bestimmen von 3 oder 5 Stellen
   int digits_adjust=1;
   if(m_symbol.Digits()==3 || m_symbol.Digits()==5)
      digits_adjust=10;
   m_adjusted_point=m_symbol.Point()*digits_adjust;
//--- Einstellungen der standardmäßigen Differenzen für den Handel 
   m_traling_stop    =InpTrailingStop*m_adjusted_point;
   m_take_profit     =InpTakeProfit*m_adjusted_point;
   m_stop_losse      =InpStopLoss*m_adjusted_point;
//--- Einstellungen der standardmäßigen Differenzen für den Handel in angepassten Punkten
   m_trade.SetDeviationInPoints(3*digits_adjust);
//---
   int x=InpTFreg;
   switch(x)
     {
      case 0: {mas_tf[0]=PERIOD_M1;mas_tf[1]=PERIOD_M5;mas_tf[2]=PERIOD_M15;}
      break;
      case 1: {mas_tf[0]=PERIOD_M5;mas_tf[1]=PERIOD_M15;mas_tf[2]=PERIOD_H1;}
      break;
      case 2: {mas_tf[0]=PERIOD_M15;mas_tf[1]=PERIOD_H1;mas_tf[2]=PERIOD_H4;}
      break;
      case 3: {mas_tf[0]=PERIOD_H1;mas_tf[1]=PERIOD_H4;mas_tf[2]=PERIOD_D1;}
      break;
     }
//---
   if(!InitCheckParameters(digits_adjust))
      return(false);
   if(!InitIndicators())
      return(false);
//--- Ergebnis
   return(true);
  }
//+------------------------------------------------------------------+
//| Verifikation der Eingabeparameter                                |
//+------------------------------------------------------------------+
bool CSampleExpert::InitCheckParameters(const int digits_adjust)
  {
//--- Prüfen der Quelldaten
   if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel())
     {
      printf("Take Profit must be greater than %d",m_symbol.StopsLevel());
      return(false);
     }
   if(InpTrailingStop*digits_adjust<m_symbol.StopsLevel())
     {
      printf("Trailing Stop must be greater than %d",m_symbol.StopsLevel());
      return(false);
     }
//--- Prüfen der Losgröße
   if(InpLots<m_symbol.LotsMin() || InpLots>m_symbol.LotsMax())
     {
      printf("Lots must be in the range between %f and %f",m_symbol.LotsMin(),m_symbol.LotsMax());
      return(false);
     }
   if(MathAbs(InpLots/m_symbol.LotsStep()-MathRound(InpLots/m_symbol.LotsStep()))>1.0E-10)
     {
      printf("The amount does not correspond to the lot step %f",m_symbol.LotsStep());
      return(false);
     }
//--- Warnungen
   if(InpTakeProfit<=InpTrailingStop)
      printf("Warning: Trailing Stop must be less than Take Profit");
//--- Ergebnis
   return(true);
  }
//+------------------------------------------------------------------+
//| Initialisierung der Indikatoren                                  |
//+------------------------------------------------------------------+
bool CSampleExpert::InitIndicators(void)
  {
//--- Erstellen des MACD
   if(handle_MACD==INVALID_HANDLE)
     {
      //---
      handle_MACD=iCustom(NULL,0,"MTF\\Oscillators\\MTF_MACD",mas_tf[2],Signal_MACD_PeriodFast,Signal_MACD_PeriodSlow);
      //---
      if(handle_MACD==INVALID_HANDLE)
        {
         printf("Error occurred while creating MACD");
         return(false);
        }
     }
//--- Erstellen des MA
   if(handle_MA==INVALID_HANDLE)
     {
      //---
      handle_MA=iCustom(NULL,0,"MTF\\Trend\\MA_MultiTF",mas_tf[2],Signal_MA_PeriodMA,0,MODE_EMA,PRICE_CLOSE);
      //---
      if(handle_MA==INVALID_HANDLE)
        {
         printf("Error occurred while creating MA");
         return(false);
        }
     }
//--- Erstellen der Stochastik
   if(handle_Stochastic==INVALID_HANDLE)
     {
      //---
      handle_Stochastic=iCustom(NULL,0,"MTF\\Oscillators\\MTF_Stochastic",mas_tf[1],Signal_Stoch_PeriodK,Signal_Stoch_PeriodD);
      //---
      if(handle_Stochastic==INVALID_HANDLE)
        {
         printf("Error occurred while creating Stochastic");
         return(false);
        }
     }
//--- Ergebnis
   return(true);
  }
//+------------------------------------------------------------------+
//|          Ändern der Kaufposition                                 |
//+------------------------------------------------------------------+
bool CSampleExpert::LongModified(void)
  {
   bool res=false;
//--- Prüfen des Trailing-Stops
   if(InpTrailingStop>0)
     {
      if(m_symbol.Bid()-m_position.PriceOpen()>m_adjusted_point*InpTrailingStop)
        {
         double sl=NormalizeDouble(m_symbol.Bid()-m_traling_stop,m_symbol.Digits());
         double tp=m_position.TakeProfit();
         if(m_position.StopLoss()<sl || m_position.StopLoss()==0.0)
           {
            //--- Ändern der Position
            if(m_trade.PositionModify(Symbol(),sl,tp))
               printf("Long position by %s to be modified",Symbol());
            else
              {
               printf("Error modifying position by %s : '%s'",Symbol(),m_trade.ResultComment());
               printf("Modify parameters : SL=%f,TP=%f",sl,tp);
              }
            //--- wurde geändert und muss den Expert Advisor verlassen
            res=true;
           }
        }
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//|                    Ändern der Verkaufsposition                   |
//+------------------------------------------------------------------+
bool CSampleExpert::ShortModified(void)
  {
   bool   res=false;
//--- Prüfen des Trailing-Stops
   if(InpTrailingStop>0)
     {
      if((m_position.PriceOpen()-m_symbol.Ask())>(m_adjusted_point*InpTrailingStop))
        {
         double sl=NormalizeDouble(m_symbol.Ask()+m_traling_stop,m_symbol.Digits());
         double tp=m_position.TakeProfit();
         if(m_position.StopLoss()>sl || m_position.StopLoss()==0.0)
           {
            //--- Ändern der Position
            if(m_trade.PositionModify(Symbol(),sl,tp))
               printf("Short position by %s to be modified",Symbol());
            else
              {
               printf("Error modifying position by %s : '%s'",Symbol(),m_trade.ResultComment());
               printf("Modify parameters : SL=%f,TP=%f",sl,tp);
              }
            //--- wurde geändert und muss den Expert Advisor verlassen
            res=true;
           }
        }
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//| Prüfen der Bedingungen zum Öffnen von Kaufpositionen             |
//+------------------------------------------------------------------+
bool CSampleExpert::LongOpened(void)
  {
   bool res=false;
   level_stoch=InpLevel_S;
//--- Prüfen der Möglichkeit eine Kaufposition (BUY) zu öffnen
   if(stoch_ind_1<level_stoch && stoch_ind_0>level_stoch && macd_ind_1>macd_ind_0 && ma_ind<close[1] && ma_ind<open[1])//&& ma_ind<close[1] && ma_ind<open[1]
     {
      //--- verlassen des EAs
      res=true;
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//|         Öffnen einer Buy-Stop-Order                              |
//+------------------------------------------------------------------+
bool CSampleExpert::OpenBuyStop(void)
  {
   bool res=false;
   double tp=0,sl=0;
//---
   if(LongOpened())
     {
      res=true;
      //--- Deklarieren und Initialisieren der Anforderung und des Ergebnisses
      MqlTradeRequest request={0};
      MqlTradeResult  result={0};
      //--- Parameter der Pending-Orders
      request.action   =TRADE_ACTION_PENDING;                             // Typ der Handelsoperation
      request.symbol   =Symbol();                                         // Symbol
      request.deviation=5;                                                // erlaubte Abweichung vom Preis
      request.volume   =InpLots;                                          // Volumen in Lots
      request.magic    =EXPERT_MAGIC;                                     // MagicNummer der Aufträge
      double offset=InpOffset;                                            // Abstand in Punkten vom aktuellen Preis zum Platzieren der Aufträge
      double price;                                                       // Auslösepreis der Aufträge
      double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                // Punktgröße
      int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // Anzahl der Dezimalstellen (digits)
      //--- operation type
      request.type=ORDER_TYPE_BUY_STOP;                                   // Typ des Auftrags
      price=high[1]+offset*m_adjusted_point;                              // Eröffnungspreis
      request.price=NormalizeDouble(price,digits);                        // Normalisierter Eröffnungspreis
      tp=price+m_take_profit;
      sl=price-m_stop_losse;
      request.sl       =NormalizeDouble(sl,_Digits);                       // neuen Stop-Loss zu Struktur hinzufügen
      request.tp       =NormalizeDouble(tp,_Digits);                       // neuen Take-Profit zu Struktur hinzufügen
      //--- Anforderung senden
      if(!OrderSend(request,result))
        {res=false;printf("OrderSend error %d",GetLastError());}           // wenn der Auftrag nicht erfolgreich gesendet wurde, ausdrucken der Fehlernummer
      //--- Information über die Operation
      printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
      //--- verlassen des EAs
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//| Prüfen der Bedingungen für das Öffnen einer Verkaufsposition     |
//+------------------------------------------------------------------+
bool CSampleExpert::ShortOpened(void)
  {
   bool res=false;
   level_stoch=100-InpLevel_S;
//--- Prüfen der Möglichkeit zu verkaufen (SELL) 
   if(stoch_ind_1>level_stoch && stoch_ind_0<level_stoch && macd_ind_1<macd_ind_0 && ma_ind>close[1] && ma_ind>open[1])
     {
      //--- verlassen des EAs
      res=true;
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//|         Öffnen einer Sell-Stop-Order                              |
//+------------------------------------------------------------------+
bool CSampleExpert::OpenSellStop(void)
  {
   bool res=false;
   double tp=0,sl=0;
//---
   if(ShortOpened())
     {
      res=true;
      //--- Deklarieren und Initialisieren der Anforderung und des Ergebnisses
      MqlTradeRequest request={0};
      MqlTradeResult  result={0};
      //--- Parameter der Pending-Orders
      request.action   =TRADE_ACTION_PENDING;                             // Typ der Handelsoperation
      request.symbol   =Symbol();                                         // Symbol
      request.deviation=5;                                                // erlaubte Abweichung vom Preis
      request.volume=InpLots;                                             // Volumen in Lots
      request.magic=EXPERT_MAGIC;                                         // MagicNummer des Auftrags
      int offset=InpOffset;                                                // Abstand in Punkten vom aktuellen Preis zum Platzieren der Aufträge
      double price;                                                       // Auslösepreis der Aufträge
      int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // Anzahl der Dezimalstellen (digits)
      request.type=ORDER_TYPE_SELL_STOP;                                  // Ordertyp
      price=low[1]-offset*m_adjusted_point;                               // Eröffnungspreis
      request.price=NormalizeDouble(price,digits);                        // Normalisierter Eröffnungspreis
      tp=price-m_take_profit;
      sl=price+m_stop_losse;
      request.sl       =NormalizeDouble(sl,_Digits);                       // neuen Stop-Loss zu Struktur hinzufügen
      request.tp       =NormalizeDouble(tp,_Digits);                       // neuen Take-Profit zu Struktur hinzufügen
      //--- Anforderung senden
      if(!OrderSend(request,result))
        {res=false;printf("OrderSend error %d",GetLastError());}           // wenn der Auftrag nicht erfolgreich gesendet wurde, ausdrucken der Fehlernummer
      //--- Information über die Operation
      printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
      //--- verlassen des EAs
     }
//--- Ergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//| Die Hauptfunktion liefert 'true', wenn eine der Positionen       |
//| bearbeitet wurde                                                 |
//+------------------------------------------------------------------+
bool CSampleExpert::Processing(void)
  {
//   MqlDateTime dt;
//--- Aktualisierungsfrequenz
   if(!m_symbol.RefreshRates())
      return(false);
//--- Aktualisierung des Indikators
   if(BarsCalculated(handle_Stochastic)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_Stochastic,0,0,barsCalculated,stoch_buff)!=barsCalculated)
      return(false);
//---
   if(BarsCalculated(handle_MACD)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_MACD,0,0,barsCalculated,macd_buff)!=barsCalculated)
      return(false);
//---
   if(BarsCalculated(handle_MA)<barsCalculated)
      return(false);
   if(CopyBuffer(handle_MA,0,0,barsCalculated,ma_buff)!=barsCalculated)
      return(false);
//---
   if(!Copy())return(false);
//--- Programmvereinfachung und ein schnellerer Zugriff
//--- die Daten werden in internen Variablen gespeichert
   macd_ind_0   = macd_buff[1];
   macd_ind_1   = macd_buff[0];
   ma_ind       = ma_buff[1];
   stoch_ind_0  = stoch_buff[1];
   stoch_ind_1  = stoch_buff[0];

//--- es ist wichtig, alles richtig zu verlassen ...   
//--- Zuerst die Prüfung, ob Positionen existieren - Versuch sie auszuwählen
   bool bord=false,sord=false;
   ulong ticket;
//+--- Es gibt offene Positionen und Trailing-Stop ist aktiviert --------+
   if(m_position.Select(Symbol()) && PositionsTotal()>0 && m_traling_stop!=0)
     {
      if(m_position.PositionType()==POSITION_TYPE_BUY)
        {
         bord=true;
         //--- Ändern einer Kaufposition
         if(LongModified())
            return(true);
        }
      else
        {
         sord=true;
         //--- Ändern einer Verkaufsposition
         if(ShortModified())
            return(true);
        }
     }
//+--------- Handels-STOP-Orders ------------------------------------+
// In dieser Schleife werden alle platzierten Pending-Order einzeln überprüft
   for(int i=0;i<OrdersTotal();i++)
     {
      // auswählen jedes Auftrags und Abfragen seines Tickets
      ticket=OrderGetTicket(i);
      // Bearbeiten der Buy-Stop-Orders
      if(m_order.OrderType()==ORDER_TYPE_BUY_STOP)
        {
         // Einstellen des Flags für die Existenz eines Buy-Stop
         bord=true;
         //--- Es ist wichtig, den Markt korrekt zu betreten, verschieben falls notwendig des Auftrags 
         if(bord)OrderModifyBuyStop(); // modify the BUYSTOP order
        }
      // Bearbeitung des Sell-Stop
      if(m_order.OrderType()==ORDER_TYPE_SELL_STOP)
        {
         // Einstellen des Flags für die Existenz eines Sell-Stop
         sord=true;
         //--- Es ist wichtig, den Markt korrekt zu betreten, verschieben falls notwendig des Auftrags 
         if(sord)OrderModifySellStop(); // Ändern der SELLSTOP-Order
        }
     }
//--- Wenn es keine Aufträge gibt, platziere sie -------------------+
   if(!sord)if(OpenSellStop()){sord=true;return(true);}
   if(!bord)if(OpenBuyStop()){bord=true;return(true);}
//--- verlassen ohne Positionsbearbeitung
   return(false);
  }
//+------------------------------------------------------------------+
//|Ändern der Parameter eine bereits platzierten Sell-Stop-Order     |
//+------------------------------------------------------------------+
bool CSampleExpert::OrderModifySellStop(void)
  {
   bool res=true;
   ulong ticket;
   double tp=0,sl=0;
   double offset=InpOffset;                                            // Abstand in Punkten vom aktuellen Preis zum Platzieren der Aufträge
   double price;                                                       // Auslösepreis der Aufträge
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // Anzahl der Dezimalstellen (digits)
                                                                       // declare and initialize the trade request and result
   MqlTradeRequest request={0};
   MqlTradeResult  result={0};
   int total=OrdersTotal(); // Anzahl der Pending-Orders
//--- Iterieren über alle platzierten Pending-Orders
   for(int i=total-1; i>=0; i--)
     {
      // auswählen jedes Auftrags und Abfragen seines Tickets
      ticket=OrderGetTicket(i);
      // Bearbeitung des Sell-Stop
      if(m_order.OrderType()==ORDER_TYPE_SELL_STOP)
        {
         ulong  magic=OrderGetInteger(ORDER_MAGIC);               // MagicNummer der Order
         //--- falls die MagicNummer passt
         if(magic==EXPERT_MAGIC)
           {
               price=low[1]-offset*m_adjusted_point;                         // Eröffnungspreis
            if(price>m_order.PriceOpen()) // Prüfen der Bedingungen
            if(low[1]>low[2]) // Prüfen der Bedingungen
              {
               request.action=TRADE_ACTION_MODIFY;                           // Typ der Handelsoperation
               request.order = OrderGetTicket(i);                            // Ticket der Order
               request.symbol   =Symbol();                                   // Symbol
               request.deviation=InpOffset;                                  // erlaubte Abweichung vom Preis
               price=low[1]-offset*m_adjusted_point;                         // Eröffnungspreis
               request.price=NormalizeDouble(price,digits);                  // normalisierter Eröffnungspreis
               tp=price-m_take_profit;
               sl=price+m_stop_losse;
               request.sl       =NormalizeDouble(sl,_Digits);                // neuen Stop-Loss zu Struktur hinzufügen
               request.tp       =NormalizeDouble(tp,_Digits);                // neuen Take-Profit zu Struktur hinzufügen
               //--- Anforderung senden
               if(!OrderSend(request,result))
                 {
                  // Einstellen des Flags dafür, dass die Sell-Stop-Order nicht gelöscht wurde
                  res=true; printf("OrderSend error %d",GetLastError());
                 }  // wenn der Auftrag nicht erfolgreich gesendet wurde, ausdrucken der Fehlernummer
               //--- Information über die Operation   
               printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
              }
           }
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+
//|Ändern der Parameter eine bereits platzierten Buy-Stops-Order     |
//+------------------------------------------------------------------+
bool CSampleExpert::OrderModifyBuyStop(void)
  {
   bool res=true;
   ulong ticket;
   double tp=0,sl=0;
   double offset=InpOffset;                                            // Abstand in Punkten vom aktuellen Preis zum Platzieren der Aufträge
   double price;                                                       // Auslösepreis der Aufträge
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);           // Anzahl der Dezimalstellen (digits)
                                                                       //--- Deklarieren und Initialisieren der Anforderung und des Ergebnisses
   MqlTradeRequest request={0};
   MqlTradeResult  result={0};
   int total=OrdersTotal(); // Anzahl der Pending-Orders
//--- Iterieren über alle platzierten Pending-Orders
   for(int i=total-1; i>=0; i--)
     {
      // auswählen jedes Auftrags und Abfragen seines Tickets
      ticket=OrderGetTicket(i);
      // Bearbeiten der Buy-Stop-Orders
      if(m_order.OrderType()==ORDER_TYPE_BUY_STOP)
        {
         ulong  magic=OrderGetInteger(ORDER_MAGIC);               // MagicNummer der Order
         //--- falls die MagicNummer passt
         if(magic==EXPERT_MAGIC)
           {
               price=high[1]+offset*m_adjusted_point;                        // Eröffnungspreis
            if(price<m_order.PriceOpen()) // Prüfen der Bedingungen
              {
               request.action=TRADE_ACTION_MODIFY;                           // Typ der Handelsoperation
               request.symbol   =Symbol();                                   // Symbol
               request.action=TRADE_ACTION_MODIFY;                           // Typ der Handelsoperation
               request.order = OrderGetTicket(i);                            // Ticket der Order
               request.symbol   =Symbol();                                   // Symbol
               request.deviation=InpOffset;                                  // erlaubte Abweichung vom Preis
               //--- Setzen von Preis, Take-Profit und Stop-Loss 

               request.price=NormalizeDouble(price,digits);                  // normalisierter Eröffnungspreis
               tp=price+m_take_profit;
               sl=price-m_stop_losse;
               request.sl       =NormalizeDouble(sl,_Digits);                       // neuen Stop-Loss zu Struktur hinzufügen
               request.tp       =NormalizeDouble(tp,_Digits);                       // neuen Take-Profit zu Struktur hinzufügen
               //--- Anforderung senden
               if(!OrderSend(request,result))
                 {
                  // Einstellen des Flags dafür, dass die Buy-Stop-Order nicht gelöscht wurde
                  res=true;
                  printf("OrderSend error %d",GetLastError());
                 }  // wenn der Auftrag nicht erfolgreich gesendet wurde, ausdrucken der Fehlernummer
               //--- Information über die Operation   
               printf("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
              }
           }
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Erstellen aller benötigten Objekte
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Funktion für die Tickbearbeitung                                 |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   static datetime limit_time=0; // der Zeitpunkt der letzten Berechnungen + timeout
//--- keine Beachtung des Timeout
   if(TimeCurrent()>=limit_time)
     {
      //--- Datenprüfung
      if(Bars(Symbol(),Period())>barsCalculated)
        {
         //--- Prüfen der Zeitbegrenzung bezüglich des Timeouts in Sekunden, wenn zu verarbeitenden
         if(ExtExpert.Processing())
            limit_time=TimeCurrent()+ExtTimeOut;
        }
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSampleExpert::Copy(void)
  {
//--- zu kopierender Umfang 
   int copied=3;
//---
   ArrayResize(high,copied);
   ArrayResize(low,copied);
   ArrayResize(close,copied);
   ArrayResize(open,copied);
//+------- Festlegen der Richtung der Indices der Arrays ------------+
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(open,true);
//--- kopieren der Preis-Bars 
   if(CopyHigh(NULL,mas_tf[0],0,copied,high)<0)
     {printf("No copied High",GetLastError());return(false);}
//---
   if(CopyLow(NULL,mas_tf[0],0,copied,low)<0)
     {printf("No copied Low",GetLastError());return(false);}
//---
   if(CopyClose(NULL,mas_tf[2],0,copied,close)<0)
     {printf("No copied Close",GetLastError());return(false);}
//---
   if(CopyOpen(NULL,mas_tf[2],0,copied,open)<0)
     {printf("No copied Open",GetLastError());return(false);}
//---
   return(true);
  }
//+------------------------------------------------------------------+


Die Verwendung dieses EAs ist viel einfacher:



Abb. 18. EA-Test mit den implementierten MTF-Tools

Jetzt können wir neben der Betrachtung der Funktionsweise des Expert Advisors auch dessen mögliche Nachteile analysieren und so von den vielfältigen Möglichkeiten des Testers profitieren.


Schlussfolgerung

Obwohl die Autoren der MQL5-Softwaremodule keine direkte Möglichkeit bieten, solche Algorithmen zu erstellen, können diese Indikatoren sehr nützlich sein. In einigen Fällen ermöglichen sie die gleichzeitige Marktzustandsanalyse bei verschiedenen TFs, die Verbesserung der Effizienz des Strategietesters und die Verbesserung der Glättung. Die vorgestellten Codebeispiele können Nachteile haben, so dass es viele Möglichkeiten zur Weiterentwicklung der Idee über die MTF-Indikatoren mit der Programmiersprache MQL5 gibt.

Anlagen

Name Typ Ort
MA_MultiPeriod Trend MQL5\Indicators\MA_MultiPeriod.mq5
MA_MultiTF Trend MQL5\Indicators\MTF\Trend\MA_MultiTF.mq5
MTF_BB Trend MQL5\Indicators\MTF\Trend\MTF_BB.mq5
MTF_Envelopes Trend MQL5\Indicators\MTF\Trend\MTF_Envelopes.mq5
MTF_ParabolicSAR Trend MQL5\Indicators\MTF\Trend\MTF_ParabolicSAR.mq5
MTF_StdDev Trend MQL5\Indicators\MTF\Trend\MTF_StdDev.mq5
MTF_CCI Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_CCI.mq5
MTF_Force_Index Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_Force_Index.mq5
MTF_MACD Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_MACD.mq5
MTF_Momentum Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_Momentum.mq5
MTF_RSI Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_RSI.mq5
MTF_RVI Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_RVI.mq5
MTF_Stochastic Oszillatoren MQL5\Indicators\MTF\Oscillators\MTF_Stochastic.mq5
MTF_WPR  Oszillatoren  MQL5\Indicators\MTF\Oscillators\MTF_WPR.mq5
Triple Screen Trading System 1.0  Expert Advisor