Jeden Indikator aus einem Expert Advisor auslesen: Pfeile, Linien und Farbindizes in CopyBuffer

13 November 2026, 08:30
Bohdan Klok
0
5

Einleitung

Früher oder später bittet jeder Trader, der einen Indikator verwendet, um "dasselbe, nur dass es von selbst handelt". Die Antwort des Programmierers klingt einfach: den Indikator mit iCustom() oder IndicatorCreate() erzeugen, seine Werte mit CopyBuffer() lesen und eine Position eröffnen, sobald das Signal erscheint. In der Praxis geht es bei der ersten Frage gar nicht ums Handeln. Sie lautet: In welchem Puffer steht das Signal?

Ein Pfeilindikator kann einen Puffer für Kaufpfeile und einen anderen für Verkaufspfeile haben oder einen gemeinsamen Puffer für beide mit einem Farbindex daneben. Ein "Trend"-Indikator zeichnet vielleicht eine einzige Linie, deren Farbe wechselt, und die Farbe steht in einem separaten Puffer, der nie gezeichnet wird. Ein kompilierter Indikator aus dem Market kommt ohne Quellcode, seine SetIndexBuffer()-Aufrufe lassen sich also nicht nachlesen. Das Datenfenster hilft ein wenig, zeigt aber nur die gezeichneten Werte jeweils eines einzigen Bars und verbirgt die Farb- und Berechnungspuffer vollständig.

Dieser Beitrag zeigt, wie man die Frage von außen beantwortet, und zwar für jeden Indikator: wie Puffer gespeichert werden, wie man sie alle aus einem anderen Programm liest, wie man einen Pfeilpuffer allein anhand seiner Werte von einer Linie und von einem Farbindex unterscheidet, wie man prüft, ob die Signale nachträglich neu gezeichnet werden, und wie man die Antwort wieder auf den Chart bringt, damit der Nutzer sieht, welche Nummer zu welchem Pfeil gehört. Das Ergebnis sind drei kleine Programme mit Quellcode: ein Skript, das jeden Indikator auf einem Chart untersucht (kostenlos in der Code Base), ein Demonstrationsindikator, der jede Art von Puffer besitzt, und ein Expert Advisor, der jeden beliebigen Indikator nach vier allgemeinen Regeln handelt (die beiden letzten sind am Ende des Beitrags vollständig abgedruckt).

1. Was ein Indikatorpuffer wirklich ist

Ein Indikator legt seine Ausgabe in Arrays ab, die mit SetIndexBuffer() gebunden werden. Es gibt drei Arten:

  • INDICATOR_DATA: Werte, die gezeichnet werden (Linien, Histogramme, Pfeile) und im Datenfenster erscheinen;
  • INDICATOR_COLOR_INDEX: bei den Stilen DRAW_COLOR_* die Nummer der Farbe, die auf dem jeweiligen Bar verwendet wird;
  • INDICATOR_CALCULATIONS: Zwischenarrays, die weder gezeichnet noch angezeigt werden.

Alle drei werden in einer gemeinsamen Folge nummeriert, in der Reihenfolge, in der der Indikator sie deklariert hat, und alle drei lassen sich mit CopyBuffer() lesen. Das ist für viele Programmierer die erste Überraschung: Ein Puffer, den man auf dem Chart nie sieht, ist trotzdem verfügbar, und bei Farbindikatoren ist er oft der nützlichste.

Grafische Darstellungen (Plots) und Puffer sind nicht dasselbe. Ein DRAW_COLOR_LINE-Plot verwendet zwei Puffer (Wert und Farbindex), DRAW_COLOR_CANDLES fünf (vier Preise und den Farbindex), DRAW_COLOR_ARROW zwei (den Preis des Pfeils und seine Farbe). Der Indikator Heiken Ashi aus dem Standardlieferumfang ist ein gutes Beispiel: Die Puffer 0 bis 3 sind die Kerzenpreise, Puffer 4 ist der Farbindex, 0 für eine steigende und 1 für eine fallende Kerze.

Der zweite Punkt ist, wie "nichts" gespeichert wird. Ein Linienpuffer enthält in der Regel auf jedem Bar einen Wert. Ein Pfeilpuffer enthält nur auf den Bars mit Pfeil einen Preis und sonst einen "leeren" Wert. Welcher Wert "leer" bedeutet, entscheidet der Autor: EMPTY_VALUE (DBL_MAX) ist die übliche Wahl, gesetzt mit PlotIndexSetDouble(PLOT_EMPTY_VALUE), doch viele Indikatoren verwenden 0.0. Wer beim Lesen 0.0 als gültigen Pfeilpreis behandelt, sieht auf jedem Bar einen Pfeil.

