Verschiedene Wege zur Ermittlung eines Trends in MQL5

Dmitriy Skub | 13 Januar, 2016

Einleitung

Jeder Händler kennt die Devise: „der Trend ist dein Freund. Folge dem Trend!“ Aber irgendwie hat jeder seine eigne Vorstellung davon, was ein Trend ist. Fast jeder Händler hat schon einmal Schauergeschichten über Händler gehört oder gelesen, die sich selbst ruiniert haben, weil sie gegen den Trend gehandelt haben.

Welcher Händler würde nicht alles für die Möglichkeit geben, das Vorliegen eines Trends zu jedem beliebigen Zeitpunkt exakt zu ermitteln? Das ist möglicherweise der Heilige Gral, nach dem alle suchen. In diesem Beitrag werden wir verschiedene Wege zur Ermittlung eines Trends betrachten. Genauer gesagt sehen wir uns an, wie wir mithilfe von MQL5 einige klassische Arten der Trendermittlung programmieren können.


1. Was ein Trend ist und weshalb man ihn kennen sollte

Für den Anfang formulieren wir den Grundgedanken eines Trends.

Also, bei einem Trend handelt es sich um die langfristige Entwicklungstendenz (Richtung) von Kursänderungen am Markt. Aus dieser allgemeinen Begriffsbestimmung ergeben sich unmittelbar folgende Konsequenzen:

Veranschaulichen wir uns das Gesagte:

Abbildung 1. Trendanalyse

Abbildung 1. Trendanalyse

Bei Betrachtung der Abbildung sieht man, dass die allgemeine Entwicklungstendenz von Ende 2005 bis Mai 2006 ansteigend ist (der grüne Pfeil im Diagramm). Sieht man sich jedoch die kleineren Abschnitte des Kursdiagramms an, lässt sich entdecken, dass der Trend im Februar 2006 deutlich absteigend war (roter Pfeil), und der Kurs sich fast den gesamten Januar lang in einem seitlich verlaufenden Korridor befand (gelber Pfeil).

Wir müssen also, bevor wir den Trend ermitteln, zunächst einmal festlegen, welcher Zeitraum uns interessiert. In Bezug auf den Handel hängt von dem Zeitraum in erster Linie die Zeit ab, über die eine Position am Markt gehalten wird, von ihrer Eröffnung, bis sie geschlossen wird. Darüber hinaus hängen von ihm auch die Sicherheitsgrenzen (Stop Loss), die Zielkurse für die geplante Schließung sowie die Häufigkeit der Ausführung einzelner Handelsoperationen ab.

Ein Ziel dieses Beitrages besteht darin angehenden Händlern dabei zu helfen, den von der Plattform MetaTrader 5 bereitgestellten Werkzeugbestand zur Ermittlung von Trends sachkundig zu nutzen. Außerdem sollen hier die Grundkenntnisse zur Programmierung einfachster Indikatoren zur Automatisierung dieses Vorgehens vermittelt werden. Abschließendes Ziel ist die Programmierung unkomplizierter Expert-Systeme, die diese Indikatoren für den automatischen Handel nutzen.  

  Zur Abgrenzung betrachten wir das Tageskursdiagramm (den Zeitraum D1 im Ausgabegerät) des liquidesten Instruments auf dem Devisenmarkt: EURUSD. Die „Haltezeit“ der Position kann bei einem solchen Zeitraum zwischen einigen Tagen bis zu mehreren Monaten betragen. Dementsprechend besteht das Ziel darin, hunderte oder sogar tausende Punkte mitzunehmen, und die Stop Loss-Grenzen werden im Abstand von einigen hundert Punkten platziert.

Generell kann alles im Folgenden Dargestellte bei jedem Zeitraum eingesetzt werden. Dabei ist allerdings zu beachten, dass, die Auswirkungen des durch Meldungen, Spekulationen großer Marktteilnehmer und andere die Volatilität des Marktes beeinflussende Faktoren hervorgerufenen Rauschens umso größer sind, je kleiner der Zeitraum im Diagramm ist.

Wenn man zudem bedenkt, dass, je länger eine Tendenz anhält, die Wahrscheinlichkeit ihrer Änderung umso geringer ist, das Handeln mit dem Trend eine höhere Wahrscheinlichkeit aufweist, Geld zu verdienen als zu verlieren Bleibt also nur noch herauszufinden, wie man in einem Kursdiagramm einen Trend ermittelt. Genau darum geht es in diesem Beitrag.


2. Wie man einen Trend ermittelt

Es folgen einige bekannte Arten der Trendermittlung:

  1. anhand gleitender Durchschnittswerte (Moving Averages, MA)
  2. anhand der Höchstwerte des ZigZag-Indikators
  3. anhand der ADX-Anzeigen
  4. anhand des Indikators NRTR
  5. anhand der Farbe der Heiken Ashi-Kerzen

Wir werden diese Verfahren mit all ihren Vor- und Nachteilen betrachten. Anschließend vergleichen wir sie über denselben Zeitraum.

2.1. Trendermittlung anhand gleitender Durchschnittswerte

Die wahrscheinlich einfachste Art zur Bestimmung eines Trends und seiner Richtung ist die Verwendung der gleitenden Durchschnittswerte. Eines der ersten Instrumente der technischen Analyse, die gleitenden Durchschnittswerte, wird nach wie vor in unterschiedlichen Variationen eingesetzt und bildet die Grundlage für die meisten Indikatoren. Händler benutzen sowohl einen einzelnen gleitenden Durchschnittswert als auch manchmal als „Fächer“ bezeichnete, ganze Sätze aus ihnen. 

Formulieren wir eine einfache Regel für einen gleitenden Durchschnittswert:

In diesem Fall verwenden wir den Schlusskurs des Balkens, um die Anzahl der „falschen“ (false) Trendänderungen bei kurzfristigen Kursschwankungen in unmittelbarer Nähe des gleitenden Durchschnittswerts (das sogenannte „Bouncing“) zu verringern.  

Veranschaulichen wir uns das Gesagte:

Abbildung 2. Bestimmung eines Trends anhand des gleitenden Durchschnitts (MA)

Abbildung 2. Bestimmung eines Trends anhand des gleitenden Durchschnitts (MA)

Hier kommt das Diagramm EURUSD D1 mit einem einfachen gleitenden Durchschnitt über einen Zeitraum von 200 aufbauend auf die Schlusskurse zum Einsatz (die rote Linie im Diagramm). Im unteren Teil des Schaubildes ist der von uns eigens entwickelte Trendindikator MATrendDetector abgebildet. Die Richtung des Trends wird durch die Lage des Indikatorhistogramms relativ zur Nullachse angezeigt. +1 entspricht einem aufsteigenden Trend; -1 einem absteigenden. Darüber und über andere in diesem Beitrag verwendete Detektoren erfahren Sie weiter unten mehr.

Wie zu sehen ist, dreht sich der Kurs, wenn der Balken ober-/unterhalb des gleitenden Durchschnitts schließt, häufig in die entgegengesetzte Richtung. Das bedeutet, dass diese Verfahren eine Menge falscher Signale liefert. Aus diesem Grund ist seine Verwendung in Expert-Systemen und Indikatoren sehr beschränkt. Er dient allenfalls als ein „äußerst grober“ Trendfilter.

2.2. Trendermittlung anhand dreier gleitender Durchschnittswerte

Was ist zu tun, um die Qualität der Trendermittlung mittels gleitender Durchschnittswerte zu verbessern? Wir können beispielsweise zwei oder mehr gleitende Durchschnittswerte für unterschiedliche Zeiträume heranziehen. Entsprechend wird die Regel zur Bestimmung des Trends für eine beliebige Anzahl (> 1) von gleitenden Durchschnittswerten mit unterschiedlichen wie folgt aussehen:

Wir verwenden hier folgende Begriffe:

Diese „richtige Reihenfolge von Durchschnittswerten“ wird wegen des ähnlichen Aussehens auch als das Öffnen des Durchschnittswertefächers nach oben/unten bezeichnet.

Veranschaulichen wir uns das Gesagte:

Abbildung 3. Trendermittlung anhand mehrerer gleitender Durchschnittswerte

Abbildung 3. Trendermittlung anhand mehrerer gleitender Durchschnittswerte

Hier verwenden wir das Diagramm EURUSD D1 sowie einfache auf den Schlusskursen beruhende gleitende Durchschnittswerte mit den Zeiträumen, 200 (starke rote Linie), 50 (gelbe mittelstarke Linie) und 21 (dünne violette Linie).

