MQL5: Erstellen Ihres eigenen Indikators

MetaQuotes | 11 Januar, 2016

Einleitung

Was ist ein Indikator? Es ist ein Satz berechneter Werte, die auf praktische Weise auf dem Bildschirm angezeigt werden sollen. Sätze von Werten werden in Programmen als Arrays dargestellt. Somit bedeutet das Erstellen eines Indikators, einen Algorithmus zu schreiben, der bestimmte Arrays bearbeitet (Preis-Arrays) und die Ergebnisse der Bearbeitung für andere Arrays (Indikator-Arrays) aufzeichnet.

Obwohl es zahlreiche fertige Indikatoren gibt, die bereits als Klassiker gelten, wird immer die Notwendigkeit bleiben, eigene Indikatoren zu erstellen. Solche Indikatoren, die wir mithilfe unserer eigenen Algorithmen erstellen, heißen benutzerdefinierte Indikatoren. In diesem Beitrag besprechen wir, wie ein einfacher benutzerdefinierter Indikator erstellt wird.

Indikatoren sind unterschiedlich

Ein Indikator kann in Form von farbigen Linien oder Bereichen dargestellt oder durch spezielle Labels, die auf günstige Momente für die Eingabe von Positionen hindeuten, angezeigt werden. Diese Typen können auch kombiniert werden, was noch mehr Typen von Indikatoren ermöglicht. Wir konzentrieren uns auf die Erstellung eines Indikators basierend auf dem Beispiel des bekannten, von William Blau entwickelten True Strength Index.

True Strength Index

Der TSI-Indikator basiert auf einem doppelt geglätteten Momentum, um Trends sowie überverkaufte/überkaufte Bereiche zu erkennen. Die mathematische Erklärung finden Sie in Momentum, Direction, and Divergence von William Blau. Hier berücksichtigen wir nur die Berechnungsformel.

TSI(CLOSE,r,s) =100*EMA(EMA(mtm,r),s) / EMA(EMA(|mtm|,r),s)

Dabei bedeutet:

Aus dieser Formel können wir drei Parameter ableiten, die die Berechnung des Indikators beeinflussen. Dies sind die Perioden r und s sowie der Typ von Preisen in den Berechnungen. In unserem Fall wählen wir den CLOSE-Preis.

MQL5 Wizard

Stellen wir den TSI als blaue Linie dar. An dieser Stelle müssen wir den MQL5 Wizard starten. Als Erstes müssen wir den Typ von Programm angeben, den wir erstellen wollen – benutzerdefinierter Indikator. Als Zweites geben wir den Namen des Programms, die Parameter r und s und ihre Werte ein.

MQL5 Wizard: Eingabe des Namen und der Parameter eines Indikators

Anschließend legen wir fest, dass der Indikator als blaue Linie in einem separaten Fenster angezeigt werden soll, und kennzeichnen diese Linie als TSI.

MQL5 Wizard: Festlegung des Indikatortypen

Alle Eingangsdaten wurden eingegeben, also klicken wir auf Fertig und erhalten einen Entwurf unseres Indikators. 

//+------------------------------------------------------------------+
//|                                          True Strength Index.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
//---- plot TSI
#property indicator_label1  "TSI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  Blue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input int      r=25;
input int      s=13;
//--- indicator buffers
double         TSIBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Der MQL5 Wizard erstellt den Header des Indikators, in den er die Eigenschaften des Indikators schreibt:

Alle Vorbereitungen sind abgeschlossen. Nun können wir unseren Code verfeinern und verbessern.

OnCalculate()

Die Funktion OnCalculate() ist der Handler des Calculate-Ereignisses, der bei Bedarf in Aktion tritt, um Indikatorwerte neu zu berechnen und das Diagramm neu zu zeichnen. Das ist das Ereignis eines neuen Tick-Eingangs, einer Aktualisierung der Symbol-Historie usw. Deshalb muss der Hauptcode für alle Berechnungen von Indikatorwerten sich in genau dieser Funktion befinden.

Natürlich können Hilfsberechnungen in anderen Funktionen implementiert werden, aber diese Funktionen müssen im OnCalculate-Handler verwendet werden.

Standardmäßig erstellt der MQL5 Wizard die zweite Form von OnCalculate(), die Zugriff auf alle Typen von Zeitreihen ermöglicht:

In unserem Fall benötigen wir allerdings nur ein Daten-Array. Deshalb ändern wir OnCalculate() zur ersten Form des Aufrufs.

int OnCalculate (const int rates_total,      // size of the price[] array
                 const int prev_calculated,  // number of available bars at the previous call
                 const int begin,            // from what index in price[] authentic data start
                 const double& price[])      // array, on which the indicator will be calculated
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }  

Dadurch können wir den Indikator weiterhin nicht nur auf Preisdaten anwenden, sondern auch einen Indikator erstellen, der auf Werten anderer Indikatoren basiert.

Angeben des Datentyps für die Berechnung des benutzerdefinierten Indikators

Wählen wir in der Registerkarte Parameters Close (Standardauswahl), enthält das Array price[], das an OnCalculate() übergeben wird, Schließungspreise. Wählen wir beispielsweise Typical Price (Typischer Preis), enthält das Array price[] die Preise (High+Low+Close)/3 für jede Periode.

Der Parameter rates_total kennzeichnet die Größe des Arrays price[]. Dies wird für die zyklusmäßige Organisation von Berechnungen nützlich sein. Die Indizierung der Elemente in price[] beginnt bei Null und bewegt sich von der Vergangenheit in Richtung Zukunft. Das bedeutet, das Element price[0] enthält den ältesten wert, während price[rates_total-1] das aktuellste Array-Element enthält.

Organisieren von Indikator-Hilfspuffern

Es wird nur eine Linie im Diagramm angezeigt, d. h. die Daten eines Indikator-Arrays. Vorher müssen wir allerdings noch die Zwischenberechnungen organisieren. Zwischendaten werden in Indikator-Arrays gespeichert, die durch das Attribut INDICATOR_CALCULATIONS gekennzeichnet werden. In der Formel sehen wir, dass wir zusätzliche Arrays benötigen:

  1. für mtm-Werte – Array MTMBuffer[];
  2. für |mtm|-Werte – Array AbsMTMBuffer[];
  3. für EMA(mtm,r) – Array EMA_MTMBuffer[];
  4. für EMA(EMA(mtm,r),s) – Array EMA2-MTMBuffer[];
  5. für EMA(|mtm|,r) – Array EMA_AbsMTMBuffer[];
  6. für EMA(EMA(|mtm|,r),s) – Array EMA2_AbsMTMBuffer[].

Insgesamt müssen wir 6 weitere double-Arrays auf globaler Ebene hinzufügen und diese Arrays mit den Indikatorpuffern in der OnInit()-Funktion verbinden. Vergessen Sie nicht, die neue Anzahl von Indikatorpuffern anzugeben. Die Eigenschaft indicator_buffers muss gleich 7 sein (es gab 1 und 6 weitere Puffer wurden hinzugefügt).

#property indicator_buffers 7

Jetzt sieht der Indikatorcode folgendermaßen aus:

#property indicator_separate_window
#property indicator_buffers 7
#property indicator_plots   1
//---- plot TSI
#property indicator_label1  "TSI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  Blue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input int      r=25;
input int      s=13;
//--- indicator buffers
double         TSIBuffer[];
double         MTMBuffer[];
double         AbsMTMBuffer[];
double         EMA_MTMBuffer[];
double         EMA2_MTMBuffer[];
double         EMA_AbsMTMBuffer[];
double         EMA2_AbsMTMBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS);
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,    // size of the price[] array;
                 const int prev_calculated,// number of available bars;
                                           // during the previous call;
                 const int begin,          // from what index in  
                                           // price[] authentic data start;
                 const double& price[])    // array, on which the indicator will be calculated;
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Zwischenberechnungen

Es ist sehr einfach, die Berechnung der Werte für die Puffer MTMBuffer[] und AbsMTMBuffer[] zu organisieren. Gehen Sie in der Schleife die Werte von price[1] bis price[rates_total-1] Stück für Stück durch und schreiben Sie die Differenz in ein Array und den absoluten Differenzwert in das zweite.

