Erzeugung eines Indikators mit mehreren Indikator-Buffern für Anfänger
Einleitung
In meinen vorherigen Beiträgen "Angepasste Indikatoren in MQL5 für Anfänger" und "Praktische Implementierung von Digital Filers in MQL5 für Anfänger" habe ich mich detailliert mit der Indikatorstruktur mit einem Indikator-Buffer beschäftigt.
Eine derartige Methode kann ganz klar großflächig zum Schreiben von angepassten Indikatoren eingesetzt werden, doch das echte Leben kann ja kaum auf ihre Verwendung beschränkt werden, also ist es an der Zeit, sich komplexere Aufbaumethoden des Indikator-Codes anzusehen. Glücklicherweise sind die Fähigkeiten von MQL5 hier unerschöpflich und nur durch das RAM unserer PCs begrenzt.
Der Aroon Indikator als Beispiel der Code-Verdopplung
Die Formel für diesen Indikator enthält zwei Komponenten: die Hausse- und die Baisse-Indikatoren, die in einem extra Chart-Fenster grafisch dargestellt werden:
HAUSSE = (1 - (Balken - SHIFT(MAX(HIGH(), AroonPeriod)))/AroonPeriod) * 100
BAISSE = (1 - (Balken - SHIFT(MIN (LOW (), AroonPeriod)))/AroonPeriod) * 100
wobei:
- HAUSSE - die Stärke der Hausse ist;
- BAISSE - die Stärke der Baisse ist;
- SHIFT() - die Funktion zur Festlegung der Indexposition des Balkens ist;
- MAX() - Die Suchfunktion nach dem Maximum über dem AroonPeriod Zeitraum ist;
- MIN() - Die Suchfunktion nach dem Minimum über dem AroonPeriod Zeitraum ist;
- HIGH() und LOW() - die relevanten Kurs-Arrays darstellen;
Wir können direkt aus den Formeln des Indikators ersehen, dass wir beim Aufbau eines Indikators nur zwei Indikator-Buffer haben dürfen und die Indikatorstruktur nur kaum von der im vorigen Beitrag beschriebenen SMA_1.mq5 Struktur abweicht.
Tatsächlich ist das einfach der gleiche duplizierte Code, nur mit einer anderen Anzahl Indikator-Buffers. Öffnen wir also den Code dieses Indikators im MetaEditor und speichern ihn als Aroon.mq5. In den ersten 11 Zeilen des Codes, die mit Copyright und seiner Versionsnummer zu tun haben, ersetzen wir nur den Namen des Indikators:
//+------------------------------------------------------------------+ //| Aroon.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ //---- copyright #property copyright "2010, MetaQuotes Software Corp." //---- link to the author's site #property link "http://www.mql5.com" //---- version number #property version "1.00"
Danach müssen wir in Zeile 12 des Codes die Zeichnung des Indikators vom einfachen Chart-Fenster in ein separates Fenster verändern:
//---- plot indicator in the separate window #property indicator_separate_window
Das dieser Indikator einen komplett anderen Bereich an Werten hat, erfolgt seine Zeichnung auch in einem extra Fenster.
Danach verändern wir in den folgenden 4 Codezeilen (die allgemeinen Indikatoreigenschaften) die Menge der verwendeten Indikator-Buffer auf zwei:
//---- two buffers are used #property indicator_buffers 2 //---- two plots are used #property indicator_plots 2
Die folgenden 10 Zeilen des Codes hängen mit der Zeichnung des Indikators von einem spezifischen Indikator-Buffer zusammen, dessen Benennung dupliziert werden muss. Danach müssen wir alle Indices von 1 auf 2 ersetzen. Ebenfalls müssen wir alle Kennzeichnungen der Indikator-Buffer verändern:
//+----------------------------------------------+ //| bullish strength indicator parameters | //+----------------------------------------------+ //---- drawing style = line #property indicator_type1 DRAW_LINE //---- drawing color = Lime #property indicator_color1 Lime //---- line style = solid line #property indicator_style1 STYLE_SOLID //---- line width = 1 #property indicator_width1 1 //---- label of the BullsAroon indicator #property indicator_label1 "BullsAroon" //+----------------------------------------------+ //| bearish strength indicator parameters | //+----------------------------------------------+ //---- drawing style = line #property indicator_type2 DRAW_LINE //---- drawing color = Red #property indicator_color2 Red //---- line style = solid line #property indicator_style2 STYLE_SOLID //---- line width = 1 #property indicator_width2 1 //---- label of the BearsAroon indicator #property indicator_label2 "BearsAroon"
Dieser Indikator arbeitet mit drei waagrechten Ebenen mit den Werten 30, 50 und 70.
Um diese Ebenen zeichnen zu können, müssen wir fünf weitere Codezeilen in den Indikator-Code einfügen.
//+----------------------------------------------+ //| Horizontal levels | //+----------------------------------------------+ #property indicator_level1 70.0 #property indicator_level2 50.0 #property indicator_level3 30.0 #property indicator_levelcolor Gray #property indicator_levelstyle STYLE_DASHDOTDOT
Bei den Eingabeparametern des Indikators bleibt alles unverändert, verglichen mit dem vorigen Indikator, außer kleineren Veränderungen an den Titeln:
//+----------------------------------------------+ //| Indicator input parameters | //+----------------------------------------------+ input int AroonPeriod = 9; // Period input int AroonShift = 0; // Horizontal shift of the indicator in bars
Jetzt gibt es allerdings zwei Arrays, die als Indikator-Buffer verwendet werden und sie müssen passende Namen tragen:
//--- declare the dynamic arrays used further as indicator buffers double BullsAroonBuffer[]; double BearsAroonBuffer[];
Und ganz genauso gehen wir beim Code der OnInit() Funktion vor.
Zunächst verändern wir die Zeilen des Codes für den Buffer "0":
//--- set BullsAroonBuffer dynamic array as indicator buffer SetIndexBuffer(0, BullsAroonBuffer, INDICATOR_DATA); //--- horizontal shift (AroonShift) of the indicator 1 PlotIndexSetInteger(0, PLOT_SHIFT, AroonShift); //--- plot draw begin (AroonPeriod) of the indicator 1 PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, AroonPeriod); //--- label shown in DataWindow PlotIndexSetString(0, PLOT_LABEL, "BearsAroon");
Danach wird der gesamte Code in die Windows Zwischenablage kopiert und dann direkt nach dem gleichen Code eingefügt.
Im gerade eingefügten Code ändern wir die Anzahl der Indikator-Buffer von 0 auf 1, geben dem Indikator-Array einen neuen Namen und benennen den Indikator:
//--- set BearsAroonBuffer dynamic array as indicator buffer SetIndexBuffer(1, BearsAroonBuffer, INDICATOR_DATA); //--- horizontal shift (AroonShift) of the indicator 2 PlotIndexSetInteger(1, PLOT_SHIFT, AroonShift); //--- plot draw begin (AroonPeriod) of the indicator 2 PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, AroonPeriod); //--- label shown in DataWindow PlotIndexSetString(1, PLOT_LABEL, "BullsAroon");
Der Kurzname des Indikators hat auch einige kleine Veränderungen erfahren:
//--- initialization of the variable for a short indicator name string shortname; StringConcatenate(shortname, "Aroon(", AroonPeriod, ", ", AroonShift, ")");
Schauen wir uns jetzt die Genauigkeit der Zeichnung des Indikators an. Der tatsächliche Indikatorbereich geht von 0 - 100, und wird die ganze Zeit gezeigt
Hier kann man gut nur die ganzzahligen Werte des Indikators, die auf dem Chart gezeichnet werden, verwenden. Daher nehmen wir zur Zeichnung des Indikators 0 für alle Ziffern nach der Dezimalstelle:
//--- set accuracy of drawing of indicator values IndicatorSetInteger(INDICATOR_DIGITS, 0);
Im SMA_1.mq5 Indikator haben wir die erste Aufrufform der OnCalculate() Funktion verwendet
Das ist für den Aroon Indikator nicht gut, da er keine high[] and low[] Kurs-Arrays hat. Diese Arrays bekommen wir jedoch in der zweiten Aufrufform dieser Funktion. Und deshalb müssen wir die Kopfzeile der Funktion ändern:
int OnCalculate( const int rates_total, // total bars on the current tick const int prev_calculated,// total bars on the previous tick const datetime& time[], const double& open[], const double& high[], // price array of the maximum prices for the indicator calculations const double& low[], // price array of the minimum prices for the indicator calculations const double& close[], const long& tick_volume[], const long& volume[], const int& spread[] )
Nach dieser Veränderung macht die Verwendung des Beginn-Parameters keinen Sinn mehr, also muss der aus dem Code verschwinden!
Der Code zur Berechnung der Limits der Variablenveränderungen des Operationszyklus und die Datenüberprüfung für ausreichende Berechnung, sind praktisch unverändert geblieben.
//--- checking the number of bars if (rates_total < AroonPeriod - 1) return(0); //--- declare the local variables int first, bar; double BULLS, BEARS; //--- calculation of the first (staring index) for the main loop if (prev_calculated == 0) // checking for the first call of the OnCalculate function first = AroonPeriod - 1; // starting index for calculating all of the bars else first = prev_calculated - 1; // starting index for calculating new bars
Dennoch treten gewisse Probleme mit den Algorithmen zur Berechnung der Indikator-Werte auf. Das liegt daran, dass MQL5 keine eingebauten Funktionen zur Feststellung der Indices für Maximal und Minimal für den Zeitraum vom aktuellen Balken in der Richtung des abnehmenden Index hat.
Eine Möglichkeit, dies zu lösen, ist diese Funktionen selbst zu schreiben. Doch zum Glück gibt es diese Funktionen bereits im ZigZag.mq5 Indikator in den angepassten Indikatoren im "MetaTrader5\MQL5\Indikators\Examples" Ordner.
Am einfachsten geht das, wenn man den Code dieser Funktionen im ZigZag.mq5 Indikator auswählt, sie dann in die Windows-Ablage kopiert und in unserem Code einfügt, z.B. direkt nach der Beschreibung der OnInit() Funktion auf der globalen Ebene:
//+------------------------------------------------------------------+ //| searching index of the highest bar | //+------------------------------------------------------------------+ int iHighest(const double &array[], // array for searching for the index of the maximum element int count, // number of the elements in the array (in the decreasing order), int startPos // starting index ) { //---+ int index = startPos; //---- checking the starting index if (startPos < 0) { Print("Incorrect value in the function iHighest, startPos = ", startPos); return (0); } //---- checking the startPos values if (startPos - count < 0) count = startPos; double max = array[startPos]; //---- index search for(int i = startPos; i > startPos - count; i--) { if(array[i] > max) { index = i; max = array[i]; } } //---+ return of the index of the largest bar return(index); } //+------------------------------------------------------------------+ //| searching index of the lowest bar | //+------------------------------------------------------------------+ int iLowest( const double &array[], // array for searching for the index of the maximum element int count, // number of the elements in the array (in the decreasing order), int startPos // starting index ) { //---+ int index = startPos; //--- checking the stating index if (startPos < 0) { Print("Incorrect value in the iLowest function, startPos = ",startPos); return(0); } //--- checking the startPos value if (startPos - count < 0) count = startPos; double min = array[startPos]; //--- index search for(int i = startPos; i > startPos - count; i--) { if (array[i] < min) { index = i; min = array[i]; } } //---+ return of the index of the smallest bar return(index); }
Danach sollte der Code der OnCalculate() Funktion so aussehen:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate( const int rates_total, // total number of bars on the current tick const int prev_calculated,// number of calculated bars on the previous tick const datetime& time[], const double& open[], const double& high[], // price array for the maximum price for the indicator calculation const double& low[], // price array for the minimum price for the indicator calculation const double& close[], const long& tick_volume[], const long& volume[], const int& spread[] ) { //---+ //--- checking the number of bars if (rates_total < AroonPeriod - 1) return(0); //--- declare the local variables int first, bar; double BULLS, BEARS; //--- calculation of the starting bar number if (prev_calculated == 0) // checking for the first start of the indicator calculation first = AroonPeriod - 1; // starting number for the calculation of all of the bars else first = prev_calculated - 1; // starting number for the calculation of new bars //--- main loop for(bar = first; bar < rates_total; bar++) { //--- calculation of values BULLS = 100 - (bar - iHighest(high, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod; BEARS = 100 - (bar - iLowest (low, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod; //--- filling the indicator buffers with the calculated values BullsAroonBuffer[bar] = BULLS; BearsAroonBuffer[bar] = BEARS; } //---+ return(rates_total); } //+------------------------------------------------------------------+
Aus Gründen der Achsensymmetrie habe ich den Code leicht korrigiert, indem ich die waagrechte Verschiebung des Indikators, verglichen mit dem Original-Code, mit Hilfe des Wertes 0,5 hinzugefügt haben.
Hier sehen Sie die Ergebnisse der Arbeit dieses Indikators auf dem Chart:
Um die Position des Elements mit den maximalen oder minimalen Werten in einem Abstand nicht größer als AroonPeriod vom aktuellen Balken zu finden, verwenden wir die in MQL5 eingebauten ArrayMaximum() und ArrayMinimum() Funktionen, die zugleich auch nach den Extremen suchen. Diese Funktionen führen ihre Suche jedoch in aufsteigenden Reihenfolge aus
Die Suche muss jedoch auch in absteigender Reihenfolge der Indices stattfinden. Die hierfür einfachste Lösung ist die Richtung der Indizierung im Indikator und den Kurs-Buffern zu ändern, und zwar mit Hilfe der ArraySetAsSeries() Funktion.
Doch wir müssen ja auch die Richtung der Balkenanordnung in der Berechnungsschleife und den Berechnungsalgorithmus der ersten Variable ändern.
In diesem Fall sieht die OnCalculate() Funktion so aus:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate( const int rates_total, // total number of bars on the current tick const int prev_calculated,// number of calculated bars on the previous tick const datetime& time[], const double& open[], const double& high[], // price array for the maximum price for the indicator calculation const double& low[], // price array for the minimum price for the indicator calculation const double& close[], const long& tick_volume[], const long& volume[], const int& spread[] ) { //---+ //--- checking the number of bars if (rates_total < AroonPeriod - 1) return(0); //--- set indexation as timeseries ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(BullsAroonBuffer, true); ArraySetAsSeries(BearsAroonBuffer, true); //--- declare the local variables int limit, bar; double BULLS, BEARS; //--- calculation of the starting bar index if (prev_calculated == 0) // check for the first call of OnCalculate function limit = rates_total - AroonPeriod - 1; // starting index for the calculation of all of the bars else limit = rates_total - prev_calculated; // starting index for the calculation of new bars //--- main loop for(bar = limit; bar >= 0; bar--) { //--- calculation of the indicator values BULLS = 100 + (bar - ArrayMaximum(high, bar, AroonPeriod) - 0.5) * 100 / AroonPeriod; BEARS = 100 + (bar - ArrayMinimum(low, bar, AroonPeriod) - 0.5) * 100 / AroonPeriod; //--- filling the indicator buffers with the calculated values BullsAroonBuffer[bar] = BULLS; BearsAroonBuffer[bar] = BEARS; } //----+ return(rates_total); } //+------------------------------------------------------------------+
Ich habe den Namen der Variabel von "Erste" zu "Limit" geändert, das ist für unser Beispiel besser geeignet.
In diesem Fall wird der Code der primären Schleife ähnlich erzeugt, wie bereits schon in MQL4. Schreibt man die OnCalculate() Funktion auf diese Art, können die Indikatoren bei nur minimalen Codeveränderungen von MQL4 zu MQL5 konvertiert werden.
Fazit
Fertig! Der Indikator ist geschrieben - und sogar in zwei Versionen.
Für eine exakte, konservative und schlaue Möglichkeit zur Lösung derartiger Probleme, erweisen sich die Lösungen nur unwesentlich komplizierter als das Basteln eines Kinderspielzeugs mit dem Lego-Constructor.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/48
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.