Damit es etwas Konkretes zum Anschauen gibt, deklariert der Indikator DemoBuffers.mq5 (Quellcode am Ende) jede Art von Puffer: einen einfachen gleitenden Durchschnitt als Linie, Pfeile dort, wo der Schlusskurs den Durchschnitt nach oben kreuzt, einen exponentiellen Durchschnitt, der als Farblinie gezeichnet wird, und die Steigung dieses Durchschnitts als Berechnungspuffer.

SetIndexBuffer(0,ExtSma,INDICATOR_DATA);             // line
SetIndexBuffer(1,ExtCross,INDICATOR_DATA);           // arrows, EMPTY_VALUE where there is none
SetIndexBuffer(2,ExtEma,INDICATOR_DATA);             // colour line: the value
SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); // colour line: 0 rising, 1 falling
SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);   // not drawn, not in the Data Window
PlotIndexSetInteger(1,PLOT_ARROW,233);
PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);

Das Datenfenster zeigt für ihn drei Werte: den Durchschnitt, den Pfeil und den exponentiellen Durchschnitt. Die Puffer 3 und 4 sind dort unsichtbar, und doch steckt die Farbe der Linie - das naheliegendste "Trend"-Signal dieses Indikators - in Puffer 3. Abbildung 1 zeigt, was ein Programm sieht, wenn es alle fünf liest.

Abbildung 1. DemoBuffers auf EURUSD H1: zwei Linien, ein Pfeilpuffer, ein Farbindex und ein Berechnungspuffer, ausgelesen vom Skript Buffer Inspector Kb.

2. Alle Puffer eines Indikators lesen

Für einen Indikator, der bereits auf einem Chart liegt, ist das Handle verfügbar, ohne dass man seine Eingabeparameter kennt:

for(int w=0; w<(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); w++)
   for(int i=0; i<ChartIndicatorsTotal(0,w); i++)
     {
      string name=ChartIndicatorName(0,w,i);
      int    h=ChartIndicatorGet(0,w,name);   // the same instance the user sees
      ...
      IndicatorRelease(h);                    // releases our handle, not the indicator
     }

Das ist wichtiger, als es scheint: Der Nutzer hat die Eingabeparameter des Indikators auf dem Chart bereits gewählt, und das Programm liest genau diese Instanz. Für einen Indikator, der nicht auf dem Chart liegt, nimmt IndicatorCreate() mit IND_CUSTOM den Namen relativ zu MQL5\Indicators und ein Array von MqlParam entgegen. Kommen die Eingabeparameter aus einem Textfeld, zum Beispiel "14,0,1.5,true", muss jedem Element der richtige Typ zugewiesen werden:

int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;               // the first element is always the name
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        { p[i+1].type=TYPE_BOOL;   p[i+1].integer_value=(s=="true"); }
      else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
        { p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); }
      else if(s=="0" || StringToInteger(s)!=0)
        { p[i+1].type=TYPE_INT;    p[i+1].integer_value=StringToInteger(s); }
      else
        { p[i+1].type=TYPE_STRING; p[i+1].string_value=s; }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }

Drei Regeln sparen bei dieser Funktion viel Zeit. Die Werte müssen der Reihenfolge im Fenster der Eingabeparameter des Indikators folgen, und kein Parameter darf übersprungen werden: Um den dritten Parameter zu setzen, müssen auch die ersten beiden angegeben werden. Eine Enumeration wird als ihr Zahlenwert übergeben, MODE_EMA ist also 1 und PRICE_CLOSE ist 1. Und nicht angegebene Parameter behalten ihre Standardwerte, sodass ein leerer String den Indikator genau so erzeugt, wie er ausgeliefert wird.

Wie viele Puffer gibt es? Das Handle verrät es nicht, CopyBuffer() aber schon: Für eine Puffernummer, die nicht existiert, gibt es -1 zurück. Liest man die Puffer 0, 1, 2 und so weiter bis zum ersten Fehlschlag, erhält man ihre Anzahl, Daten-, Farb- und Berechnungspuffer zusammen.