//--- calculate values of mtm and |mtm|
   for(int i=1;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

Der nächste Schritt ist die Berechnung des exponentiellen Durchschnitts dieser Arrays. Dies kann auf zwei Arten bewerkstelligt werden. Entweder versuchen wir, den gesamten Algorithmus fehlerfrei zu schreiben, oder wir nutzen fertige Funktionen, die bereits debuggt und genau für diese Zwecke gedacht sind.

In MQL5 gibt es keine integrierten Funktionen für die Berechnung gleitender Mittelwerte nach Array-Werten, doch mit MovingAverages.mqh gibt es eine fertige Bibliothek von Funktionen. Der vollständige Pfad lautet terminal_directory/MQL5/Include/MovingAverages.mqh. terminal_directory ist ein Katalog, in dem das MetaTrader-5-Terminal installiert ist. Die Bibliothek ist eine Include-Datei. Sie enthält Funktionen für die Berechnung von gleitenden Mittelwerten in Arrays mithilfe einer von vier herkömmlichen Methoden:

Fügen Sie in jedem beliebigen MQL5-Programm Folgendes in das Code-Heading ein, um diese Funktionen zu nutzen:

#include <MovingAverages.mqh>

Wir brauchen die Funktion ExponentialMAOnBuffer(), die den exponentiellen gleitenden Mittelwert in einem Array von Werten berechnet und Durchschnittswerte in einem anderen Array aufzeichnet.

Glätten von Arrays

Insgesamt beinhaltet die Include-Datei MovingAverages.mqh acht Funktionen, die in zwei Funktionsgruppen mit je 4 Funktionen aufgeteilt werden können. Die erste Gruppe besteht aus Funktionen, die ein Array erhalten und einfach einen Wert eines gleitendenden Mittelwerts bei einer festgelegten Funktion ausgeben:

Diese Funktionen dienen dazu, einmalig den Wert eines Mittelwerts eines Arrays zu erhalten, und sind nicht für mehrere Aufrufe optimiert. Falls Sie eine Funktion aus dieser Gruppe in einer Schleife verwenden müssen (zum Berechnen von Werten eines Mittelwerts und Schreiben jedes berechneten Wertes in ein Array), müssen Sie einen optimalen Algorithmus organisieren.

Die zweite Funktionsgruppe dient dem Befüllen eines Empfänger-Arrays mit Werten eines gleitenden Mittelwerts auf Basis der Ausgangswerte des Arrays:

Alle angegebenen Funktionen mit Ausnahme der Arrays buffer[] und price[] und des Mittelungszeitraums period erhalten 3 weitere Parameter, deren Zweck dem der Parameter der OnCalculate()-Funktion entspricht – rates_total, prev_calculated und begin. Funktionen dieser Gruppe verarbeiten die übergebenen Arrays price[] und buffer[] korrekt unter Berücksichtigung der Richtung der Indizierung (Flag AS_SERIES) weiter.

Der Parameter begin kennzeichnet den Index eines Quell-Arrays, ab dem sinnvolle Daten beginnen, d. h. Daten, die bearbeitet werden müssen. Bei dem Array MTMBuffer[] beginnen die realen Daten mit dem Index 1, weil MTMBuffer[1]=price[1]-price[0]. Der Wert von MTMBuffer[0] ist nicht definiert, deshalb begin=1.

//--- calculate the first moving
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,  // index, starting from which data for smoothing are available 
                         r,  // period of the exponential average
                         MTMBuffer,       // buffer to calculate average
                         EMA_MTMBuffer);  // into this buffer locate value of the average
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

Bei der Mittelung sollte der Periodenwert berücksichtigt werden, da die berechneten Werte im Ausgabe-Array mit einer geringen Verzögerung ausgefüllt werden, die bei größeren Mittelungszeiträumen größer ist. Zum Beispiel: Ist period=10, beginnen die Werte im resultierenden Array mit begin+period-1=begin+10-1. Dies sollte bei weiteren Aufrufen von buffer[] berücksichtigt werden und die Bearbeitung sollte mit dem Index begin+period-1 beginnen.

So erhalten wir einfach den zweiten exponentiellen Mittelwert aus den Arrays MTMBuffer[] und AbsMTMBuffer:

//--- calculate the second moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);

Der Wert von begin entspricht nun r, weil begin=1+r-1 (r ist der Zeitraum der primären exponentiellen Mittelung, die Bearbeitung beginnt mit dem Index 1). In den Ausgabe-Arrays EMA2_MTMBuffer[] und EMA2_AbsMTMBuffer[] beginnen die berechneten Werte mit dem Index r+s-1, weil wir die Bearbeitung von Eingabe-Arrays mit dem Index r begonnen haben und der Zeitraum für die zweite exponentielle Mittelung gleich s ist.