Im unteren Teil des Schaubildes ist der von uns eigens entwickelte Trendindikator FanTrendDetector abgebildet. Die Richtung des Trends wird durch die Lage des Indikatorhistogramms relativ zur Nullachse angezeigt. +1 entspricht einem aufsteigenden Trend; -1 einem absteigenden. Ist der Histogrammwert gleich Null, so bedeutet das, dass die Richtung des Trends nicht ermittelt werden kann. Zum Vergleich wird auch der Indikator MATrendDetector abgebildet.

Es ist offenkundig, dass die Anzahl der falschen Signale bezüglich einer Richtungsänderung des Trends zurück gegangen ist, aber dafür hat sich die Verzögerung bei der Trendermittlung vergrößert. Das ist folgerichtig, denn bis alle Durchschnittswerte in die „richtige“ Reihenfolge gebracht sind, vergeht einige Zeit. Was besser und was schlechte ist, hängt von dem jeweiligen Handelssystem ab, innerhalb dessen diese Verfahren eingesetzt werden.

In unserem Fall wurden die Werte für die Zeiträume der Durchschnittswerte nicht gezielt ausgewählt, es wurden einfach einige der unter Händlern sowie nach der Erfahrung des Verfassers Gebräuchlichsten genommen. Man kann versuchen, die Eigenschaften dieses Verfahrens zur Ermittlung des Trends für ein bestimmtes Währungspaar durch die gezielte Auswahl der Durchschnittswerte und ihrer Anzahl zu verbessern.

2.3. Trendermittlung anhand der Höchst- und Tiefstwerte des gezackten ZigZag-Indikators

Wir werden jetzt versuchen, uns der Ermittlung des Trends von der Position der Klassiker der technischen Analyse aus anzunähern. Und zwar bedienen wir uns dabei der folgenden Regel Charles Dows:

Die lokalen Höchst- bzw. Tiefstwerte sind anhand der Zacken des Zigzag-Indikators leicht auszumachen.

Veranschaulichen wir uns das Gesagte:

Abbildung 4. Trendermittlung mithilfe des ZigZag-Indikators

Abbildung 4. Trendermittlung mithilfe des ZigZag-Indikators

Hier helfen uns das Diagramm EURUSD D1 sowie der ZigZag-Indikator mit folgenden Parametern: ExtDepth = 5, ExtDeviation = 5, ExtBackstep = 3.

Im unteren Teil des Schaubildes ist der von uns eigens entwickelte Trendindikator ZigZagTrendDetector abgebildet.

Der Hauptmangel dieses Verfahrens zur Trendermittlung besteht darin, dass es unmöglich ist in Echtzeit zu erkennen, ob sich bereits ein Extremwert gebildet hat oder nicht. In der Rückschau sind die Extremwerte gut zu sehen und es wird klar, wo sie sich gebildet haben. Bei der realen Kursentwicklung kann ein gebildeter Extremwert ebenso plötzlich verschwinden, wie er aufgetaucht ist. Um sich das zu veranschaulichen, reicht es sich die Bildung der ZigZag-Linien in der grafischen Darstellung des Probelaufs eines beliebigen automatischen Handelssystems anzusehen.

Dieser Mangel macht das Verfahren für den praktischen Einsatz im Handel ungeeignet. Dafür ist es umso nützlicher für die technische Analyse älterer Daten, um Gesetzmäßigkeit zu entdecken sowie um die Qualität unterschiedlicher Handelssysteme zu beurteilen.

2.4. Trendermittlung mithilfe des Indikators ADX

Das nächste der von uns betrachteten Verfahren ist die Ermittlung des Trends mithilfe des Indikators ADX (Average Directional Movement Index, dt. Index der durchschnittlichen Bewegungsrichtung). Dieser Indikator wird nicht nur zur Bestimmung der Richtung des Trends sondern auch zur Bewertung seiner Stärke verwendet. Das ist eine überaus wertvolle Eigenschaft des Indikators ADX. Die Stärke eines Trends wird anhand der Hauptlinie des ADX bestimmt: liegt der Wert über 20 (dem gebräuchlichen, wenn auch zu dem gegebenen Zeitpunkt nicht zwangsläufig optimalen Niveau), dann gilt der Trend als stark genug.

Die Richtung des Trends wird mithilfe der Lage der Linien +DI und -DI relativ zueinander bestimmt. Der Indikator glättet alle drei Linien durch die Bildung exponentieller Durchschnittswerte, weshalb er nur mit Verzögerung auf Kursänderungen reagiert.

Wir formulieren folgende Regel für die Ermittlung des Trends:

In diesem Fall wird die Linie des Indikators ADX nicht zur Ermittlung der Trendrichtung verwendet. Sie wird vielmehr benötigt, um die Anzahl der falschen Signalauslösungen bei Verwendung dieses Indikators zu verringern. Bei einem schwachen Trend (ADX unter 20) ist es am besten, zunächst abzuwarten, bis er stärker wird, und erst dann damit zu beginnen, entsprechend dem Trend zu handeln.

Veranschaulichen wir uns das Gesagte:

Abbildung 5. Bestimmung eines Trends mithilfe des Indikators ADX

Abbildung 5. Bestimmung eines Trends mithilfe des Indikators ADX

Hier kommen das Diagramm EURUSD D1 und der Indikator ADX mit folgenden Parametern zum Einsatz: PeriodADX = 21 (die starke blaue Linie - der Wert der Stärke des ADX-Trends, die dünne grüne Linie - der +DI-Wert, die dünne rote Linie - der -DI-Wert).

Im unteren Teil des Schaubildes ist der von uns eigens entwickelte Trendindikator ADXTrendDetector abgebildet. Zum Vergleich, das obere Diagramm des Indikators ADXTrendDetector (karmesinrot) wurde mit ausgeschaltetem Trendstärkefilter (ADXTrendLevel = 0) erstellt, während er in dem unteren Schaubild (blaue Farbe) eingeschaltet ist (ADXTrendLevel = 20).

Bemerkenswert ist, dass ein Teil des so genannten „Zurückprallens“ oder „Hüpfens“ bei der Ermittlung der Richtung des Trends beim Einschalten des Trendstärkenfilters ausgesiebt wurde. Deshalb wäre es wünschenswert, diesen Filter im tatsächlichen Betrieb zu verwenden. Eine weitere Steigerung der Qualität der Leistung des Indikators kann durch die sachkundige Auswahl der externen Parameter je nach aktueller Marktlage (Flat/Range/Trend) sowie entsprechend der Art der Bewegung des jeweiligen Währungspaares erreicht werden.

Alles in allem bietet dieser Indikator als Eingangsfilter vorteilhafte Möglichkeiten zur Erstellung den Trend verfolgender Handelssysteme.

2.5. Trendermittlung mithilfe des Indikators NRTR

Das folgende Trendermittlungsverfahren verwendet den Indikator NRTR (Nick Rypock Trailing Reverse). Dieser Indikator befindet sich stets in gleich bleibendem Abstand von den erreichten Extremkursen, unterhalb des Kurses des aufsteigenden Trends und über dem Kurs des absteigenden. Hauptzweck dieses Indikators ist die Ausblendung dem Haupttrend widersprechender geringfügiger Korrekturbewegungen, während im Falle dem Haupttrend gegenläufiger Bewegungen bei Übersteigen einer bestimmten Grenze ein Signal für einen Richtungswechsel ausgegeben wird.

Daraus ergibt sich folgende Regel für die Ermittlung der Trendrichtung:

Wir verwenden bei der Überprüfung der Lage der NRTR-Linie die Schlusskurse, um die Auswirkungen unechter Änderungen der Trendrichtung bei Kursschwankungen zu beschränken.

Veranschaulichen wir uns das Gesagte:

Abbildung 6. Bestimmung eines Trends mithilfe des Indikators NRTR

Abbildung 6. Bestimmung eines Trends mithilfe des Indikators NRTR

Hier geben die großen blauen Punkte den aufsteigenden Trend wieder, während die großen roten Punkte dem absteigenden entsprechen. Im unteren Teil des Diagramms ist unser weiter unten beschriebener Trendindikator NRTRTrendDetector abgebildet.

2.6. Trendermittlung anhand von drei Heiken Ashi-Kerzen