Zwei praktische Details:

  • nach dem Erzeugen des Handles braucht der Indikator Zeit für die Berechnung; BarsCalculated(handle) gibt bis dahin -1 zurück, deshalb wartet ein Skript in einer kurzen Schleife mit Sleep() darauf, und ein Expert Advisor versucht es einfach beim nächsten Tick erneut;
  • beginnen Sie das Kopieren bei Bar 1, dem letzten geschlossenen Bar. Der sich bildende Bar ändert sich bei vielen Pfeilindikatoren, bis er schließt, und eine Analyse, die auf ihm beruht, zeigt Pfeile, die später verschwinden.

3. Pfeile, Linien und Farbindizes unterscheiden

Liegen N geschlossene Bars eines Puffers in einem Array, genügen drei einfache Kennzahlen:

  • gültige Werte: Werte, die weder EMPTY_VALUE noch NaN sind;
  • Pfeile: Werte, die gültig und ungleich 0.0 sind;
  • kleine ganze Zahlen: ganzzahlige Werte mit kleinem Absolutbetrag.

Daraus ergibt sich die Einordnung:

Was der Puffer enthält Wie er über 500 Bars aussieht
nichts keine gültigen Werte oder nur Nullen
Pfeile einige Werte, aber auf höchstens 30 % der Bars
Farbindex nur kleine ganze Zahlen in einem engen Bereich (zum Beispiel 0 und 1)
Linie alles andere: ein Wert auf (fast) jedem Bar
int valid=0, arrows=0, ints=0;
double lo=DBL_MAX, hi=-DBL_MAX;
for(int i=0; i<n; i++)
  {
   if(v[i]==EMPTY_VALUE || !MathIsValidNumber(v[i])) continue;
   valid++; lo=MathMin(lo,v[i]); hi=MathMax(hi,v[i]);
   if(v[i]!=0.0) arrows++;
   if(MathAbs(v[i]-MathRound(v[i]))<1e-9 && MathAbs(v[i])<64) ints++;
  }
if(valid==0 || (arrows==0 && hi==0.0 && lo==0.0))  kind="empty";
else if(arrows>0 && arrows<=n*0.3)                  kind="arrows";
else if(ints==valid && hi-lo<=16)                    kind="colour index";
else                                                 kind="line";

Warum 30 %? Pfeilindikatoren markieren Wendepunkte, und die sind naturgemäß selten: Fractals markiert einige Prozent der Bars, typische "Signal"-Indikatoren weniger als 10 %. Eine Linie, die auf einem Teil der Bars leer ist (ein gleitender Durchschnitt am Anfang der Historie), hat trotzdem auf den meisten Bars Werte. Die Schwelle ist bewusst großzügig gewählt; Fälle in ihrer Nähe werden dem Nutzer gemeldet und nicht stillschweigend geraten.

Warum "kleine ganze Zahlen in einem engen Bereich" für Farben? Ein Farbindex nimmt die Werte 0, 1, 2 und so weiter an, einen pro Farbe des Plots. Auch eine Linie kann ganzzahlig sein (ein vom Autor gerundeter Oszillator, eine Stufenfunktion), bewegt sich dann aber über einen viel größeren Bereich als die Handvoll Farben, die ein Plot haben kann.

Abbildung 2 zeigt das Ergebnis für Heiken Ashi: vier Preislinien und in Puffer 4 den Farbindex mit den Werten 0 und 1 - genau das, was ein Expert Advisor braucht, um auf einen Farbwechsel der Kerze zu reagieren.

Abbildung 2. Heiken Ashi: vier Preislinien und der Farbindex in Puffer 4, den das Datenfenster nicht anzeigt.

Einige Fälle verdienen ein eigenes Wort, denn gerade bei ihnen führt ein schneller Blick zur falschen Antwort:

  • Pfeile, die mit 0.0 statt mit EMPTY_VALUE gespeichert sind. Die obige Einordnung behandelt 0.0 bereits als "kein Pfeil", aber ein Programm, das nur mit EMPTY_VALUE vergleicht, löst auf jedem Bar aus. Prüfen Sie immer beides.
  • Punkte auf jedem Bar, etwa beim Parabolic SAR. Auf dem Chart sehen sie wie Pfeile aus, doch der Puffer hat auf jedem Bar einen Wert, ist also eine Linie, und das Signal ist der Moment, in dem die Punkte auf die andere Seite des Preises springen, nicht das Vorhandensein eines Punktes.
  • Farbige Pfeile (DRAW_COLOR_ARROW). Der Pfeilpuffer liefert den Bar und den Preis, und der Farbindex daneben gibt an, ob es ein Kauf- oder ein Verkaufspfeil ist. Für ein einziges Signal werden beide Puffer gebraucht.
  • Berechnungspuffer. Sie werden meist als Linien eingeordnet, oft mit Werten weit weg vom Preis. Ein Blick auf sie lohnt sich: Manche Indikatoren halten dort das "rohe" Signal vor Glättung oder Filterung.