Alle Vorabberechnungen sind abgeschlossen. Nun können wir die Werte des Indikatorpuffers TSIBuffer[] berechnen, die im Diagramm wiedergegeben werden.

//--- now calculate values of the indicator
   for(int i=r+s-1;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }
Kompilieren Sie den Code, indem Sie F5 drücken, und starten Sie ihn im MetaTrader-5-Terminal. Es funktioniert!

Die erste Version des True Strength Index

Es sind allerdings immer noch Fragen offen.

Optimieren von Berechnungen

Tatsächlich reicht es nicht aus, einfach einen funktionierenden Indikator zu schreiben. Wenn wir die aktuelle Implementierung von OnCalculate() genau betrachten, sehen wir, dass sie nicht optimal ist.

int OnCalculate (const int rates_total,    // size of the price[] array;
                 const int prev_calculated,// number of available bars;
                 // at the previous call;
                 const int begin,// from what index of the 
                 // price[] array true data start;
                 const double &price[]) // array, at which the indicator will be calculated;
  {
//--- calculate values of mtm and |mtm|
   MTMBuffer[0]=0.0;
   AbsMTMBuffer[0]=0.0;
   for(int i=1;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }
//--- calculate the first moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,  // index, starting from which data for smoothing are available 
                         r,  // period of the exponential average
                         MTMBuffer,       // buffer to calculate average
                         EMA_MTMBuffer);  // into this buffer locate value of the average
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         1,r,AbsMTMBuffer,EMA_AbsMTMBuffer);

//--- calculate the second moving average on arrays
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_MTMBuffer,EMA2_MTMBuffer);
   ExponentialMAOnBuffer(rates_total,prev_calculated,
                         r,s,EMA_AbsMTMBuffer,EMA2_AbsMTMBuffer);