Recht beliebt ist das Trendermittlungsverfahren mithilfe der Heiken Ashi-Kerzen. Bei den Heiken Ashi-Diagrammen handelt es sich um abgewandelte japanische Kerzendiagramme, deren Werte zum Teil mit denen der jeweils vorhergehenden Kerze gemittelt werden.

Veranschaulichen wir uns das Gesagte:

Abbildung 7. Trendermittlung anhand der Farbe von Heiken Ashi-Kerzen

Abbildung 7. Trendermittlung anhand der Farbe von Heiken Ashi-Kerzen

Unübersehbar ist auch dieses Verfahren bei Kursschwankungen in einem seitlich verlaufenden Korridor nicht frei von „falschen“ Signalen. Noch ungünstiger ist jedoch, dass dieser Indikator nicht nur den letzten Balken neu zeichnen kann, sondern auch den vorletzten. Was bedeutet, dass das Signal, auf welches hin wir eingestiegen sind, sich beim nächsten Balken in sein Gegenteil verkehren kann. Das geschieht, weil bei der Bestimmung der Farbe der Kerzen zwei Balken analysiert werden, weshalb es sich empfiehlt, dieses Verfahren nur zusammen mit anderen bestätigenden Signalen zu verwenden.


3. Trendindikatoren

Kommen wir jetzt zur Erstellung von Trendindikatoren.

3.1. Trendindikator auf der Grundlage gleitender Durchschnittswerte

Der einfachste Indikator sowie die einfachste Methode zur Ermittlung eines Trends beruht auf dem gleitenden Durchschnittswert (Moving Average, MA). Sehen wir uns also an, aus welchen Bestandteilen er sich zusammensetzt. Der vollständige Programmcode für den Indikator befindet sich in der Datei MATrendDetector.MQ5 im Anhang zu diesem Beitrag.

Zu Beginn des Programmtextes für den Indikator steht eine Zeile, die ihn mit der Bibliothek zur Berechnung der unterschiedlichen gleitenden Durchschnittswerte verknüpft. Diese Bibliothek ist im Lieferumfang des Ausgabegerätes enthalten und kann unmittelbar nach dessen Einrichtung verwendet werden. Besagte Zeile hat folgendes Aussehen:

#include <MovingAverages.mqh>

Wir verwenden eine Funktion aus ihr zur Berechnung eines einfachen gleitenden Durchschnittswerts:

double SimpleMA(const int position, const int period, const double &price[])

Dort werden die Eingangsparameter mit folgenden Werten angegeben:

Die Funktion gibt den errechneten Wert für den gleitenden Durchschnitt aus.

Der nächste Teil des Textes enthält die ersten Einstellungen zur Abbildung des Indikators auf dem Bildschirm:

//---------------------------------------------------------------------
#property indicator_separate_window
//---------------------------------------------------------------------
#property indicator_applied_price       PRICE_CLOSE
#property indicator_minimum             -1.4
#property indicator_maximum             +1.4
//---------------------------------------------------------------------
#property indicator_buffers             1
#property indicator_plots               1
//---------------------------------------------------------------------
#property indicator_type1               DRAW_HISTOGRAM
#property indicator_color1              Black
#property indicator_width1              2
//---------------------------------------------------------------------

Folgende Einstellungen werden festgelegt:

Die beiden zuletzt genannten Parameter ermöglichen die Angabe eines festen Maßstabs zur Abbildung des Indikatordiagramms. Das ist möglich, weil wir die Extremwerte unseres Indikators, „-1“ bis „1“ jeweils einschließlich, kennen. Das geschieht, damit das Diagramm hübsch anzusehen ist und nicht über die Ränder des Fensters und über die Titelzeile hinausläuft.

Es folgt der Teil zur Eingabe der externen Parameter des Indikators, die sich im Verlauf der Arbeit sowie bei der Abbildung des Indikators im Diagramm ändern können.

input int   MAPeriod = 200;

Hier liegt lediglich ein Parameter vor, der Wert des Zeitraumes für den gleitenden Durchschnitt.

Der nächste wesentliche Teil des Indikators sind die Funktionen zur Verarbeitung unterschiedlicher im Verlauf der Arbeit des Indikators in dem Diagramm eintretender Ereignisse.

Die erste dieser Funktionen ist die Bereitstellungsfunktion OnInit(). Sie wird unmittelbar nach dem Laden des Indikators aufgerufen. Bei unserem Indikator sieht sie folgendermaßen aus:

void OnInit()
{
  SetIndexBuffer( 0, TrendBuffer, INDICATOR_DATA );
  PlotIndexSetInteger( 0, PLOT_DRAW_BEGIN, MAPeriod );
}

Die Funktion SetIndexBuffer() verknüpft das bereits zuvor deklarierte Datenfeld TrendBuffer[] zur Speicherung der Werte der Trendrichtung mit einem der Indikatorpuffer. Bei uns liegt insgesamt lediglich ein Indikatorpuffer vor, und er trägt die Kennziffer Null („0“).

Die Funktion PlotIndexSetInteger() legt die Anzahl der Anfangsbalken fest, ohne sie im Fenster des Indikators abzubilden.

Da es mathematisch unmöglich ist, einen einfachen gleitenden Durchschnittswert anhand einer Anzahl von Balken zu berechnen, die geringer ist als sein Zeitraum, geben wir eine Balkenzahl vor, die dem Zeitraum für den Durchschnittswert entspricht.

Weiter geht die Funktion zur Verarbeitung von Ereignissen bezüglich der Notwendigkeit zur Neuberechnung des Indikators OnCalculate():

int OnCalculate(const int _rates_total, 
                const int _prev_calculated,
                const int _begin, 
                const double& _price[ ] )
{
  int  start, i;

//   If number of bars on the screen is less than averaging period, calculations can't be made:
  if( _rates_total < MAPeriod )
  {
    return( 0 );
  }

//  Determine the initial bar for indicator buffer calculation:
  if( _prev_calculated == 0 )
  {
    start = MAPeriod;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//      Loop of calculating the indicator buffer values:
  for( i = start; i < _rates_total; i++ )
  {
    TrendBuffer[ i ] = TrendDetector( i, _price );
  }

  return( _rates_total );
}

Diese Funktion wird erstmals unmittelbar nach Bereitstellung des Indikators aufgerufen und danach bei jeder Änderung der Kursdaten. Etwa bei Auftreten einer Kursänderung bei dem Kürzel, für das der Indikator berechnet wird. Sehen wir sie uns etwas genauer an:

Zunächst prüfen wir, ob in dem Diagramm eine ausreichende Balkenanzahl vorhanden ist, ist diese geringer als der Zeitraum des gleitenden Durchschnittswerts, haben wir nichts zu berechnen, und die Ausführung der Funktion wird mithilfe des Operators return beendet. Ist die Anzahl der Balken groß genug für die Berechnungen, legen wir den ersten Balken fest, ab dem der Indikator berechnet wird. Das geschieht, damit nicht alle Indikatorwerte bei jeder Kursänderung neu berechnet werden müssen.

Wir verwenden dazu das in dem Ausgabegerät angelegte Verfahren. Bei jedem Aufruf der Verarbeitungsfunktion überprüfen wir den Wert des Arguments der Funktion _prev_calculated, dabei handelt es sich um die Anzahl der Balken, die beim vorhergehenden Aufruf der Funktion OnCalculate() verarbeitet wurden. Ist dieser gleich Null, so müssen alle Indikatorwerte neu berechnet werden. Andernfalls berechnen wir nur den letzten Balken neu, er hat die Kennziffer _prev_calculated - 1.

Der Arbeitsgang zur Berechnung der Werte des Indikatorpuffers wird von dem Operator for ausgeführt, in seinem Hauptteil wird die Trendermittlungsfunktion TrendDetector für jeden neu berechneten Wert des Indikatorpuffers aufgerufen. Auf diese Weise können wir durch die entsprechende Neufestlegung nur dieser Funktion unterschiedliche Algorithmen zur Berechnung der Trendrichtung umsetzen. Dabei bleiben die übrigen Teile des Indikators nahezu unverändert (möglicherweise ändern sich vorgegebene externe Parameter).

Kommen wir jetzt zur Betrachtung der Trendermittlungsfunktion TrendDetector selbst.

int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma;
  int     trend_direction = 0;

  current_ma = SimpleMA(_shift, MAPeriod, _price);

  if(_price[_shift] > current_ma)
  {
    trend_direction = 1;
  }
  else if(_price[_shift] < current_ma)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Die Funktion führt folgende Aufgaben aus:

Die Ausgabe von „0“ besagt, dass die Richtung des Trends nicht bestimmt werden konnte.

Das Ergebnis der Arbeit des Indikators zeigen die Abbildungen 2 und 3.

3.2. Trendindikator auf der Grundlage eines „Fächers“ gleitender Durchschnittswerte

Sehen wir einmal nach, wie wir ausgehend von diesem Indikator einen etwas umfangreicheren Indikator erstellen können, der zur Ermittlung des Trends einen ganzen „Fächer“ gleitender Durchschnittswerte verwendet.

Der vollständige Programmcode für den Indikator befindet sich in der Datei FanTrendDetector.MQ5 im Anhang zu diesem Beitrag.

Die Unterschiede zwischen diesem Indikator und dem vorhergehenden sind folgende:

input int MA1Period = 200; // period value of senior moving average
input int MA2Period = 50;  // period value of medium moving average
input int MA3Period = 21;  // period value of junior moving average
int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma1, current_ma2, current_ma3;
  int     trend_direction = 0;

  current_ma1 = SimpleMA(_shift, MA1Period, _price);
  current_ma2 = SimpleMA(_shift, MA2Period, _price);
  current_ma3 = SimpleMA(_shift, MA3Period, _price);

  if(current_ma3 > current_ma2 && current_ma2 > current_ma1)
  {
    trend_direction = 1;
  }
  else if(current_ma3 < current_ma2 && current_ma2 < current_ma1)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Die Funktion überprüft die „Richtigkeit“ der Anordnung der gleitenden Durchschnittswerte, indem sie sie und ihre Reihenfolge mithilfe der Operatoren if...else miteinander vergleicht. Wenn die Durchschnittswerte in aufsteigender Reihenfolge angeordnet sind, wird eine „1“ für einen aufsteigenden Trend ausgegeben. Sind sie dagegen in absteigender Reihenfolge angeordnet, lautet die Ausgabe „-1“ für einen absteigenden Trend. Wenn beide zu prüfenden if-Bedingungen unzutreffend (false) sind, wird eine Null ausgegeben (Es konnte kein Trend ermittelt werden). In die Funktion werden zwei Argumente eingegeben: die Verlagerung im Puffer für den analysierten Balken sowie der Puffer mit der Kursreihe selbst.

Die übrigen Teile dieses Indikators stimmen mit denen des vorherigen überein.

3.3. Trendindikator auf der Grundlage des ZigZag-Indikators

Jetzt betrachten wir die Erstellung eines Indikators, der die Ausschläge der geknickten ZigZag-Linie zur Ermittlung des Vorliegens von Extremwerten sowie zur Bestimmung der Trendrichtung nach Charles Dow verwendet. Der vollständige Programmcode für den Indikator befindet sich in der Datei ZigZagTrendDetector.MQ5 im Anhang zu diesem Beitrag.

In den externen Variablen werden die Werte der Parameter des aufgerufenen Indikators ZigZag festgelegt:

//---------------------------------------------------------------------
//  External parameters:
//---------------------------------------------------------------------
input int   ExtDepth = 5;
input int   ExtDeviation = 5;
input int   ExtBackstep = 3;
//---------------------------------------------------------------------

Eine wichtige Besonderheit dieses Indikators besteht in der Anzahl der Indikatorpuffer. Hier kommen außer dem abgebildeten Puffer zwei weitere Berechnungspuffer zum Einsatz. Deshalb wurde die entsprechende Einstellung im Code des Indikators geändert:

#property indicator_buffers  3

Wir fügen zwei weitere Puffer hinzu. In ihnen werden die aus dem externen Indikator ZigZag erhaltenen Extremwerte gespeichert:

double ZigZagHighs[];  // zigzag's upper turnarounds
double ZigZagLows[];   // zigzag's lower turnarounds

Außerdem müssen wir die Verarbeitung des Ereignisses der Bereitstellung des Indikators modifizieren und die Verwendung dieser beiden zusätzlichen Puffer als Berechnungspuffer festlegen.

//  Buffers to store zigzag's turnarounds
SetIndexBuffer(1, ZigZagHighs, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, ZigZagLows, INDICATOR_CALCULATIONS);

Im Berechnungsteil des Codes der Funktion OnCalculate ist ebenfalls das Einlesen der Ausschläge der Zickzacklinie in unsere Puffer vorzusehen. Das geschieht folgendermaßen:

//  Copy upper and lower zigzag's turnarounds to buffers:
  CopyBuffer(indicator_handle, 1, 0, _rates_total - _prev_calculated, ZigZagHighs);
  CopyBuffer(indicator_handle, 2, 0, _rates_total - _prev_calculated, ZigZagLows);

//  Loop of calculating the indicator buffer values:
  for(i = start; i < _rates_total; i++)
  {
    TrendBuffer[i] = TrendDetector(i);
  }

Die Funktion TrendDetector hat folgendes Aussehen:

//---------------------------------------------------------------------
//  Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
double    ZigZagExtHigh[2];
double    ZigZagExtLow[2];
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int    trend_direction = 0;

//  Find last four zigzag's turnarounds:
  int    ext_high_count = 0;
  int    ext_low_count = 0;
  for(int i = _shift; i >= 0; i--)
  {
    if(ZigZagHighs[i] > 0.1)
    {
      if(ext_high_count < 2)
      {
        ZigZagExtHigh[ext_high_count] = ZigZagHighs[i];
        ext_high_count++;
      }
    }
    else if(ZigZagLows[i] > 0.1)
    {
      if(ext_low_count < 2)
      {
        ZigZagExtLow[ext_low_count] = ZigZagLows[i];
        ext_low_count++;
      }
    }

//  If two pairs of extrema are found, break the loop:
    if(ext_low_count == 2 && ext_high_count == 2)
    {
      break;
    }
  }

//  If required number of extrema is not found, the trend can't be determined:
  if(ext_low_count != 2 || ext_high_count != 2)
  {
    return(trend_direction);
  }

//  Check Dow's condition fulfillment:
  if(ZigZagExtHigh[0] > ZigZagExtHigh[1] && ZigZagExtLow[0] > ZigZagExtLow[1])
  {
    trend_direction = 1;
  }
  else if(ZigZagExtHigh[0] < ZigZagExtHigh[1] && ZigZagExtLow[0] < ZigZagExtLow[1])
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Hier werden die vier letzten Extremwerte der Zickzacklinie gesucht. Die Besonderheit dieser Suche besteht darin, dass sie tief in die Vergangenheit zurückgeht. Aus diesem Grund nimmt die Kennziffer im Arbeitsgang for mit jeder Iteration um eins ab bis zum Wert Null. Wenn Extremwerte vorhanden sind, werden sie im Hinblick auf die Übereinstimmung mit der Trendermittlung nach Dow miteinander verglichen. Es gibt zwei mögliche Varianten der Anordnung der Extremwerte, eine für einen aufsteigenden und eine für einen absteigenden Trend. Diese Varianten werden mithilfe der Operatoren if...else überprüft.

3.4. Trendindikator auf der Grundlage des Indikators ADX

Jetzt kommen wir zu dem Aufbau des Trendindikators ADXTrendDetector, der sich des Indikators ADX bedient. Der vollständige Programmcode für den Indikator befindet sich in der Datei ADXTrendDetector.MQ5 im Anhang zu diesem Beitrag. In den externen Variablen werden die Werte der Parameter des aufgerufenen Indikators ADX festgelegt:

//---------------------------------------------------------------------
//      External parameters
//---------------------------------------------------------------------
input int  PeriodADX     = 14;
input int  ADXTrendLevel = 20;

Die Funktion TrendDetector hat folgendes Aussehen:

//---------------------------------------------------------------------
//  Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  ADXBuffer[ 1 ];
  double  PlusDIBuffer[ 1 ];
  double  MinusDIBuffer[ 1 ];

//  Copy ADX indicator values to buffers:
  CopyBuffer(indicator_handle, 0, _shift, 1, ADXBuffer);
  CopyBuffer(indicator_handle, 1, _shift, 1, PlusDIBuffer);
  CopyBuffer(indicator_handle, 2, _shift, 1, MinusDIBuffer);

//  If ADX value is considered (trend strength):
  if(ADXTrendLevel > 0)
  {
    if(ADXBuffer[0] < ADXTrendLevel)
    {
      return(trend_direction);
    }
  }

//  Check +DI and -DI positions relative to each other:
  if(PlusDIBuffer[0] > MinusDIBuffer[0])
  {
    trend_direction = 1;
  }
  else if(PlusDIBuffer[0] < MinusDIBuffer[0])
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

Mithilfe der Funktion CopyBuffer() erhalten wir die Werte aus den erforderlichen Indikatorpuffern des externen Indikators ADX für die von dem Argument _shift vorgegebene Balkennummer. Weiterhin analysieren wir die Lage der Linien +DI und -DI relativ zueinander. Gegebenenfalls müssen wir auch den Wert für die Stärke des Trends berücksichtigen, denn wenn die Trendstärke nicht der Vorgabe entspricht, kann der Trend nicht ermittelt werden.

3.5. Trendindikator auf der Grundlage des Indikators NRTR

Der Aufbau des Trendindikators NRTRTrendDetector, der auf dem Indikator NRTR beruht, ist mit dem vorherigen vergleichbar. Der vollständige Programmcode für den Indikator befindet sich in der Datei NRTRTrendDetector.MQ5 im Anhang zu diesem Beitrag.

Der erste Unterschied findet sich in dem Codeblock mit der Vorgabe der externen Parameter:

//---------------------------------------------------------------------
//      External parameters:
//---------------------------------------------------------------------
input int     ATRPeriod =  40;    // ATR period, in bars
input double  Koeff     = 2.0;    // Coefficient of ATR value change   
//---------------------------------------------------------------------

Der zweite Unterschied liegt in der Funktion TrendDetector zur Ermittlung der Richtung des Trends:

//---------------------------------------------------------------------
//      Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  Support[1];
  double  Resistance[1];

//      Copy NRTR indicator values to buffers::
  CopyBuffer(indicator_handle, 0, _shift, 1, Support);
  CopyBuffer(indicator_handle, 1, _shift, 1, Resistance);

//  Check values of indicator lines:
  if(Support[0] > 0.0 && Resistance[0] == 0.0)
  {
    trend_direction = 1;
  }
  else if(Resistance[0] > 0.0 && Support[0] == 0.0)
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

Hier lesen wir die Werte aus den zwei Puffern des externen Indikators NRTR mit den Kennziffern 0 und 1 aus. Die Werte in dem unterstützenden Puffer (Support) sind bei einem aufsteigenden Trend nicht gleich Null, während die Werte in dem Widerstandspuffer (Resistance) bei einem absteigenden Trend ungleich Null sind.

3.6. Trendermittlung anhand von Heiken Ashi-Kerzen

Jetzt geht es um den Aufbau eines Trendindikators auf der Grundlage der Heiken Ashi-Kerzen.

In diesem Fall wird der externe Indikator nicht aufgerufen, stattdessen berechnen wir die Kerzen eigenhändig. Dadurch wird die Leistungsfähigkeit des Indikators erhöht und Prozessorkapazität für wichtigere Aufgaben freigehalten. Der vollständige Programmcode für den Indikator befindet sich in der Datei HeikenAshiTrendDetector.MQ5 im Anhang zu diesem Beitrag.

Da der Indikator Heiken Ashi keine Festlegung externer Parameter voraussetzt, entfernen wir den Block mit den Eingabeoperatoren (input). Die wesentlichen Veränderungen erwarten uns bei der Verarbeitung von Ereignissen, die eine Neuberechnung des Indikators bedingen. Hier kommt eine alternative Variante der Verarbeitungsroutine zum Einsatz, die uns Zugriff auf alle Kursdatenfelder des jeweiligen Diagramms verschafft.

Die Funktion OnCalculate() hat nunmehr folgendes Aussehen:

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& TickVolume[],
              const long& Volume[], 
              const int& Spread[])
{
  int     start, i;
  double  open, close, ha_open, ha_close;

//  Determine the initial bar for indicator buffer calculation:
  if(_prev_calculated == 0)
  {
    open = Open[0];
    close = Close[0];
    start = 1;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//  Loop of calculating the indicator buffer values:
  for(i = start; i < _rates_total; i++)
  {
//  Heiken Ashi candlestick open price:
    ha_open = (open + close) / 2.0;

//  Heiken Ashi candlestick close price:
    ha_close = (Open[i] + High[i] + Low[i] + Close[i]) / 4.0;

    TrendBuffer[i] = TrendDetector(ha_open, ha_close);

    open = ha_open;
    close = ha_close;
  }

  return(_rates_total);
}

Weil wir zur Bestimmung der Farbe der Heiken Ashi-Kerzen nur zwei Kurse benötigen, den Eröffnungs- und den Schlusskurs, werden auch nur diese berechnet. 

Nach der Ermittlung der Richtung des Trends durch Aufruf der Funktion TrendDetector, speichern wir die aktuellen Kurswerte der Heiken Ashi-Kerzen in die Hilfsvariablen open und close. Die Funktion TrendDetector sieht also überaus einfach aus. Ihr Code könnte vollständig in die Verarbeitungsroutine OnCalculate eingefügt werden, wir belassen ihn jedoch im Interesse einer größeren Vielseitigkeit bei der eventuellen Weiterentwicklung und Verkomplizierung des Algorithmus‘ in dieser Funktion. Hier haben wir ihre grafische Gestalt:

int TrendDetector(double _open, double _close)
{
  int    trend_direction = 0;

  if(_close > _open)         // if candlestick is growing, then it is the up trend
  {
    trend_direction = 1;
  }
  else if(_close < _open)     // if candlestick is falling, then it is the down trend
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

Als Argumente werden zwei Kurse für die Heiken Ashi-Kerze in die Funktion eingegeben, der Eröffnungs- und der Schlusskurs, durch sie wird die Richtung der Funktion bestimmt.


4. Beispiel für die Verwendung eines Indikators zur Trendermittlung in einem Expert-System

Wir werden ein Expert-System programmieren, der sich unterschiedlicher Indikatoren bedient. Es wird interessant, die Ergebnisse der Arbeit von Expert-Systemen zu vergleichen, in denen unterschiedliche Verfahren zur Verfolgung des Trends zum Einsatz kommen. Zunächst überprüfen wir die mithilfe der Standardparameter gewonnenen Ergebnisse und versuchen, die geeignetsten auszusondern.

In diesem Fall besteht der Zweck der Entwicklung der Expert-Systeme im Vergleich der Trendermittlungsverfahren hinsichtlich ihrer Genauigkeit und Schnelligkeit. Deshalb formulieren wir die allgemeinen Grundsätze für die Erstellung sämtlicher Expert-Systeme:

Alle von uns erstellten Trendindikatoren beinhalten einen Indikatorpuffer mit der Kennziffer Null, in dem die erforderlichen Daten bezüglich der Trendrichtung zwischengespeichert sind. Er kommt in Expert-Systemen zum Empfang von Signalen für das Eröffnen/Schließen einer Position zum Einsatz.

Da wir Handelsfunktionen benötigen, haben wir eine Verknüpfung zu der im Lieferumfang von MetaTrader 5 enthaltenen Bibliothek hergestellt. Diese Bibliothek enthält die Klasse CTrade sowie einige Methoden für die Arbeit mit Positionen und Aufträgen. Das vereinfacht die Routinearbeit mit Handelsfunktionen. Die Einbindung der Bibliothek erfolgt in folgender Zeile:

#include <Trade\Trade.mqh>

Wir werden zwei ihrer Methoden verwenden: Eröffnen einer Position und Schließen einer Position. Die erste Methode ermöglicht uns das Eröffnen einer Position der vorgegebenen Richtung und Größe:

PositionOpen(const string symbol, 
             ENUM_ORDER_TYPE order_type,
             double volume, double price,
             double sl, double tp, const string comment )

Es folgen die Eingabeargumente:

Mit der zweiten Methode kann eine Position geschlossen werden:

PositionClose( const string symbol, ulong deviation )

Sie hat folgende Eingabeargumente:

Untersuchen wir den Aufbau des Expert-Systems, das den Indikator MATrendDetector verwendet, etwas genauer. Der vollständige Programmcode für dieses Expert-System befindet sich in der Datei MATrendExpert.MQ5 im Anhang zu diesem Beitrag. Sein erster wichtiger Block ist der zur Festlegung der externen Parameter.

input double Lots = 0.1;
input int    MAPeriod = 200;

Ein Parameter des Expert-Systems ist der Umfang des Postens bei Eröffnung der Position, er heißt Lots (Posten). Um vergleichbare Ergebnisse der Arbeit unterschiedlicher Methoden zur Trendermittlung zu erhalten, verwenden wir einen konstanten Posten (Lot) ohne Kapitalverwaltungsfunktion. Alle weiteren externen Parameter beziehen sich auf die oben erörterten Trendindikatoren. Ihre Anordnung und Zweckbestimmung sind genau dieselben wie bei dem jeweiligen Indikator.

Der zweite wichtige Block im Code des Expert-Systems bezieht sich auf die Verarbeitungsroutine für Ereignisse bezüglich der Bereitstellung des Expert-Systems.

//---------------------------------------------------------------------
//      Initialization event handler:
//---------------------------------------------------------------------
int OnInit()
{
//  Create external indicator handle for future reference to it:
  ResetLastError();
  indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Examples\\MATrendDetector", MAPeriod);

// If initialization was unsuccessful, return nonzero code:
  if(indicator_handle == INVALID_HANDLE)
  {
    Print("MATrendDetector initialization error, Code = ", GetLastError());
    return(-1);
  }
  return(0);
}

Hier legen wir ein Handle an, das auf den Trendindikator verweist, und erhalten im Erfolgsfall als Ausgabe die Kennziffer Null. Wenn das Indikator-Handle nicht erstellt werden konnte (zum Beispiel weil der Indikator nicht im Format EX5 zusammengestellt wurde), wird eine entsprechende Meldung ausgegeben und die Kennziffer ist nicht Null. In diesem Fall wird die Arbeit des Expert-Systems abgebrochen, und es wird mit einem entsprechenden Protokolleintrag von dem Ausgabegerät entfernt.

Der nächste Block gilt der Verarbeitungsroutine für Ereignisse bezüglich der Bereitstellung des Expert-Systems.

//---------------------------------------------------------------------
//      Indicator deinitialization event handler:
//---------------------------------------------------------------------
void OnDeinit(const int _reason)
{
//  Delete indicator handle:
  if(indicator_handle != INVALID_HANDLE)
  {
    IndicatorRelease(indicator_handle);
  }
}

Hier erfolgt die Entfernung des Indikatorhandles sowie die Freigabe des von ihm belegten Speicherplatzes.

Weitere Bereinigungsmaßnahmen sind nicht erforderlich.

Weiter geht es mit dem Hauptblock des Expert-Systems, der Verarbeitungsroutine für das Ereignis einer neuen Kursänderung, eines neuen Ticks, bei dem aktuellen Kürzel.

//---------------------------------------------------------------------
//  Handler of event about new tick by the current symbol:
//---------------------------------------------------------------------
int    current_signal = 0;
int    prev_signal = 0;
bool   is_first_signal = true;
//---------------------------------------------------------------------
void OnTick()
{
//  Wait for beginning of a new bar:
  if(CheckNewBar() != 1)
  {
    return;
  }

//  Get signal to open/close position:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

//  Select position by current symbol:
  if(PositionSelect(Symbol()) == true)
  {
//  Check if we need to close a reverse position:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

//  Check if there is the BUY signal:
  if(CheckBuySignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK ), 0, 0);
  }

//  Check if there is the SELL signal:
  if(CheckSellSignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID ), 0, 0);
  }

//  Save current signal:
  prev_signal = current_signal;
}

Kommen wir zu den von dem Expert-System verwendeten Zusatzfunktionen.

Vor allem muss unser Expert-System prüfen, ob ein Signal zur Eröffnung des nächsten Diagrammbalkens vorhanden ist. Dazu verwendet er die Funktion CheckNewBar:

//---------------------------------------------------------------------
//  Returns flag of a new bar:
//---------------------------------------------------------------------
//  - if it returns 1, there is a new bar
//---------------------------------------------------------------------
int CheckNewBar()
{
  MqlRates  current_rates[1];

  ResetLastError();
  if(CopyRates(Symbol(), Period(), 0, 1, current_rates)!= 1)
  {
    Print("CopyRates copy error, Code = ", GetLastError());
    return(0);
  }

  if(current_rates[0].tick_volume>1)
  {
    return(0);
  }

  return(1);
}

Das Vorliegen eines neuen Balkens wird durch die Größe der Kursänderung bestimmt. Bei der Eröffnung eines neuen Balkens ist sein anfänglicher Umfang gleich Null (da für ihn noch keine einzige Notierung vorlag). Bei Eingang der ersten Kursänderung erhält der Umfang den Wert „1“.

In dieser Funktion legen wir das aus einem Element bestehende Datenfeld current_rates[] mit Strukturen der Art MqlRates an, kopieren die aktuellen Informationen über Kurse und Umfänge hinein und überprüfen anschließend den Wert für den Umfang der Kursänderung. 

In unserer Ereignisverarbeitungsroutine für neue Kursänderungen bei dem jeweiligen Kürzel setzen wir diese Funktion folgendermaßen ein:

//  Wait for beginning of a new bar:
if(CheckNewBar()!= 1)
{
  return;
}

Somit wurde ein neuer Balken eröffnet, und wir können ein Signal bezüglich der Richtung des aktuellen Trends erhalten. Das geschieht folgendermaßen:

//  Get signal to open/close position:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

Da wir die Trendänderungen beobachten müssen, ist es unverzichtbar, den Wert des Trends im vorhergehenden Balken zu speichern. In dem oben abgebildeten Codeausschnitt wird dazu die Variable prev_signal genutzt. Außerdem sollte eine Markierung (Flag) verwendet werden, um darauf hinzuweisen, dass es sich um das erste Signal handelt (vorhergehende gibt es noch nicht). Dabei handelt es sich um die Variable is_first_signal. Wenn die Markierung den Wert „true“ aufweist, wird die Variable prev_signal mit dem Anfangswert bereitgestellt.

Hier kommt die Funktion GetSignal zum Einsatz, sie gibt die von unserem Indikator bezogene Richtung des aktuellen Trends aus. So sieht sie aus:  

//---------------------------------------------------------------------
//      Get signal to open/close position:
//---------------------------------------------------------------------
int GetSignal()
{
  double    trend_direction[1];

//  Get signal from trend indicator:
  ResetLastError();
  if(CopyBuffer(indicator_handle, 0, 0, 1, trend_direction) != 1)
  {
    Print("CopyBuffer copy error, Code = ", GetLastError());
    return(0);
  }

  return((int)trend_direction[0]);
}

Die Daten des Trendindikators werden aus dem Nullpuffer in unser aus einem Element bestehendes Datenfeld trend_direction kopiert. Und der Wert des Datenfeldelements wird von der Funktion ausgegeben. Dabei erfolgt auch die Umwandlung der Art double in die Art int, um die Ausgabe einer Warnmeldung bei der Zusammenstellung durch den Compiler zu vermeiden.

Vor der Eröffnung einer neuen Position ist zu überprüfen, ob nicht zunächst eine bereits früher eröffnete gegenläufige Position geschlossen werden muss. Außerdem muss geprüft werden, ob nicht bereits eine offene Position in derselben Richtung vorhanden ist. Das wird alles von dem folgenden Codeausschnitt erledigt:

//  Select position by current symbol:
  if(PositionSelect(Symbol()) == true)
  {
//  Check if we need to close a reverse position:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

Um auf die Position zugreifen zu können, muss sie zunächst ausgewählt werden, was mithilfe der Funktion PositionSelect() für das entsprechende Kürzel geschieht. Gibt die Funktion den Wert „true“ aus, so bedeutet dies, dass die Position vorhanden ist und erfolgreich ausgewählt wurde, wir können also mit ihrer Bearbeitung beginnen.

Zum Schließen der gegenläufigen Position steht die Funktion CheckPositionClose bereit:

//---------------------------------------------------------------------
//  Check if we need to close position:
//---------------------------------------------------------------------
//  Returns:
//    0 - no open position
//    1 - position already opened in signal's direction
//---------------------------------------------------------------------
int CheckPositionClose(int _signal)
{
  long    position_type = PositionGetInteger(POSITION_TYPE);

  if(_signal == 1)
  {
//  If there is the BUY position already opened, then return:
    if(position_type == (long)POSITION_TYPE_BUY)
    {
      return(1);
    }
  }

  if(_signal==-1)
  {
//  If there is the SELL position already opened, then return:
    if( position_type == ( long )POSITION_TYPE_SELL )
    {
      return(1);
    }
  }

//  Close position:
  CTrade  trade;
  trade.PositionClose(Symbol(), 10);

  return(0);
}

Zunächst wird geprüft, ob die Position in Richtung des Trends eröffnet wurde. Ist dem so, gibt die Funktion eine Zahleneinheit (1) aus und die aktuelle Position wird nicht geschlossen. Wenn jedoch eine Position in der gegenläufigen Trendrichtung offen ist, muss diese zunächst geschlossen werde. Das erfolgt durch die oben dargestellte Methode PositionClose. Wenn keine weiteren offenen Positionen mehr vorhanden sind, wird eine „Null“ ausgegeben.

Nach Abschluss aller erforderlichen Überprüfungen und Maßnahmen an den bestehenden Positionen ist zu prüfen, ob ein neues Signal vorliegt. Das erfolgt mittels des folgenden Codeausschnitts:

//  Check if there is the BUY signal:
if(CheckBuySignal(current_signal, prev_signal)==1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK), 0, 0);
}

Liegt ein Kaufsignal (Buy) vor, so wird eine lange Position (long) in dem vorgegebenen Umfang zum aktuellen Nachfragekurs SYMBOL_ASK eröffnet. Da alle Positionen durch das gegenläufige Signal geschlossen werden, werden weder Take Profit (tp) noch Stop Loss (sl) benötigt, das Expert-System befindet „ständig am Markt“. 

Im wirklichen Handel empfiehlt sich der Einsatz eines Stop Loss als Sicherheit zur Verlustbegrenzung im Fall unvorhergesehener Umstände, beispielsweise bei einer Unterbrechung der Verbindung zu dem DC-Server sowie anderer Akte höherer Gewalt.

Hinsichtlich der Verkaufssignale verhält sich alles ähnlich:

//  Check if there is the SELL signal:
if(CheckSellSignal(current_signal, prev_signal) == 1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID), 0, 0);
}

Der einzige Unterschied besteht im Verkaufspreis - SYMBOL_BID.

Das Vorliegen eines Signals für den Kauf (Buy) wird mithilfe der Funktion CheckBuySignal geprüft, für den Verkauf (Sell) mit CheckSellSignal. Diese Funktionen sind sehr schlicht und leicht verständlich:

//---------------------------------------------------------------------
//  Check if signal has changed to BUY:
//---------------------------------------------------------------------
//  Returns:
//    0 - no signal
//    1 - there is the BUY signal
//---------------------------------------------------------------------
int CheckBuySignal(int _curr_signal, int _prev_signal)
{
//  Check if signal has changed to BUY:
  if((_curr_signal==1 && _prev_signal==0) || (_curr_signal==1 && _prev_signal==-1))
  {
    return(1);
  }

  return(0);
}

//---------------------------------------------------------------------
//  Check if there is the SELL signal:
//---------------------------------------------------------------------
//  Returns:
//    0 - no signal
//    1 - there is the SELL signal
//---------------------------------------------------------------------
int CheckSellSignal(int _curr_signal, int _prev_signal)
{
//  Check if signal has changed to SELL:
  if((_curr_signal==-1 && _prev_signal==0) || (_curr_signal==-1 && _prev_signal==1))
  {
    return(1);
  }

  return(0);
}

Hier wird geprüft, ob sich der Trend umgekehrt hat oder ob eine Trendrichtung ermittelt wurde. Ist eine dieser Bedingungen erfüllt, meldet die Funktion das Vorliegen eines Signals.

Im Großen und Ganzen bietet dieser Aufbau eines Expert-Systems eine recht universelle Struktur, die leicht zu aktualisieren und zu erweitern ist, um komplexere Algorithmen zu verarbeiten.

Alle anderen Expert-Systeme folgen demselben Aufbau. Die Hauptunterschiede liegen einzig im Block für die Festlegung der externen Parameter. Diese müssen dem jeweils verwendeten Trendindikator entsprechen und werden bei der Erstellung des Indikatorhandles als Argumente weitergegeben.

Sehen wir uns jetzt die Arbeitsergebnisse unseres ersten Expert-Systems anhand historischer Daten an. Wir verwenden dazu die historischen Daten des Kürzels EURUSD in der Zeit vom 01. 04. 2004 bis zum 06. 08. 2010 anhand der Tagesbalken. Nachdem das Expert-System das Testprogramm (Strategy Tester) mit den Standardparametern durchlaufen hat, erhalten wir folgendes Bild:

Abbildung 8. Prüfergebnis für das auf dem Indikator MATrendDetector beruhende Expert-System

Abbildung 8. Prüfergebnis für das auf dem Indikator MATrendDetector beruhende Expert-System

Strategietesterbericht
MetaQuotes-Demo (Build 302)

Einstellungen
Expertenprogramm: MATrendExpert
Symbol: EURUSD
Periode: Daily (01. 04. 2004 - 06. 08. 2010)
Parameter: Lots = 0,100000

MAPeriod = 200
Broker: MetaQuotes Software Corp.
Währung: USD
Ersteinlage: 10.000,00

Ergebnisse
Balken: 1.649 Ticks: 8.462.551
Nettogewinn gesamt: 3.624,59 Bruttogewinn: 7.029,16 Bruttoverlust: -3.404,57
Profitfaktor: 2,06 Erwartetes Ergebnis: 92,94
Erholungsfaktor: 1,21 Sharpe-Ratio: 0,14

Rückgang Kontostand:
Rückgang Kontostand absolut: 2.822,83 Rückgang Kontostand maximal: 2.822,83 (28,23 %) Rückgang Kontostand relativ: 28,23 % (2.822,83)
Rückgang Equity:
Rückgang Equity absolut: 2.903,68 Rückgang Equity maximal: 2.989,93 (29,64 %) Rückgang Equity relativ: 29,64 % (2.989,93)

Gesamtanzahl Trades: 39 Sell-Positionen (davon gewonnen %): 20 (20,00 %) Buy-Positionen (davon gewonnen %): 19 (15,79 %)
Anzahl Deals: 78 Gewonnene Trades (in % von Gesamt): 7 (17,95 %) Verlorene Trades (in % von Gesamt): 32 (82,05 %)

Größter Gewinntrade: 3.184,14 Größter Verlusttrade: -226,65

Durchschnitt Gewinntrade: 1.004,17 Durchschnitt Verlusttrade: -106,39

Maximum Gewinntrades in Folge (Gewinn in Geld): 4 (5.892,18) Maximum Verlusttrades in Folge (Verlust in Geld): 27 (2.822,83)

Maximum Gewinn aufeinanderfolgender Gewinntrades (Anzahl): 5.892,18 (4) Maximum Verlust aufeinanderfolgender Verlusttrades (Anzahl): -2.822,83 (27)

Durchschnitt Gewinntrades in Folge: 2 Durchschnitt Verlusttrades in Folge: 8


Im Allgemeinen sieht das ganz gut aus, abgesehen von dem Abschnitt ab Beginn der Prüfung bis zum 22. 09. 2004. Bei Betrachtung dieses Abschnitts im Diagramm ist zu erkennen, dass innerhalb eines begrenzten Bereichs eine Seitwärtsbewegung vorherrschte. Unter derartigen Bedingungen hat sich unser einfaches Expert-System als nicht ganz auf der Höhe erwiesen. Hier sehen Sie die Abbildung dieses Zeitraums mit eingezeichneten Abschlüssen:

Abbildung 9. Abschnitt mit Seitwärtsbewegung

Abbildung 9. Abschnitt mit Seitwärtsbewegung

Außerdem wird in dem Diagramm der gleitende Durchschnittswert SMA200 abgebildet.

Sehen wir uns einmal an, was das „weiterentwickelte“ Expert-System auf der Grundlage eines Indikators mit mehreren gleitenden Durchschnittswerten in demselben Zeitraum und mit denselben Standardparametern anzeigt.

Abbildung 10. Prüfergebnis für das auf dem Indikator FanTrendDetector beruhende Expert-System

Abbildung 10. Prüfergebnis für das auf dem Indikator FanTrendDetector beruhende Expert-System

Strategy Tester-Prüfbericht
MetaQuotes-Demo (Build 302)

Einstellungen
Expertenprogramm: FanTrendExpert
Symbol: EURUSD
Periode: Daily (01. 04. 2004 - 06. 08. 2010)
Parameter: Lots = 0,100000

MA1Period = 200 

MA2Period = 50

MA3Period = 21
Broker: MetaQuotes Software Corp.
Währung: USD
Ersteinlage: 10.000,00

Ergebnisse
Balken: 1.649 Ticks: 8462551
Nettogewinn gesamt: 2.839,63 Bruttogewinn: 5.242,93 Bruttoverlust: -2.403,30
Profitfaktor: 2,18 Erwartetes Ergebnis: 149,45
Erholungsfaktor: 1,06 Sharpe-Ratio: 0,32

Rückgang Kontostand:
Rückgang Kontostand absolut: 105,20 Rückgang Kontostand maximal: 1.473,65 (11,73 %) Rückgang Kontostand relativ: 11,73 % (1.473,65)
Rückgang Equity:
Rückgang Equity absolut: 207,05 Rückgang Equity maximal: 2.671,98 (19,78 %) Rückgang Equity relativ: 19,78 % (2.671,98)

Gesamtanzahl Trades: 19 Sell-Positionen (davon gewonnen %): 8 (50,00 %) Buy-Positionen (davon gewonnen %): 11 (63,64 %)
Anzahl Deals: 38 Gewonnene Trades (in % von Gesamt): 11 (57,89 %) Verlorene Trades (in % von Gesamt) 8 (42,11 %)

Größter Gewinntrade: 1.128,30 Größter Verlusttrade: -830,20

Durchschnitt Gewinntrade: 476,63 Durchschnitt Verlusttrade: -300,41

Maximum Gewinntrades in Folge (Gewinn in Geld): 2 (1.747,78) Maximum Verlusttrades in Folge (Verlust in Geld): 2 (-105,20)

Maximum Gewinn aufeinanderfolgender Gewinntrades (Anzahl): 1.747,78 (2) Maximum Verlust aufeinanderfolgender Verlusttrades (Anzahl): -830,20 (1)

Durchschnitt Gewinntrades in Folge: 2 Durchschnitt Verlusttrades in Folge: 1

Schon viel besser. Wenn wir uns unseren „Problemabschnitt“ ansehen, vor dem das vorherige Expert-System kapituliert hat, erhalten wir folgendes Bild:

Abbildung 11. FanTrendExpert-Prüfergebnisse in dem Abschnitt mit der Seitwärtsbewegung

Abbildung 11. FanTrendExpert-Prüfergebnisse in dem Abschnitt mit der Seitwärtsbewegung

Im Vergleich zur Abbildung 9 ist offenkundig, dass die Mehrzahl der „falschen“ Einträge in diesem Abschnitt ausgesondert wurde. Aber auch die Anzahl der Abschlüsse hat sich halbiert, was jedoch vollkommen folgerichtig ist. Bei der Analyse der Kurven der Bilanz/Mittel beider Expert-Systeme sieht man, dass etliche Abschlüsse aus der Perspektive der Erzielung des größtmöglichen Gewinns nicht optimal getätigt wurden. Deshalb besteht die nächste Weiterentwicklung des Expert-Systems in der Verbesserung des Algorithmus für den Abschluss von Handelsvorgängen. Aber das sprengt den Rahmen dieses Beitrags. Wir überlassen es unseren Lesern, das selbst zu tun.


5. Prüfergebnisse der Expert-Systeme

Wir führen eine Überprüfung unserer Expert-Systeme durch. Die Arbeitsergebnisse anhand aller in dem Ausgabegerät verfügbaren historischen Daten von 1993 bis 2010 für das Währungspaar EURUSD und den Zeitraum D1 werden unten abgebildet.

Abbildung 12. Prüfung des Expert-Systems MATrendExpert

Abbildung 12. Prüfung des Expert-Systems MATrendExpert

Abbildung 13. Prüfung des Expert-Systems FanTrendExpert

Abbildung 13. Prüfung des Expert-Systems FanTrendExpert

Abbildung 14. Prüfung des Expert-Systems ADXTrendExpert (ADXTrendLevel = 0)

Abbildung 14. Prüfung des Expert-Systems ADXTrendExpert (ADXTrendLevel = 0)

Abbildung 15. Prüfung des Expert-Systems ADXTrendExpert (ADXTrendLevel = 20)

Abbildung 15. Prüfung des Expert-Systems ADXTrendExpert (ADXTrendLevel = 20)

Abbildung 16. Prüfung des Expert-Systems NRTRTrendExpert

Abbildung 16. Prüfung des Expert-Systems NRTRTrendExpert

Abbildung 17. Prüfung des Expert-Systems Heiken Ashi

Abbildung 17. Prüfung des Expert-Systems Heiken Ashi

Sehen wir uns die Prüfergebnisse genauer an.

Spitzenreiter sind zwei der einfachsten Expert-Systeme, das eine auf der Grundlage gleitender Durchschnittswerte, das andere mit einem „Fächer“ gleitender Durchschnittswerte. Tatsächlich kommen diese Expert-Systeme der Regel zur Verfolgung eines Trends (und somit des Kurses) von allen am nächsten und das einfach nur dank der Verwendung einer geglätteten Reihe von Kursen für den jüngsten Zeitraum. Da ein recht „schwergewichtiger“ gleitender Durchschnittswert mit einem Zeitraum von 200 angelegt wird, nimmt der Einfluss der Volatilität des Marktes sichtlich ab.

Die geringe Anzahl der Abschlüsse bei diesen Expert-Systemen ist kein Nachteil, da die Haltezeit einer Position bis zu einigen Monaten betragen kann, wir verfolgen schließlich einen 200 Tage-Trend. Es ist interessant, wie sich bei dem MATrendExpert die Trendabschnitte, in denen der Kontostand wächst, mit „Flats“ (im Kontext dieses Expert-Systems), in denen Mittel verloren gehen, abwechseln.

Auch das Trendermittlungsverfahren mithilfe des Indikators ADX hat befriedigende Ergebnisse gezeitigt. Dort wurde der Zeitraum PeriodADX leicht verändert und erhielt den Wert 17, wodurch es über den gesamten Verlauf zu gleichmäßigeren Ergebnissen kommt. Der Einfluss des Trendstärkefilters ist unerheblich. Möglicherweise muss der Parameter ADXTrendLevel ausgewählt oder je nach aktueller Marktvolatilität sogar dynamisch eingestellt werden. Es liegen mehrere Zeiträume mit Wertverlusten vor, weshalb ergänzende Maßnahmen zur Angleichung der Kontokurve erforderlich sind.

Der Indikator NRTR legt bei den Standardeinstellungen praktisch eine Ertragsfähigkeit von Null an den Tag, und das sowohl über den gesamten Prüfzeitraum als auch über einen beliebig festgelegten längeren Zeitraum. In gewissem Maße ist dies eine Zeichen für die Beständigkeit der Arbeit dieses Verfahrens zur Trendermittlung. Vielleicht macht eine Anpassung der Parameter dieses Expert-System zu einem gewinnbringenden, das heißt: es ist eine Optimierung erforderlich.

Das auf dem Indikator Heiken Ashi beruhende Expert-System war offenkundig nicht gewinnbringend. Obwohl im historischen Verlauf wohl aufgrund der Abbildung in Echtzeit alles recht nett aussieht, sind die Prüfergebnisse alles andere als ideal. Möglicherweise ergeben sich bei Verwendung der geglätteten Ausführung dieses Indikators, des Smoothed Heiken Ashi, bessere Ergebnisse, da dieser nicht dem Zwang der unmittelbaren Abbildung unterliegt.

Zweifellos stellt ein System zur Pflege offener Positionen mit dynamischer Nachziehung der Stop-Grenze und Festlegung der Zielebene für keines der gewinnbringenden Expert-Systeme ein Hindernis dar. Auch ein Kapitalverwaltungssystem zur Minimierung der Wertverluste wäre alles andere als überflüssig und könnte den Gewinn über einen langen Zeitraum möglicherweise sogar steigern.


Fazit

Es ist also nicht allzu schwierig, einen Programmcode zu schreiben, der die Ermittlung eines Trends ermöglicht. Die Hauptsache dabei ist ein Arbeits- und Denkansatz, der gewisse Gesetzmäßigkeiten des Marktes zu nutzen weiß. Und je grundlegender diese Gesetzmäßigkeiten sind, desto größer wird die Gewissheit, dass das auf ihnen basierende Handelssystem, nicht nach kurzer Einsatzzeit in sich zusammenfällt.