4. Dem Nutzer zeigen, welcher Pfeil welcher ist

Eine Tabelle mit "buffer 1: arrows, 37, the latest on 2026.10.07 08:00" ist korrekt, aber ein Trader muss trotzdem Datumsangaben mit dem Chart abgleichen, um zu sehen, ob Puffer 1 der Aufwärts- oder der Abwärtspfeil ist. Viel schneller geht es, wenn die Puffernummer direkt neben die Pfeile geschrieben wird:

for(int i=n-1, done=0; i>=0 && done<20; i--)
  {
   if(v[i]==EMPTY_VALUE || v[i]==0.0) continue;
   int      shift=n-i;                          // v[] runs from shift n (oldest) to shift 1
   datetime t=iTime(_Symbol,_Period,shift);
   string   obj=StringFormat("BufInspector_%d_%d",buffer,shift);
   ObjectCreate(0,obj,OBJ_TEXT,0,t,v[i]);       // at the arrow's own price
   ObjectSetString(0,obj,OBJPROP_TEXT,"B"+(string)buffer);
   done++;
  }

Die Beschriftung wird auf den im Puffer gespeicherten Wert gesetzt, der bei Pfeilindikatoren der Preis ist, an dem der Pfeil gezeichnet wird; so erscheinen "B0" und "B1" direkt auf den Pfeilen. Mit einer eigenen Farbe pro Puffer lassen sich zwei Pfeilpuffer leicht auseinanderhalten.

Abbildung 3. Examples\Fractals: B0 steht über den oberen Fraktalen, B1 unter den unteren.

5. Zeichnet der Indikator nachträglich neu?

Ein Puffer mit Pfeilen ist nur die halbe Antwort. Die andere Hälfte ist, ob ein Pfeil, der einmal auf einem geschlossenen Bar erschienen ist, dort auch bleibt. Manche Indikatoren verschieben oder löschen ihre Pfeile, wenn neue Bars eintreffen - Fractals tut es konstruktionsbedingt für die letzten zwei Bars, ZigZag-ähnliche Indikatoren über viel längere Zeit. Ein Expert Advisor, der einen Pfeil handelt, der später verschwindet, handelt ein Signal, das es im Nachhinein nie gegeben hat, und seine Ergebnisse im Strategietester werden nicht zum Chart passen.

Die Prüfung lässt sich leicht automatisieren: die Werte der letzten geschlossenen Bars anhand ihrer Eröffnungszeit merken, einige neue Bars abwarten, dieselben Bars erneut lesen und vergleichen.

datetime ExtTimes[];   // open times of the remembered bars
double   ExtValues[];  // buffer values on those bars
//--- remember the last 100 closed bars of buffer b
int Remember(const int handle,const int b)
  {
   if(CopyTime(_Symbol,_Period,1,100,ExtTimes)!=100) return(0);
   return(CopyBuffer(handle,b,1,100,ExtValues));
  }
//--- later: how many of the remembered bars changed their value?
int Changed(const int handle,const int b)
  {
   int changed=0;
   for(int i=0; i<ArraySize(ExtTimes); i++)
     {
      double v[];
      if(CopyBuffer(handle,b,ExtTimes[i],1,v)==1 && v[0]!=ExtValues[i])
         changed++;
     }
   return(changed);
  }

Entscheidend ist hier, nach Zeit und nicht nach Index zu lesen: Nachdem neue Bars eingetroffen sind, hat derselbe Bar einen größeren Index. Das Ergebnis zeigt, bis zu wie vielen Bars zurück der Indikator seine Meinung noch ändert, und aus dieser Zahl ergibt sich der Signalbar, den man verwenden sollte. Bei Fractals lautet die Antwort immer zwei Bars, deshalb wird ein Fraktal auf Bar 3 gelesen, nicht auf Bar 1. Ein Indikator, der seine Pfeile noch zehn Bars zurück ändert, lässt sich nicht auf dem Bar handeln, auf dem der Pfeil zum ersten Mal erscheint, wie gut sein Bild im Strategietester auch aussehen mag.

6. Wenn es keinen Puffer zum Lesen gibt

Manche Indikatoren zeichnen ihre Pfeile mit ObjectCreate() statt mit Puffern. Die Pfeile sind auf dem Chart, das Datenfenster ist leer, und CopyBuffer() liefert nur Linien oder gar nichts. Kein Programm kann solche Signale über das Handle des Indikators lesen. Das sollte man dem Nutzer offen sagen, denn sonst endet die Suche nach "der richtigen Puffernummer" nie. Ein Skript kann die Situation indirekt erkennen: Ein Indikator, der keinen Pfeilpuffer hat, obwohl der Nutzer Pfeile sieht, zeichnet so gut wie sicher Objekte. Buffer Inspector Kb weist in seinem Bericht darauf hin.

7. Von Puffern zu Handelsregeln

Sobald der Puffer bekannt ist, decken vier Regeln fast jeden Indikator ab, mit dem ein Trader kommt:

Regel Typische Indikatoren Bedingung auf dem Signalbar
ein Pfeil erscheint Fractals, Pfeilindikatoren der Wert ist nicht leer und nicht 0
ein Puffer kreuzt ein Niveau RSI 30/70, CCI +-100, Oszillatoren um 0 vorher < Niveau <= aktuell
ein Puffer kreuzt einen anderen MACD Haupt-/Signallinie, Stochastic %K/%D, Alligator-Linien vorher a < b und aktuell a >= b
ein Farbindex nimmt einen Wert an Heiken Ashi, farbige Durchschnitte, "Trend"-Bars vorher != k und aktuell == k

BufferSignalEA.mq5 (Quellcode am Ende) setzt genau diese Regeln um; Name des Indikators, seine Eingabeparameter, die Regel und die Puffernummern sind dabei selbst Eingabeparameter, sodass sich ein einziger Expert Advisor auf jeden Indikator ansetzen lässt. Sein Kern ist eine Funktion, die zwei Werte liest, den des Signalbars und den des Bars davor:

bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }

Ein Farbindex wird mit einer Toleranz von 0,5 verglichen, weil er als double gespeichert ist. Die Entscheidung fällt einmal pro neuem Bar, vor allem anderen in OnTick():

const datetime bar=iTime(_Symbol,_Period,0);
if(bar==ExtLastBar)
   return;
if(BarsCalculated(ExtHandle)<InpSignalBar+2)
   return;                                     // the indicator is not ready yet: try on the next tick
ExtLastBar=bar;
const bool buy =RuleFires(InpBuyRule, InpBuyBuffer, InpBuyLevel, InpBuyBuffer2);
const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
if(buy==sell || HasPosition())
   return;                                     // no signal, both at once, or already in the market

Eine Regel, die bei jedem Tick des sich bildenden Bars ausgewertet wird, würde mehrmals auslösen, und zwar auf Signale, die wieder verschwinden; eine einzige Entscheidung pro Bar auf einem geschlossenen Bar sorgt dafür, dass Strategietester und Live-Chart übereinstimmen.

Um zu prüfen, ob jede Regel tatsächlich auslöst, wurde der Expert Advisor im Strategietester mit fünf Indikatoren getestet, für alle mit demselben einfachen Ausstieg (0,1 Lot, Stop Loss 300 Punkte, Take Profit 600 Punkte, jeweils nur eine Position). Die folgenden Zahlen sind ein Funktionstest der Regeln, kein Handelsergebnis: Ein Indikator, der ohne jeden Filter und ohne Positionsmanagement gehandelt wird, ist keine Strategie.

Indikator und Regel Puffer Trades Gewinntrades
Fractals, ein Pfeil erscheint (Signalbar 3) Kauf 1, Verkauf 0 184 53
MACD, Hauptlinie kreuzt Signallinie 0 und 1 160 46
RSI(14), kreuzt 30 nach oben / 70 nach unten 0 113 36
Heiken Ashi, Farbe wird 0 / 1 4 170 53
DemoBuffers, Pfeil für Kauf, Farbe 1 für Verkauf 1 und 3 152 39

EURUSD H1, 2025.10.01 - 2026.10.01, Modell "1 Minute OHLC". Alle fünf Tests laufen ohne eine einzige fehlgeschlagene Anfrage; die unterschiedliche Zahl der Trades ergibt sich daraus, wie oft der jeweilige Indikator ein Signal gibt, während keine Position offen ist.

8. Einen Indikator auf einem anderen Zeitrahmen lesen

Dasselbe Verfahren funktioniert für einen Indikator auf einem höheren Zeitrahmen, und so werden die meisten Filter gebaut ("nur kaufen, wenn der Preis auf H4 über dem 200er EMA liegt"). Das Handle wird mit diesem Zeitrahmen erzeugt, und Bar 1 dieses Handles ist der letzte geschlossene Bar dieses Zeitrahmens:

int h4=iMA(_Symbol,PERIOD_H4,200,0,MODE_EMA,PRICE_CLOSE);
...
double ma[];
if(CopyBuffer(h4,0,1,1,ma)==1 && iClose(_Symbol,PERIOD_H4,1)>ma[0])
   allowBuy=true;

Zwei Anmerkungen. Erstens sind die Daten eines anderen Zeitrahmens beim Programmstart möglicherweise noch nicht bereit: BarsCalculated() gibt -1 zurück und CopyBuffer() schlägt fehl, bis das Terminal die Historie aufgebaut hat; die Prüfung muss also wiederholt werden und darf nicht als "kein Signal" gelten. Zweitens: Lesen Sie in einer Entscheidung, die auf dem niedrigeren Zeitrahmen getroffen wird, niemals Bar 0 des höheren. Auf einem H1-Chart ändert sich der sich bildende H4-Bar mit jeder Stunde, sodass ein Filter, der ihn liest, im Tester perfekt aussieht, weil der Tester bereits weiß, wie der H4-Bar endet, und sich auf einem Live-Chart anders verhält.

9. Die Programme

  • Buffer Inspector Kb (Skript, MQL5\Scripts, kostenlos in der Code Base): Ziehen Sie es auf einen Chart, auf dem der Indikator bereits liegt. Es untersucht jeden Indikator in jedem Fenster, schreibt die Tabelle in das Journal auf der Registerkarte "Experten", zeigt sie mit Comment() auf dem Chart an und beschriftet die letzten Pfeile jedes Pfeilpuffers. Steht ein Indikatorname in seinen Eingabeparametern, erzeugt es den Indikator selbst, was für kompilierte Indikatoren aus dem Market nützlich ist. Ein Start mit "Only remove the labels" räumt den Chart auf.
  • DemoBuffers.mq5 (Indikator, MQL5\Indicators): ein einziger Indikator mit einer Linie, Pfeilen, einer Farblinie samt Farbindex und einem Berechnungspuffer, zum Experimentieren. Quellcode unten.
  • BufferSignalEA.mq5 (Expert Advisor, MQL5\Experts): handelt jeden Indikator nach den vier obigen Regeln; der Indikator, seine Eingabeparameter, die Regeln und die Puffernummern sind Eingabeparameter. Quellcode unten.

Fazit

Die Puffer eines Indikators sind seine öffentliche Schnittstelle, auch wenn sein Quellcode nicht verfügbar ist. Wer alle Puffer liest, sie nach ihren Werten einordnet, die Pfeile auf dem Chart beschriftet und prüft, wie weit zurück sich die Werte ändern, macht aus "Welche Puffernummer soll ich verwenden?" statt einer Frage an den Autor des Indikators eine Prüfung von zehn Sekunden. Dieselbe Einordnung ist die Grundlage für vier allgemeine Handelsregeln - Pfeil, Niveau, Linienkreuzung und Farbwechsel -, mit denen ein einziger Expert Advisor mit Indikatoren arbeiten kann, die er nie zuvor gesehen hat, und zwar auf geschlossenen Bars, sodass das, was der Strategietester zeigt, auch das ist, was auf einem Live-Chart geschieht.

Quellcode

Speichern Sie jedes Listing unter seinem Namen im angegebenen Ordner (im Terminal über Datei → Dateiordner öffnen) und kompilieren Sie es in MetaEditor mit F7.

DemoBuffers.mq5 (MQL5\Indicators)

//+------------------------------------------------------------------+
//|                                                  DemoBuffers.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: one indicator with every kind of buffer - a line, arrows,"
#property description "a colour line with its colour index, and a calculation buffer that is not drawn."
#property indicator_chart_window
#property indicator_buffers 5
#property indicator_plots   3
//--- plot 0: a plain line (buffer 0)
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrSilver
#property indicator_width1  1
//--- plot 1: arrows (buffer 1), drawn only where the close crosses the SMA upward
#property indicator_label2  "Cross up"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrDodgerBlue
#property indicator_width2  2
//--- plot 2: a colour line (buffer 2 = value, buffer 3 = colour index)
#property indicator_label3  "Trend EMA"
#property indicator_type3   DRAW_COLOR_LINE
#property indicator_color3  clrSeaGreen,clrOrangeRed
#property indicator_width3  2
//--- input parameters
input int InpSmaPeriod=20;   // SMA period
input int InpEmaPeriod=50;   // EMA period
//--- indicator buffers
double ExtSma[];             // 0: line
double ExtCross[];           // 1: arrows, EMPTY_VALUE where there is none
double ExtEma[];             // 2: colour line value
double ExtEmaColor[];        // 3: colour index, 0 rising, 1 falling
double ExtSlope[];           // 4: calculations, EMA change over one bar
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,ExtSma,INDICATOR_DATA);
   SetIndexBuffer(1,ExtCross,INDICATOR_DATA);
   SetIndexBuffer(2,ExtEma,INDICATOR_DATA);
   SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS);
   PlotIndexSetInteger(1,PLOT_ARROW,233);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpSmaPeriod);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,InpEmaPeriod);
   IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("DemoBuffers(%d,%d)",InpSmaPeriod,InpEmaPeriod));
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
   if(rates_total<InpEmaPeriod+2)
      return(0);
   const double k=2.0/(InpEmaPeriod+1.0);
   int start=(prev_calculated>1) ? prev_calculated-1 : 1;
   if(prev_calculated==0)
     {
      ExtEma[0]=close[0];
      ExtSlope[0]=0.0;
      ExtEmaColor[0]=0.0;
     }
   for(int i=start; i<rates_total; i++)
     {
      //--- SMA, empty until there are enough bars
      if(i>=InpSmaPeriod-1)
        {
         double sum=0.0;
         for(int j=0; j<InpSmaPeriod; j++)
            sum+=close[i-j];
         ExtSma[i]=sum/InpSmaPeriod;
        }
      else
         ExtSma[i]=EMPTY_VALUE;
      //--- EMA, its slope and the colour of the slope
      ExtEma[i]=ExtEma[i-1]+k*(close[i]-ExtEma[i-1]);
      ExtSlope[i]=ExtEma[i]-ExtEma[i-1];
      ExtEmaColor[i]=(ExtSlope[i]>=0.0) ? 0.0 : 1.0;
      //--- an arrow under the bar where the close crosses the SMA upward
      ExtCross[i]=EMPTY_VALUE;
      if(i>=InpSmaPeriod && ExtSma[i-1]!=EMPTY_VALUE && close[i-1]<ExtSma[i-1] && close[i]>=ExtSma[i])
         ExtCross[i]=low[i];
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+

BufferSignalEA.mq5 (MQL5\Experts)

//+------------------------------------------------------------------+
//|                                               BufferSignalEA.mq5 |
//|                                                      Bohdan Klok |
//|                             https://www.mql5.com/en/users/kbposht |
//+------------------------------------------------------------------+
#property copyright   "Bohdan Klok"
#property link        "https://www.mql5.com/en/users/kbposht"
#property version     "1.00"
#property description "Example: trades the signals of any indicator by four generic rules on its"
#property description "buffers - an arrow appears, a level is crossed, one buffer crosses another, a colour"
#property description "index becomes a value. Signals are read on the closed bar, once per bar."
#include <Trade\Trade.mqh>
//--- the four generic rules
enum ENUM_SIGNAL_RULE
  {
   SIGNAL_OFF=0,          // Disabled
   SIGNAL_ARROW=1,        // Arrow appears in the buffer
   SIGNAL_CROSS_UP=2,     // Buffer crosses the level upward
   SIGNAL_CROSS_DOWN=3,   // Buffer crosses the level downward
   SIGNAL_LINE_UP=4,      // Buffer crosses the second buffer upward
   SIGNAL_LINE_DOWN=5,    // Buffer crosses the second buffer downward
   SIGNAL_BECOMES=6       // Buffer becomes equal to the level (colour index)
  };
//--- input parameters
input string           InpIndicator="Examples\\Fractals"; // Indicator name inside MQL5\Indicators
input string           InpParams="";                      // Its inputs, comma separated (empty = defaults)
input int              InpSignalBar=3;                    // Signal bar (1 = last closed bar)
input ENUM_SIGNAL_RULE InpBuyRule=SIGNAL_ARROW;           // Buy rule
input int              InpBuyBuffer=1;                    // Buy: buffer
input double           InpBuyLevel=0.0;                   // Buy: level
input int              InpBuyBuffer2=0;                   // Buy: second buffer
input ENUM_SIGNAL_RULE InpSellRule=SIGNAL_ARROW;          // Sell rule
input int              InpSellBuffer=0;                   // Sell: buffer
input double           InpSellLevel=0.0;                  // Sell: level
input int              InpSellBuffer2=1;                  // Sell: second buffer
input double           InpLots=0.1;                       // Lot
input int              InpStopPoints=300;                 // Stop loss, points
input int              InpTakePoints=600;                 // Take profit, points
input ulong            InpMagic=20261010;                 // Magic number
//--- global variables
CTrade   ExtTrade;
int      ExtHandle=INVALID_HANDLE;
datetime ExtLastBar=0;
//+------------------------------------------------------------------+
//| Indicator inputs from "14,0,1.5,true,text"                       |
//+------------------------------------------------------------------+
int CreateByName(const string name,const string params)
  {
   MqlParam p[];
   ArrayResize(p,1);
   p[0].type=TYPE_STRING;
   p[0].string_value=name;
   string parts[];
   const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
   ArrayResize(p,n+1);
   for(int i=0; i<n; i++)
     {
      string s=parts[i];
      StringTrimLeft(s);
      StringTrimRight(s);
      if(s=="true" || s=="false")
        {
         p[i+1].type=TYPE_BOOL;
         p[i+1].integer_value=(s=="true");
        }
      else
         if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
           {
            p[i+1].type=TYPE_DOUBLE;
            p[i+1].double_value=StringToDouble(s);
           }
         else
            if(s=="0" || StringToInteger(s)!=0)
              {
               p[i+1].type=TYPE_INT;
               p[i+1].integer_value=StringToInteger(s);
              }
            else
              {
               p[i+1].type=TYPE_STRING;
               p[i+1].string_value=s;
              }
     }
   return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
  }
//+------------------------------------------------------------------+
//| A value is empty if it is EMPTY_VALUE or not a number            |
//+------------------------------------------------------------------+
bool IsEmpty(const double v)
  {
   return(v==EMPTY_VALUE || !MathIsValidNumber(v));
  }
//+------------------------------------------------------------------+
//| Does the rule fire on the signal bar?                            |
//+------------------------------------------------------------------+
bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
  {
   if(rule==SIGNAL_OFF)
      return(false);
   double a[],b[];
   //--- two values: a[0] = the bar before the signal bar, a[1] = the signal bar
   if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
      return(false);
   switch(rule)
     {
      case SIGNAL_ARROW:
         return(!IsEmpty(a[1]) && a[1]!=0.0);
      case SIGNAL_CROSS_UP:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
      case SIGNAL_CROSS_DOWN:
         return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
      case SIGNAL_BECOMES:
         return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
      case SIGNAL_LINE_UP:
      case SIGNAL_LINE_DOWN:
        {
         if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
            return(false);
         if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
            return(false);
         if(rule==SIGNAL_LINE_UP)
            return(a[0]<b[0] && a[1]>=b[1]);
         return(a[0]>b[0] && a[1]<=b[1]);
        }
     }
   return(false);
  }
//+------------------------------------------------------------------+
//| Is there an open position of this EA?                            |
//+------------------------------------------------------------------+
bool HasPosition()
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==(long)InpMagic)
         return(true);
   return(false);
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpSignalBar<1)
     {
      Print("Signal bar must be 1 or more: the forming bar can still change");
      return(INIT_PARAMETERS_INCORRECT);
     }
   ExtHandle=CreateByName(InpIndicator,InpParams);
   if(ExtHandle==INVALID_HANDLE)
     {
      PrintFormat("Cannot load %s (error %d)",InpIndicator,GetLastError());
      return(INIT_FAILED);
     }
   ExtTrade.SetExpertMagicNumber(InpMagic);
   ExtLastBar=0;
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(ExtHandle!=INVALID_HANDLE)
      IndicatorRelease(ExtHandle);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- one decision per bar
   const datetime bar=iTime(_Symbol,_Period,0);
   if(bar==ExtLastBar)
      return;
   if(BarsCalculated(ExtHandle)<InpSignalBar+2)
      return;
   ExtLastBar=bar;
   const bool buy=RuleFires(InpBuyRule,InpBuyBuffer,InpBuyLevel,InpBuyBuffer2);
   const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2);
   if(buy==sell || HasPosition())
      return;
   const double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   if(buy)
     {
      const double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      ExtTrade.Buy(InpLots,_Symbol,ask,ask-InpStopPoints*point,ask+InpTakePoints*point);
     }
   else
     {
      const double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      ExtTrade.Sell(InpLots,_Symbol,bid,bid+InpStopPoints*point,bid-InpTakePoints*point);
     }
  }
//+------------------------------------------------------------------+