//--- now calculate values of the indicator
   for(int i=r+s-1;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Bei jedem Start der Funktion berechnen wir die Werte in den Arrays MTMBuffer[] und AbsMTMBuffer[]. In diesem Fall können unnötig wiederholte Berechnungen sämtliche CPU-Ressourcen beanspruchen, so leistungsstark die CPU auch ist, wenn die Größe von price[] hunderttausende oder gar Millionen beträgt.

Zum Organisieren optimaler Berechnungen nutzen wir den Eingabeparameter prev_calculated, der dem von OnCalculate() beim vorherigen Aufruf ausgegebenen Wert entspricht. Beim ersten Aufruf der Funktion ist der Wert von prev_calculated immer gleich 0. In diesem Fall berechnen wir alle Werte im Indikatorpuffer. Beim nächsten Aufruf müssen wir nicht den gesamten Puffer berechnen. Es wird nur der letzte Wert berechnet. Schreiben wir es uns so auf:

//--- if it is the first call 
   if(prev_calculated==0)
     {
      //--- set zero values to zero indexes
      MTMBuffer[0]=0.0;
      AbsMTMBuffer[0]=0.0;
     }
//--- calculate values of mtm and |mtm|
   int start;
   if(prev_calculated==0) start=1;  // start filling out MTMBuffer[] and AbsMTMBuffer[] from the 1st index 
   else start=prev_calculated-1;    // set start equal to the last index in the arrays 
   for(int i=start;i<rates_total;i++)
     {
      MTMBuffer[i]=price[i]-price[i-1];
      AbsMTMBuffer[i]=fabs(MTMBuffer[i]);
     }

Berechnungsblöcke von EMA_MTMBuffer[], EMA_AbsMTMBuffer[], EMA2_MTMBuffer[] und EMA2_AbsMTMBuffer[] benötigen keine Optimierung der Berechnungen, weil ExponentialMAOnBuffer() bereits optimal geschrieben ist. Wir müssen nur die Berechnung der Werte des Arrays TSIBuffer[] optimieren. Wir nutzen die gleiche Methode wie für MTMBuffer[].

//--- now calculate the indicator values
   if(prev_calculated==0) start=r+s-1; // set the starting index for input arrays
   for(int i=start;i<rates_total;i++)
     {
      TSIBuffer[i]=100*EMA2_MTMBuffer[i]/EMA2_AbsMTMBuffer[i];
     }
//--- return value of prev_calculated for next call
   return(rates_total);

Die letzte Bemerkung für den Optimierungsvorgang: OnCalculate() gibt den Wert von rates_total aus. Das ist die Zahl der Elemente im Eingabe-Array price[], die für Indikatorberechnungen verwendet wird.

Der von OnCalculate() ausgegebene Wert wird im Terminal-Speicher gespeichert und wird beim nächsten Aufruf von OnCalculate() als Wert des Eingabeparameters prev_calculated an die Funktion übergeben.

Damit kennen wir immer die Größe des Eingabe-Arrays beim vorherigen Aufruf von OnCalculate() und starten die Berechnung von Indikatorpuffern bei einem korrekten Index ohne unnötige Neuberechnungen.

Überprüfen von Eingabedaten

Es gibt noch eines, was wir erledigen müssen, damit OnCalculate() perfekt funktioniert. Fügen wir eine Überprüfung des Arrays price[] hinzu, anhand dessen die Indikatorwerte berechnet werden. Wenn die Größe des Arrays (rates_total) zu gering ist, sind keine Berechnungen erforderlich. Wir müssen bis zum nächsten Aufruf von OnCalculate(), bei dem ausreichende Daten vorliegen, warten.

//--- if the size of price[] is too small
  if(rates_total<r+s) return(0); // do not calculate or draw anything
//--- if it's the first call 
   if(prev_calculated==0)
     {
      //--- set zero values for zero indexes
      MTMBuffer[0]=0.0;
      AbsMTMBuffer[0]=0.0;
     }

Da die exponentielle Glättung zweimal sequentiell zur Berechnung des True Strength Index verwendet wird, muss die Größe von price[] mindestens so groß sein wie die Summe der Perioden r und s. Andernfalls wird die Ausführung beendet und OnCalculate() gibt 0 aus. Der ausgegebene Nullwert bedeutet, dass der Indikator nicht im Diagramm wiedergegeben wird, weil seine Werte nicht berechnet werden.

Einrichten der Darstellung

Was die Richtigkeit der Berechnungen betrifft, ist der Indikator einsatzbereit. Rufen wir ihn allerdings über ein anderes mql5-Programm auf, basiert er standardmäßig auf Close-Preisen. Wir können einen anderen Standard-Preistyp angeben. Geben Sie einen Wert der Aufzählung ENUM_APPLIED_PRICE in der Eigenschaft indicator_applied_price des Indikators an. 

Schreiben wir beispielsweise Folgendes, um einen typischen Preis ( (high+low+close)/3) als Preis einzurichten:

#property indicator_applied_price PRICE_TYPICAL

Falls wir nur die Werte mithilfe von iCustom() oder IndicatorCreate() verwenden möchten, sind keine weiteren Anpassungen erforderlich. Allerdings werden bei direkter Verwendung, d. h. bei Darstellung im Diagramm, zusätzliche Einstellungen empfohlen:

Diese Einstellungen können mithilfe von Funktionen der Gruppe Benutzerdefinierte Indikatoren im Handler OnInit() angepasst werden. Fügen Sie neue Zeilen ein und speichern Sie den Indikator als True_Strength_Index_ver2.mq5.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,TSIBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,EMA_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS);
//--- bar, starting from which the indicator is drawn
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,r+s-1);
   string shortname;
   StringConcatenate(shortname,"TSI(",r,",",s,")");
//--- set a label do display in DataWindow
   PlotIndexSetString(0,PLOT_LABEL,shortname);   
//--- set a name to show in a separate sub-window or a pop-up help
   IndicatorSetString(INDICATOR_SHORTNAME,shortname);
//--- set accuracy of displaying the indicator values
   IndicatorSetInteger(INDICATOR_DIGITS,2);
//---
   return(0);
  }

Wenn wir beide Versionen starten und im Diagramm zu seinem Beginn blättern, sehen wir alle Unterschiede.


Die zweite Version des Indikators True Strength Index sieht besser aus

Fazit

Auf Basis des Beispiels der Erstellung des Indikators True Strength Index können wir die wichtigsten Momente beim Schreiben eines Indikators in MQL5 erläutern: