English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Verringerung der Speicherbelegung mittels Hilfsindikatoren

Verringerung der Speicherbelegung mittels Hilfsindikatoren

MetaTrader 5Indikatoren | 15 April 2016, 16:33
701 0
ds2
ds2

1. Das Problem

Sie haben wahrscheinlich bereits Expert Advisors oder Indikatoren verwendet oder erzeugt, die für ihre Arbeit Hilfsindikatoren verwenden.

So nutzt der berühmte MACD-Indikator zwei Kopien des EMA-Indikators (exponential gleitenden Mittelwerts) zur Berechnung des Unterschieds ihrer Werte:


So ein zusammengesetzter Indikator entspricht in der Tat mehreren einfachen Indikatoren. Der gerade erwähnte MACD braucht dreimal so viel Speicherplatz und Prozessorzeit wie ein einziger EMA, da er sowohl dem Puffer des Hauptindikators als auch dem Puffer all seiner Hilfsindikatoren Speicherplatz zuweisen muss.

Und neben dem MACD, gibt es noch komplexere Indikatoren, die mehr als zwei Hilfsindikatoren verwenden.

Darüber hinaus nimmt diese Speicherplatzbelegung noch erheblich zu, wenn:

  • der Indikator mehrere Zeitrahmen verwendet (wenn er z.B. die Gleichzeitigkeit von Wellen auf mehreren Zeitrahmen nachverfolgt), und somit für jeden Zeitrahmen separate Kopien der Hilfsindikatoren erzeugt;
  • der Indikator ein Mehrwährungs-Indikator ist;
  • ein Händler den Indikator zum Handel auf mehreren Währungspaaren verwendet (ich weiß von Personen, die auf mehr als zwei Dutzend Paaren gleichzeitig handeln).

Eine Kombination all dieser Bedingungen kann dazu führen, dass auf einem Computer einfach nicht mehr genügend Speicherplatz vorhanden ist (ich kenne echte Fälle, in denen ein Client-Terminal GB an Speicherplatz aufgrund der Arbeit mit solchen Indikatoren gefordert hat). Im Folgenden ein Beispiel mangelnden Speicherplatzes im MetaTrader 5 Client-Terminal:


In so einem Fall kann das Terminal den Indikator nicht auf ein Chart platzieren oder ihn korrekt berechnen (wenn der Indikator-Code den Fehler der Speicherplatzzuweisung nicht verarbeiten kann), und schaltet sich manchmal auch einfach ab.

Glücklicherweise kann das Terminal mangelnden Speicherplatz durch Verwendung von mehr virtuellem Speicherplatz ausgleichen, d.h. ein Teil der Information wird auf der Festplatte abgelegt. Alle Programme funktionieren, aber sehr sehr langsam....


2. Der zusammengesetzte Test-Indikator

Um unsere Betrachtungen im Rahmen dieses Beitrags fortzusetzen, erzeugen wir nun einen zusammengesetzten Indikator, denjenigen, der komplexer als der MACD ist.

Sagen wir, dieser Indikator soll das Auslösen von Trends nachverfolgen. Er fasst Signale von 5 Zeitrahmen zusammen, z.B.: H4, H1, M15, M5, M1. Und erlaubt, das Echo von großen und kleinen aufkommenden Trends festzuhalten, was zu einer verbesserten Zuverlässigkeit von Prognosen führen sollte. Als Ursprung der Signale für jeden Zeitrahmen verwenden wir die Ichimoku und Price_Channel Indikatoren, die in der MetaTrader 5-Version mit dabei sind:

  • liegt die Tenkan-Linie (rot) des Ichimoku über der Kijun-Linie (blau), steigt der Trend an; liegt sie darunter, nimmt der Trend ab;


  • liegt der Kurs oberhalb der Mittellinie des Price_Channels, steigt der Trend an; liegt er darunter, nimmt der Trend ab.


Unser Indikator wird insgesamt 10 Hilfsindikatoren verwenden: 5 Zeitrahmen mit jeweils 2 Indikatoren. Nennen wir unseren Indikator einfach 'Trender'.

Und das ist sein kompletter Quellcode (auch im Anhang an diesen Beitrag vorhanden):

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// The only buffer of the indicator
double ExtBuffer[];

// Timeframes of auxiliary indicators
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles of auxiliary indicators for all timeframes
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Create auxiliary indicators
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators\\Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators\\Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Time of the current bar
      datetime Time  = time [bar];
      
      //--- Gather signals from all timeframes
      double Signal = 0; // total signal
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Bar price
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== The Ichimoku indicator         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The channel indicator
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

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

Diesen Indikator sollten Sie auf einem Chart mit dem kleinsten Zeitrahmen der Zeitrahmen verwenden, von denen er die Signale abholt, denn nur so können Sie auch die kleinen Trends erkennen. In unserem Fall ist das der Zeitrahmen M1. Und so sieht der Indikator aus:


Kommen wir nun zum wichtigsten Teil, der Berechnung des Speicherplatzes, den der Indikator brauchen wird.

Sehen wir uns kurz die Quellcodes des Ichimoku-Indikators (der komplette Code ist unten angehängt):

#property indicator_buffers 5

und des Price_Channel Indikators (der komplette Code ist unten angehängt) an:

#property indicator_buffers 3

In diesen Zeilen erkennen Sie, dass 8 Puffer erzeugt wurden. Diese muss man mit den 5 Zeitrahmen multiplizieren und noch einen 1 Puffer für den 'Trender'-Indikator addieren. Ergibt insgesamt also 41 Puffer! Sie sehen, aus welch beeindruckenden Werte diese scheinbar so einfachen Indikatoren (einfach auf dem Chart) bestehen.

Per standardmäßigen Eigenschaften des Client-Terminals, enthält ein Puffer fast 100.000 Werte des Typs double, die jeweils 8 Bytes brauchen. Also belegen 41 Puffer annähernd 31 MB Speicherplatz. Und wir reden hier nur von den Werten an sich. Ich weiß nicht, welche Dienstinformationen noch zusätzlich in den Puffern abgelegt sind.

Klar können Sie jetzt sagen: "31 MB, ist doch lässig". Doch sobald ein Händler viele Währungspaare verwendet, wird so ein Volumen schnell zu einem Problem. Und zusätzlich zu den Indikatoren belegt das Chart an sich ebenfalls eine Menge Speicherplatz. Denn anders als Indikatoren, besitzt jeder Bar zugleich mehrere Werte: OHLC, Zeit und Volumen. Und wir soll das alles in einen Computer passen?


3. Mehrere Möglichkeiten, das Problem zu lösen

Klar können Sie Ihrem Computer mit mehr Speicherplatz aufstocken. Doch diese Möglichkeit ist aufgrund technischer, finanzieller oder anderer Gründe, nicht besonders geeignet - wenn Sie den Speicherplatz, der auf Ihrem Computer installiert werden kann, bereits aufgebraucht haben und es immer noch nicht reicht. Dann sollten Sie diese Speicherplatz fressenden Indikatoren genauer ansehen und ihren Appetit zügeln.

Und dazu... denken Sie einfach an die Geometriestunde in der Schule zurück. Stellen Sie sich vor: alle Puffer unserer zusammengesetzten Indikatoren sind ein solides Rechteck:


Die Fläche dieses Rechtecks ist der belegte Speicherplatz. Diese Fläche können Sie verringern, indem Sie die Breite oder Höhe reduzieren.

In unserem Fall ist die Breite die Anzahl der Bars auf denen die Indikatoren gezeichnet werden. Und die Höhe ist die Anzahl der Indikator-Puffer.


4. Die Zahl der Bars reduzieren

4.1 Die einfache Lösung

Um die Einstellungen von MetaTrader anzupassen, müssen Sie kein Programmierer sein:



Durch Reduzierung des Wertes "max. Bars im Chart", verringern Sie die Größe der Indikator-Puffer in diesen Fenstern. Eine einfache, wirksame und für jeden machbare Lösung (falls ein Händler keine eingehende Kurs-Historie beim Handeln braucht).

4.2 Geht das auch noch anders?

Die MQL5-Programmierer wissen, dass Indikator-Puffer in Indikatoren als dynamische Arrays ohne voreingestellte Größe deklariert werden. So z.B. 5 Puffer des Ichimoku:

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

Die Größe der Arrays ist nicht festgelegt, da sie für die komplette, vorhandenen Historie vom MetaTrader 5 Client-Terminal selbst eingerichtet wird.

Das gleiche gilt für die OnCalculate Funktion:

int OnCalculate (const int rates_total,      // size of the array price[]
               const int prev_calculated,  // number of bars processed at the previous call
               const int begin,            // the start of reliable data
               const double& price[]       // array for the calculation
   );

Hier wird der Kurs-Puffer an den Indikator übertragen. Sein Speicherplatz ist bereits vom Terminal zugewiesen, und der Programmierer kann seine Größe nicht beeinflussen.

Darüber hinaus erlaubt es MQL5, einen Puffer eines Indikator als Kurs-Puffer für einen anderen zu verwenden ( "ein Indikator, der auf einem Indikator beruht"). Doch auch hier kann der Programmierer keinerlei Größenobergrenzen einrichten, sondern nur den Indikator-Handle übertragen.

Also gibt es in MQL5 keine Möglichkeit, die Größe von Indikator-Puffern zu beschränken.


5. Die Zahl der Puffer reduzieren

Hier hat ein Programmierer eine Menge Möglichkeiten. Ich habe mehrere, einfache theoretische Wege gefunden, die Zahl der Puffer eines zusammengesetzten Indikators zu senken. Doch sie alle bedeuten, die Anzahl der Puffer der Hilfsindikatoren zu reduzieren, da im Hauptindikator alle Puffer notwendig sind.

Sehen wir uns diese Möglichkeiten mal genauer an und prüfen, ob sie auch wirklich funktionieren und wo ihre Vor- und Nachteile liegen.

5.1 Die "Need" Methode

Wenn ein Hilfsindikator viele Puffer enthält, werden oftmals anscheinend nicht alle für den Haupt-Indikator gebraucht. Also können wir nicht benötigte Indikatoren deaktivieren, um mehr freien Speicherplatz zu erhalten. Dazu müssen wir im Quellcode des Hilfsindikators einige Änderungen vornehmen.

Und das tun wir bei einem unserer Hilfsindikatoren - nämlich Price_Channel. Er enthält drei Puffer und unser 'Trender' liest davon nur einen, also können wir Unnötiges entfernen.

Der gesamte Code der Indikatoren Price_Channel (ursprünglicher Indikator) und Price_Channel-Need (komplett umgeschrieben) ist ebenfalls am Ende des Beitrags angehängt. Ich gehe hier nur auf die Änderungen ein, die gemacht wurden.

Zuerst muss man den Zähler der Puffer von 3 auf 1 herabsetzen:

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

Dann zwei unnötige Puffer-Array entfernen:

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

Wenn wir diesen Indikator nun zu erstellen versuchen, zeigt uns der Compiler alle Reihen an, in denen diese Arrays aufgerufen werden:


Mit dieser Methode können wir rasch alles ermitteln, was geändert werden muss. Das ist bei einem umfangreichen Indikator-Code ziemlich bequem.

In unserem Fall haben wir 4 "nicht deklarierter Identifikator" Zeilen insgesamt. Die berichtigen wir.

Und zwei davon befinden sich in OnInit - was uns nicht überrascht. Zusammen mit ihnen mussten wir auch die Zeile mit dem notwendigen ExtMiddBuffer entfernen und an ihrer Stelle fügen wir eine ähnliche Zeile hinzu, jedoch mit einem anderen Puffer-Index. Da der Indikator keinen Puffer mit Index 2 mehr hat, geht nur noch 0:

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
   SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

Wenn Sie mit dem "Einschnitt"-Indikator in einem visuellen Modus arbeiten möchten, sollten Sie daran denken, zusammen mit dem Puffer-Index auch die Darstellungseinstellungen zu ändern. In unserem Fall:

//#property indicator_type1   DRAW_FILLING
#property indicator_type1   DRAW_LINE

Wenn Sie keine Visualisierung brauchen, dann können Sie dies außer Acht lassen - es wird zu keinen Fehlern kommen.

Beschäftigen wir uns weiter mit der Liste "nicht deklarierter Identifikator". Zwei weitere Änderungen (auch das war zu erwarten) sind in OnCalculate zu machen, dort wo die Arrays der Indikator-Puffer befüllt werden. Da der notwendige ExtMiddBuffer die entfernten ExtHighBuffer und ExtLowBuffer aufruft, müssen hier an ihrer Stelle Zwischenvariablen eingesetzt werden:

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
      double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
      double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
      ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

Sie sehen, an unserem "Eingriff" ist überhaupt nichts kompliziert. Es war sofort ersichtlich, was alles zu tun ist und mit einigen kleinen "Schnitten" haben wir zwei Puffer abgetrennt. Innerhalb des gesamten zusammengesetzten Indikators 'Trender' haben wir nun bereits 10 Puffer (2*5 Zeitrahmen) eingespart.

Öffnen Sie einfach Price_Channel und Price_Channel-Need jeweils untereinander, dann erkennen Sie die Puffer, die nun verschwunden sind:


Um im 'Trender'-Indikator Price_Channel-Need verwenden zu können, müssen wir den Namen des Hilfsindikators "Price_Channel" in "Price_Channel-Need" im Code von 'Trender' umändern. Und zudem auch den Index der erforderlichen Puffer von 2 auf 0 ändern. Der einsatzbereite Trender-Need ist am Ende des Beitrags angehängt.


5.2 Die "Aggregate" Methode

Liest ein Hauptindikator Daten von mehr als einem Puffer eines Hilfsindikators und führt dann damit eine zusammenfassende Handlung aus (z.B. Addition oder Vergleich), muss das nicht notwendigerweise im Hauptindikator geschehen. Wir können das auch in einem Hilfsindikator ablaufen und anschließend das Ergebnis an den Hauptindikator übertragen lassen. Also brauchen wir auch nicht mehrere Puffer - sie alle werden durch einen einzigen ersetzt.

In unserem Fall ist diese Methode auf Ichimoku anwendbar, denn 'Trender' verwendet 2 Puffer von Ichimoku (0 - Tenkan und 1 - Kijun):

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

Fassen wir 0 und 1 Puffer von Ichimoku zu einem einzigen Signal-Puffer zusammen, wird das oben gezeigte Fragment von 'Trender' durch das folgende ersetzt:

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

Der komplette Trender-Aggregate ist am Ende des Betirags angehängt.

Sehen wir uns nun die wichtigsten Änderungen an, die bei Ichimoku gemacht werden sollten.

Dieser Indikator enthält zudem ebenfalls nicht genutzte Puffer. Also können wir, zusätzlich zur "Aggregate"-Methode, auch die "Need"-Methode anwenden. Also bleibt in Ichimoku nur noch einer von 5 Puffern übrig - der, der die notwendigen Puffer zusammenfasst:

//#property indicator_buffers 5
#property indicator_buffers 1
//#property indicator_plots   4
#property indicator_plots   1

Und diesen einzigen Puffer benennen wir um:

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

Sein neuer Name hat eine ganz praktische Bedeutung - man kann damit alle Namen der zuvor benutzten Puffer aus dem Indikator löschen. Mit seiner Hilfe (mittels der in der "Need"-Methode beschriebenen Erstellung) findet man rasch alle Zeilen, die verändert werden müssen.

Wenn Sie den Indikator visuell auf einem Chart abbilden möchten, sollten Sie daran denken, auch die Darstellungseinstellungen zu ändern. In unserem Fall sollten Sie zudem berücksichtigen, dass der zusammenfassende Puffer, im Vergleich zu zwei von ihm belegten Puffern, einen anderen Wertebereich hat. Er zeigt jetzt kein Kursderivat, doch welcher der zwei Puffer ist größer? Derartige Ergebnisse lassen sich bequemer in einem extra Fenster unterhalb des Chart anzeigen:

//#property indicator_chart_window
#property indicator_separate_window

Also müssen in OnInit folgende Änderungen vorgenommen werden:

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

Doch der interessanteste Teil ist OnCalculate. Hinweis: Drei unnötige Puffer werden einfach gelöscht (während wir die "Need"-Methode verwenden), und die notwendigen ExtTenkanBuffer und ExtKijunBuffer werden durch die vorläufigen Variablen Tenkan und Kijun ersetzt. Diese Variablen werden am Ende des Zyklus zur Berechnung des zusammenfassenden Puffers Puffer ExtSignalBuffer verwendet:

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

Ergibt insgesamt 4 Puffer weniger. Hätten wir nur die "Need"-Methode auf Ichimoku angewendet, hätten wir uns nur 3 Puffer gespart.

Innerhalb des gesamten 'Trender' haben wir inzwischen schon 20 Puffer insgesamt eingespart (4*5 Zeitrahmen).

Der komplette Code von Ichimoku-Aggregate ist am Ende des Beitrags angehängt. Um diesen Indikator mit dem ursprünglichen zu vergleichen, öffnen Sie einfach beide auf einem einzigen Chart. Sie erinnern sich: Der veränderte Indikator wird nun unterhalb des Charts in einem extra Fenster angezeigt.


5.3 Die "Include" Methode

Die radikalste Möglichkeit, die Zahl der Puffer zu reduzieren, ist einfach alle Hilfsindikatoren loszuwerden. Dann bleibt nur noch 1 Puffer in unserem Indikator übrig - der, der zum Hauptindikator gehört. Weniger Puffer geht nicht.

Das gleiche Ergebnis erzielt man, wenn man den Code der Hilfsindikatoren in den Hauptindikator verschiebt. Das ist manchmal reichlich zeitaufwendig, doch das Endergebnis lohnt die Mühe. Der schwierigste Teil ist die Anpassung des von den Indikatoren verschobenen Codes, da er nicht dazu gedacht ist, innerhalb des Codes eines anderen Indikators zu funktionieren.

Und das sind die größten Probleme in genau diesem Fall:

  • Namenskonflikt. Die gleichen Namen für Variablen und Funktionen (insb. Systemfunktionen wie z.B. OnCalculate);
  • Fehlen von Puffern. Dies kann bei einigen Indikatoren ein unüberwindliches Problem darstellen, wenn die Logik der Indikatoren eng mit der Speicherung/Verarbeitung von Daten in Puffern zusammenhängt. Das Ersetzen der Puffer durch einfache Arrays, wie in unserem Fall, ist kein Allheilmittel, da wir ja Speicherplatzbelegung verringern wollen. Wichtig ist, auf keinen Fall gigantische Historydaten im Speicher abzulegen.

Ich zeige Ihnen die Methoden, mit denen sich diese Probleme wirkungsvoll lösen lassen.

Jeder Hilfsindikator sollte als Klasse geschrieben werden. Dann haben alle Variablen und Funktionen des Indikators (innerhalb ihrer Klasse) immer eindeutige Namen und kommen so den anderen Indikatoren nicht in die Quere.

Werden viele Indikatoren verschoben, könnte man sich auch eine Standardisierung dieser Klassen überlegen, damit man bei ihrer Verwendung später nicht durcheinander kommt. Zu diesem Zweck erzeugen wir einen Basis-Indikator und erben von ihm die Klassen aller Hilfsindikatoren.

Ich habe die folgende Klasse geschrieben:

class CIndicator
  {
protected:
   string symbol;             // currency pair
   ENUM_TIMEFRAMES timeframe;  // timeframe

   double Open[], High[], Low[], Close[]; // simulation of price buffers
   int BufLen; // necessary depth of filling of price buffers

public:
   //--- Analogs of standard functions of indicators
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - address of bar that should be calculated
  };

Auf ihrer Grundlage erzeugen wir nun eine Klasse für den Ichimoku-Indikator. Zunächst müssen in Form von Eigenschaften die Eingabeparameter mit den Originalnamen geschrieben werden. Damit später nichts im Code des Indikators verändert werden muss:

class CIchimoku: public CIndicator
  {
private:
   // Simulation of input parameters of the indicator
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

Die Namen aller Puffer werden beibehalten. Sie haben richtig gehört: wir deklarieren alle 5 Puffer dieses Indikators. Doch sie sind nur Attrappen, da sie jeweils nur aus einem Bar bestehen:

public:   
   // Simulation of indicator buffers
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

Warum wir das machen? Um später noch weniger Veränderungen im Code vornehmen zu müssen. Warten Sie - wird gleich klar! Definieren Sie die ererbte Methode CIchimoku.Calculate erneut, indem Sie sie mit dem Code der OnCalculate Funktion füllen, die Sie aus Ichimoku nehmen.

Bitte beachten: die Schleife nach den History-Bars ist beim Verschieben dieser Funktion entfernt worden. Hier wird jetzt nur noch ein Bar mit einer spezifizierten Zeit berechnet. Doch der Hauptcode der Berechnungen bleibt derselbe. Genau deswegen haben wir fein säuberlich alle Namen der Puffer und Parameter des Indikators beibehalten.

Des Weiteren sollten Sie auch darauf achten, dass Kurspuffer zu Anfang der Methode 'Berechnen' mit Werten gefüllt werden. Und zwar so viele Werte, wie zur Berechnung eines Bars notwendig sind.

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

Natürlich könnten wir es uns auch schenken, den ursprünglichen Code beizubehalten Doch in diesem Fall, müssten wir einen beträchtlichen Teil neu schreiben, was natürlich das Verständnis der Logik seiner Funktionsweise erfordert. In unserem Fall ist der Indikator einfach, sodass er sehr leicht zu verstehen ist. Doch was ist, wenn der Indikator komplex ist? Ich habe Ihnen die Methode gezeigt, die in so einem Fall helfen kann.

Füllen wir nun die Methode CIchimoku.Init - hier ist alles reichlich simpel:

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

Der Ichimoku enthält zwei weitere Funktionen, die in die Klasse CIchimoku übernommen werden sollten: Höchster und Niedrigster. Sie suchen nach den Maximal- und Minimalwerten in einem festgelegten Teil des Kurspuffers.

Unsere Kurspuffer sind nicht echt, sie sind nur sehr klein (wie sie gefüllt wurden, haben Sie in der Methode 'Berechnen' oben gesehen). Deshalb müssen wir die Logik der Funktionsweise der Funktionen 'Höchster' und 'Niedrigster' leicht abändern.

Auch hier bin ich dem Prinzip, möglichst wenig Veränderungen vorzunehmen, treu geblieben. Die einzige Veränderung ist das Hinzufügen einer Zeile, die die Indizierung der Bars im Puffer vom globalen (wenn die Pufferlänge die komplett verfügbare History ist) zum lokalen (die Kurspuffer enthalten jetzt nur noch die Werte, die zur Berechnung eines Indikator-Bars notwendig sind) verändert:

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

Die Methode 'Niedrigster' wird auf die gleiche Art und Weise geändert.

Ähnliche Veränderungen werden am Price_Channel Indikator vorgenommen, doch wird er als Klasse namens CChannel dargestellt. Der komplette Code beider Klassen ist in der Datei Trender-Include vorhanden, die am Ende dieses Beitrags angehängt ist.

Ich habe nun den Hauptaspekt beim Verschieben des Codes beschrieben. Meiner Meinung nach reichen diese Methoden für die meisten Indikatoren aus.

Indikatoren ohne Standard-Einstellungen verursachen eventuell zusätzliche Schwierigkkeiten. So enthält z.B. Price_Channel unscheinbare Zeilen:

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

Sie bedeuten, dass das Indikator-Chart um 1 Bar verschoben wird. Das führt in unserem Fall zu der Situation, dass, wenn die CopyBuffer und CopyHigh Funktionen zwei unterschiedliche Bars verwenden, die gleichen Bar-Koordinaten (Zeit) in ihren Parametern eingerichtet sind.

Dies Problem ist in Trender-Include gelöst ("1er" werden in den notwendigen Teilen der CChannel Klasse in von der CIchimoku Klasse, wo das Problem nicht besteht, unterschiedlicher Form hinzugefügt). Wenn Sie also so einen "gewieften" Indikator brauchen, wissen Sie nun, wo Sie ihn finden.

Gut. Mit dem Verschieben sind wir also fertig, und beide Indikatoren sind nun als zwei Klassen innerhalb des Trender-Include Indikators geschrieben. Jetzt müssen wir nur noch die Art ändern, wie diese Indikatoren aufgerufen werden. Im 'Trender' hatten wir die Arrays der Handles, und in 'Trender-Include' sind sie durch Objekt-Arrays ersetzt worden:

// Handles of auxiliary indicator for all timeframes
//int h_Ichimoku[5], h_Channel[5];
// Instances of embedded auxiliary indicators
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

Die Erzeugung der Hilfsindikatoren in OnInit sieht jetzt folgendermaßen aus:

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

Und in OnCalculate wird CopyBuffer nun durch einen direkten Aufruf an die Eigenschaften der Objekte ersetzt:

         //=== The Ichimoku indicator
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== The Channel indicator
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

Weitere 40 Puffer gespart. Das lohnt sich also alles durchaus.

Nach jeder Veränderung des 'Trender', gemäß der zuvor beschriebenen "Need"- und "Aggregate"-Methoden, habe ich die so erhaltenen Indikatoren im visuellen Modus getestet.

Und so einen Test führen wir jetzt aus: Dazu öffnen wir den ursprünglichen Indikator (Trender) und den neu geschriebenen (Trender-Include) auf einem Chart. Wir sehen, dass alles korrekt gemacht wurde, da die Linien beider Indikatoren exakt miteinander übereinstimmen:


5.4 Und wenn man die Indikatoren nacheinander in den Speicher laden würde?

Wir haben bislang bereits drei Möglichkeiten betrachtet, wie man die Zahl der Puffer von Hilfsindikatoren verringern kann. Doch was wäre, wenn wir unseren Ansatz radikal ändern - und versuchen, die Zahl der gleichzeitig im Speicher abgelegten Puffer zu reduzieren. anstatt ihre Gesamtzahl? Mit anderen Worten: Wir laden die Indikatoren einen nach dem anderen in den Speicher, anstatt alle auf einmal. Dazu müssen wir einen "Kreisverkehr" anlegen. also einen Hilfsindikator erzeugen, seine Daten lesen, ihn löschen, den nächsten erzeugen, usw. - bis wir alle Zeitrahmen abgedeckt haben. Der Ichimoku-Indikator besitzt die größte Zahl an Puffern - 5. So können theoretisch maximal 5 Puffer gleichzeitig im Speicher abgelegt (plus 1 Puffer des Hauptindikators) und trotzdem noch 35 Puffer eingespart werden!

Ist so etwas möglich? Es gibt in MQL5 eine spezielle Funktion zum Löschen von Indikatoren - IndicatorRelease.

Doch das geht bei weitem nicht so leicht, wie es zunächst aussieht. MetaTrader 5 geht es darum, dass die MQL5-Programme alle extrem schnell laufen, daher werden alle aufgerufenen Zeitreihen im Cache gespeichert - falls sie von einem EA, Indikator oder Script benötigt werden. Und nur wenn sie über einen langen Zeitraum nicht aufgerufen werden, werden sie entladen, um Speicherplatz freizumachen. Diese 'Auszeit' kann bis zu 30 Minuten dauern.

Daher führt das ständige Erzeugen und Löschen von Indikatoren eben nicht dazu, dass wir sofort eine Menge Speicherplatz einsparen. Es kann hingegen den Computer erheblich verlangsamen, da ein Indikator jedes Mal, wenn er erzeugt wird, für die gesamte Kurshistorie berechnet wird. Denken Sie also darüber nach, wie vernünftig es ist, bei jedem Bar des Hauptindikators eine derartige Handlung auszuführen.

Trotzdem war die Idee des "Indikator-Kreisverkehrs" als "Brainstorming" ziemlich interessant. Wenn Ihnen noch andere originelle Ideen zur Optimierung von Indikator-Speicherplatz einfallen, dann freue ich mich über Ihre Kommentare zu diesem Beitrag. Denn vielleicht werden sie in den nächsten Beiträgen zu diesem Thema theoretisch oder sogar praktisch auf den Prüfstein gestellt.


6. Die tatsächliche Belegung an Speicherplatz messen

In den vorherigen Kapiteln haben wir drei Arbeitsmethoden implementiert, die alle die Zahl der Puffer von Hilfsindikatoren verringern. Jetzt müssen wir noch analysieren, wie sich das auf die tatsächliche Belegung an Speicherplatz auswirkt.

Mit Hilfe des "Task Managers" in MS Windows messen wir die Menge des Speicherplatzes, der vom Terminal belegt wird. In der Registerkarte "Verlauf" können Sie die Größe des RAM und den virtuellen Speicherplatz sehen, der vom Client-Terminal belegt ist. Zum Beispiel:


Derartige Messungen werden gemäß des folgenden Algorithmus gemacht, mit dem man die Minimalbelegung des Speicherplatzes durch das Terminal erkennen kann (kommt der Belegung von Speicherplatz durch Indikatoren ziemlich nahe):

  1. Laden Sie eine umfassende Kurs-History aus dem MetaQuotes-Demo Server herunter (es genügt hier, einen Test auf einem Symbol für automatische Downloads seiner History zu machen);
  2. Richten Sie das Terminal für eine nächste Maßnahme ein (Öffnen der notwendigen Charts und Indikatoren) und starten es erneut, um den Speicher von unnötigen Informationen zu leeren;
  3. Warten Sie, bis das neu gestartete Terminal mit der Berechnung aller Indikatoren fertig ist. Das erkennen Sie daran, wenn der Prozessor nicht mehr lädt (Null-Loading);
  4. Verkleinern Sie das Terminal in der Aufgabenleiste (durch Anklicken der Standardschaltfläche "Verkleinern" oben rechts im Terminal). Dadurch wird Speicherplatz, der im Moment nicht für Berechnungen gebraucht wird, freigemacht (der Screenshot oben zeigt ein Beispiel an Speicherbelegung in bereits verkleinertem Zustand: es wird weit weniger RAM als virtueller Speicherplatz belegt);
  5. Im "Task Manager" rechnen Sie dann die Spalten "Speicher-Belegung" (RAM) und "VM-Größe" (virtueller Speicherplatz) zusammen. So heißen sie in Windows XP; in anderen Versionen des Betriebssystems können sie leicht abweichende Namen haben

Messungsparameter:

  • Damit unsere Messungen auch ganz exakt sind, verwenden wir alle aktuell im MetaQuotes Demo-Konto zur Verfügung stehenden Währungspaare, anstatt nur ein Kurschart, d.h. 22 M1 Charts. Anschließend berechnen wir die Durchschnittswerte;
  • Die Option "Max. Bars im Chart" (in Kap. 4.1 beschrieben) hat einen Standardwert - 100.000;
  • BS - Windows XP, 32bit.

Was kann man vom Messergebnis erwarten? Zwei Dinge sind hier anzumerken:

  1. Selbst obwohl der Indikator 'Trender' 41 Puffer benutzt, heißt das nicht, dass er 41*100.00 Bars belegt. Das liegt daran, weil die Puffer auf 5 Zeitrahmen verteilt sind, und die größeren Zeitrahmen weniger Bars als die kleineren haben. So enthält die M1 History von EURUSD z.B. fast 4 Millionen Bars, die H1 History hingegen nur 70.000 (4.000.000/60). Daher sollten wir nicht die gleiche Verringerung an Speicherbelegung nach Reduzierung der Zahl der Puffer im 'Trender' erwarten;
  2. Der Speicherplatz wird nicht nur vom Indikator an sich belegt, sondern auch von der Kursserie, die dieser Indikator benutzt. 'Trender' benutzt fünf Zeitrahmen. Wenn wir also die Zahl der Puffer mehrmals verringern, nimmt die Gesamtbelegung des Speicherplatzes deshalb nicht genauso ab, da ja alle fünf Kursserien im Speicher verwendet werden.

Bei der Messung des belegten Speicherplatzes werden Sie noch auf einige andere Faktoren stoßen, die sich auf Speicherbelegung auswirken. Aus diesem Grund führen ja auch diese praktischen Messungen durch - um die tatsächliche Einsparung infolge der Optimierung des Indikators zu erfahren.

Unten steht die Tabelle mit dem Ergebnis aller Messungen. Zuerst habe ich gemessen, wie viel Speicherplatz das leere Terminal belegt hat. Durch Subtraktion dieses Werts von der nächsten Messung, können wir berechnen, wie viel Speicherplatz von einem Chart belegt wird. Wenn wir beide Angaben (also vom Terminal und von einem Chart belegter Speicherplatz) von den nächsten Messungen abziehen, erhalten wir Größe des von jedem Indikator belegten Speicherplatzes.

Verbraucher von Speicherplatz
Indikator-Puffer
Zeitrahmen
Speicherbelegung
Der Client-Terminal
0
0
38 MB für das Terminal
Chart
0
1
12 MB für ein leeres Chart
Der Indikator 'Trender'
41
5
46 MB für einen Indikator
Der Indikator 'Trender-Need'
31
5
42 MB für einen Indikator
Der Indikator 'Trender-Aggregate' 21
5
37 MB für einen Indikator
Der Indikator ' Trender-Include' 1
5
38 MB für einen Indikator


Auf Basis der Ergebnisse dieser Messungen kommen wir zu folgendem Schluss:

  • Eine Verringerung der Zahl der Indikator-Puffer führt nicht zu einer identischen Verringerung des vom Indikator belegten Speicherplatzes.
Der Grund hierfür ist weiter oben in diesem Kapitel beschrieben. Die Verringerung der Zahl der Puffer würde sich vielleicht deutlicher niederschlagen, wenn der Indikator weniger Zeitrahmen verwenden würde.
  • Ein Verschieben des Codes der Hilfsindikatoren in den Hauptindikator führt nicht immer zum optimalsten Ergebnis.

Warum ist also die 'Include'-Methode weniger effektiv als die 'Aggregate'-Methode? Um die Gründe hierfür festzustellen, müssen wir uns kurz die Hauptunterschiede des Codes dieser Indikatoren in Erinnerung rufen. In 'Aggregate' werden die zur Berechnung notwendigen Kursserien durch das Terminal als Eingabe-Arrays in OnCalculate übertragen. In 'Include' werden alle Daten (für alle Zeitrahmen) für jeden Bar aktiv abgefragt und zwar mit Hilfe von CopyHigh, CopyLow und CopyClose. Dies ist wahrscheinlich der Grund für die zusätzliche Belegung von Speicherplatz, infolge der Besonderheiten des Caching der Zeitreihen, wenn diese Funktionen genutzt werden.


Fazit

In diesem Beitrag ging es um 3 Arbeitsmethoden zur Verringerung von Speicherplatzbelegung bei Hilfsindikatoren und 1 Methode zum Einsparen von Speicherplatz durch Anpassung des Client-Terminals.

Welche Methode angewendet werden soll, hängt ab, welche in Ihrer bestimmten Situation am geeignetsten und akzeptabelsten ist. Die Zahl der eingesparten Puffer und Megabytes hängt von den Indikatoren ab, mit denen Sie arbeiten: bei einigen können Sie eine Menge "anschneiden", bei anderen geht u.U. gar nichts.

Durch Einsparen von Speicherplatz kann die Zahl der im Terminal zeitgleich verwendeten Währungspaare angehoben werden. Das steigert die Zuverlässigkeit Ihres Handels-Portfolios. Diese einfache Berücksichtigung der technischen Ressourcen Ihres Rechners kann sich in echtem Gewinn in Ihrem Depot niederschlagen.


Anhänge

Die in diesem Beitrag beschriebenen Indikatoren sind angehängt. Damit auch alles reibungslos funktioniert, speichern Sie sie im Ordner "MQL5\Indikators\TestSlaveIndikators", da alle Versionen des Indikators 'Trender' (außer 'Trender-Include') dort nach ihren Hilfsindikatoren suchen.


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/259

Beigefügte Dateien |
ichimoku.mq5 (4.97 KB)
price_channel.mq5 (4.34 KB)
trender.mq5 (2.94 KB)
trender-need.mq5 (2.94 KB)
Implementierung der Automatischen Analyse der Elliott-Wellen in MQL5 Implementierung der Automatischen Analyse der Elliott-Wellen in MQL5
Eine der populärsten Methoden zur Analyse von Märkten ist das Prinzip der Elliott-Wellen. Diese Analyse ist jedoch ziemlich kompliziert, sodass wir hierfür zusätzliche Tools verwenden müssen. Eines dieser Instrumente ist der automatische Marker. Dieser Beitrag beschreibt die Erzeugung eines automatischen Analyseinstruments der Elliott-Wellen in der MQL5-Sprache.
Seinen eigenen Expert Advisor im MQL5-Assistent erstellen Seinen eigenen Expert Advisor im MQL5-Assistent erstellen
Um Handelsroboter zu erstellen, muss man nicht mehr unbedingt Programmiersprachen kennen. Früher bedeuteten nicht vorhandene Programmierfähigkeiten ein schier unüberwindliches Problem bei der Implementierung der eigenen Handelsstrategien, doch seit es den MQL5 Assistenten gibt, hat sich das eindeutig geändert. Neulinge unter den Händlern müssen sich jetzt keine Sorgen mehr machen, weil sie über zu wenig Programmiererfahrung verfügen - mit dem neuen Assistenten, mit dessen Hilfe jeder einen Expert Advisor Code generieren kann, ist diese Erfahrung nicht mehr notwendig.
Der MQL5-Assistent für Neueinsteiger Der MQL5-Assistent für Neueinsteiger
Anfang 2011 haben wir die erste Fassung des MQL5-Assistenten veröffentlicht. Damit hatten die Devisenhändler ein einfaches und verständliches Werkzeug zur automatischen Erzeugung von automatischen Handelssystemen in der Hand. Jeder Anwender von MetaTrader 5 erhielt so die Möglichkeit, ohne MQL5-Programmierkenntnisse sein eigenes Expert-System zu schreiben.
Wie man einen Expert Advisor bestellt und das gewünschte Ergebnis erhält Wie man einen Expert Advisor bestellt und das gewünschte Ergebnis erhält
Wie man die Anforderungsspezifikationen richtig schreibt. Was man von einem Programmierer bei der Bestellung eines Expert Advisors oder Indikators erwarten darf und was nicht. Wie man die Kommunikation aufrecht hält und auf welche Phasen man besonders achten muss. Dieser Beitrag versucht diese sowie weitere Fragen zu beantworten, die oft für viele Menschen nicht offensichtlich sind.